diff --git a/src/lib/admin-queries.ts b/src/lib/admin-queries.ts index 0fb11a0..7e86f3c 100644 --- a/src/lib/admin-queries.ts +++ b/src/lib/admin-queries.ts @@ -23,7 +23,8 @@ import { leads, tags, } from "@/db/schema"; -import { eq, inArray, asc, isNull, sql, and } from "drizzle-orm"; +import { eq, inArray, asc, desc, isNull, sql, and } from "drizzle-orm"; +import { LEAD_STAGES } from "@/lib/lead-validators"; import type { Client, Project, @@ -878,4 +879,65 @@ export async function getAllOfferMacrosWithMicros(): Promise< .filter((micro) => micro.macro_id === macro.id) .sort((a, b) => a.sort_order - b.sort_order), })); +} + +// ── LeadWithTags — leads + tags (Phase 14 database-view) ───────────────────── +// Lead tags are stored in the polymorphic `tags` table, scoped by +// entity_type="leads". `status` is a fixed enum (LEAD_STAGES), not a +// dynamic pool — exposed via getLeadFieldOptions for the OptionSelect UI. + +const LEADS_TAG_ENTITY = "leads"; + +export type LeadWithTags = Lead & { tags: string[] }; + +export async function getLeadsWithTags(): Promise { + const rows = await db + .select({ + id: leads.id, + name: leads.name, + email: leads.email, + phone: leads.phone, + company: leads.company, + status: leads.status, + last_contact_date: leads.last_contact_date, + next_action: leads.next_action, + next_action_date: leads.next_action_date, + notes: leads.notes, + created_at: leads.created_at, + updated_at: leads.updated_at, + tag_name: tags.name, + }) + .from(leads) + .leftJoin( + tags, + and( + eq(tags.entity_id, leads.id), + eq(tags.entity_type, LEADS_TAG_ENTITY) + ) + ) + .orderBy(desc(leads.updated_at), asc(tags.name)); + + const leadMap = new Map(); + for (const row of rows) { + const { tag_name, ...leadFields } = row; + if (!leadMap.has(row.id)) leadMap.set(row.id, { ...leadFields, tags: [] }); + if (tag_name) leadMap.get(row.id)!.tags.push(tag_name); + } + return Array.from(leadMap.values()); +} + +export type LeadFieldOptions = { status: string[]; tags: string[] }; + +export async function getLeadFieldOptions(): Promise { + const tagRows = await db + .selectDistinct({ name: tags.name }) + .from(tags) + .where(eq(tags.entity_type, LEADS_TAG_ENTITY)); + + return { + status: [...LEAD_STAGES], + tags: tagRows + .map((r) => r.name) + .sort((a, b) => a.localeCompare(b, "it")), + }; } \ No newline at end of file