diff --git a/src/app/admin/leads/actions.ts b/src/app/admin/leads/actions.ts index f9edfd5..03239f8 100644 --- a/src/app/admin/leads/actions.ts +++ b/src/app/admin/leads/actions.ts @@ -2,7 +2,8 @@ import { z } from "zod"; import { db } from "@/db"; -import { leads, quotes, tags } from "@/db/schema"; +import { leads, quotes, tags, clientTranscripts } from "@/db/schema"; +import { nanoid } from "nanoid"; import { eq, and } from "drizzle-orm"; import { createLeadSchema, @@ -252,3 +253,58 @@ export async function renameLeadTag(oldValue: string, newValue: string) { revalidatePath("/admin/leads"); } + +// ── Transcript actions (Phase 20 — KB-02) ──────────────────────────────────── + +const addTranscriptSchema = z.object({ + lead_id: z.string().min(1), + title: z.string().optional(), + content: z.string().min(1, "Il testo del transcript è obbligatorio"), + call_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Data non valida (YYYY-MM-DD)"), +}); + +export async function addTranscript(data: z.infer) { + await requireAdmin(); + + const parsed = addTranscriptSchema.safeParse(data); + if (!parsed.success) { + return { success: false, error: parsed.error.issues[0].message }; + } + + try { + const [transcript] = await db + .insert(clientTranscripts) + .values({ + id: nanoid(), + lead_id: parsed.data.lead_id, + title: parsed.data.title || null, + content: parsed.data.content, + call_date: parsed.data.call_date, + }) + .returning(); + + revalidatePath(`/admin/leads/${parsed.data.lead_id}`); + return { success: true, transcript }; + } catch (error) { + console.error("addTranscript error:", error); + return { success: false, error: "Errore nel salvataggio del transcript" }; + } +} + +export async function deleteTranscript(transcriptId: string, leadId: string) { + await requireAdmin(); + + if (!transcriptId) throw new Error("ID transcript richiesto"); + + try { + await db + .delete(clientTranscripts) + .where(eq(clientTranscripts.id, transcriptId)); + + revalidatePath(`/admin/leads/${leadId}`); + return { success: true }; + } catch (error) { + console.error("deleteTranscript error:", error); + return { success: false, error: "Errore nell'eliminazione del transcript" }; + } +} diff --git a/src/db/schema.ts b/src/db/schema.ts index 5079418..3ba13aa 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -498,10 +498,31 @@ export const reminders = pgTable("reminders", { .defaultNow(), }); +// ============ CLIENT TRANSCRIPTS TABLE (Knowledge Base — Phase 20) ============ +// Transcript datati delle call, multipli per lead o cliente. +// lead_id e client_id sono entrambi nullable (D-01): un transcript può appartenere +// a un lead pre-conversione (Phase 20 UI) o a un cliente post-conversione (futuro). +export const clientTranscripts = pgTable("client_transcripts", { + id: text("id") + .primaryKey() + .$defaultFn(() => nanoid()), + lead_id: text("lead_id") + .references(() => leads.id, { onDelete: "cascade" }), + client_id: text("client_id") + .references(() => clients.id, { onDelete: "cascade" }), + title: text("title"), + content: text("content").notNull(), + call_date: text("call_date").notNull(), // "YYYY-MM-DD" — coerente con + created_at: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), +}); + // ============ RELATIONS ============ export const clientsRelations = relations(clients, ({ many }) => ({ projects: many(projects), + transcripts: many(clientTranscripts), })); export const projectsRelations = relations(projects, ({ one, many }) => ({ @@ -632,6 +653,7 @@ export const leadsRelations = relations(leads, ({ many }) => ({ quotes: many(quotes), activities: many(activities), reminders: many(reminders), + transcripts: many(clientTranscripts), })); export const activitiesRelations = relations(activities, ({ one }) => ({ @@ -642,6 +664,17 @@ export const remindersRelations = relations(reminders, ({ one }) => ({ lead: one(leads, { fields: [reminders.lead_id], references: [leads.id] }), })); +export const clientTranscriptsRelations = relations(clientTranscripts, ({ one }) => ({ + lead: one(leads, { + fields: [clientTranscripts.lead_id], + references: [leads.id], + }), + client: one(clients, { + fields: [clientTranscripts.client_id], + references: [clients.id], + }), +})); + export const quotesRelations = relations(quotes, ({ one, many }) => ({ lead: one(leads, { fields: [quotes.lead_id], references: [leads.id] }), client: one(clients, { fields: [quotes.client_id], references: [clients.id] }), @@ -704,4 +737,6 @@ export type NewLead = typeof leads.$inferInsert; export type Activity = typeof activities.$inferSelect; export type NewActivity = typeof activities.$inferInsert; export type Reminder = typeof reminders.$inferSelect; -export type NewReminder = typeof reminders.$inferInsert; \ No newline at end of file +export type NewReminder = typeof reminders.$inferInsert; +export type ClientTranscript = typeof clientTranscripts.$inferSelect; +export type NewClientTranscript = typeof clientTranscripts.$inferInsert; \ No newline at end of file diff --git a/src/lib/lead-service.ts b/src/lib/lead-service.ts index d436229..ca312ad 100644 --- a/src/lib/lead-service.ts +++ b/src/lib/lead-service.ts @@ -1,6 +1,6 @@ import { eq, and, isNull, desc, gte, lte, ilike, or } from "drizzle-orm"; import { db } from "@/db"; -import { leads, activities, reminders, quotes } from "@/db/schema"; +import { leads, activities, reminders, quotes, clientTranscripts } from "@/db/schema"; // Get all leads export async function getAllLeads(limit: number = 100, offset: number = 0) { @@ -209,3 +209,13 @@ export async function getQuotesByLeadId(leadId: string) { .where(eq(quotes.lead_id, leadId)) .orderBy(desc(quotes.created_at)); } + +// Get transcript log for a lead — call_date DESC (D-05) +// Returns all fields including full content (Phase 21 reads them in bulk). +export async function getTranscripts(leadId: string) { + return await db + .select() + .from(clientTranscripts) + .where(eq(clientTranscripts.lead_id, leadId)) + .orderBy(desc(clientTranscripts.call_date)); +}