Files
pitchfast/lib/dashboard-model.ts
2026-06-05 16:47:22 +02:00

435 lines
9.8 KiB
TypeScript

import {
Gauge,
MailCheck,
MapPinned,
ShieldCheck,
UsersRound,
type LucideIcon,
} from "lucide-react";
export { dashboardNavigation } from "./dashboard-navigation";
export type PipelineStage = {
title: string;
description: string;
count: number;
meta: string;
icon: LucideIcon;
};
export type DashboardKpi = {
label: string;
value: string;
detail: string;
};
export type ReviewQueueItem = {
title: string;
company: string;
detail: string;
};
export type LeadPriority = "high" | "medium" | "low" | "defer" | "blocked";
export type LeadContactStatus =
| "new"
| "missing_contact"
| "audit_ready"
| "outreach_ready"
| "contacted"
| "replied"
| "do_not_contact";
export type LeadBlacklistStatus = "clear" | "blocked";
export type LeadDuplicateStatus =
| "unchecked"
| "unique"
| "possible_duplicate"
| "duplicate";
export type OutreachApprovalStatus = "draft" | "approved" | "rejected";
export type OutreachSendStatus = "not_sent" | "queued" | "sent" | "failed";
export type OutreachResponseStatus =
| "none"
| "manual_reply_recorded"
| "no_interest"
| "follow_up_needed";
export type OutreachSalesStatus =
| "follow_up_planned"
| "follow_up_sent"
| "reply_received"
| "not_interested"
| "later"
| "meeting_scheduled"
| "proposal_requested"
| "proposal_sent"
| "won"
| "lost"
| "do_not_pursue";
export type LeadFunnelStageId =
| "missing_contact"
| "audit_ready"
| "review_open"
| "contacted"
| "follow_up"
| "deferred";
export type LeadFunnelStage = {
id: LeadFunnelStageId;
title: string;
description: string;
};
export type LeadFunnelOutreach = {
approvalStatus?: OutreachApprovalStatus | null;
sendStatus?: OutreachSendStatus | null;
responseStatus?: OutreachResponseStatus | null;
salesStatus?: OutreachSalesStatus | null;
};
export type LeadFunnelInput = {
id: string;
companyName: string;
niche?: string | null;
address?: string | null;
city?: string | null;
postalCode?: string | null;
priority: LeadPriority;
contactStatus: LeadContactStatus;
blacklistStatus: LeadBlacklistStatus;
email?: string | null;
phone?: string | null;
contactPerson?: string | null;
websiteDomain?: string | null;
outreach?: LeadFunnelOutreach | null;
};
export type LeadFunnelCard = {
id: string;
stageId: LeadFunnelStageId;
company: string;
niche: string;
location: string;
priorityLabel: string;
contactStatusLabel: string;
nextAction: string;
websiteDomain?: string | null;
contactDetail: string;
};
export type LeadFunnelGroup = {
stage: LeadFunnelStage;
cards: LeadFunnelCard[];
};
export const leadFunnelStages: LeadFunnelStage[] = [
{
id: "missing_contact",
title: "Kontakt fehlt",
description: "Leads ohne belastbare E-Mail oder Telefonnummer.",
},
{
id: "audit_ready",
title: "Audit bereit",
description: "Analyse ist vorbereitet und braucht Einordnung.",
},
{
id: "review_open",
title: "Freigabe offen",
description: "Kontaktstrategie, Audit-Link oder Text warten auf Review.",
},
{
id: "contacted",
title: "Kontaktiert",
description: "Erstkontakt ist erfolgt; Antwort wird manuell gepflegt.",
},
{
id: "follow_up",
title: "Follow-up",
description: "Respektvolle Wiedervorlage ohne automatischen Versand.",
},
{
id: "deferred",
title: "Zurückgestellt",
description: "Nicht jetzt kontaktieren oder bewusst pausieren.",
},
];
export const leadPriorityLabels: Record<LeadPriority, string> = {
high: "Hoch",
medium: "Mittel",
low: "Niedrig",
defer: "Zurückstellen",
blocked: "Gesperrt",
};
export const leadContactStatusLabels: Record<LeadContactStatus, string> = {
new: "Neu",
missing_contact: "Kontakt fehlt",
audit_ready: "Audit bereit",
outreach_ready: "Freigabe offen",
contacted: "Kontaktiert",
replied: "Antwort erfasst",
do_not_contact: "Nicht kontaktieren",
};
export const leadDuplicateStatusLabels: Record<LeadDuplicateStatus, string> = {
unchecked: "Noch nicht geprüft",
unique: "Einzigartig",
possible_duplicate: "Möglicher Doppelter",
duplicate: "Duplikat",
};
export const leadBlacklistStatusLabels: Record<LeadBlacklistStatus, string> = {
clear: "Offen",
blocked: "Gesperrt",
};
export const leadPriorityOptions: LeadPriority[] = [
"high",
"medium",
"low",
"defer",
"blocked",
];
export const leadContactStatusOptions: LeadContactStatus[] = [
"new",
"missing_contact",
"audit_ready",
"outreach_ready",
"contacted",
"replied",
"do_not_contact",
];
export const leadDuplicateStatusOptions: LeadDuplicateStatus[] = [
"unchecked",
"unique",
"possible_duplicate",
"duplicate",
];
export const leadBlacklistStatusOptions: LeadBlacklistStatus[] = ["clear", "blocked"];
export function getLeadPriorityLabel(priority: LeadPriority): string {
return leadPriorityLabels[priority];
}
export function getLeadContactStatusLabel(status: LeadContactStatus): string {
return leadContactStatusLabels[status];
}
export function getLeadDuplicateStatusLabel(status: LeadDuplicateStatus): string {
return leadDuplicateStatusLabels[status];
}
export function getLeadBlacklistStatusLabel(status: LeadBlacklistStatus): string {
return leadBlacklistStatusLabels[status];
}
export function toLeadFunnelCard(lead: LeadFunnelInput): LeadFunnelCard {
return {
id: lead.id,
stageId: getLeadFunnelStageId(lead),
company: lead.companyName,
niche: lead.niche ?? "Nische offen",
location: formatLeadLocation(lead),
priorityLabel: getLeadPriorityLabel(lead.priority),
contactStatusLabel: getLeadContactStatusLabel(lead.contactStatus),
nextAction: getLeadNextAction(lead),
websiteDomain: lead.websiteDomain,
contactDetail: formatContactDetail(lead),
};
}
export function groupLeadFunnelCards(
leads: LeadFunnelInput[],
): LeadFunnelGroup[] {
const cards = leads.map(toLeadFunnelCard);
return leadFunnelStages.map((stage) => ({
stage,
cards: cards.filter((card) => card.stageId === stage.id),
}));
}
function getLeadFunnelStageId(lead: LeadFunnelInput): LeadFunnelStageId {
if (
lead.blacklistStatus === "blocked" ||
lead.priority === "defer" ||
lead.priority === "blocked" ||
lead.contactStatus === "do_not_contact"
) {
return "deferred";
}
if (lead.outreach?.responseStatus === "follow_up_needed") {
return "follow_up";
}
if (
lead.outreach?.salesStatus === "follow_up_planned" &&
lead.outreach.sendStatus === "sent"
) {
return "follow_up";
}
if (
lead.contactStatus === "contacted" ||
lead.contactStatus === "replied" ||
lead.outreach?.sendStatus === "sent"
) {
return "contacted";
}
if (
lead.contactStatus === "outreach_ready" ||
lead.outreach?.approvalStatus === "draft" ||
lead.outreach?.approvalStatus === "approved"
) {
return "review_open";
}
if (lead.contactStatus === "audit_ready") {
return "audit_ready";
}
return "missing_contact";
}
function getLeadNextAction(lead: LeadFunnelInput): string {
const stageId = getLeadFunnelStageId(lead);
if (stageId === "deferred") {
return "Zurückstellung prüfen";
}
if (stageId === "follow_up") {
return "Follow-up manuell prüfen";
}
if (stageId === "contacted") {
return "Antwortstatus nachtragen";
}
if (stageId === "review_open") {
return "Freigabe im Review öffnen";
}
if (stageId === "audit_ready") {
return "Audit prüfen";
}
return "Kontaktquelle recherchieren";
}
function formatLeadLocation(lead: LeadFunnelInput): string {
if (lead.postalCode && lead.city) {
return `${lead.postalCode} ${lead.city}`;
}
return lead.city ?? lead.postalCode ?? lead.address ?? "Ort offen";
}
function formatContactDetail(lead: LeadFunnelInput): string {
const details = [lead.email, lead.phone].filter(Boolean);
if (details.length > 0) {
return details.join(" · ");
}
return "Keine Kontaktdaten";
}
export const pipelineStages: PipelineStage[] = [
{
title: "Kampagnen",
description: "Aktive Suchlaeufe nach Kategorie, PLZ und Radius.",
count: 3,
meta: "1 Lauf heute geplant",
icon: MapPinned,
},
{
title: "Lead-Recherche",
description: "Neue Places-Quellen, Kontaktluecken und Dubletten.",
count: 18,
meta: "5 Leads brauchen E-Mail-Quelle",
icon: UsersRound,
},
{
title: "Audit-Freigabe",
description: "Interne Audits warten auf manuelle Prüfung.",
count: 6,
meta: "2 Seiten bereit zur Veröffentlichung",
icon: ShieldCheck,
},
{
title: "Outreach",
description: "Freigegebene E-Mails und Telefon-Skripte.",
count: 4,
meta: "Keine automatische Kontaktaufnahme",
icon: MailCheck,
},
];
export const dashboardKpis: DashboardKpi[] = [
{
label: "Neue Leads",
value: "18",
detail: "aus 3 aktiven Kampagnen",
},
{
label: "Audit-Entwürfe",
value: "6",
detail: "manuelle Freigabe offen",
},
{
label: "Outreach bereit",
value: "4",
detail: "E-Mail und Telefon-Skript",
},
{
label: "Antworten",
value: "2",
detail: "manuell nachzutragen",
},
];
export const reviewQueue: ReviewQueueItem[] = [
{
title: "Audit-Freigabe pruefen",
company: "Malerbetrieb Klein",
detail: "Mobile Kontaktfuehrung und lokaler CTA fehlen.",
},
{
title: "Kontaktstrategie bestaetigen",
company: "Physio am Park",
detail: "Telefon zuerst, E-Mail nach persoenlicher Einordnung.",
},
{
title: "Follow-up vormerken",
company: "Tischlerei Weber",
detail: "Respektvolles Follow-up in 5 Tagen, kein Autoversand.",
},
];
export const pipelineHealth = [
{
label: "Recherche",
value: "hoch",
icon: Gauge,
},
{
label: "Freigabe",
value: "manuell",
icon: ShieldCheck,
},
{
label: "Versand",
value: "gesperrt bis Review",
icon: MailCheck,
},
];