import React, { useCallback, useEffect, useState } from 'react'; import { formatDate, formatStamp, type Lead } from '../../types'; import { leadsApi, suspectsApi, type Suspect } from '../../lib/api'; import { Modal } from '../ui/Modal'; import { Avatar } from '../ui/Avatar'; import { PrimaryButton, SecondaryButton } from '../ui/form'; import { useNavigation } from '../../lib/navigation'; import { useToast } from '../Toast'; /* * Erinnerungen an Leads und Suspects. * * Drei Faelle, alle als Pop-up: * * faellig eine gesetzte Lead-Erinnerung ist erreicht ("in 2 Tagen anrufen") * alt ein Lead wurde einen Monat lang nicht angefasst; dann fragt das * CRM, ob er archiviert werden soll. Wird er das nicht, laeuft die * Frist von vorn - das erledigt der Server, indem das Quittieren * `last_contact_at` auf jetzt setzt. * suspect der "naechste Impuls" eines Suspects ist erreicht: die Person * erneut ansprechen. "Erledigt" setzt Letzter Kontakt auf heute. * * Abgefragt wird beim Anmelden und danach stuendlich. Oefter waere sinnlos: * die Fristen sind Tage, nicht Minuten. */ const POLL_MS = 60 * 60 * 1000; type Item = { kind: 'due' | 'stale'; lead: Lead } | { kind: 'suspect'; suspect: Suspect }; export const ReminderWatcher: React.FC<{ onChanged: () => void }> = ({ onChanged }) => { const { toast } = useToast(); const { open, setView } = useNavigation(); const [queue, setQueue] = useState([]); const [staleDays, setStaleDays] = useState(30); const check = useCallback(async () => { // Beide Abfragen unabhaengig: faellt eine aus, kommt die andere trotzdem. const [leads, suspects] = await Promise.allSettled([leadsApi.reminders(), suspectsApi.reminders()]); const items: Item[] = []; if (leads.status === 'fulfilled') { setStaleDays(leads.value.stale_days); items.push( ...leads.value.due.map((lead) => ({ lead, kind: 'due' as const })), ...leads.value.stale.map((lead) => ({ lead, kind: 'stale' as const })) ); } if (suspects.status === 'fulfilled') { items.push(...suspects.value.due.map((suspect) => ({ suspect, kind: 'suspect' as const }))); } setQueue(items); }, []); useEffect(() => { void check(); const timer = setInterval(() => void check(), POLL_MS); return () => clearInterval(timer); }, [check]); const current = queue[0]; if (!current) return null; const next = () => setQueue((list) => list.slice(1)); const subtitle = queue.length > 1 ? `${queue.length} offene Erinnerungen` : undefined; if (current.kind === 'suspect') { const { suspect } = current; const acknowledgeSuspect = async (snoozeDays?: number) => { try { await suspectsApi.acknowledge(suspect.id, snoozeDays); toast(snoozeDays ? `Erinnerung um ${snoozeDays} Tag(e) verschoben.` : 'Als kontaktiert vermerkt.', 'success'); } catch (err) { toast(err instanceof Error ? err.message : 'Speichern fehlgeschlagen.', 'error'); } finally { next(); onChanged(); } }; return ( void acknowledgeSuspect(7)}> In 7 Tagen erneut void acknowledgeSuspect()}> Kontakt aufgenommen } >

Zeit, {suspect.name} erneut anzusprechen.

{(suspect.role || suspect.company) && (

{[suspect.role, suspect.company].filter(Boolean).join(' · ')}

)} {suspect.linkedin && ( LinkedIn-Profil öffnen ↗ )} {suspect.notes &&

{suspect.notes}

}

Letzter Kontakt: {suspect.last_contact_on ? formatDate(suspect.last_contact_on) : 'noch keiner'}

); } const { lead, kind } = current; const acknowledge = async (snoozeDays?: number) => { try { await leadsApi.acknowledge(lead.id, snoozeDays); if (snoozeDays) toast(`Erinnerung um ${snoozeDays} Tag(e) verschoben.`, 'success'); } catch (err) { toast(err instanceof Error ? err.message : 'Speichern fehlgeschlagen.', 'error'); } finally { next(); onChanged(); } }; const archive = async () => { try { await leadsApi.update(lead.id, { archived: true }); toast(`"${lead.title}" archiviert.`, 'success'); } catch (err) { toast(err instanceof Error ? err.message : 'Archivieren fehlgeschlagen.', 'error'); } finally { next(); onChanged(); } }; // Vorschlag fuer das Verschieben: das am Lead gepflegte Intervall, sonst zwei Tage. const snooze = lead.reminder_days && lead.reminder_days > 0 ? lead.reminder_days : 2; return ( void acknowledge()} title={kind === 'due' ? 'Erinnerung' : 'Lead liegt lange still'} subtitle={subtitle} footer={ kind === 'due' ? ( <> void acknowledge(snooze)}> In {snooze} Tag(en) erneut void acknowledge()}> Erledigt ) : ( <> void acknowledge()}> Nicht archivieren Archivieren ) } >

{kind === 'due' ? ( <> {lead.title} {lead.company_name ? ` – ${lead.company_name}` : ''} {lead.contact_name ? ` · ${lead.contact_name} anrufen` : ''} ) : ( <> Achtung: {lead.title} wurde seit {staleDays} Tagen nicht kontaktiert. Soll der Lead archiviert werden? )}

{lead.company_name && ( {lead.company_name} )} {lead.contact_name && ( {lead.contact_name} {lead.contact_phone ? ` · ${lead.contact_phone}` : ''} )}

Zuletzt angefasst: {formatStamp(lead.last_contact_at ?? lead.created_at)}

); };