feat(portale): progress bar compatta, meno altezza

Segue il mock design-reference/Client-Portal-Progress-Bar: "Step N" e lo
stato passano sulla stessa riga, il titolo fase resta sotto — due righe di
testo invece di tre — e il padding del contenitore scende da py-8 a py-3.
Altezza della sezione da ~147px a ~90px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 23:08:00 +02:00
parent 4666fc4058
commit 7e58031528
3 changed files with 196 additions and 18 deletions
+170
View File
@@ -0,0 +1,170 @@
<div class="stepper-original-style">
<!-- Linea di avanzamento continua (come nello screenshot) -->
<div class="line-track">
<div class="line-progress" style="width: 20%;"></div>
</div>
<!-- STEPS -->
<div class="steps-wrapper">
<!-- Step 1: Completato -->
<div class="step-node completed">
<div class="circle">
<svg width="14" height="11" viewBox="0 0 14 11" fill="none">
<path d="M1.5 5.5L5 9L12.5 1.5" stroke="white" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="step-text">
<div class="title-row">
<span class="step-title">Step 1</span>
<span class="status completed">COMPLETATA</span>
</div>
<div class="step-desc">Fase 1 → Onboarding / Setup</div>
</div>
</div>
<!-- Step 2 -->
<div class="step-node">
<div class="circle">2</div>
<div class="step-text">
<div class="title-row">
<span class="step-title">Step 2</span>
<span class="status">DA INIZIARE</span>
</div>
<div class="step-desc">Fase 2 → Analisi / Strategia</div>
</div>
</div>
<!-- Step 3 -->
<div class="step-node">
<div class="circle">3</div>
<div class="step-text">
<div class="title-row">
<span class="step-title">Step 3</span>
<span class="status">DA INIZIARE</span>
</div>
<div class="step-desc">Fase 3 → Esecuzione / Core</div>
</div>
</div>
<!-- Step 4 -->
<div class="step-node">
<div class="circle">4</div>
<div class="step-text">
<div class="title-row">
<span class="step-title">Step 4</span>
<span class="status">DA INIZIARE</span>
</div>
<div class="step-desc">Fase 4 → Raffinamento / Extra</div>
</div>
</div>
</div>
</div>
<style>
.stepper-original-style {
position: relative;
width: 100%;
max-width: 1000px;
padding: 10px 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
box-sizing: border-box;
}
/* La linea continua dello screenshot originale */
.line-track {
position: absolute;
top: 28px; /* Centrata perfettamente dietro i cerchi */
left: 60px;
right: 60px;
height: 4px;
background-color: #edf2f4; /* Grigio chiaro dello screen */
border-radius: 2px;
z-index: 1;
}
.line-progress {
height: 100%;
background-color: #0d4a3a; /* Verde scuro originale */
border-radius: 2px;
transition: width 0.3s ease;
}
/* Griglia dei nodi */
.steps-wrapper {
position: relative;
z-index: 2;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.step-node {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
flex: 1;
}
/* Cerchi identici allo screen originale */
.circle {
width: 38px;
height: 38px;
border-radius: 50%;
background-color: #ffffff;
color: #334155;
font-size: 15px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
/* Ombra morbida originale */
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
margin-bottom: 8px;
}
/* Cerchio completato (Verde) */
.step-node.completed .circle {
background-color: #0d4a3a;
color: #ffffff;
box-shadow: 0 4px 10px rgba(13, 74, 58, 0.2);
}
/* Testi compatti a 2 righe */
.step-text {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.title-row {
display: flex;
align-items: center;
gap: 6px;
}
.step-title {
font-size: 13px;
font-weight: 700;
color: #0f172a;
}
.status {
font-size: 10px;
font-weight: 700;
color: #94a3b8;
letter-spacing: 0.2px;
}
.status.completed {
color: #059669; /* Verde stato completato */
}
.step-desc {
font-size: 11px;
color: #64748b;
font-weight: 400;
white-space: nowrap;
}
</style>
+1 -1
View File
@@ -146,7 +146,7 @@ across all future admin pages.
| `LeadTable` | `src/components/admin/leads/LeadTable.tsx` | Luxury table restyle: `bg-card rounded-xl shadow-card`, uppercase muted headers, `hover:bg-muted/40` rows, uses `StatusBadge`. |
| `LeadsKanbanBoard` | `src/components/admin/leads/LeadsKanbanBoard.tsx` | Kanban restyle: `bg-muted/60` columns with count pills, `bg-card` cards with `hover:border-primary/30`. Uses `@dnd-kit`, keeps all 6 lead stages. |
| `LeadsViewToggle` | `src/components/admin/leads/LeadsViewToggle.tsx` | Wires `SearchInput` + `SegmentedToggle` together with the table/kanban views. |
| `MilestoneStepper` | `src/components/client/MilestoneStepper.tsx` | Client-portal horizontal milestone stepper: one node per real phase, connecting line filled by `global_progress_pct`. Done = filled `bg-primary` ✓, active = `border-primary` outline, upcoming = `bg-muted`. |
| `MilestoneStepper` | `src/components/client/MilestoneStepper.tsx` | Client-portal horizontal milestone stepper: one node per real phase, connecting line filled by `global_progress_pct`. Done = filled `bg-primary` ✓, active = `border-primary` outline, upcoming = `bg-muted`. Layout **compatto** (`py-3`, due sole righe di testo: `Step N` + stato in linea, titolo fase sotto) — mock: `design-reference/Client-Portal-Progress-Bar`. |
| `LeadDetail` | `src/components/admin/leads/LeadDetail.tsx` | Lead detail page (`/admin/pipeline/[id]`): bespoke header (name + inline `StatusBadge` + action bar), asymmetric `lg:grid-cols-3` layout (1/3 profile card w/ icon-chip rows + tags, 2/3 Note/Follow-up + unified timeline). |
| `ClientProfitability` | `src/components/admin/dashboard/ClientProfitability.tsx` | Dashboard widget: valore orario reale per cliente (`getClientProfitability` → contrattualizzato ÷ ore tracciate) con badge margine (Ottimo `bg-primary/10 text-primary` / In Linea `bg-muted`). |
| `YearSelector` | `src/components/admin/YearSelector.tsx` | Selettore anno restilizzato a pill (`rounded-full border-border bg-card shadow-card`), frecce ←/→, valore `font-mono`. Guida i dati year-scoped della dashboard via `?year=`. |
+25 -17
View File
@@ -18,6 +18,10 @@ const statusWordClass: Record<"upcoming" | "active" | "done", string> = {
* Horizontal milestone stepper — replica del "STEP PROGRESS TIMELINE" del mock.
* Un nodo per fase reale (non 5 fissi); la linea di fondo si riempie in base a
* global_progress_pct.
*
* Layout compatto (design-reference/Client-Portal-Progress-Bar): il nodo occupa
* due righe di testo — "Step N" e lo stato sulla stessa riga, il titolo fase
* sotto — con padding ridotto, così la barra ruba poca altezza alla dashboard.
*/
export function MilestoneStepper({
phases,
@@ -29,10 +33,10 @@ export function MilestoneStepper({
if (phases.length === 0) return null;
return (
<div className="bg-card border-b border-border-light py-8 px-6">
<div className="bg-card border-b border-border-light px-6 py-3">
<div className="relative mx-auto max-w-[1200px]">
{/* Linea di connessione + riempimento dinamico */}
<div className="absolute left-[5%] right-[5%] top-[15px] z-0 h-1 rounded-full bg-muted">
{/* Linea di connessione + riempimento dinamico (centrata sui cerchi) */}
<div className="absolute left-[5%] right-[5%] top-[14px] z-0 h-1 rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary transition-[width] duration-500 ease-[cubic-bezier(0.4,0,0.2,1)]"
style={{ width: `${globalProgress}%` }}
@@ -41,13 +45,13 @@ export function MilestoneStepper({
{/* Nodi milestone */}
<div
className="relative z-10 grid gap-4"
className="relative z-10 grid gap-3"
style={{ gridTemplateColumns: `repeat(${phases.length}, minmax(0, 1fr))` }}
>
{phases.map((phase, i) => {
const s = phase.status;
return (
<div key={phase.id} className="flex flex-col items-center text-center">
<div key={phase.id} className="flex min-w-0 flex-col items-center text-center">
<div
className={
"flex h-8 w-8 items-center justify-center rounded-full border-4 border-card text-xs font-bold shadow-md transition-all " +
@@ -60,20 +64,24 @@ export function MilestoneStepper({
>
{s === "done" ? "✓" : i + 1}
</div>
<span
className={
"mt-2 text-[11px] font-bold " +
(s === "upcoming" ? "text-muted-foreground" : "text-foreground")
}
>
Step {i + 1}
</span>
<span className="text-[10px] font-medium text-muted-foreground line-clamp-1">
<div className="mt-1.5 flex max-w-full items-baseline gap-1.5">
<span
className={
"text-[11px] font-bold leading-tight " +
(s === "upcoming" ? "text-muted-foreground" : "text-foreground")
}
>
Step {i + 1}
</span>
<span
className={`truncate text-[9px] font-bold uppercase leading-tight tracking-wide ${statusWordClass[s]}`}
>
{statusWord[s]}
</span>
</div>
<span className="max-w-full truncate text-[10px] font-medium leading-tight text-muted-foreground">
{phase.title}
</span>
<span className={`mt-1 text-[9px] font-bold uppercase ${statusWordClass[s]}`}>
{statusWord[s]}
</span>
</div>
);
})}