feat: client edit/delete/archive + time tracker + analytics time section
Schema: - clients.archived boolean (default false) - time_entries table (client_id, started_at, ended_at, duration_seconds) Client management: - /admin/clients/[id]/edit — form pre-compilato con nome, brand, brief - ClientActions: Modifica / Archivia / Elimina con doppia conferma - setClientArchived: toggle archiviazione senza perdere dati - deleteClient: elimina con cascade, redirect a /admin - Admin list: toggle "Mostra archiviati" via ?archived=1, righe archiviate opache Time tracker: - startTimer: crea sessione, ferma automaticamente quella precedente - stopTimer: chiude sessione, calcola duration_seconds - TimerCell: ▶/⏹ per ogni cliente, contatore live in secondi, totale cumulativo - Una sola sessione attiva alla volta su tutta la lista Analytics: - Sezione "Fatturato" (invariata) + sezione "Tempo tracciato" separata - Ore totali per anno + barre orizzontali per cliente - getTotalTrackedHours, getTimeByClient queries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { db } from "@/db";
|
||||
import { clients } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { updateClient } from "@/app/admin/clients/[id]/actions";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import Link from "next/link";
|
||||
|
||||
export default async function EditClientPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const { id } = await params;
|
||||
const [client] = await db.select().from(clients).where(eq(clients.id, id)).limit(1);
|
||||
if (!client) notFound();
|
||||
|
||||
return (
|
||||
<div className="max-w-lg">
|
||||
<div className="mb-6">
|
||||
<Link
|
||||
href={`/admin/clients/${id}`}
|
||||
className="text-sm text-[#71717a] hover:text-[#1a1a1a]"
|
||||
>
|
||||
← {client.name}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h1 className="text-xl font-bold text-[#1a1a1a] mb-6">Modifica cliente</h1>
|
||||
|
||||
<form
|
||||
action={async (fd: FormData) => {
|
||||
"use server";
|
||||
await updateClient(id, fd);
|
||||
}}
|
||||
className="space-y-5"
|
||||
>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="name">Nome cliente</Label>
|
||||
<Input id="name" name="name" defaultValue={client.name} required />
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="brand_name">Brand name</Label>
|
||||
<Input
|
||||
id="brand_name"
|
||||
name="brand_name"
|
||||
defaultValue={client.brand_name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="brief">Brief progetto</Label>
|
||||
<Textarea
|
||||
id="brief"
|
||||
name="brief"
|
||||
defaultValue={client.brief}
|
||||
rows={4}
|
||||
placeholder="Descrizione breve del progetto..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-2">
|
||||
<Button type="submit">Salva modifiche</Button>
|
||||
<Button asChild variant="ghost">
|
||||
<Link href={`/admin/clients/${id}`}>Annulla</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user