feat: LeadVault - zentrale Lead-Datenbank mit CRM-Funktionen

- Prisma-Schema: Lead + LeadEvent Modelle (Migration 20260320)
- lib/services/leadVault.ts: sinkLeadsToVault mit Deduplizierung
- Auto-Sync: alle 4 Pipelines schreiben Leads in LeadVault
- GET /api/leads: Filter, Sortierung, Pagination (Server-side)
- PATCH/DELETE /api/leads/[id]: Status, Priorität, Tags, Notizen
- POST /api/leads/bulk: Bulk-Aktionen für mehrere Leads
- GET /api/leads/stats: Statistiken + 7-Tage-Sparkline
- POST /api/leads/quick-serp: SERP-Capture ohne Enrichment
- GET /api/leads/export: CSV-Export mit allen Feldern
- app/leadvault/page.tsx: vollständige UI mit Stats, Quick SERP,
  Filter-Leiste, sortierbare Tabelle, Bulk-Aktionen, Side Panel
- Sidebar: LeadVault-Eintrag mit Live-Badge (neue Leads)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Timo Uttenweiler
2026-03-20 17:33:12 +01:00
parent 6711633a5d
commit 042fbeb672
16 changed files with 1800 additions and 5 deletions

View File

@@ -0,0 +1,56 @@
-- CreateTable
CREATE TABLE "Lead" (
"id" TEXT NOT NULL PRIMARY KEY,
"domain" TEXT,
"companyName" TEXT,
"contactName" TEXT,
"contactTitle" TEXT,
"email" TEXT,
"linkedinUrl" TEXT,
"phone" TEXT,
"sourceTab" TEXT NOT NULL,
"sourceTerm" TEXT,
"sourceJobId" TEXT,
"serpTitle" TEXT,
"serpSnippet" TEXT,
"serpRank" INTEGER,
"serpUrl" TEXT,
"emailConfidence" REAL,
"status" TEXT NOT NULL DEFAULT 'new',
"priority" TEXT NOT NULL DEFAULT 'normal',
"notes" TEXT,
"tags" TEXT,
"country" TEXT,
"headcount" TEXT,
"industry" TEXT,
"capturedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"contactedAt" DATETIME,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "LeadEvent" (
"id" TEXT NOT NULL PRIMARY KEY,
"leadId" TEXT NOT NULL,
"event" TEXT NOT NULL,
"at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LeadEvent_leadId_fkey" FOREIGN KEY ("leadId") REFERENCES "Lead" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE INDEX "Lead_domain_idx" ON "Lead"("domain");
-- CreateIndex
CREATE INDEX "Lead_status_idx" ON "Lead"("status");
-- CreateIndex
CREATE INDEX "Lead_sourceTab_idx" ON "Lead"("sourceTab");
-- CreateIndex
CREATE INDEX "Lead_capturedAt_idx" ON "Lead"("capturedAt");
-- CreateIndex
CREATE INDEX "Lead_email_idx" ON "Lead"("email");
-- CreateIndex
CREATE INDEX "LeadEvent_leadId_idx" ON "LeadEvent"("leadId");

View File

@@ -41,3 +41,57 @@ model LeadResult {
source String?
createdAt DateTime @default(now())
}
model Lead {
id String @id @default(cuid())
domain String?
companyName String?
contactName String?
contactTitle String?
email String?
linkedinUrl String?
phone String?
sourceTab String
sourceTerm String?
sourceJobId String?
serpTitle String?
serpSnippet String?
serpRank Int?
serpUrl String?
emailConfidence Float?
status String @default("new")
priority String @default("normal")
notes String?
tags String?
country String?
headcount String?
industry String?
capturedAt DateTime @default(now())
contactedAt DateTime?
updatedAt DateTime @updatedAt
events LeadEvent[]
@@index([domain])
@@index([status])
@@index([sourceTab])
@@index([capturedAt])
@@index([email])
}
model LeadEvent {
id String @id @default(cuid())
leadId String
lead Lead @relation(fields: [leadId], references: [id], onDelete: Cascade)
event String
at DateTime @default(now())
@@index([leadId])
}