diff --git a/design-reference/DESIGN-SYSTEM.md b/design-reference/DESIGN-SYSTEM.md index fef7eb5..85db563 100644 --- a/design-reference/DESIGN-SYSTEM.md +++ b/design-reference/DESIGN-SYSTEM.md @@ -282,6 +282,27 @@ uppercase muted (`bg-muted/50`, `text-[11px] font-semibold uppercase tracking-wi - **Empty state**: `border-dashed border-border rounded-xl`, `FileText` muted + CTA "Genera il primo preventivo →". Footer "Mostrando N di N preventivi generati". +### Progetti (lista) notes + +`/admin/projects` migrata a "Quiet Luxury" (replica `design-reference/pagina-progetti`). +Stesso contenitore tabella di Preventivi (`bg-card rounded-xl border-border shadow-card`, +header `bg-muted/50` uppercase muted, righe `hover:bg-muted/40` + `divide-y divide-border`). + +- **Header**: `PageHeader` "Progetti" + sottotitolo (redditività oraria), azione `Button` + "Nuovo Progetto" (`+`, link a `/admin/projects/new`). +- **Colonne**: Progetto (link → dettaglio + cliente muted, badge "Archiviato" se archiviato), + Valore Totale (`text-right font-mono`), Acconto (badge centrato), Saldo (badge centrato), + Timer (`TimerCell` centrato), Redditività €/H (`text-right`). +- **Badge pagamento** (`ProjectRow`): pill rounded-full `text-[10px] uppercase border` dual + light/dark — saldato→emerald, da_saldare→rose, inviata→amber; prefissati "Acconto:"/"Saldo:". + Pagamento assente → `—` muted. +- **TimerCell**: ora una pill `rounded-full border font-mono` — idle neutro (`bg-muted`, + play triangle su `bg-foreground/10`), running emerald (`bg-emerald-50/60` dark + `emerald-950/30`, pausa su `bg-emerald-500`), contatore live. Stessa pill riusata nel + `TimerTab` del dettaglio progetto. +- **Redditività**: `null` (0h tracciate) → `—` muted; `>0` → emerald bold con dot + `bg-emerald-500`; `=0` → mono muted. Footer "Mostrando N di N progetti attivi". + ### Client Portal restyle notes The `/client/[token]` portal was migrated to the same "Quiet Luxury" tokens diff --git a/design-reference/pagina-progetti b/design-reference/pagina-progetti new file mode 100644 index 0000000..e10ab35 --- /dev/null +++ b/design-reference/pagina-progetti @@ -0,0 +1,332 @@ + + + + + + Progetti — Luxury Admin CRM + + + + + + + + + + + + + + + +
+ + +
+
+ + + Gestione Progetti +
+
+ Admin Panel +
+
+ + +
+ + +
+
+

Progetti

+

Traccia le attività, monitora gli incassi e verifica la redditività oraria effettiva

+
+ + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProgettoValore TotaleAccontoSaldoTimerRedditività (€/H)
+
Protocollo Estetico
+
George Vlad
+
€3.500,00 + + Acconto: Saldato + + + + Saldo: Da saldare + + +
+ + 0:00 +
+
+
Caruso Speaker
+
Gian Luca Caruso
+
€5.000,00 + + Acconto: Saldato + + + + Saldo: Da saldare + + +
+ + 0:00 +
+
+
Incarichi Online
+
George Vlad
+
€3.500,00 + + Acconto: Da saldare + + + + Saldo: Da saldare + + +
+ + 0:00 +
+
+
Teckell
+
Gianfranco Barban
+
€0,00 +
+ + 1:30:00 +
+
€0,00/h
+
Rossi Inc
+
Mario Rossi
+
€7.000,00 + + Acconto: Da saldare + + + + Saldo: Da saldare + + +
+ + + 30:00:08 +
+
+
+ + €233,32/h +
+
+
+ + +
+ Mostrando 5 di 5 progetti attivi +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/src/app/admin/projects/page.tsx b/src/app/admin/projects/page.tsx index 1537364..aaaa7bc 100644 --- a/src/app/admin/projects/page.tsx +++ b/src/app/admin/projects/page.tsx @@ -1,7 +1,9 @@ import { getAllProjectsWithPayments } from "@/lib/admin-queries"; import { ProjectRow } from "@/components/admin/ProjectRow"; import Link from "next/link"; +import { FolderOpen, Plus } from "lucide-react"; import { PageHeader } from "@/components/admin/PageHeader"; +import { Button } from "@/components/ui/button"; export const revalidate = 0; @@ -9,43 +11,57 @@ export default async function ProjectsPage() { const projects = await getAllProjectsWithPayments(); const projectiAction = ( - - + Nuovo Progetto - + ); return (
- + {projects.length === 0 ? ( -
-

Nessun progetto ancora. Creane uno dal dettaglio di un cliente.

+
+ +

+ Nessun progetto ancora. Creane uno dal dettaglio di un cliente. +

) : ( -
- - - - - - - - - - - - - {projects.map((project) => ( - - ))} - -
ProgettoValoreAccontoSaldoTimer€/h
+
+
+ + + + + + + + + + + + + {projects.map((project) => ( + + ))} + +
ProgettoValore TotaleAccontoSaldoTimerRedditività (€/H)
+
+ +
+ Mostrando {projects.length} di {projects.length}{" "} + {projects.length === 1 ? "progetto attivo" : "progetti attivi"} +
)}
); -} \ No newline at end of file +} diff --git a/src/components/admin/ProjectRow.tsx b/src/components/admin/ProjectRow.tsx index 0f422bb..af448ae 100644 --- a/src/components/admin/ProjectRow.tsx +++ b/src/components/admin/ProjectRow.tsx @@ -1,66 +1,111 @@ import Link from "next/link"; -import { Badge } from "@/components/ui/badge"; import { TimerCell } from "@/components/admin/TimerCell"; import type { ProjectWithPayments } from "@/lib/admin-queries"; const statusConfig: Record = { - da_saldare: { label: "Da saldare", className: "bg-red-100 text-red-700 border-transparent" }, - inviata: { label: "Inviata", className: "bg-[#DEF168]/30 text-[#1A463C] border-transparent" }, - saldato: { label: "Saldato", className: "bg-[#1A463C]/10 text-[#1A463C] border-transparent font-medium" }, + da_saldare: { + label: "Da saldare", + className: + "bg-rose-50 text-rose-700 border-rose-100 dark:bg-rose-950/40 dark:text-rose-300 dark:border-rose-900", + }, + inviata: { + label: "Inviata", + className: + "bg-amber-50 text-amber-600 border-amber-100 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-900", + }, + saldato: { + label: "Saldato", + className: + "bg-emerald-50 text-emerald-700 border-emerald-100 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-900", + }, }; +function PaymentBadge({ prefix, status }: { prefix: string; status: string }) { + const info = statusConfig[status] ?? { + label: status, + className: "bg-muted text-muted-foreground border-border", + }; + return ( + + {prefix}: {info.label} + + ); +} + export function ProjectRow({ project }: { project: ProjectWithPayments }) { const acconto = project.payments.find((p) => p.label.toLowerCase().includes("acconto")); - const saldo = project.payments.find((p) => p.label.toLowerCase().includes("saldo")); + const saldo = project.payments.find((p) => p.label.toLowerCase().includes("saldo")); const hours = project.totalTrackedSeconds / 3600; const eurPerHour = hours > 0 ? parseFloat(project.accepted_total) / hours : null; return ( - - + + {project.name} -

{project.client.name}

+

{project.client.name}

{project.archived && ( - + Archiviato )} - + + €{parseFloat(project.accepted_total).toLocaleString("it-IT", { minimumFractionDigits: 2 })} - - {acconto && ( - - Acconto: {statusConfig[acconto.status]?.label ?? acconto.status} - + + + {acconto ? ( + + ) : ( + )} - - {saldo && ( - - Saldo: {statusConfig[saldo.status]?.label ?? saldo.status} - + + + {saldo ? ( + + ) : ( + )} - - + + +
+ +
- - {eurPerHour !== null ? `€${eurPerHour.toFixed(2)}/h` : "—"} + + + {eurPerHour === null ? ( + + ) : eurPerHour > 0 ? ( + + + €{eurPerHour.toFixed(2)}/h + + ) : ( + + €{eurPerHour.toFixed(2)}/h + + )} ); -} \ No newline at end of file +} diff --git a/src/components/admin/TimerCell.tsx b/src/components/admin/TimerCell.tsx index 1dc7e9d..ce657d7 100644 --- a/src/components/admin/TimerCell.tsx +++ b/src/components/admin/TimerCell.tsx @@ -60,36 +60,37 @@ export function TimerCell({ const displayTotal = formatDuration(totalTrackedSeconds + (isRunning ? elapsed : 0)); return ( -
+
- - {isRunning ? formatDuration(elapsed) : displayTotal} - + {isRunning ? formatDuration(elapsed) : displayTotal}
); } \ No newline at end of file diff --git a/src/components/admin/conversazioni/ConversationsView.tsx b/src/components/admin/conversazioni/ConversationsView.tsx index 9891907..f819d30 100644 --- a/src/components/admin/conversazioni/ConversationsView.tsx +++ b/src/components/admin/conversazioni/ConversationsView.tsx @@ -109,8 +109,8 @@ function ConversationListItem({ href={`/admin/conversazioni?c=${conv.clientId}`} className={cn( "block p-4 transition-colors hover:bg-muted/40", - active && "bg-muted/60 border-l-2 border-primary", - !active && "border-l-2 border-transparent" + active && "bg-muted/60 border-l-[3px] border-l-primary", + !active && "border-l-[3px] border-l-transparent" )} >