feat(10-02): implement lead management UI with list, detail, and form pages

- Create /admin/leads list page with LeadTable component showing all leads
- Implement /admin/leads/[id] detail page with activity log and reminders
- Add LeadForm component (CreateLeadModal and EditLeadModal) for CRUD operations
- Create server actions (createLead, updateLead, deleteLead) with Zod validation
- Add missing UI components (dialog.tsx, form.tsx) from shadcn
- Install date-fns for date formatting with Italian locale support
- All routes compile successfully with npm run build

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 12:32:07 +02:00
parent 1aed8a0909
commit 2d9cf1ac9d
10 changed files with 1372 additions and 6 deletions
+82
View File
@@ -0,0 +1,82 @@
"use server";
import { z } from "zod";
import { db } from "@/db";
import { leads } from "@/db/schema";
import { eq } from "drizzle-orm";
import { createLeadSchema, updateLeadSchema } from "@/lib/lead-validators";
import { revalidatePath } from "next/cache";
export async function createLead(data: z.infer<typeof createLeadSchema>) {
const parsed = createLeadSchema.safeParse(data);
if (!parsed.success) {
return { success: false, error: parsed.error.issues[0].message };
}
try {
const [lead] = await db
.insert(leads)
.values({
name: parsed.data.name,
email: parsed.data.email || null,
phone: parsed.data.phone || null,
company: parsed.data.company || null,
status: parsed.data.status || "contacted",
notes: parsed.data.notes || null,
})
.returning();
revalidatePath("/admin/leads");
return { success: true, lead };
} catch (error) {
console.error("createLead error:", error);
return { success: false, error: "Errore nella creazione del lead" };
}
}
export async function updateLead(
id: string,
data: z.infer<typeof updateLeadSchema>
) {
const parsed = updateLeadSchema.safeParse(data);
if (!parsed.success) {
return { success: false, error: parsed.error.issues[0].message };
}
try {
const updateData: Record<string, any> = { updated_at: new Date() };
if (parsed.data.name !== undefined) updateData.name = parsed.data.name;
if (parsed.data.email !== undefined) updateData.email = parsed.data.email || null;
if (parsed.data.phone !== undefined) updateData.phone = parsed.data.phone || null;
if (parsed.data.company !== undefined) updateData.company = parsed.data.company || null;
if (parsed.data.status !== undefined) updateData.status = parsed.data.status;
if (parsed.data.notes !== undefined) updateData.notes = parsed.data.notes || null;
const [updated] = await db
.update(leads)
.set(updateData)
.where(eq(leads.id, id))
.returning();
revalidatePath("/admin/leads");
revalidatePath(`/admin/leads/${id}`);
return { success: true, lead: updated };
} catch (error) {
console.error("updateLead error:", error);
return { success: false, error: "Errore nell'aggiornamento" };
}
}
export async function deleteLead(id: string) {
try {
await db.delete(leads).where(eq(leads.id, id));
revalidatePath("/admin/leads");
return { success: true };
} catch (error) {
console.error("deleteLead error:", error);
return { success: false, error: "Errore nell'eliminazione" };
}
}