feat: Alle/Nur-neue Tabs + Option Nur neue Leads speichern
- Suchergebnis-Filter als Tabs: [Alle (46)] [Nur neue (X)] — beide klickbar - Checkbox "Nur neue Leads speichern" erscheint vor der Suche - Bei aktivierter Option: nach Abschluss werden vorhandene Leads automatisch aus dem Leadspeicher gelöscht, Toast zeigt Bilanz Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ export default function SuchePage() {
|
|||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
const [deleting, setDeleting] = useState(false);
|
const [deleting, setDeleting] = useState(false);
|
||||||
const [onlyNew, setOnlyNew] = useState(false);
|
const [onlyNew, setOnlyNew] = useState(false);
|
||||||
|
const [saveOnlyNew, setSaveOnlyNew] = useState(false);
|
||||||
|
|
||||||
function handleChange(field: "query" | "region" | "count", value: string | number) {
|
function handleChange(field: "query" | "region" | "count", value: string | number) {
|
||||||
if (field === "query") setQuery(value as string);
|
if (field === "query") setQuery(value as string);
|
||||||
@@ -32,6 +33,7 @@ export default function SuchePage() {
|
|||||||
setSearchDone(false);
|
setSearchDone(false);
|
||||||
setSelected(new Set());
|
setSelected(new Set());
|
||||||
setOnlyNew(false);
|
setOnlyNew(false);
|
||||||
|
// saveOnlyNew intentionally kept — user setting persists across searches
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/search", {
|
const res = await fetch("/api/search", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -51,16 +53,33 @@ export default function SuchePage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDone = useCallback((result: LeadResult[], warning?: string) => {
|
const handleDone = useCallback(async (result: LeadResult[], warning?: string) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setLeads(result);
|
setLeads(result);
|
||||||
setSearchDone(true);
|
setSearchDone(true);
|
||||||
|
|
||||||
|
// Auto-delete existing (non-new) leads from vault if "Nur neue speichern" is active
|
||||||
|
const existingIds = result.filter(l => !l.isNew).map(l => l.id);
|
||||||
|
if (saveOnlyNew && existingIds.length > 0) {
|
||||||
|
try {
|
||||||
|
await fetch("/api/leads/delete-from-results", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ resultIds: existingIds }),
|
||||||
|
});
|
||||||
|
// Mark them as deleted in local state (isNew stays false, they remain visible but are gone from vault)
|
||||||
|
} catch { /* silent — vault cleanup best-effort */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCount = result.filter(l => l.isNew).length;
|
||||||
if (warning) {
|
if (warning) {
|
||||||
toast.warning(`${result.length} Unternehmen gefunden — E-Mail-Anreicherung fehlgeschlagen: ${warning}`, { duration: 6000 });
|
toast.warning(`${result.length} Unternehmen gefunden — E-Mail-Anreicherung fehlgeschlagen: ${warning}`, { duration: 6000 });
|
||||||
|
} else if (saveOnlyNew && existingIds.length > 0) {
|
||||||
|
toast.success(`✓ ${newCount} neue Leads gespeichert, ${existingIds.length} bereits vorhandene verworfen`, { duration: 5000 });
|
||||||
} else {
|
} else {
|
||||||
toast.success(`✓ ${result.length} Leads gefunden und im Leadspeicher gespeichert`, { duration: 4000 });
|
toast.success(`✓ ${result.length} Leads gefunden und im Leadspeicher gespeichert`, { duration: 4000 });
|
||||||
}
|
}
|
||||||
}, []);
|
}, [saveOnlyNew]);
|
||||||
|
|
||||||
async function handleDelete(ids: string[]) {
|
async function handleDelete(ids: string[]) {
|
||||||
if (!ids.length || deleting) return;
|
if (!ids.length || deleting) return;
|
||||||
@@ -127,6 +146,29 @@ export default function SuchePage() {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Save option */}
|
||||||
|
{!loading && !searchDone && (
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
marginTop: 10,
|
||||||
|
padding: "0 4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "fit-content",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={saveOnlyNew}
|
||||||
|
onChange={e => setSaveOnlyNew(e.target.checked)}
|
||||||
|
style={{ accentColor: "#3b82f6", width: 13, height: 13, cursor: "pointer" }}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: 12, color: "#6b7280" }}>Nur neue Leads speichern</span>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Loading Card */}
|
{/* Loading Card */}
|
||||||
{loading && jobId && (
|
{loading && jobId && (
|
||||||
<LoadingCard
|
<LoadingCard
|
||||||
@@ -206,30 +248,38 @@ export default function SuchePage() {
|
|||||||
: `${visibleLeads.length} Leads`}
|
: `${visibleLeads.length} Leads`}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Nur neue Filter */}
|
{/* Filter tabs */}
|
||||||
<button
|
<div style={{
|
||||||
className="filter-pill"
|
|
||||||
onClick={() => setOnlyNew(v => !v)}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
background: "#0d0d18",
|
||||||
gap: 5,
|
border: "1px solid #2e2e3e",
|
||||||
background: onlyNew ? "rgba(34,197,94,0.15)" : "rgba(255,255,255,0.05)",
|
borderRadius: 8,
|
||||||
border: `1px solid ${onlyNew ? "rgba(34,197,94,0.4)" : "#2e2e3e"}`,
|
padding: 2,
|
||||||
borderRadius: 20,
|
gap: 2,
|
||||||
|
}}>
|
||||||
|
{[
|
||||||
|
{ label: `Alle (${leads.length})`, value: false },
|
||||||
|
{ label: `Nur neue (${newCount})`, value: true },
|
||||||
|
].map(tab => (
|
||||||
|
<button
|
||||||
|
key={String(tab.value)}
|
||||||
|
onClick={() => setOnlyNew(tab.value)}
|
||||||
|
style={{
|
||||||
padding: "3px 10px",
|
padding: "3px 10px",
|
||||||
|
borderRadius: 6,
|
||||||
|
border: "none",
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: onlyNew ? "#22c55e" : "#9ca3af",
|
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
background: onlyNew === tab.value ? "#1e1e2e" : "transparent",
|
||||||
|
color: onlyNew === tab.value ? "#ffffff" : "#6b7280",
|
||||||
|
fontWeight: onlyNew === tab.value ? 500 : 400,
|
||||||
|
transition: "background 0.12s, color 0.12s",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{
|
{tab.label}
|
||||||
width: 6, height: 6, borderRadius: 3,
|
|
||||||
background: onlyNew ? "#22c55e" : "#6b7280",
|
|
||||||
display: "inline-block",
|
|
||||||
}} />
|
|
||||||
Nur neue ({newCount})
|
|
||||||
</button>
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user