feat(04-02): admin projects list + client detail project cards
- NavBar: add Progetti (/admin/projects) and Impostazioni links - ProjectRow: project table row with €/h calc (accepted_total / tracked hours) - /admin/projects: table listing all projects with value, payments, timer, €/h - /admin/projects/new: create form with client select + ?client_id pre-selection - project-actions: createProject, archiveProject, unarchiveProject, updateProjectAcceptedTotal - /admin/clients/[id]: rewritten to show project cards grid instead of tabbed workspace - getAllClientsWithPayments: add projectBrands[] and ltv (sum of project accepted_totals) - ClientRow: show project brand names under client name, LTV column replaces Totale Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,5 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { getClientFullDetail } from "@/lib/admin-queries";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { PhasesTab } from "@/components/admin/tabs/PhasesTab";
|
||||
import { PaymentsTab } from "@/components/admin/tabs/PaymentsTab";
|
||||
import { DocumentsTab } from "@/components/admin/tabs/DocumentsTab";
|
||||
import { CommentsTab } from "@/components/admin/tabs/CommentsTab";
|
||||
import { QuoteTab } from "@/components/admin/tabs/QuoteTab";
|
||||
import { PhasesViewToggle } from "@/components/admin/kanban/PhasesViewToggle";
|
||||
import { getClientWithProjects } from "@/lib/admin-queries";
|
||||
import { ClientActions } from "@/components/admin/ClientActions";
|
||||
import Link from "next/link";
|
||||
|
||||
@@ -18,10 +11,12 @@ export default async function ClientDetailPage({
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const { id } = await params;
|
||||
const detail = await getClientFullDetail(id);
|
||||
if (!detail) notFound();
|
||||
const data = await getClientWithProjects(id);
|
||||
if (!data) notFound();
|
||||
|
||||
const { client, phases, payments, documents, comments, quoteItems, activeServices } = detail;
|
||||
const { projects, ...client } = data;
|
||||
const activeProjects = projects.filter((p) => !p.archived);
|
||||
const archivedProjects = projects.filter((p) => p.archived);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -41,7 +36,13 @@ export default async function ClientDetailPage({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Link
|
||||
href={`/admin/projects/new?client_id=${id}`}
|
||||
className="text-sm bg-[#1A463C] text-white px-4 py-2 rounded-lg hover:bg-[#1A463C]/90 transition-colors"
|
||||
>
|
||||
+ Nuovo Progetto
|
||||
</Link>
|
||||
<a
|
||||
href={`/client/${client.token}`}
|
||||
target="_blank"
|
||||
@@ -54,44 +55,56 @@ export default async function ClientDetailPage({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="phases" className="w-full">
|
||||
<TabsList className="mb-6">
|
||||
<TabsTrigger value="phases">Fasi & Task</TabsTrigger>
|
||||
<TabsTrigger value="payments">Pagamenti</TabsTrigger>
|
||||
<TabsTrigger value="documents">Documenti</TabsTrigger>
|
||||
<TabsTrigger value="comments">Commenti</TabsTrigger>
|
||||
<TabsTrigger value="quote">Preventivo</TabsTrigger>
|
||||
</TabsList>
|
||||
{activeProjects.length === 0 && (
|
||||
<div className="bg-white rounded-xl border border-[#e5e7eb] p-12 text-center">
|
||||
<p className="text-[#71717a] mb-4">Nessun progetto ancora per questo cliente.</p>
|
||||
<Link
|
||||
href={`/admin/projects/new?client_id=${id}`}
|
||||
className="text-sm text-[#1A463C] hover:underline"
|
||||
>
|
||||
+ Crea il primo progetto
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TabsContent value="phases">
|
||||
<PhasesViewToggle
|
||||
listView={<PhasesTab phases={phases} clientId={client.id} />}
|
||||
phases={phases}
|
||||
clientId={client.id}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="payments">
|
||||
<PaymentsTab
|
||||
payments={payments}
|
||||
acceptedTotal={client.accepted_total ?? "0"}
|
||||
clientId={client.id}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="documents">
|
||||
<DocumentsTab documents={documents} clientId={client.id} />
|
||||
</TabsContent>
|
||||
<TabsContent value="comments">
|
||||
<CommentsTab comments={comments} phases={phases} clientId={client.id} />
|
||||
</TabsContent>
|
||||
<TabsContent value="quote">
|
||||
<QuoteTab
|
||||
clientId={client.id}
|
||||
items={quoteItems}
|
||||
activeServices={activeServices}
|
||||
acceptedTotal={client.accepted_total ?? "0"}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
{activeProjects.length > 0 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{activeProjects.map((project) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
href={`/admin/projects/${project.id}`}
|
||||
className="bg-white border border-[#e5e7eb] rounded-xl p-5 hover:shadow-md hover:border-[#1A463C]/20 transition-all"
|
||||
>
|
||||
<h3 className="font-bold text-[#1a1a1a] mb-1">{project.name}</h3>
|
||||
<p className="text-sm text-[#71717a]">
|
||||
{project.accepted_total && parseFloat(project.accepted_total) > 0
|
||||
? `€${parseFloat(project.accepted_total).toLocaleString("it-IT", { minimumFractionDigits: 2 })}`
|
||||
: "Preventivo non impostato"}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{archivedProjects.length > 0 && (
|
||||
<div className="mt-8">
|
||||
<p className="text-xs text-[#71717a] font-semibold uppercase tracking-wider mb-3">
|
||||
Archiviati ({archivedProjects.length})
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 opacity-60">
|
||||
{archivedProjects.map((project) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
href={`/admin/projects/${project.id}`}
|
||||
className="bg-white border border-[#e5e7eb] rounded-xl p-5 hover:shadow-md transition-all"
|
||||
>
|
||||
<h3 className="font-bold text-[#1a1a1a] mb-1">{project.name}</h3>
|
||||
<p className="text-xs text-[#71717a]">Archiviato</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user