11870e15d3
- New MilestoneStepper primitive (horizontal per-phase progress) - Token-migrate portal shell, phase cards, sidebar cards, kanban to dual-theme - Soft-tint status pills (emerald/amber/muted) replacing solid badges - embedded prop on ClientDashboard fixes double-header in multi-project tabs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
import type { ClientView } from "@/lib/client-view";
|
|
|
|
type Phase = ClientView["phases"][number];
|
|
|
|
const statusWord: Record<"upcoming" | "active" | "done", string> = {
|
|
done: "Completata",
|
|
active: "In corso",
|
|
upcoming: "Da iniziare",
|
|
};
|
|
|
|
const statusWordClass: Record<"upcoming" | "active" | "done", string> = {
|
|
done: "text-emerald-600 dark:text-emerald-400",
|
|
active: "text-amber-600 dark:text-amber-400",
|
|
upcoming: "text-muted-foreground",
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
export function MilestoneStepper({
|
|
phases,
|
|
globalProgress,
|
|
}: {
|
|
phases: Phase[];
|
|
globalProgress: number;
|
|
}) {
|
|
if (phases.length === 0) return null;
|
|
|
|
return (
|
|
<div className="bg-card border-b border-border-light py-8 px-6">
|
|
<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">
|
|
<div
|
|
className="h-full rounded-full bg-primary transition-[width] duration-500 ease-[cubic-bezier(0.4,0,0.2,1)]"
|
|
style={{ width: `${globalProgress}%` }}
|
|
/>
|
|
</div>
|
|
|
|
{/* Nodi milestone */}
|
|
<div
|
|
className="relative z-10 grid gap-4"
|
|
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
|
|
className={
|
|
"flex h-8 w-8 items-center justify-center rounded-full border-4 border-card text-xs font-bold shadow-md transition-all " +
|
|
(s === "done"
|
|
? "bg-primary text-primary-foreground"
|
|
: s === "active"
|
|
? "border-primary bg-card text-primary"
|
|
: "bg-muted text-muted-foreground")
|
|
}
|
|
>
|
|
{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">
|
|
{phase.title}
|
|
</span>
|
|
<span className={`mt-1 text-[9px] font-bold uppercase ${statusWordClass[s]}`}>
|
|
{statusWord[s]}
|
|
</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|