feat: Client Portal redesign to Quiet Luxury + milestone stepper
- 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>
This commit is contained in:
@@ -7,29 +7,29 @@ type Task = ClientView["phases"][number]["tasks"][number] & {
|
||||
phaseTitle: string;
|
||||
};
|
||||
|
||||
const COLUMNS: { id: "todo" | "in_progress" | "done"; label: string; dotClass: string; headerClass: string }[] = [
|
||||
{ id: "todo", label: "Da fare", dotClass: "bg-[#d4d4d8]", headerClass: "text-[#71717a]" },
|
||||
{ id: "in_progress", label: "In corso", dotClass: "bg-[#DEF168]", headerClass: "text-[#1A463C]" },
|
||||
{ id: "done", label: "Fatto", dotClass: "bg-[#1A463C]", headerClass: "text-[#1A463C]" },
|
||||
const COLUMNS: { id: "todo" | "in_progress" | "done"; label: string }[] = [
|
||||
{ id: "todo", label: "Da fare" },
|
||||
{ id: "in_progress", label: "In corso" },
|
||||
{ id: "done", label: "Fatto" },
|
||||
];
|
||||
|
||||
function TaskCard({ task, token }: { task: Task; token: string }) {
|
||||
return (
|
||||
<div className="bg-white rounded-lg border border-[#e5e7eb] px-3 py-2.5 shadow-sm">
|
||||
<p className="text-[10px] font-medium text-[#71717a] uppercase tracking-wide mb-1 truncate">
|
||||
<div className="bg-card rounded-lg border border-border-light px-4 py-4 shadow-sm hover:border-primary/30 hover:shadow-card-hover transition-all">
|
||||
<p className="text-[9px] font-semibold text-muted-foreground uppercase tracking-wide mb-1 truncate">
|
||||
{task.phaseTitle}
|
||||
</p>
|
||||
<p className={`text-sm font-medium leading-snug mb-2 ${task.status === "done" ? "line-through text-[#71717a]" : "text-[#1a1a1a]"}`}>
|
||||
<p className={`text-xs font-medium leading-snug ${task.status === "done" ? "line-through text-muted-foreground" : "text-foreground"}`}>
|
||||
{task.title}
|
||||
</p>
|
||||
{task.description && (
|
||||
<p className="text-xs text-[#71717a] mb-2 leading-snug">{task.description}</p>
|
||||
<p className="text-[11px] text-muted-foreground mt-2 leading-snug">{task.description}</p>
|
||||
)}
|
||||
{task.deliverables.length > 0 && (
|
||||
<ul className="space-y-1.5 mt-2 pt-2 border-t border-[#f4f4f5]">
|
||||
<ul className="space-y-1.5 mt-2 pt-2 border-t border-border-light">
|
||||
{task.deliverables.map((d) => (
|
||||
<li key={d.id} className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs text-[#666666] truncate">{d.title}</span>
|
||||
<span className="text-xs text-muted-foreground truncate">{d.title}</span>
|
||||
{(d.status === "pending" || d.status === "submitted" || d.approved_at !== null) && (
|
||||
<ApproveButton deliverableId={d.id} token={token} approvedAt={d.approved_at} />
|
||||
)}
|
||||
@@ -53,24 +53,21 @@ export function ClientKanban({ phases, token }: { phases: ClientView["phases"];
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
||||
{COLUMNS.map((col) => (
|
||||
<div key={col.id} className="flex flex-col rounded-xl border border-[#e5e7eb] bg-[#f9f9f9] min-h-[200px]">
|
||||
<div className={`px-4 py-3 flex items-center justify-between ${col.headerClass}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`w-2 h-2 rounded-full ${col.dotClass}`} />
|
||||
<span className="text-xs font-bold uppercase tracking-wider">{col.label}</span>
|
||||
</div>
|
||||
<span className="text-xs font-semibold tabular-nums bg-white rounded-full px-2 py-0.5 border border-[#e5e7eb]">
|
||||
<div key={col.id} className="flex flex-col gap-4 rounded-xl border border-border-light bg-muted/60 p-4 min-h-[300px]">
|
||||
<div className="flex items-center justify-between border-b border-border-light pb-2">
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-muted-foreground">{col.label}</span>
|
||||
<span className="text-[10px] font-bold tabular-nums bg-secondary text-secondary-foreground rounded-full px-2 py-0.5">
|
||||
{tasksByStatus[col.id].length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 p-3 space-y-2">
|
||||
<div className="flex-1 flex flex-col gap-3">
|
||||
{tasksByStatus[col.id].map((task) => (
|
||||
<TaskCard key={task.id} task={task} token={token} />
|
||||
))}
|
||||
{tasksByStatus[col.id].length === 0 && (
|
||||
<p className="text-xs text-[#d4d4d8] italic text-center py-10">Nessun task</p>
|
||||
<p className="text-xs text-muted-foreground italic text-center py-10">Nessun task</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,20 +18,20 @@ export function PhaseViewToggle({
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-bold text-[#1a1a1a]">Fasi del Progetto</h2>
|
||||
<div className="flex items-center gap-1 bg-[#f4f4f5] rounded-lg p-1">
|
||||
<h2 className="text-lg font-bold text-foreground tracking-tight">Fasi del Progetto</h2>
|
||||
<div className="flex items-center gap-1 bg-muted rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setView("timeline")}
|
||||
className={`px-3 py-1.5 rounded-md text-xs font-semibold transition-all ${
|
||||
view === "timeline" ? "bg-white text-[#1A463C] shadow-sm" : "text-[#71717a] hover:text-[#1a1a1a]"
|
||||
className={`px-5 py-1.5 rounded-md text-[11px] font-semibold transition-all ${
|
||||
view === "timeline" ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
Timeline
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setView("kanban")}
|
||||
className={`px-3 py-1.5 rounded-md text-xs font-semibold transition-all ${
|
||||
view === "kanban" ? "bg-white text-[#1A463C] shadow-sm" : "text-[#71717a] hover:text-[#1a1a1a]"
|
||||
className={`px-5 py-1.5 rounded-md text-[11px] font-semibold transition-all ${
|
||||
view === "kanban" ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
Kanban
|
||||
|
||||
Reference in New Issue
Block a user