- New Topbar: logo, 2-tab pill switcher, live "Neu" badge - /suche page: SearchCard, LoadingCard, ExamplePills - /leadspeicher page: full leads table with filters, pagination - StatusBadge, StatusPopover, LeadSidePanel, BulkActionBar - POST /api/search: unified search entry point → serp-enrich - Remove Sidebar + old TopBar from layout - Title: OnyvaLeads, redirect / → /suche Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
137 lines
3.9 KiB
TypeScript
137 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { useRef } from "react";
|
|
|
|
interface LeadsToolbarProps {
|
|
search: string;
|
|
status: string;
|
|
onSearchChange: (v: string) => void;
|
|
onStatusChange: (v: string) => void;
|
|
exportParams: Record<string, string>;
|
|
}
|
|
|
|
export function LeadsToolbar({
|
|
search,
|
|
status,
|
|
onSearchChange,
|
|
onStatusChange,
|
|
exportParams,
|
|
}: LeadsToolbarProps) {
|
|
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
function handleSearchInput(e: React.ChangeEvent<HTMLInputElement>) {
|
|
const val = e.target.value;
|
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
debounceRef.current = setTimeout(() => onSearchChange(val), 300);
|
|
// immediate update of input
|
|
e.currentTarget.value = val;
|
|
}
|
|
|
|
function handleExport() {
|
|
const params = new URLSearchParams({ format: "xlsx", ...exportParams });
|
|
window.location.href = `/api/leads/export?${params.toString()}`;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
position: "sticky",
|
|
top: 52,
|
|
zIndex: 40,
|
|
background: "#111118",
|
|
borderBottom: "1px solid #1e1e2e",
|
|
padding: "14px 20px",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 12,
|
|
}}
|
|
>
|
|
{/* Search input */}
|
|
<div style={{ flex: 1, position: "relative" }}>
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
left: 10,
|
|
top: "50%",
|
|
transform: "translateY(-50%)",
|
|
pointerEvents: "none",
|
|
color: "#6b7280",
|
|
}}
|
|
>
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
<circle cx="6" cy="6" r="4.5" stroke="currentColor" strokeWidth="1.5" />
|
|
<line x1="9.5" y1="9.5" x2="12.5" y2="12.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
</svg>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
defaultValue={search}
|
|
onChange={handleSearchInput}
|
|
placeholder="Unternehmen, E-Mail, Branche…"
|
|
style={{
|
|
width: "100%",
|
|
background: "#0d0d18",
|
|
border: "1px solid #1e1e2e",
|
|
borderRadius: 8,
|
|
padding: "8px 12px 8px 32px",
|
|
fontSize: 13,
|
|
color: "#ffffff",
|
|
outline: "none",
|
|
boxSizing: "border-box",
|
|
}}
|
|
onFocus={(e) => { e.currentTarget.style.borderColor = "#3b82f6"; }}
|
|
onBlur={(e) => { e.currentTarget.style.borderColor = "#1e1e2e"; }}
|
|
/>
|
|
</div>
|
|
|
|
{/* Status filter */}
|
|
<select
|
|
value={status}
|
|
onChange={(e) => onStatusChange(e.target.value)}
|
|
style={{
|
|
width: 150,
|
|
background: "#0d0d18",
|
|
border: "1px solid #1e1e2e",
|
|
borderRadius: 8,
|
|
padding: "8px 12px",
|
|
fontSize: 13,
|
|
color: "#ffffff",
|
|
cursor: "pointer",
|
|
outline: "none",
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
<option value="">Alle Status</option>
|
|
<option value="new">Neu</option>
|
|
<option value="contacted">Kontaktiert</option>
|
|
<option value="in_progress">In Bearbeitung</option>
|
|
<option value="not_relevant">Nicht relevant</option>
|
|
<option value="converted">Konvertiert</option>
|
|
</select>
|
|
|
|
{/* Export button */}
|
|
<button
|
|
onClick={handleExport}
|
|
style={{
|
|
background: "#0d0d18",
|
|
border: "1px solid #2e2e3e",
|
|
borderRadius: 8,
|
|
padding: "8px 14px",
|
|
fontSize: 13,
|
|
color: "#9ca3af",
|
|
cursor: "pointer",
|
|
flexShrink: 0,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 6,
|
|
transition: "color 0.15s",
|
|
}}
|
|
onMouseEnter={(e) => { (e.currentTarget as HTMLButtonElement).style.color = "#ffffff"; }}
|
|
onMouseLeave={(e) => { (e.currentTarget as HTMLButtonElement).style.color = "#9ca3af"; }}
|
|
>
|
|
↓ Excel Export
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|