Files
clienthub/src/app/admin/clients/[id]/page.tsx
T
simone 20f4fd8603 feat(05-03): add getClientActiveOffers query + active offers section on client detail page
- Add ClientActiveOfferSummary type to admin-queries.ts
- Add getClientActiveOffers() function joining project_offers with offer_micros
- Only selects public_name (never internal_name) per CLAUDE.md constraint
- Update /admin/clients/[id] to fetch active offers in parallel with project data
- Add Offerte Attive section at bottom of client detail page
2026-05-30 16:28:09 +02:00

136 lines
5.1 KiB
TypeScript

import { notFound } from "next/navigation";
import { getClientWithProjects, getClientActiveOffers } from "@/lib/admin-queries";
import { ClientActions } from "@/components/admin/ClientActions";
import Link from "next/link";
export const revalidate = 0;
export default async function ClientDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const [data, activeOffers] = await Promise.all([
getClientWithProjects(id),
getClientActiveOffers(id),
]);
if (!data) notFound();
const { projects, ...client } = data;
const activeProjects = projects.filter((p) => !p.archived);
const archivedProjects = projects.filter((p) => p.archived);
return (
<div>
<div className="mb-4">
<Link href="/admin" className="text-sm text-[#71717a] hover:text-[#1a1a1a]">
Clienti
</Link>
</div>
<div className="mb-6 flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-2xl font-bold text-[#1a1a1a]">{client.name}</h1>
<p className="text-sm text-[#71717a]">{client.brand_name}</p>
{client.archived && (
<span className="inline-block mt-1 text-xs font-medium bg-[#f4f4f5] text-[#71717a] px-2 py-0.5 rounded-full">
Archiviato
</span>
)}
</div>
<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"
rel="noopener noreferrer"
className="text-xs text-[#1A463C] hover:underline font-mono bg-[#1A463C]/5 px-2 py-1 rounded"
>
Link cliente
</a>
<ClientActions clientId={client.id} archived={client.archived ?? false} />
</div>
</div>
{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>
)}
{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>
)}
{activeOffers.length > 0 && (
<div className="mt-8">
<p className="text-xs text-[#71717a] font-semibold uppercase tracking-wider mb-3">
Offerte Attive ({activeOffers.length})
</p>
<div className="bg-white rounded-xl border border-[#e5e7eb] divide-y divide-[#e5e7eb]">
{activeOffers.map((offer) => (
<div key={offer.offer_id} className="flex items-center justify-between px-4 py-3">
<div>
<p className="text-sm font-medium text-[#1a1a1a]">{offer.public_name}</p>
<p className="text-xs text-[#71717a]">{offer.project_name}</p>
</div>
{offer.accepted_total && (
<span className="text-sm font-mono text-[#1a1a1a]">
&euro;{parseFloat(offer.accepted_total).toLocaleString("it-IT", { minimumFractionDigits: 2 })}
</span>
)}
</div>
))}
</div>
</div>
)}
</div>
);
}