"use client"; import { useState, useCallback } from "react"; import Link from "next/link"; import { toast } from "sonner"; 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 [queueRunning, setQueueRunning] = useState(false); const [queueIndex, setQueueIndex] = useState(0); const [queueTotal, setQueueTotal] = useState(0); const [queueLabel, setQueueLabel] = useState(""); const [industrieRunning, setIndustrieRunning] = useState(false); const [industrieIndex, setIndustrieIndex] = useState(0); const [industrieTotal, setIndustrieTotal] = useState(0); const [industrieLabel, setIndustrieLabel] = useState(""); const INDUSTRIE_TERMS = ["Netzbetreiber", "Fernwärme", "Industriepark"]; 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 runSearchAndWait(query: string, region: string, historyMode = "stadtwerke") { try { const res = await fetch("/api/search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, region, count: 50 }), }); const data = await res.json() as { jobId?: string }; if (!data.jobId) return; // Save to history fetch("/api/search-history", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, region, searchMode: historyMode }), }).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 */ } } async function startStadtwerkeQueue() { if (loading || queueRunning) return; setQueueRunning(true); setQueueIndex(0); setLeads([]); setSearchDone(false); // Fetch already-used regions to skip Bundesländer already done let usedRegions = new Set(); try { const hRes = await fetch("/api/search-history?mode=stadtwerke"); if (hRes.ok) { const hist = await hRes.json() as Array<{ region: string }>; usedRegions = new Set(hist.map(h => h.region)); } } catch { /* ignore */ } // Phase 1: Unused Bundesländer const unusedBL = BUNDESLAENDER.filter(bl => !usedRegions.has(bl)); // Phase 2: Next batch of unused cities let cities: string[] = []; try { const cRes = await fetch("/api/stadtwerke-cities?count=50"); if (cRes.ok) { const cData = await cRes.json() as { cities: string[]; exhausted: boolean }; cities = cData.cities; if (cData.exhausted) { toast.info("Alle vordefinierten Städte durchsucht — KI generiert neue Vorschläge", { duration: 4000 }); } } } catch { /* ignore */ } const allTargets = [ ...unusedBL.map(bl => ({ label: `Bundesland: ${bl}`, query: "Stadtwerke", region: bl })), ...cities.map(city => ({ label: `Stadt: ${city}`, query: "Stadtwerke", region: city })), ]; if (allTargets.length === 0) { setQueueRunning(false); toast.info("Alle bekannten Regionen wurden bereits durchsucht", { duration: 4000 }); return; } setQueueTotal(allTargets.length); for (let i = 0; i < allTargets.length; i++) { const target = allTargets[i]; setQueueIndex(i + 1); setQueueLabel(target.label); await runSearchAndWait(target.query, target.region); } setQueueRunning(false); const blCount = unusedBL.length; const cityCount = cities.length; const parts = []; if (blCount > 0) parts.push(`${blCount} Bundesländer`); if (cityCount > 0) parts.push(`${cityCount} Städte`); toast.success(`✓ ${parts.join(" + ")} durchsucht — Leads im Leadspeicher`, { duration: 5000 }); } async function startIndustrieQueue() { if (loading || queueRunning || industrieRunning) return; setIndustrieRunning(true); setIndustrieIndex(0); // Load already-searched [term::location] combos let usedKeys = new Set(); try { const hRes = await fetch("/api/search-history?mode=industrie"); if (hRes.ok) { const hist = await hRes.json() as Array<{ region: string }>; usedKeys = new Set(hist.map(h => h.region)); } } catch { /* ignore */ } // Cities data (inline priority order) const BW_CITIES = [ "Stuttgart","Karlsruhe","Mannheim","Freiburg","Heidelberg","Ulm","Heilbronn","Pforzheim","Reutlingen","Ludwigsburg", "Esslingen","Tübingen","Villingen-Schwenningen","Konstanz","Aalen","Friedrichshafen","Sindelfingen","Ravensburg","Offenburg","Göppingen", "Böblingen","Schwäbisch Gmünd","Lahr","Waiblingen","Baden-Baden","Bruchsal","Weinheim","Leonberg","Bietigheim-Bissingen","Heidenheim", "Schwäbisch Hall","Nagold","Singen","Nürtingen","Fellbach","Tuttlingen","Überlingen","Backnang","Ditzingen","Kirchheim", "Schorndorf","Filderstadt","Leinfelden-Echterdingen","Ettlingen","Weil am Rhein","Rottenburg","Rheinfelden","Leutkirch","Mosbach","Crailsheim", ]; const BAYERN_CITIES = [ "München","Nürnberg","Augsburg","Regensburg","Ingolstadt","Würzburg","Fürth","Erlangen","Bayreuth","Landshut", "Rosenheim","Kempten","Bamberg","Aschaffenburg","Neu-Ulm","Schweinfurt","Ansbach","Straubing","Passau","Coburg", "Dachau","Freising","Germering","Memmingen","Kaufbeuren","Hof","Amberg","Weiden","Pfaffenhofen","Starnberg", "Traunreut","Gauting","Garching","Erding","Fürstenfeldbruck","Unterschleißheim","Waldkraiburg","Marktoberdorf","Neumarkt","Altötting", "Weißenburg","Schwabach","Deggendorf","Traunstein","Burghausen","Bad Reichenhall","Neuburg an der Donau","Kelheim","Dillingen","Günzburg", ]; const OTHER_BL = ["NRW","Hessen","Niedersachsen","Sachsen","Berlin","Hamburg","Bremen","Thüringen","Sachsen-Anhalt","Brandenburg","Mecklenburg-Vorpommern","Saarland","Rheinland-Pfalz","Schleswig-Holstein"]; // Build priority targets: BW + Bayern first, then rest const priorityLocations = [ "Baden-Württemberg", ...BW_CITIES, "Bayern", ...BAYERN_CITIES, ]; const restLocations = OTHER_BL; // All [term, location] combos in priority order const allTargets: Array<{ label: string; term: string; location: string }> = []; for (const loc of [...priorityLocations, ...restLocations]) { for (const term of INDUSTRIE_TERMS) { const key = `${term}::${loc}`; if (!usedKeys.has(key)) { allTargets.push({ label: `${term} · ${loc}`, term, location: loc }); } } } if (allTargets.length === 0) { setIndustrieRunning(false); toast.info("Alle Energieversorger-Suchen wurden bereits durchgeführt", { duration: 4000 }); return; } setIndustrieTotal(allTargets.length); for (let i = 0; i < allTargets.length; i++) { const t = allTargets[i]; setIndustrieIndex(i + 1); setIndustrieLabel(t.label); await runSearchAndWait(t.term, t.location, "industrie"); // Override the region saved in history with the key so we can track term+location combos fetch("/api/search-history", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: t.term, region: `${t.term}::${t.location}`, searchMode: "industrie" }), }).catch(() => {}); } setIndustrieRunning(false); toast.success(`✓ ${allTargets.length} Energieversorger-Suchen abgeschlossen — 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 (
{/* ── Strategische Kampagnen ── */}

Strategische Energiewirtschafts-Kampagnen

{/* CTA Card — Stadtwerke */}

Hauptkampagne

Bundesweite
Lead-Gewinnung

Startet sofort den Deep-Scrape aller Stadtwerke in allen deutschen Bundesländern.

{queueRunning ? (
{queueLabel || "Wird durchsucht…"} ({queueIndex}/{queueTotal})
Leads werden automatisch gespeichert
) : ( )}
{/* CTA Card — Energieversorger */}

Energieversorger-Kampagne

Industrie &
Energieversorger

Sucht nach Netzbetreiber, Fernwärme und Industriepark — priorisiert BW & Bayern, dann alle weiteren Bundesländer.

{industrieRunning ? (
{industrieLabel || "Wird durchsucht…"} ({industrieIndex}/{industrieTotal})
Leads werden automatisch gespeichert
) : ( )}
{/* ── Manuelle Suche ── */}

Manuelle Lead- & Nischensuche

Verfeinern Sie Ihre Zielparameter für eine hochspezifische Extraktion.

{/* Form fields */}
handleChange("query", e.target.value)} onKeyDown={e => { if (e.key === "Enter" && query.trim() && !loading) handleSubmit(); }} placeholder="z.B. Stadtwerke" onFocus={e => { e.currentTarget.style.borderColor = "#adc7ff"; e.currentTarget.style.boxShadow = "0 0 0 2px rgba(173,199,255,0.2)"; }} onBlur={e => { e.currentTarget.style.borderColor = "rgba(65,71,84,0.4)"; e.currentTarget.style.boxShadow = "none"; }} /> search
handleChange("region", e.target.value)} onKeyDown={e => { if (e.key === "Enter" && query.trim() && !loading) handleSubmit(); }} placeholder="z.B. Bayern" onFocus={e => { e.currentTarget.style.borderColor = "#adc7ff"; e.currentTarget.style.boxShadow = "0 0 0 2px rgba(173,199,255,0.2)"; }} onBlur={e => { e.currentTarget.style.borderColor = "rgba(65,71,84,0.4)"; e.currentTarget.style.boxShadow = "none"; }} /> location_on
{/* Quick Presets */}

Quick Presets

{["Stadtwerke", "Energieversorger", "Industrie-Energie", "Gemeindewerke", "Netzbetreiber"].map(preset => ( ))}
{/* 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: "custom" }), }).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} ) : ( )}
); })()}
); }