"use client"; import { useState } from "react"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { ProgressCard } from "@/components/shared/ProgressCard"; import { ResultsTable, type ResultRow } from "@/components/shared/ResultsTable"; import { ExportButtons } from "@/components/shared/ExportButtons"; import { EmptyState } from "@/components/shared/EmptyState"; import { toast } from "sonner"; import { MapPin, ChevronRight, Plus, X, Info } from "lucide-react"; import { useAppStore } from "@/lib/store"; import type { DecisionMakerCategory } from "@/lib/services/anymailfinder"; import type { ExportRow } from "@/lib/utils/csv"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; const CATEGORY_OPTIONS: { value: DecisionMakerCategory; label: string }[] = [ { value: "ceo", label: "CEO / Owner / Founder" }, { value: "engineering", label: "Engineering" }, { value: "marketing", label: "Marketing" }, { value: "sales", label: "Sales" }, { value: "operations", label: "Operations" }, { value: "finance", label: "Finance" }, { value: "hr", label: "HR" }, { value: "it", label: "IT" }, { value: "buyer", label: "Procurement" }, { value: "logistics", label: "Logistics" }, ]; const RESULTS_OPTIONS = [ { value: "20", label: "20 per query" }, { value: "40", label: "40 per query" }, { value: "60", label: "60 per query (max)" }, ]; // Preset queries for common German solar use cases const PRESET_QUERIES = [ "Solaranlage Installateur Deutschland", "Photovoltaik Montage", "Solar Handwerker", "Solarstrom Installation", ]; const GERMAN_REGIONS = [ "Bayern", "Baden-Württemberg", "Nordrhein-Westfalen", "Hessen", "Niedersachsen", "Sachsen", "Rheinland-Pfalz", "Brandenburg", "Berlin", "Hamburg", "München", "Frankfurt", "Stuttgart", "Düsseldorf", "Köln", "Leipzig", "Dresden", ]; type Stage = "idle" | "running" | "done" | "failed"; export default function MapsPage() { const [keyword, setKeyword] = useState("Solaranlage Installateur"); const [regions, setRegions] = useState(["Bayern", "Baden-Württemberg"]); const [regionInput, setRegionInput] = useState(""); const [maxResults, setMaxResults] = useState("60"); const [enrichEmails, setEnrichEmails] = useState(true); const [categories, setCategories] = useState(["ceo"]); const [stage, setStage] = useState("idle"); const [jobId, setJobId] = useState(null); const [progress, setProgress] = useState({ current: 0, total: 0, phase: "" }); const [results, setResults] = useState([]); const { addJob, updateJob, removeJob } = useAppStore(); // Build the final queries array: one per region const queries = regions.length > 0 ? regions.map(r => `${keyword} ${r}`) : [keyword]; const addRegion = (r: string) => { const trimmed = r.trim(); if (trimmed && !regions.includes(trimmed)) { setRegions(prev => [...prev, trimmed]); } setRegionInput(""); }; const removeRegion = (r: string) => setRegions(prev => prev.filter(x => x !== r)); const startJob = async () => { if (!keyword.trim()) return toast.error("Enter a search keyword"); if (!categories.length && enrichEmails) return toast.error("Select at least one email category"); setStage("running"); setResults([]); setProgress({ current: 0, total: queries.length, phase: "Searching Google Maps..." }); try { const res = await fetch("/api/jobs/maps-enrich", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ queries, maxResultsPerQuery: Number(maxResults), languageCode: "de", categories, enrichEmails, }), }); const data = await res.json() as { jobId?: string; error?: string }; if (!res.ok || !data.jobId) throw new Error(data.error || "Failed"); setJobId(data.jobId); addJob({ id: data.jobId, type: "maps", status: "running", progress: 0, total: queries.length * Number(maxResults) }); pollJob(data.jobId); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to start"); setStage("failed"); } }; const pollJob = (id: string) => { let phase = "Searching Google Maps..."; const interval = setInterval(async () => { try { const res = await fetch(`/api/jobs/${id}/status`); const data = await res.json() as { status: string; totalLeads: number; emailsFound: number; results: ResultRow[]; }; if (data.totalLeads > 0 && data.emailsFound === 0 && enrichEmails) { phase = "Enriching with Anymailfinder..."; } if (data.emailsFound > 0) { phase = `Found ${data.emailsFound} emails so far...`; } setProgress({ current: enrichEmails ? data.emailsFound : data.totalLeads, total: enrichEmails ? data.totalLeads : queries.length * Number(maxResults), phase, }); if (data.results?.length) setResults(data.results); updateJob(id, { status: data.status, progress: data.emailsFound, total: data.totalLeads }); if (data.status === "complete" || data.status === "failed") { clearInterval(interval); removeJob(id); setResults(data.results || []); if (data.status === "complete") { setStage("done"); const msg = enrichEmails ? `Done! ${data.totalLeads} companies found, ${data.emailsFound} emails enriched` : `Done! ${data.totalLeads} companies found`; toast.success(msg); } else { setStage("failed"); toast.error("Job failed. Check your Google Maps API key in Settings."); } } } catch { clearInterval(interval); setStage("failed"); } }, 2000); }; const exportRows: ExportRow[] = results.map(r => ({ company_name: r.companyName, domain: r.domain, contact_name: r.contactName, contact_title: r.contactTitle, email: r.email, confidence_score: r.confidence !== undefined ? Math.round(r.confidence * 100) : undefined, source_tab: "maps", job_id: jobId || "", found_at: new Date().toISOString(), })); const emailsFound = results.filter(r => r.email).length; const hitRate = results.length > 0 ? Math.round((emailsFound / results.length) * 100) : 0; const totalExpected = queries.length * Number(maxResults); return (
{/* Header */}
Tab 4 Google Maps

Google Maps → Email

Finde lokale Unternehmen über Google Maps und bereichere sie mit Entscheider-Emails.

{/* Info banner */}

Nutzt die Google Maps Places API (New). Max. 60 Ergebnisse pro Suchanfrage. Füge mehrere Regionen hinzu um mehr Ergebnisse zu erhalten. $200 Free Credit/Monat ≈ ~6.000 kostenlose Searches.

{/* Step 1: Search config */}

1 Suchanfrage konfigurieren

{/* Keyword */}
setKeyword(e.target.value)} className="bg-[#0d0d18] border-[#2e2e3e] text-white placeholder:text-gray-600 focus:border-green-500" /> {/* Presets */}
{PRESET_QUERIES.map(q => ( ))}
{/* Regions */}
{/* Region chips */}
{regions.map(r => ( {r} ))} setRegionInput(e.target.value)} onKeyDown={e => { if (e.key === "Enter" || e.key === ",") { e.preventDefault(); addRegion(regionInput); } }} onBlur={() => regionInput && addRegion(regionInput)} />
{/* Region presets */}
{GERMAN_REGIONS.filter(r => !regions.includes(r)).map(r => ( ))}
{/* Max results + language */}

= bis zu{" "} {totalExpected.toLocaleString()}{" "} Ergebnisse total ({queries.length} {queries.length === 1 ? "Query" : "Queries"})

{/* Preview queries */} {queries.length > 0 && (

Suchanfragen die ausgeführt werden:

{queries.slice(0, 5).map((q, i) => (
{i+1} "{q}"
))} {queries.length > 5 && (

+{queries.length - 5} weitere...

)}
)}
{/* Step 2: Email enrichment */}

2 Email Enrichment

setEnrichEmails(!!v)} className="border-[#2e2e3e] mt-0.5" />

Entscheider-Emails für alle gefundenen Domains suchen (2 Credits/gültige Email)

{enrichEmails && (
{CATEGORY_OPTIONS.map(opt => ( ))}
)}
{/* Step 3: Run */}

3 Starten

{stage === "idle" || stage === "failed" ? ( ) : stage === "running" ? ( ) : ( )}
{/* Results */} {results.length > 0 && (

Ergebnisse ({results.length} Unternehmen{enrichEmails && emailsFound > 0 ? `, ${emailsFound} Emails` : ""})

)} {stage === "idle" && ( )}
); }