diff --git a/apps/api/src/routers/lead.ts b/apps/api/src/routers/lead.ts index b063de5..2799722 100644 --- a/apps/api/src/routers/lead.ts +++ b/apps/api/src/routers/lead.ts @@ -1,10 +1,11 @@ import { TRPCError } from '@trpc/server'; -import { asc, desc, eq, sql } from 'drizzle-orm'; +import { and, asc, desc, eq, inArray, isNotNull, lte, sql } from 'drizzle-orm'; import { lead, leadContact, leadTouchpoint, deal, area, user, project } from '@cockpit/db'; import type { Db } from '@cockpit/db'; import { LeadSchemas } from '@cockpit/shared'; import { router, protectedProcedure } from '../trpc.js'; import { parseCsv, normalizeHeader } from '../lib/csv.js'; +import { todayIso } from '../lib/werktag.js'; const SALES_AREA_SLUG = 'vertrieb'; @@ -30,6 +31,35 @@ export const leadRouter = router({ return rows.map((r) => ({ ...r.lead, ownerName: r.ownerName, projectName: r.projectName })); }), + /** + * Fällige Wiedervorlagen: aktive Leads (cold/warm/hot), deren geplanter nächster + * Schritt heute oder überfällig ist. naechsterSchrittDatum wurde erfasst, aber + * nie als Liste gebündelt — so geht keine warme Spur mehr unter. + */ + dueFollowUps: protectedProcedure.query(async ({ ctx }) => { + const today = todayIso(); + const rows = await ctx.db + .select({ + id: lead.id, + firma: lead.firma, + status: lead.status, + ownerName: user.name, + naechsterSchritt: lead.naechsterSchritt, + faelligAm: lead.naechsterSchrittDatum, + }) + .from(lead) + .leftJoin(user, eq(user.id, lead.ownerUserId)) + .where( + and( + inArray(lead.status, ['cold', 'warm', 'hot']), + isNotNull(lead.naechsterSchrittDatum), + lte(lead.naechsterSchrittDatum, today), + ), + ) + .orderBy(asc(lead.naechsterSchrittDatum)); + return rows; + }), + // Leads, die einem Projekt zugeordnet sind (Vertriebs-Sicht im Projekt). forProject: protectedProcedure .input(LeadSchemas.leadsForProjectInput) diff --git a/apps/web/src/routes/leads.tsx b/apps/web/src/routes/leads.tsx index bf07ffc..289dae4 100644 --- a/apps/web/src/routes/leads.tsx +++ b/apps/web/src/routes/leads.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { Link } from 'react-router'; +import { BellRing } from 'lucide-react'; import { clsx } from 'clsx'; import { toast } from 'sonner'; import { trpc } from '../lib/trpc'; @@ -41,6 +42,8 @@ export function LeadsRoute() { + + {leads.length === 0 ? ( @@ -75,6 +78,36 @@ export function LeadsRoute() { ); } +function DueFollowUps() { + const q = trpc.lead.dueFollowUps.useQuery(); + if (!q.data || q.data.length === 0) return null; + return ( + + +

+ + Fällige Wiedervorlagen ({q.data.length}) +

+
    + {q.data.map((l) => ( +
  • + + {l.firma} + {l.naechsterSchritt && ( + — {l.naechsterSchritt} + )} + + + {l.faelligAm} + +
  • + ))} +
+
+
+ ); +} + function LeadImportDialog({ onClose }: { onClose: () => void }) { const projectsQ = trpc.project.list.useQuery(); const projects = (projectsQ.data ?? []).map((r) => ({ id: r.project.id, name: r.project.name }));