feat: Interviews, Mail-Papierkorb, Kontaktart

Drei Aenderungen aus dem Alltagsgebrauch:

- Der Kalender heisst jetzt "Interviews" - dafuer wird er benutzt. Der Wert in
  ViewState wandert mit, damit Menue und Kopfzeile nicht auseinanderlaufen.
  Sichtbare Reste ("Kalenderfarbe") heissen jetzt Terminfarbe.

- Mails haben einen Papierkorb, der unter dem Menuepunkt aufklappt. Loeschen
  heisst dabei ausschliesslich: im CRM. Im Postfach crm@perinco.de bleibt die
  Mail unberuehrt - Schreibrechte darauf hat die Anwendung nicht. Damit der
  Delta-Abgleich geloeschte Mails nicht wieder einliest, bleibt nach dem Leeren
  eine leere Zeile als Merker stehen (crm_purged); runMailSync() ueberspringt
  alles, was im CRM geloescht ist. Zurueckholen und Leeren darf jeder
  Angemeldete, Leeren nach einer Warnung.

- Der Kontaktstatus (Neu .. Verloren) wird zur Kontaktart: Recruiter, Partner,
  Endkunde. Die Spalte heisst jetzt kind, weil "Status" das Feld falsch
  benannte. migrateLeadKind() stellt bestehende Datenbanken um und setzt alle
  vorhandenen Kontakte auf Recruiter - welche Art jemand ist, sagt der alte
  Status nicht (so mit dem Nutzer entschieden).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-08-18 11:12:49 +02:00
co-authored by Claude Opus 5
parent 025390e18a
commit 8741a9ae0c
39 changed files with 719 additions and 199 deletions
+14 -5
View File
@@ -87,7 +87,7 @@ const App: React.FC = () => {
if (!silent) setIsDataLoading(true);
try {
// Die Benutzerliste braucht jeder: Netzwerkdiagramm, Zustaendigkeit,
// @-Mentions, Teilnehmer und Farben im Kalender. /users/directory ist
// @-Mentions, Teilnehmer und Farben der Termine. /users/directory ist
// deshalb - anders als /users - fuer alle Rollen freigegeben.
const [leadRows, connectionRows, eventRows, userRows, opportunityRows] = await Promise.all([
leadsApi.list(),
@@ -118,7 +118,7 @@ const App: React.FC = () => {
if (!user) return <LoginScreen />;
/*
* Kontakte und Vorbereich trennen. Ueberall im CRM - Opportunities, Kalender,
* Kontakte und Vorbereich trennen. Ueberall im CRM - Opportunities, Interviews,
* Netzwerk, Mailzuordnung - gilt nur der gepflegte Bestand; die Funde aus dem
* Postfach sind Rohmaterial und sollen in keiner Auswahlliste auftauchen.
*/
@@ -139,6 +139,7 @@ const App: React.FC = () => {
// mit, deshalb steht hier nur, worum es auf der Seite geht.
const STATIC_SUBTITLES: Partial<Record<ViewState, string>> = {
Mails: 'Postfach crm@perinco.de - Mails zuordnen und Kontakte übernehmen',
Papierkorb: 'Im CRM gelöschte Mails - im Postfach crm@perinco.de bleiben sie erhalten',
Scanner: 'Projekt Scanner - Suchagenten, Portale und Projektübereinstimmungen',
Unpassend: 'Projekt Scanner - als „passt nicht" markierte Treffer',
Suchagenten: 'Projekt Scanner - Beraterprofile mit CV und Suchbegriffen',
@@ -159,8 +160,8 @@ const App: React.FC = () => {
? `${mailPoolLeads.length} ${
mailPoolLeads.length === 1 ? 'Fund' : 'Funde'
} aus dem Postfach · noch nicht in den Kontakten`
: currentView === 'Kalender'
? `Gemeinsamer Teamkalender · ${events.length} ${
: currentView === 'Interviews'
? `Gemeinsame Termine im Team · ${events.length} ${
events.length === 1 ? 'Termin' : 'Termine'
} · ${upcomingCount} anstehend`
: STATIC_SUBTITLES[currentView];
@@ -193,7 +194,7 @@ const App: React.FC = () => {
reload={() => loadData(true)}
/>
)}
{currentView === 'Kalender' && (
{currentView === 'Interviews' && (
<Calendar
events={events}
leads={leads}
@@ -226,6 +227,14 @@ const App: React.FC = () => {
{currentView === 'Mails' && (
<Mails leads={leads} opportunities={opportunities} reload={() => loadData(true)} />
)}
{currentView === 'Papierkorb' && (
<Mails
leads={leads}
opportunities={opportunities}
reload={() => loadData(true)}
mode="papierkorb"
/>
)}
{currentView === 'Scanner' && <ScannerDashboard />}
{currentView === 'Unpassend' && <ScannerRejected />}
{currentView === 'Suchagenten' && <ScannerAgents />}