"use client"; import { useState, useEffect, useCallback, useRef } from "react"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { LeadsToolbar } from "@/components/leadspeicher/LeadsToolbar"; import { LeadsTable } from "@/components/leadspeicher/LeadsTable"; import type { Lead } from "@/components/leadspeicher/LeadsTable"; import { BulkActionBar } from "@/components/leadspeicher/BulkActionBar"; interface LeadsResponse { leads: Lead[]; total: number; page: number; pages: number; perPage: number; } interface StatsResponse { total: number; new: number; withEmail: number; } export default function LeadspeicherPage() { const router = useRouter(); const [leads, setLeads] = useState([]); const [total, setTotal] = useState(0); const [pages, setPages] = useState(1); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(50); const [search, setSearch] = useState(""); const [status, setStatus] = useState(""); const [loading, setLoading] = useState(true); const [selected, setSelected] = useState>(new Set()); const [stats, setStats] = useState({ total: 0, new: 0, withEmail: 0 }); const fetchRef = useRef(0); const fetchLeads = useCallback(async () => { const id = ++fetchRef.current; setLoading(true); try { const params = new URLSearchParams({ page: String(page), perPage: String(perPage), }); if (search) params.set("search", search); if (status) params.append("status", status); const res = await fetch(`/api/leads?${params.toString()}`); if (!res.ok) throw new Error("fetch failed"); const data = await res.json() as LeadsResponse; if (id === fetchRef.current) { setLeads(data.leads); setTotal(data.total); setPages(data.pages); } } catch { // silent } finally { if (id === fetchRef.current) setLoading(false); } }, [page, perPage, search, status]); const fetchStats = useCallback(async () => { try { const res = await fetch("/api/leads/stats"); const data = await res.json() as StatsResponse; setStats(data); } catch { // silent } }, []); useEffect(() => { fetchLeads(); fetchStats(); }, [fetchLeads, fetchStats]); function handleLeadUpdate(id: string, updates: Partial) { setLeads((prev) => prev.map((l) => (l.id === id ? { ...l, ...updates } : l)) ); } function handleToggleSelect(id: string) { setSelected((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } function handleToggleAll() { if (selected.size === leads.length && leads.length > 0) { setSelected(new Set()); } else { setSelected(new Set(leads.map((l) => l.id))); } } async function handleBulkStatus(newStatus: string) { const ids = Array.from(selected); // optimistic setLeads((prev) => prev.map((l) => (selected.has(l.id) ? { ...l, status: newStatus } : l)) ); setSelected(new Set()); try { await fetch("/api/leads/bulk", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids, status: newStatus }), }); } catch { toast.error("Status-Update fehlgeschlagen"); fetchLeads(); } } async function handleBulkDelete() { if (!confirm(`${selected.size} Lead(s) wirklich löschen?`)) return; const ids = Array.from(selected); setLeads((prev) => prev.filter((l) => !selected.has(l.id))); setSelected(new Set()); try { await Promise.all( ids.map((id) => fetch(`/api/leads/${id}`, { method: "DELETE" }) ) ); fetchStats(); } catch { toast.error("Löschen fehlgeschlagen"); fetchLeads(); } } const hasFilter = search !== "" || status !== ""; const withEmailCount = stats.withEmail; const withEmailPct = stats.total > 0 ? Math.round((withEmailCount / stats.total) * 100) : 0; const exportParams: Record = {}; if (search) exportParams.search = search; if (status) exportParams.status = status; return (
{/* Toolbar */} { setSearch(v); setPage(1); }} onStatusChange={(v) => { setStatus(v); setPage(1); }} exportParams={exportParams} /> {/* Table area */}
{loading && leads.length === 0 ? ( // Loading skeleton
Wird geladen…
) : leads.length === 0 ? ( // Empty state hasFilter ? (
🔍
Keine Leads gefunden
Versuche andere Filtereinstellungen.
) : (
Noch keine Leads gespeichert
Starte eine Suche — die Ergebnisse erscheinen hier automatisch.
) ) : ( 0} onLeadUpdate={handleLeadUpdate} /> )}
{/* Footer */} {(total > 0 || leads.length > 0) && (
{/* Stats */}
{total} Leads gesamt {withEmailCount > 0 && ( <> · {withEmailCount} mit E-Mail ({withEmailPct}%) )}
{/* Pagination */}
Seite {page} von {pages}
)} {/* Bulk action bar */}
); }