+
Fasi del Progetto
-
-
-
+
+
+
- Servizio in abbonamento ricorrente
-
+
Servizio in abbonamento ricorrente
+
Non ci sono fasi di progetto da tracciare. Le fasi compaiono qui quando viene avviata un'offerta a progetto.
@@ -141,12 +145,8 @@ export function ClientDashboard({ view, token, comments }: ClientDashboardProps)
-
-
-
- Questa è la tua dashboard privata — non condividere il link.
-
-
+
+ Questa è la tua dashboard privata — non condividere il link.
{/* Floating chat panel — FAB + slide-in panel */}
diff --git a/src/components/client/MilestoneStepper.tsx b/src/components/client/MilestoneStepper.tsx
new file mode 100644
index 0000000..de79e89
--- /dev/null
+++ b/src/components/client/MilestoneStepper.tsx
@@ -0,0 +1,84 @@
+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 (
+
+
+ {/* Linea di connessione + riempimento dinamico */}
+
+
+
+
+ {/* Nodi milestone */}
+
+ {phases.map((phase, i) => {
+ const s = phase.status;
+ return (
+
+
+ {s === "done" ? "✓" : i + 1}
+
+
+ Step {i + 1}
+
+
+ {phase.title}
+
+
+ {statusWord[s]}
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/src/components/client/OffersSection.tsx b/src/components/client/OffersSection.tsx
index 474a974..8e10b10 100644
--- a/src/components/client/OffersSection.tsx
+++ b/src/components/client/OffersSection.tsx
@@ -29,57 +29,57 @@ function OfferCard({ offer }: { offer: ActiveOffer }) {
const hasServices = offer.services.length > 0;
return (
-
- {/* Main info rows */}
-
- {/* Heading: macro public name */}
- {offer.offer_name}
+
+ {/* Main info rows — accento a barra sinistra (mock) */}
+
+
+ {/* Heading: macro public name */}
+ {offer.offer_name}
-
- {/* "Valore incluso" — hidden when 0 */}
- {hasPrice && (
-
- Valore incluso
-
- €{price.toFixed(2)}
-
-
- )}
+
+ {/* "Valore incluso" — hidden when 0 */}
+ {hasPrice && (
+
+ Valore incluso
+ €{price.toFixed(2)}
+
+ )}
- {/* "Prezzo finale" */}
- {offer.accepted_total && (
-
- Prezzo finale
-
- €{parseFloat(offer.accepted_total).toFixed(2)}
-
-
- )}
+ {/* "Prezzo finale" */}
+ {offer.accepted_total && (
+
+ Prezzo finale
+
+ €{parseFloat(offer.accepted_total).toFixed(2)}
+
+
+ )}
+
{/* Accordion "Cosa è compreso" — only when there are services */}
{hasServices && (
-
+
{open && (
-
+
{offer.services.map((svc, i) => (
-
- {svc.name}
+ {svc.name}
{svc.description && (
- {svc.description}
+ {svc.description}
)}
))}
diff --git a/src/components/client/PhaseCard.tsx b/src/components/client/PhaseCard.tsx
index 63eb358..ccac7d1 100644
--- a/src/components/client/PhaseCard.tsx
+++ b/src/components/client/PhaseCard.tsx
@@ -1,9 +1,6 @@
"use client";
import { useState } from "react";
-import { Progress } from "@/components/ui/progress";
-import { Badge } from "@/components/ui/badge";
-import { Card } from "@/components/ui/card";
import { ApproveButton } from "@/components/client/ApproveButton";
import { useChatContext } from "@/components/client/ChatProvider";
import type { ClientView } from "@/lib/client-view";
@@ -16,53 +13,38 @@ const phaseStatusLabel: Record<"upcoming" | "active" | "done", string> = {
done: "Completata",
};
+// Soft-tint status pill — replica del mock ("Quiet Luxury")
const phaseStatusStyle: Record<"upcoming" | "active" | "done", string> = {
- upcoming: "border-transparent bg-[#999999] text-white",
- active: "border-transparent bg-[#3b82f6] text-white",
- done: "border-transparent bg-[#1A463C] text-white",
+ upcoming: "bg-muted text-muted-foreground border border-border",
+ active:
+ "bg-amber-50 text-amber-700 border border-amber-100 dark:bg-amber-500/10 dark:text-amber-400 dark:border-amber-500/20",
+ done: "bg-emerald-50 text-emerald-700 border border-emerald-100 dark:bg-emerald-500/10 dark:text-emerald-400 dark:border-emerald-500/20",
};
-function PhaseStatusIcon({ status }: { status: "upcoming" | "active" | "done" }) {
- if (status === "done") {
- return (
-
- );
- }
- if (status === "active") {
- return (
-
- );
- }
- return (
-
- );
-}
+// Colore della barra di avanzamento per stato fase
+const phaseBarColor: Record<"upcoming" | "active" | "done", string> = {
+ upcoming: "bg-border",
+ active: "bg-amber-500",
+ done: "bg-emerald-600",
+};
function TaskStatusIcon({ status }: { status: "todo" | "in_progress" | "done" }) {
if (status === "done") {
return (
-
+
+ ✓
+
);
}
if (status === "in_progress") {
return (
-
+
+
+
);
}
return (
-
+
);
}
@@ -80,7 +62,7 @@ export function PhaseCard({
const doneCount = phase.tasks.filter((t) => t.status === "done").length;
return (
-
+
{/* Header — always visible, click to toggle */}
Servizio in abbonamento ricorrente
-+
Servizio in abbonamento ricorrente
+Non ci sono fasi di progetto da tracciare. Le fasi compaiono qui quando viene avviata un'offerta a progetto.
- Questa è la tua dashboard privata — non condividere il link. -
-{offer.offer_name}
+{offer.offer_name}
--
+
- - {svc.name} + {svc.name} {svc.description && ( - {svc.description} + {svc.description} )} ))} diff --git a/src/components/client/PhaseCard.tsx b/src/components/client/PhaseCard.tsx index 63eb358..ccac7d1 100644 --- a/src/components/client/PhaseCard.tsx +++ b/src/components/client/PhaseCard.tsx @@ -1,9 +1,6 @@ "use client"; import { useState } from "react"; -import { Progress } from "@/components/ui/progress"; -import { Badge } from "@/components/ui/badge"; -import { Card } from "@/components/ui/card"; import { ApproveButton } from "@/components/client/ApproveButton"; import { useChatContext } from "@/components/client/ChatProvider"; import type { ClientView } from "@/lib/client-view"; @@ -16,53 +13,38 @@ const phaseStatusLabel: Record<"upcoming" | "active" | "done", string> = { done: "Completata", }; +// Soft-tint status pill — replica del mock ("Quiet Luxury") const phaseStatusStyle: Record<"upcoming" | "active" | "done", string> = { - upcoming: "border-transparent bg-[#999999] text-white", - active: "border-transparent bg-[#3b82f6] text-white", - done: "border-transparent bg-[#1A463C] text-white", + upcoming: "bg-muted text-muted-foreground border border-border", + active: + "bg-amber-50 text-amber-700 border border-amber-100 dark:bg-amber-500/10 dark:text-amber-400 dark:border-amber-500/20", + done: "bg-emerald-50 text-emerald-700 border border-emerald-100 dark:bg-emerald-500/10 dark:text-emerald-400 dark:border-emerald-500/20", }; -function PhaseStatusIcon({ status }: { status: "upcoming" | "active" | "done" }) { - if (status === "done") { - return ( - - ); - } - if (status === "active") { - return ( - - ); - } - return ( - - ); -} +// Colore della barra di avanzamento per stato fase +const phaseBarColor: Record<"upcoming" | "active" | "done", string> = { + upcoming: "bg-border", + active: "bg-amber-500", + done: "bg-emerald-600", +}; function TaskStatusIcon({ status }: { status: "todo" | "in_progress" | "done" }) { if (status === "done") { return ( - + + ✓ + ); } if (status === "in_progress") { return ( - + + + ); } return ( - + ); } @@ -80,7 +62,7 @@ export function PhaseCard({ const doneCount = phase.tasks.filter((t) => t.status === "done").length; return ( -
-
{offer.services.map((svc, i) => (