"use client"; import { useEffect, useMemo, useState } from "react"; import { useQuery } from "convex/react"; import { Activity, Filter, MousePointerClick } from "lucide-react"; import { api } from "@/convex/_generated/api"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; const metricLabels: Record = { foundLeads: "Gefundene Leads", leadsWithContact: "Mit Kontakt", missingContact: "Kontakt fehlt", auditsCreated: "Audits erstellt", approvalsOpen: "Freigaben offen", emailsSent: "E-Mails gesendet", followUpsPlanned: "Follow-ups geplant", followUpsSent: "Follow-ups gesendet", responses: "Antworten", conversations: "Gespräche", offers: "Angebote", wins: "Gewonnen", losses: "Verloren", skippedDuplicates: "Duplikate übersprungen", skippedBlacklisted: "Sperrliste übersprungen", rybbitAuditOpens: "Audit-Öffnungen", rybbitCtaClicks: "CTA-Klicks", }; export function AnalyticsDashboard() { const dashboard = useQuery(api.campaignMetrics.getDashboard, { limit: 20 }); const [rybbitData, setRybbitData] = useState<{ auditOpens: number; ctaClicks: number; outboundClicks: number; } | null>(null); const [rybbitError, setRybbitError] = useState(null); const metricEntries = useMemo(() => { if (!dashboard) { return []; } return Object.entries(dashboard.metrics).filter(([key]) => key in metricLabels); }, [dashboard]); useEffect(() => { let isMounted = true; fetch("/api/internal/rybbit/campaign") .then(async (response) => { const payload = await response.json(); if (!isMounted) { return; } if (!payload.ok) { setRybbitError("Rybbit-Daten konnten nicht geladen werden."); } setRybbitData(payload.data ?? null); }) .catch(() => { if (isMounted) { setRybbitError("Rybbit-Daten konnten nicht geladen werden."); } }); return () => { isMounted = false; }; }, []); if (dashboard === undefined) { return (
); } return (

Kampagnen-Reporting

Analytics

Filter Kampagne, Nische, PLZ, Radius, Priorität, Status und Zeitraum.

Kampagne: {dashboard.filters.campaigns.length}

Nische: {dashboard.filters.niches.length}

PLZ: {dashboard.filters.postalCodes.length}

Radius: Kampagnenradius

Priorität: Hoch/Mittel/Niedrig

Status: Funnel-Status

Zeitraum: Erstellungsdatum

{metricEntries.map(([key, value]) => (

{metricLabels[key]}

{value}

))}
Run-Details Neue Leads, übersprungene Duplikate, Sperrliste, Fehler und erzeugte Audits. {dashboard.runs.length === 0 ? (

Noch keine Kampagnenläufe.

) : ( dashboard.runs.map((run) => (

{run.status}

Leads {run.newLeads} · Audits {run.auditsGenerated} · Fehler {run.errors}

{run.errorSummary ? (

{run.errorSummary}

) : null}
)) )}
Rybbit Audit-Öffnungen und CTA-Aktivität werden bei Bedarf aus der Rybbit API geladen.

Rybbit-Daten konnten nicht geladen werden, wenn API-URL, Site-ID oder API-Key fehlen.

{rybbitError ?

{rybbitError}

: null}

Audit-Öffnungen: {rybbitData?.auditOpens ?? dashboard.metrics.rybbitAuditOpens}

CTA-Klicks: {rybbitData?.ctaClicks ?? dashboard.metrics.rybbitCtaClicks}

Website-Link-Klicks: {rybbitData?.outboundClicks ?? 0}

Public-Audit Tracking läuft nur auf veröffentlichten Audit-Seiten.

); }