feat(kontakte): Vorbereich "Aus Mails" mit Dublettenabgleich
Der Postfachabgleich hat gefundene Personen bisher direkt in den Kontaktbestand geschrieben. Damit wächst eine gepflegte Liste ungefragt mit jeder Adresse aus jeder Mail - genau daran ist das Altsystem gescheitert. Funde landen jetzt in einem Vorbereich (crm_leads.mail_pool = 1), der als Unterpunkt "Aus Mails" unter dem Menüpunkt Kontakte aufklappt. Der Kontaktbestand bleibt unberührt, bis dort jemand entscheidet; in Opportunities, Kalender und Netzwerk tauchen die Funde nicht auf. Eine Spalte statt einer zweiten Tabelle, weil crm_mail_links und crm_mail_contact_candidates per Fremdschlüssel auf crm_leads zeigen - so behält der Vorbereich seine Mailbezüge. Übernehmen kennt zwei Wege: - neue Person: Kopie mit mail_pool = 0, die Mailverknüpfungen wandern mit - Dublette (Adresse, sonst voller Name): gold markiert, abweichende Felder mit altem und neuem Wert; die Feldauswahl schreibt sie in den vorhandenen Kontakt. Vorausgewählt sind nur Lücken - ein Wert aus einer alten Signatur soll nichts Gepflegtes stillschweigend ersetzen. db.js verschiebt beim ersten Start die vorhandenen Kontakte mit source = 'Mail' in den Vorbereich, einmalig über den Merker leads_mail_pool_migrated in crm_settings. Die Sidebar-Gruppe ist dabei generisch geworden (MenuGroup) - Kontakte und Scanner benutzen jetzt denselben Code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8090d0aae1
commit
ea55acdb5c
+30
-2
@@ -3,6 +3,7 @@ import { Sidebar } from './components/Sidebar';
|
||||
import { Header } from './components/Header';
|
||||
import { LoginScreen } from './components/LoginScreen';
|
||||
import { Leads } from './components/leads/Leads';
|
||||
import { MailPoolLeads } from './components/leads/MailPoolLeads';
|
||||
import { Opportunities } from './components/opportunities/Opportunities';
|
||||
import { Calendar } from './components/calendar/Calendar';
|
||||
import { parseSqlDateTime } from './components/calendar/calendarUtils';
|
||||
@@ -57,7 +58,12 @@ const App: React.FC = () => {
|
||||
*/
|
||||
const [focusLeadId, setFocusLeadId] = useState<number | null>(null);
|
||||
|
||||
const [leads, setLeads] = useState<Lead[]>([]);
|
||||
/**
|
||||
* Alle Kontakte inklusive Vorbereich - getrennt wird direkt darunter. Der
|
||||
* Server liefert absichtlich beide Bestaende in einem Aufruf: die Ansicht
|
||||
* "Aus Mails" braucht ohnehin beide, um Dubletten zu erkennen.
|
||||
*/
|
||||
const [allLeads, setAllLeads] = useState<Lead[]>([]);
|
||||
const [connections, setConnections] = useState<LeadConnection[]>([]);
|
||||
const [users, setUsers] = useState<AppUser[]>([]);
|
||||
const [events, setEvents] = useState<CalendarEvent[]>([]);
|
||||
@@ -90,7 +96,7 @@ const App: React.FC = () => {
|
||||
usersApi.directory(),
|
||||
opportunitiesApi.list(),
|
||||
]);
|
||||
setLeads(leadRows);
|
||||
setAllLeads(leadRows);
|
||||
setConnections(connectionRows);
|
||||
setEvents(eventRows);
|
||||
setUsers(userRows);
|
||||
@@ -111,6 +117,14 @@ const App: React.FC = () => {
|
||||
if (isLoading) return <Splash />;
|
||||
if (!user) return <LoginScreen />;
|
||||
|
||||
/*
|
||||
* Kontakte und Vorbereich trennen. Ueberall im CRM - Opportunities, Kalender,
|
||||
* Netzwerk, Mailzuordnung - gilt nur der gepflegte Bestand; die Funde aus dem
|
||||
* Postfach sind Rohmaterial und sollen in keiner Auswahlliste auftauchen.
|
||||
*/
|
||||
const leads = allLeads.filter((lead) => !lead.mail_pool);
|
||||
const mailPoolLeads = allLeads.filter((lead) => lead.mail_pool);
|
||||
|
||||
const upcomingCount = events.filter(
|
||||
(event) => parseSqlDateTime(event.ends_at).getTime() >= Date.now()
|
||||
).length;
|
||||
@@ -141,6 +155,10 @@ const App: React.FC = () => {
|
||||
? `${leads.length} ${leads.length === 1 ? 'Kontakt' : 'Kontakte'} · ${connections.length} ${
|
||||
connections.length === 1 ? 'Verbindung' : 'Verbindungen'
|
||||
}`
|
||||
: currentView === 'Aus Mails'
|
||||
? `${mailPoolLeads.length} ${
|
||||
mailPoolLeads.length === 1 ? 'Fund' : 'Funde'
|
||||
} aus dem Postfach · noch nicht in den Kontakten`
|
||||
: currentView === 'Kalender'
|
||||
? `Gemeinsamer Teamkalender · ${events.length} ${
|
||||
events.length === 1 ? 'Termin' : 'Termine'
|
||||
@@ -155,6 +173,7 @@ const App: React.FC = () => {
|
||||
onViewChange={goTo}
|
||||
isOpen={isSidebarOpen}
|
||||
onToggle={() => setIsSidebarOpen((open) => !open)}
|
||||
mailPoolCount={mailPoolLeads.length}
|
||||
/>
|
||||
|
||||
<main className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
@@ -195,6 +214,15 @@ const App: React.FC = () => {
|
||||
onFocusHandled={() => setFocusLeadId(null)}
|
||||
/>
|
||||
)}
|
||||
{currentView === 'Aus Mails' && (
|
||||
<MailPoolLeads
|
||||
poolLeads={mailPoolLeads}
|
||||
leads={leads}
|
||||
isLoading={isDataLoading}
|
||||
reload={() => loadData(true)}
|
||||
onOpenLead={(leadId) => goTo('Kontakte', { leadId })}
|
||||
/>
|
||||
)}
|
||||
{currentView === 'Mails' && (
|
||||
<Mails leads={leads} opportunities={opportunities} reload={() => loadData(true)} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user