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`. |
|
| `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. |
|
| `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. |
|
| `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). |
|
| `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`). |
|
| `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=`. |
|
| `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.
|
* 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
|
* Un nodo per fase reale (non 5 fissi); la linea di fondo si riempie in base a
|
||||||
* global_progress_pct.
|
* 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({
|
export function MilestoneStepper({
|
||||||
phases,
|
phases,
|
||||||
@@ -29,10 +33,10 @@ export function MilestoneStepper({
|
|||||||
if (phases.length === 0) return null;
|
if (phases.length === 0) return null;
|
||||||
|
|
||||||
return (
|
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]">
|
<div className="relative mx-auto max-w-[1200px]">
|
||||||
{/* Linea di connessione + riempimento dinamico */}
|
{/* Linea di connessione + riempimento dinamico (centrata sui cerchi) */}
|
||||||
<div className="absolute left-[5%] right-[5%] top-[15px] z-0 h-1 rounded-full bg-muted">
|
<div className="absolute left-[5%] right-[5%] top-[14px] z-0 h-1 rounded-full bg-muted">
|
||||||
<div
|
<div
|
||||||
className="h-full rounded-full bg-primary transition-[width] duration-500 ease-[cubic-bezier(0.4,0,0.2,1)]"
|
className="h-full rounded-full bg-primary transition-[width] duration-500 ease-[cubic-bezier(0.4,0,0.2,1)]"
|
||||||
style={{ width: `${globalProgress}%` }}
|
style={{ width: `${globalProgress}%` }}
|
||||||
@@ -41,13 +45,13 @@ export function MilestoneStepper({
|
|||||||
|
|
||||||
{/* Nodi milestone */}
|
{/* Nodi milestone */}
|
||||||
<div
|
<div
|
||||||
className="relative z-10 grid gap-4"
|
className="relative z-10 grid gap-3"
|
||||||
style={{ gridTemplateColumns: `repeat(${phases.length}, minmax(0, 1fr))` }}
|
style={{ gridTemplateColumns: `repeat(${phases.length}, minmax(0, 1fr))` }}
|
||||||
>
|
>
|
||||||
{phases.map((phase, i) => {
|
{phases.map((phase, i) => {
|
||||||
const s = phase.status;
|
const s = phase.status;
|
||||||
return (
|
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
|
<div
|
||||||
className={
|
className={
|
||||||
"flex h-8 w-8 items-center justify-center rounded-full border-4 border-card text-xs font-bold shadow-md transition-all " +
|
"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}
|
{s === "done" ? "✓" : i + 1}
|
||||||
</div>
|
</div>
|
||||||
<span
|
<div className="mt-1.5 flex max-w-full items-baseline gap-1.5">
|
||||||
className={
|
<span
|
||||||
"mt-2 text-[11px] font-bold " +
|
className={
|
||||||
(s === "upcoming" ? "text-muted-foreground" : "text-foreground")
|
"text-[11px] font-bold leading-tight " +
|
||||||
}
|
(s === "upcoming" ? "text-muted-foreground" : "text-foreground")
|
||||||
>
|
}
|
||||||
Step {i + 1}
|
>
|
||||||
</span>
|
Step {i + 1}
|
||||||
<span className="text-[10px] font-medium text-muted-foreground line-clamp-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}
|
{phase.title}
|
||||||
</span>
|
</span>
|
||||||
<span className={`mt-1 text-[9px] font-bold uppercase ${statusWordClass[s]}`}>
|
|
||||||
{statusWord[s]}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user