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:
@@ -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>
|
||||
@@ -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=`. |
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user