"use client"; import { useState, useCallback } from "react"; import Link from "next/link"; import { toast } from "sonner"; import { SearchCard } from "@/components/search/SearchCard"; import { LoadingCard, type LeadResult } from "@/components/search/LoadingCard"; import { AiSearchModal } from "@/components/search/AiSearchModal"; export default function SuchePage() { const [query, setQuery] = useState(""); const [region, setRegion] = useState(""); const [count, setCount] = useState(50); const [loading, setLoading] = useState(false); const [jobId, setJobId] = useState(null); const [leads, setLeads] = useState([]); const [searchDone, setSearchDone] = useState(false); const [selected, setSelected] = useState>(new Set()); const [deleting, setDeleting] = useState(false); const [onlyNew, setOnlyNew] = useState(false); const [saveOnlyNew, setSaveOnlyNew] = useState(false); const [aiOpen, setAiOpen] = useState(false); const [searchMode, setSearchMode] = useState<"stadtwerke" | "industrie" | "custom">("stadtwerke"); const [queueRunning, setQueueRunning] = useState(false); const [queueIndex, setQueueIndex] = useState(0); const [queueTotal, setQueueTotal] = useState(0); const BUNDESLAENDER = [ "Bayern", "NRW", "Baden-Württemberg", "Hessen", "Niedersachsen", "Sachsen", "Berlin", "Hamburg", "Bremen", "Thüringen", "Sachsen-Anhalt", "Brandenburg", "Mecklenburg-Vorpommern", "Saarland", "Rheinland-Pfalz", "Schleswig-Holstein", ]; function handleChange(field: "query" | "region" | "count", value: string | number) { if (field === "query") setQuery(value as string); if (field === "region") setRegion(value as string); if (field === "count") setCount(value as number); } async function handleSubmit() { if (!query.trim() || loading) return; setLoading(true); setJobId(null); setLeads([]); setSearchDone(false); setSelected(new Set()); setOnlyNew(false); // saveOnlyNew intentionally kept — user setting persists across searches try { const res = await fetch("/api/search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: query.trim(), region: region.trim(), count }), }); if (!res.ok) { const err = await res.json() as { error?: string }; throw new Error(err.error || "Fehler beim Starten der Suche"); } const data = await res.json() as { jobId: string }; setJobId(data.jobId); } catch (err) { const msg = err instanceof Error ? err.message : "Unbekannter Fehler"; toast.error(msg); setLoading(false); } } const handleDone = useCallback((result: LeadResult[], warning?: string) => { setLoading(false); setLeads(result); setSearchDone(true); // Fire-and-forget KI-Anreicherung if (jobId) { fetch("/api/enrich-leads", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jobId }), }).catch(() => {}); } if (warning) { toast.warning(`${result.length} Unternehmen gefunden — E-Mail-Anreicherung fehlgeschlagen: ${warning}`, { duration: 6000 }); } else { toast.success(`✓ ${result.length} Leads gefunden und im Leadspeicher gespeichert`, { duration: 4000 }); } }, [jobId]); async function startStadtwerkeQueue() { if (loading || queueRunning) return; setQueueRunning(true); setQueueTotal(BUNDESLAENDER.length); setQueueIndex(0); setLeads([]); setSearchDone(false); for (let i = 0; i < BUNDESLAENDER.length; i++) { setQueueIndex(i + 1); const bl = BUNDESLAENDER[i]; toast.info(`Suche ${i + 1}/${BUNDESLAENDER.length}: Stadtwerke ${bl}`, { duration: 2000 }); try { const res = await fetch("/api/search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: "Stadtwerke", region: bl, count: 50 }), }); const data = await res.json() as { jobId?: string }; if (data.jobId) { // Save to history fetch("/api/search-history", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: "Stadtwerke", region: bl, searchMode: "stadtwerke" }), }).catch(() => {}); // Poll until done await new Promise((resolve) => { const interval = setInterval(async () => { try { const statusRes = await fetch(`/api/jobs/${data.jobId}/status`); const status = await statusRes.json() as { status: string }; if (status.status === "complete" || status.status === "failed") { clearInterval(interval); resolve(); } } catch { clearInterval(interval); resolve(); } }, 3000); }); } } catch { /* continue with next */ } } setQueueRunning(false); toast.success(`✓ Alle ${BUNDESLAENDER.length} Bundesländer durchsucht — Leads im Leadspeicher`, { duration: 5000 }); } async function handleDelete(ids: string[]) { if (!ids.length || deleting) return; setDeleting(true); try { await fetch("/api/leads/delete-from-results", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ resultIds: ids }), }); setLeads(prev => prev.filter(l => !ids.includes(l.id))); setSelected(prev => { const next = new Set(prev); ids.forEach(id => next.delete(id)); return next; }); toast.success(`${ids.length} Lead${ids.length > 1 ? "s" : ""} gelöscht`); } catch { toast.error("Löschen fehlgeschlagen"); } finally { setDeleting(false); } } async function handleSaveOnlyNew() { const existingIds = leads.filter(l => !l.isNew).map(l => l.id); if (!existingIds.length || deleting) return; setDeleting(true); try { await fetch("/api/leads/delete-from-results", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ resultIds: existingIds }), }); setSaveOnlyNew(true); const newCount = leads.filter(l => l.isNew).length; toast.success(`✓ ${newCount} neue Leads behalten, ${existingIds.length} vorhandene aus Leadspeicher entfernt`, { duration: 5000 }); } catch { toast.error("Fehler beim Bereinigen"); } finally { setDeleting(false); } } const handleError = useCallback((message: string) => { setLoading(false); setJobId(null); toast.error(`Suche fehlgeschlagen: ${message}`); }, []); return (
{/* Hero */}
Lead-Suche

Leads finden

Suchbegriff eingeben — wir finden passende Unternehmen mit Kontaktdaten.

{/* Mode Tabs */}
{([ { id: "stadtwerke" as const, icon: "⚡", label: "Stadtwerke", desc: "Kommunale Energieversorger" }, { id: "industrie" as const, icon: "🏭", label: "Industriebetriebe", desc: "Energieintensive Betriebe" }, { id: "custom" as const, icon: "🔍", label: "Freie Suche", desc: "Beliebige Zielgruppe" }, ]).map(tab => ( ))}
{/* Stadtwerke Queue Button */} {searchMode === "stadtwerke" && !loading && !queueRunning && ( )} {/* Queue running indicator */} {queueRunning && (
Bundesland {queueIndex} von {queueTotal} wird durchsucht…
Nicht schließen — Leads werden automatisch gespeichert
)} {/* Search Card */} {/* Loading Card */} {loading && jobId && ( )} {/* AI Modal */} {aiOpen && ( { setAiOpen(false); if (!queries.length) return; const first = queries[0]; setQuery(first.query); setRegion(first.region); setCount(first.count); setLoading(true); setJobId(null); setLeads([]); setSearchDone(false); setSelected(new Set()); // Save to history fetch("/api/search-history", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: first.query, region: first.region, searchMode }), }).catch(() => {}); fetch("/api/search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: first.query, region: first.region, count: first.count }), }) .then(r => r.json()) .then((d: { jobId?: string; error?: string }) => { if (d.jobId) setJobId(d.jobId); else throw new Error(d.error); }) .catch(err => { toast.error(err instanceof Error ? err.message : "Fehler"); setLoading(false); }); }} onClose={() => setAiOpen(false)} /> )} {/* Results */} {searchDone && leads.length > 0 && (() => { const newCount = leads.filter(l => l.isNew).length; const visibleLeads = onlyNew ? leads.filter(l => l.isNew) : leads; const allVisibleSelected = visibleLeads.length > 0 && visibleLeads.every(l => selected.has(l.id)); const someVisibleSelected = visibleLeads.some(l => selected.has(l.id)); const selectedVisible = visibleLeads.filter(l => selected.has(l.id)); return (
{/* Header */}
{/* Select-all checkbox */} { if (el) el.indeterminate = someVisibleSelected && !allVisibleSelected; }} onChange={e => { if (e.target.checked) { setSelected(prev => { const next = new Set(prev); visibleLeads.forEach(l => next.add(l.id)); return next; }); } else { setSelected(prev => { const next = new Set(prev); visibleLeads.forEach(l => next.delete(l.id)); return next; }); } }} style={{ accentColor: "#3b82f6", cursor: "pointer", width: 14, height: 14 }} /> {selectedVisible.length > 0 ? `${selectedVisible.length} ausgewählt` : `${visibleLeads.length} Leads`} {/* Filter tabs */}
{[ { label: `Alle (${leads.length})`, value: false }, { label: `Nur neue (${newCount})`, value: true }, ].map(tab => ( ))}
{selectedVisible.length > 0 && ( )} {/* Nur neue speichern — only shown when there are existing leads */} {!saveOnlyNew && leads.some(l => !l.isNew) && ( )} Im Leadspeicher →
{/* Table */}
))} {visibleLeads.map((lead, i) => { const isSelected = selected.has(lead.id); return ( ); })}
{["Unternehmen", "Domain", "Kontakt", "E-Mail"].map(h => ( {h}
{ setSelected(prev => { const next = new Set(prev); e.target.checked ? next.add(lead.id) : next.delete(lead.id); return next; }); }} style={{ accentColor: "#3b82f6", cursor: "pointer", width: 13, height: 13 }} />
{lead.companyName || "—"} {!lead.isNew && ( vorhanden )}
{lead.domain || "—"} {lead.contactName ? `${lead.contactName}${lead.contactTitle ? ` · ${lead.contactTitle}` : ""}` : "—"} {lead.email ? ( {lead.email} ) : ( )}
); })()}
); }