UI improvements: Leadspeicher, Maps enrichment, exports

- Rename LeadVault → Leadspeicher throughout (sidebar, topbar, page)
- SidePanel: full lead detail view with contact, source, tags (read-only), Google Maps link for address
- Tags: kontaktiert stored as tag (toggleable), favorit tag toggle
- Remove Status column, StatusBadge dropdown, Priority feature
- Remove Aktualisieren button from Leadspeicher
- Bulk actions: remove status dropdown
- Export: LeadVault Excel-only, clean columns, freeze row + autofilter
- Export dropdown: click-based (fix overflow-hidden clipping)
- ExportButtons: remove CSV, Excel only everywhere
- Maps page: post-search Anymailfinder enrichment button
- ProgressCard: "Suche läuft..." instead of "Warte auf Anymailfinder-Server..."
- Quick SERP renamed to "Schnell neue Suche"
- Results page: Excel export, always-enabled download button
- Anymailfinder: fix bulk field names, array-of-arrays format
- Apify: fix countryCode lowercase
- API: sourceTerm search, contacted/favorite tag filters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Timo Uttenweiler
2026-03-21 18:12:31 +01:00
parent f914ab6e47
commit 115cdacd08
26 changed files with 511 additions and 521 deletions

View File

@@ -12,7 +12,7 @@ const navItems = [
{ href: "/linkedin", icon: Linkedin, label: "LinkedIn → Email", color: "text-blue-500" },
{ href: "/serp", icon: Search, label: "SERP → Email", color: "text-purple-400" },
{ href: "/maps", icon: MapPin, label: "Maps → Email", color: "text-green-400" },
{ href: "/leadvault", icon: Database, label: "LeadVault", color: "text-violet-400", badge: true },
{ href: "/leadvault", icon: Database, label: "Leadspeicher", color: "text-violet-400", badge: true },
{ href: "/results", icon: BarChart3, label: "Ergebnisse & Verlauf", color: "text-yellow-400" },
{ href: "/settings", icon: Settings, label: "Einstellungen", color: "text-gray-400" },
];

View File

@@ -9,7 +9,7 @@ const BREADCRUMBS: Record<string, string> = {
"/linkedin": "LinkedIn → E-Mail",
"/serp": "SERP → E-Mail",
"/maps": "Google Maps → E-Mail",
"/leadvault": "🗄️ LeadVault",
"/leadvault": "🗄️ Leadspeicher",
"/results": "Ergebnisse & Verlauf",
"/settings": "Einstellungen",
};

View File

@@ -1,8 +1,8 @@
"use client";
import { Button } from "@/components/ui/button";
import { Download, FileSpreadsheet } from "lucide-react";
import { exportToCSV, exportToExcel, type ExportRow } from "@/lib/utils/csv";
import { FileSpreadsheet } from "lucide-react";
import { exportToExcel, type ExportRow } from "@/lib/utils/csv";
interface ExportButtonsProps {
rows: ExportRow[];
@@ -15,15 +15,6 @@ export function ExportButtons({ rows, filename, disabled, summary }: ExportButto
return (
<div className="flex items-center gap-3 flex-wrap">
{summary && <span className="text-sm text-gray-400">{summary}</span>}
<Button
variant="outline"
size="sm"
disabled={disabled || rows.length === 0}
onClick={() => exportToCSV(rows, `${filename}.csv`)}
className="border-[#2e2e3e] hover:border-blue-500/50 hover:bg-blue-500/5 text-gray-300"
>
<Download className="w-4 h-4 mr-1.5" /> CSV herunterladen
</Button>
<Button
variant="outline"
size="sm"

View File

@@ -33,7 +33,7 @@ export function ProgressCard({ title, current, total, subtitle, status = "runnin
<span className="inline-block w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce" style={{ animationDelay: "0ms" }} />
<span className="inline-block w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce" style={{ animationDelay: "150ms" }} />
<span className="inline-block w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce" style={{ animationDelay: "300ms" }} />
<span className="text-blue-300 ml-1">Warte auf Anymailfinder-Server...</span>
<span className="text-blue-300 ml-1">Suche läuft...</span>
</span>
<span className="text-gray-600">{total} Domains</span>
</>

View File

@@ -3,7 +3,7 @@
import { useState } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { CheckCircle2, XCircle, AlertCircle, ChevronUp, ChevronDown } from "lucide-react";
import { CheckCircle2, XCircle, ChevronUp, ChevronDown } from "lucide-react";
export interface ResultRow {
id: string;
@@ -12,7 +12,6 @@ export interface ResultRow {
contactName?: string;
contactTitle?: string;
email?: string;
confidence?: number;
linkedinUrl?: string;
status?: string;
selected?: boolean;
@@ -26,9 +25,8 @@ interface ResultsTableProps {
extraColumns?: Array<{ key: string; label: string }>;
}
function EmailStatusIcon({ email, confidence }: { email?: string; confidence?: number }) {
function EmailStatusIcon({ email }: { email?: string }) {
if (!email) return <XCircle className="w-4 h-4 text-red-400" />;
if (confidence && confidence < 0.7) return <AlertCircle className="w-4 h-4 text-yellow-400" />;
return <CheckCircle2 className="w-4 h-4 text-green-400" />;
}
@@ -86,7 +84,6 @@ export function ResultsTable({ rows, loading, selectable, onSelectionChange, ext
<th className="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Kontakt</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Position</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">E-Mail</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Konfidenz</th>
{extraColumns?.map(col => (
<th key={col.key} className="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">{col.label}</th>
))}
@@ -120,17 +117,10 @@ export function ResultsTable({ rows, loading, selectable, onSelectionChange, ext
<td className="px-4 py-2.5 text-gray-400 text-xs">{row.contactTitle || "—"}</td>
<td className="px-4 py-2.5">
<div className="flex items-center gap-1.5">
<EmailStatusIcon email={row.email} confidence={row.confidence} />
<EmailStatusIcon email={row.email} />
<span className="text-gray-300 font-mono text-xs">{row.email || "—"}</span>
</div>
</td>
<td className="px-4 py-2.5">
{row.confidence !== undefined ? (
<span className={cn("text-xs font-medium", row.confidence >= 0.8 ? "text-green-400" : row.confidence >= 0.6 ? "text-yellow-400" : "text-red-400")}>
{Math.round(row.confidence * 100)}%
</span>
) : "—"}
</td>
{extraColumns?.map(col => (
<td key={col.key} className="px-4 py-2.5 text-gray-400 text-xs">
{((row as unknown) as Record<string, string>)[col.key] || "—"}