Commit graph

89 commits

Author SHA1 Message Date
Thomas Reitz
6bfce4af97 feat(crm): Vorgangsart (DealType) konfigurierbares Dropdown + Pipeline-Leerstate
- crm-service: Neues DealType-Model (deal_types Tabelle) mit name, color, sortOrder
  und Relation zu Deal.dealTypeId; Migration 20260313_deal_type
- crm-service: Vollstaendiger CRUD REST-Endpoint /crm/deal-types (TenantGuard)
- crm-service: CreateDealDto um optionales dealTypeId erweitert
- frontend: DealType Interface, API (dealTypesApi), Hooks (useDealTypes/...)
- frontend: CrmSettingsPage > Weitere Einstellungen > DealTypesConfig mit Farbpicker
- frontend: DealFormModal: Vorgangsart-Dropdown + Hinweis bei leerer Pipeline-Liste

Deployment: prisma migrate deploy && prisma generate im crm-service ausfuehren.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 16:15:02 +01:00
Thomas Reitz
f72ac6cb90 fix(frontend): Kalender-Eintragsfarben vereinheitlichen (einheitliches Primary-Blau statt Hash-Farben)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 15:56:14 +01:00
Thomas Reitz
8b4a71edb2 fix(frontend): Topbar auf Dashboard ausblenden – Profil nur in Tab-Leiste
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 15:53:26 +01:00
Thomas Reitz
2af54246c8 feat(frontend): E-Mail-Popup, Aktivitaeten-Zeitstrahl + Profil in Tab-Leiste
- EmailsTab: Outlook-aehnlicher Detail-Popup beim Klick auf E-Mail (Von/An/Datum/Anhang-Meta, Body-Vorschau, In Kontakt speichern als EMAIL-Aktivitaet)
- Neues EmailsTab.module.css fuer kompakte Liste und Modal
- ContactDetailPage: Aktivitaeten-Filterleiste (Typ + Zeitraum Von/Bis)
- ContactDetailPage: Zeitstrahl mit vertikaler Verbindungslinie, farbigen Typ-Badges (Note/Call/Email/Meeting/Task/FollowUp)
- DashboardPage: Profil-Bereich (Theme-Schalter, Avatar, Name, Logout) in Tab-Leiste integriert und leicht farblich abgesetzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 15:51:10 +01:00
Thomas Reitz
17a81c97ef feat(frontend): Kalender-Modal – Teilnehmer-Chips + Auto-Vorauswahl
- Suche startet mit Organisator-Name vorbelegt → CRM zeigt sofort
  passende Kontakte
- Klickbare Teilnehmer-Chips ermöglichen schnellen Wechsel zwischen
  den Event-Attendees als Suchbegriff
- Aktiver Chip wird farblich hervorgehoben

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:34:56 +01:00
Thomas Reitz
2d56ab6b3d feat(frontend): Kalender-Event-Modal + Zeitzonen-Fix
- Neues CalendarEventModal: Klick auf Termin in Agenda-/Wochenansicht
  öffnet Popup mit Betreff, Datum/Uhrzeit, Ort, Organisator, Teilnehmern
  und Online-Meeting-Link
- CRM-Aktivität: Kontaktsuche direkt im Modal; Termin als MEETING-
  Aktivität beim gewählten CRM-Kontakt speichern
- "Im Outlook öffnen"-Link im Modal-Footer
- Zeitzonen-Fix: MS Graph liefert UTC-Zeiten ohne 'Z'-Suffix →
  toDate() hängt 'Z' an → alle Termine jetzt in korrekter Ortszeit
- DayAgenda + AgendaView + WeekView: <a>-Tags durch klickbare <div>
  (role=button) ersetzt; onEventClick-Prop weitergereicht

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:30:56 +01:00
Thomas Reitz
3d75a7f9de feat(frontend+core): Dashboard Kontakte-Tab (O365) + Admin Logo-/Sidebar-Breite
Dashboard Kontakte-Tab:
- DashboardContactsTab.tsx + CSS: O365-Kontakte als Visitenkarten oder Liste
- Kachelansicht (auto-fill Grid) + Listenansicht (6-spaltig) umschaltbar
- Suchfeld (Name, E-Mail, Firma) mit Live-Filter
- Klick öffnet Detail-Modal mit allen Kontaktdaten (E-Mail/Telefon als Links)
- CRM-Import-Button: mappt M365Contact → CreateContactPayload, importiert direkt
- Nicht verbunden / Laden / Fehler States
- DashboardPage: ComingSoonTab entfernt, DashboardContactsTab eingebunden

Admin Branding — Logo-Breite + Sidebar-Breite:
- settings.controller.ts: logoWidth + sidebarWidth in GET/POST Branding
- AdminCustomizePage: Slider Logo-Breite (40–240px) + Sidebar-Breite (200–360px)
  mit Live-Vorschau (skalierte Mini-Sidebar)
- AppLayout: Logo-maxWidth aus branding.logoWidth; Sidebar width + main marginLeft
  dynamisch aus branding.sidebarWidth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:03:45 +01:00
Thomas Reitz
d9c6240a3e fix(frontend): Anwendungen-Gruppe bei Start eingeklappt, Dashboard-Klick resettet auf Home-Tab
- AppLayout: appsOpen initial false (Anwendungen immer eingeklappt beim Laden)
- DashboardPage: useLocation + useEffect auf location.key → setzt activeTab
  auf 'home' bei jedem Navigations-Klick auf Dashboard in der Sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 13:51:04 +01:00
Thomas Reitz
5f50ba1315 feat(frontend): Profil-Topbar — Avatar, Theme-Schalter und Logout nach oben rechts
Sidebar-Bottom-Bereich (Profil, Abmelden, Theme-Toggle) vollstaendig entfernt
und durch eine sticky Topbar im Hauptinhaltsbereich ersetzt:
- Theme-Schalter: kompakte Icon-Button-Gruppe (☀ hell / ☾ dunkel / ⚙ system)
- Benutzer-Avatar + Name klickbar → /profile
- Logout-Icon-Button (SVG, Tooltip "Abmelden")
Sidebar zeigt nur noch Logo, Navigation und Admin-Link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 13:46:33 +01:00
Thomas Reitz
0f63c28110 fix: WeatherWidget Ort mittig darunter; +30 Sprüche des Tages (65 gesamt) 2026-03-13 13:38:45 +01:00
Thomas Reitz
b34370345f fix: Analoguhr größer, digitale Zeit entfernt, Datum ausgeschrieben (long) 2026-03-13 13:35:17 +01:00
Thomas Reitz
baa0cafd40 fix: WeatherWidget auf volle Spaltenbreite (gleich wie Forecast-Karte) 2026-03-13 13:34:16 +01:00
Thomas Reitz
ceea82a2ac feat: Dashboard Home-Tab mit Analoguhr, 3-Tage-Prognose, Spruch des Tages + kompakte Widgets
- Analoge SVG-Uhr (AnalogClock.tsx) aktualisiert jede Sekunde via setInterval
- useWeather: 3-Tage-Prognose via Open-Meteo daily-Parameter (weather_code, tempMax, tempMin)
- Dashboard Home-Tab: 3-Spalten-Layout (Uhr+Wetter links | Aufgaben+Mails mitte | Messe+Agenda rechts)
- Spruch des Tages rechts im Header (deterministisch nach Tagesdatum, 35 dt. Zitate)
- WeatherWidget aus dem Header in die linke Spalte verschoben
- Kompaktes Aufgaben-Widget: Top 8 offene Aufgaben (CRM + O365), direkt erledigbar
- Kompaktes E-Mail-Widget: Posteingang der letzten 3 Tage, direkter Öffnen-Link
- „Alle →" Buttons schalten auf den jeweiligen Tab um

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 13:30:45 +01:00
Thomas Reitz
2348602fb0 feat: Erweiterte Profilfelder (analog O365) + Profilbild-Sync aus Microsoft 365
Neue Felder im Benutzerprofil (analog Microsoft 365 /me):
- Stellenbezeichnung (jobTitle), Abteilung (department)
- Firma (companyName), Standort (officeLocation)

Changes:
- Core: Prisma-Migration + neue Felder in User-Model, UpdateUserDto,
  findById/update/updateProfile
- CRM: M365UserProfile-Interface + getM365Profile um neue Felder erweitert;
  neue Methode getM365Photo() lädt 96x96 JPEG als Base64 Data-URL;
  neuer Endpoint GET /crm/office365/photo
- Frontend: AuthContext User-Interface, M365UserProfile-Typ, office365Api.getM365Photo()
  ProfilePage: Neues Formular-Fieldset "Organisation" mit 4 Feldern;
  manueller Sync-Button übernimmt auch Profilbild (immer überschreiben);
  useO365ProfileSync: Auto-Sync lädt Foto nur wenn noch kein INSIGHT-Avatar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 13:08:56 +01:00
Thomas Reitz
138742d385 feat: O365-Profil automatisch beim Login synchronisieren
- Neuer Hook useO365ProfileSync: läuft einmalig pro Browser-Session,
  überschreibt INSIGHT-Profil-Felder mit O365-Daten (firstName, lastName,
  phone, mobile, city, street, postalCode) — kein UI-Feedback, kein Fehler
  bricht die UX auf.
- AppLayout ruft useO365ProfileSync() auf, sodass die Synchronisation
  beim Laden der App (nach Login) automatisch startet.
- ProfilePage: "↓ O365 übernehmen"-Button überschreibt jetzt alle Felder
  wo O365 Daten hat (nicht mehr nur leere Felder) — konsistent mit dem
  Auto-Sync-Verhalten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 12:50:25 +01:00
Thomas Reitz
347a6ca418 feat: Profil mit Microsoft 365 Daten anreichern
- GraphService: getM365Profile() lädt givenName, surname, mobilePhone,
  businessPhones, city, streetAddress, postalCode, jobTitle via /me
- Office365Controller: GET /crm/office365/profile Endpunkt
- Frontend types: M365UserProfile Interface
- Frontend api: office365Api.getM365Profile()
- ProfilePage: O365 uebernehmen Button (nur wenn M365 verbunden)
  fuellt leere Felder: Vorname, Nachname, Telefon, Mobil, Ort, Strasse, PLZ
  Bestehende Werte werden NICHT ueberschrieben; Feedback zeigt welche
  Felder uebernommen wurden; User muss Speichern klicken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 12:38:43 +01:00
Thomas Reitz
403c581e57 fix: Agenda-Breite im Home-Sidebar füllt volle 300px
- DayAgenda: fullWidth-Prop ergänzt (setzt .agendaFull → width: 100%)
- Im Kalender-Tab bleibt die Agenda bei 260px (flex-shrink: 0)
- HomeSidebar übergibt fullWidth, sodass die Agenda den Sidebar
  lückenlos ausfüllt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 12:32:38 +01:00
Thomas Reitz
653464c89b feat: Kompakter Messe-Ticker im Home-Sidebar mit Detail-Popup
- EventCountdownTiles aus homeMain entfernt
- Neuer CompactMesseTicker im Sidebar: kleine klickbare Zeilen
  mit farbiger linker Linie (blau=bevorstehend, grün=läuft)
- Klick öffnet MesseDetailModal: Name, Status-Chip, Countdown,
  Fortschrittsbalken (bei laufenden Messen), Datum, Ort/Stand,
  Beschreibung, Website-Link
- HomeDayAgendaWidget → HomeSidebar: zeigt Ticker + Tages-Agenda
  in einer Spalte; Sidebar auf 300px verbreitert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 12:27:07 +01:00
Thomas Reitz
3b15c8ab9b feat: Aufgaben-Tab im Dashboard (O365 + CRM bidirektional)
- GraphService: graphPost/graphPatch Helfer; getAllTasksFlat (inkl.
  Body für CRM-Sync-Marker), createM365Task, completeM365Task
- Office365Controller: GET tasks/flat, POST tasks, PATCH tasks/:listId/:taskId/complete
- ActivitiesService/Controller: GET /crm/activities/open-tasks
  (TASK + FOLLOWUP, nicht erledigt)
- Frontend types: M365TaskFlat + CrmOpenTask Interfaces
- Frontend api/hooks: getTasksFlat, createTask, completeTask,
  getOpenTasks; neue Hooks useOffice365TasksFlat, useCrmOpenTasks,
  usePushTaskToO365, useCompleteO365Task, useCompleteCrmTask
- DashboardTasksTab: vereinheitlichte Aufgabenliste mit Farbcodierung
  (O365 blau, CRM orange, Synced grün), Push-Button, Erledigen-Button
- Bidirektionaler Sync via [INSIGHT_CRM:{activityId}] Marker im O365
  Task Body; Erledigen eines Synced-Tasks aktualisiert beide Systeme
- DashboardPage: Tasks-Tab auf DashboardTasksTab umgestellt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 12:01:04 +01:00
Thomas Reitz
fbf0b33a1f feat(dashboard): E-Mail Lesefenster + Kalender Umbau
E-Mail Tab:
- Klick auf E-Mail öffnet Detail-Modal (Lesefenster wie Outlook)
- Modal zeigt: Betreff, Absender, Datum, Anhang-Info, Body-Vorschau
- CRM-Bereich: gefundener Kontakt mit "Im CRM öffnen" + "Als Aktivität"
  speichern; kein Kontakt → "Kontakt anlegen" navigiert zu /crm/contacts
- "In Outlook öffnen" Link im Footer des Modals

Kalender Tab:
- WeekView: nur Arbeitstage Mo–Fr (5-Spalten-Grid statt 7)
- Neue Ansicht "Agenda": 14-Tage-Listenansicht (eigener Toggle-Button)
- Tages-Agenda nur bei Monat- und Wochenansicht sichtbar (nicht Agenda-View)
- Home-Tab: Tages-Agenda des heutigen Tages als Widget rechts
  (nur sichtbar wenn M365 verbunden)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 11:36:30 +01:00
Thomas Reitz
f2ed8d0a93 fix(dashboard): Kalender-Toggle immer sichtbar, E-Mail Breite auf 70% angepasst 2026-03-13 11:20:01 +01:00
Thomas Reitz
53666122d0 fix(dashboard): E-Mail Bereich auf 50% Breite begrenzen, Textumbruch aktivieren 2026-03-13 11:16:28 +01:00
Thomas Reitz
76e8dff577 feat(dashboard): Kalender-Tab mit Monats-/Wochenansicht und Tages-Agenda
- Graph API: getCalendarEventsForRange() für beliebigen Datumsbereich,
  GET /crm/office365/calendar/range?startDate=&endDate= Endpoint
  (vor bestehenden calendar-Route definiert um Routing-Konflikt zu vermeiden)
- Graph API: wellKnownName aus mailFolders $select entfernt (400-Fehler auf
  Exchange-Tenants die das OData-Property nicht unterstützen)
- Frontend: DashboardCalendarTab mit MonthView (6×7 Grid), WeekView (7 Spalten)
  und DayAgenda (rechts 1/3), Navigation vor/zurück + Heute-Button,
  deterministisches Event-Coloring, Klick öffnet Termin in Outlook Online
- Frontend: DashboardEmailTab Ordner-Sortierung auf Display-Name-Basis
  (wellKnownName optional, isInboxFolder() erkennt Posteingang/Inbox)
- Frontend: M365MailFolder.wellKnownName als optional markiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 10:54:32 +01:00
Thomas Reitz
b6b182a349 fix(dashboard): E-Mail Tab lädt sofort mit well-known Inbox-ID als Fallback
Vorher: activeFolderId war null bis Ordnerliste geladen → E-Mail-Query nie gestartet
Jetzt: Default-Ordner-ID = 'inbox' (Graph API Well-Known-Name) → sofortiger Abruf
Zusätzlich: Fehler-/Leer-Zustand in der Ordner-Sidebar anzeigen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 10:32:04 +01:00
Thomas Reitz
01dc8bb41c feat(dashboard): E-Mail Tab mit Outlook-Postfach, Ordner-Navigation und Aktivitäten-Speicherung
- GraphService: getMailFolders() + getMailsByFolder(folderId, days) Methoden
- Office365Controller: GET /crm/office365/folders und /folders/:id/messages?days=X Endpoints
- ContactsController/Service: GET /crm/contacts/lookup?email=xxx für CRM-Kontakt-Abgleich
- Frontend types: M365MailFolder + CrmContactLookup Interfaces
- Frontend API: office365Api.getMailFolders/getMailsInFolder + contactsApi.lookupByEmail
- Frontend Hooks: useOffice365MailFolders, useOffice365MailsInFolder, useContactByEmail
- DashboardEmailTab: Ordner-Sidebar, Zeitfilter (1/7/14 Tage/alle), E-Mail-Liste
  mit Outlook-Link, CRM-Badge bei bekannten Absendern, Aktivitäten-Modal mit Kommentar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 10:16:05 +01:00
Thomas Reitz
f6dd072f23 feat(dashboard): 5 Tabs — Home, E-Mail, Kalender, Aufgaben, Kontakte
Tab-Leiste auf Dashboard-Seite. Home zeigt bisherigen Inhalt,
restliche Tabs als Platzhalter (Inhalt folgt).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 09:49:12 +01:00
Thomas Reitz
ad9c48bcb6 feat(crm): Office365-Übersichtsseite + Graph API Bugfixes
- Neuer Office365-Menüpunkt mit 4 Tabs (E-Mails, Kalender, Kontakte, Aufgaben)
- CRM-Service: Office365Controller mit globalen Graph-Endpoints
- Fix: $search + $orderby Kombination in Graph API nicht erlaubt
- M365Contact Interface + attendees/hasAttachments Typen ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 06:59:52 +01:00
Thomas Reitz
1ecd7dad82 fix(ms365): OAuth-Connect via API-Call statt direktem Browser-Link
Problem: <a href="/api/v1/auth/integrations/microsoft-365"> sendet keinen
JWT-Authorization-Header (JWT liegt im Memory, nicht als Cookie).

Lösung:
- Backend: initM365Integration gibt JSON {url} zurück statt server-redirect
- Frontend: integrationsApi.connectM365() ruft Endpoint via Axios ab, dann
  window.location.href zur OAuth-URL
- ProfilePage + EmailsTab + CalendarTab + TasksTab: <a href> → <button onClick>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:57:20 +01:00
Thomas Reitz
30c4b208d9 feat(crm): Phase 3 MS365 Frontend — Integrationen + Kontakt-Tabs
- Neuer "Integrationen"-Tab in ProfilePage mit "Microsoft 365 verbinden"-
  Button, OAuth-Callback-Handling (?integration=...&status=...), Trennen-Button
- EmailsTab, CalendarTab, TasksTab fuer ContactDetailPage (via MS Graph Proxy)
- useIntegrations, useDisconnectM365, useContactEmails/Calendar/Tasks Hooks
- integrationsApi + graphApi in crm/api.ts
- M365Email, M365CalendarEvent, M365Task, UserIntegration Types in crm/types.ts
- Tabs nur sichtbar wenn Kontakt eine E-Mail-Adresse hat; ohne Verbindung Connect-Button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:48:43 +01:00
Thomas Reitz
237e0772e6 feat(crm): Kanban-Board mit Drag & Drop (Phase 3.0)
- Neue KanbanPage (/crm/kanban) mit @dnd-kit/core + @dnd-kit/sortable
- Columns pro Pipeline-Stage mit Deal-Karten
- Cross-Column Drag & Drop — stageId per PATCH /crm/deals/:id aktualisiert
- Optimistisches Update: sofortiges visuelles Feedback, Rollback bei Fehler
- Toggle-Button für abgeschlossene Vorgänge (WON/LOST)
- Pipeline-Selektor, Gesamt-Wert pro Spalte (offene Deals)
- NavLink "Kanban" in AppLayout.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:29:01 +01:00
Thomas Reitz
bfe672ec96 feat(crm): Vertragsdokumente — Datei-Upload Frontend + Backend-Briefing
Frontend (gegen API-Contract, bereit fuer Backend-Integration):
- ContractFile Interface in types.ts
- contractFilesApi (upload, list, download-as-blob, delete) in api.ts
- useContractFiles, useUploadContractFile, useDeleteContractFile in hooks.ts
- ContractsCard: Dokumente-Sektion im Edit-Modal
  - Dateiliste mit Icon (PDF/Word/Excel), Name, Groesse, Download, Loeschen
  - Upload-Button (PDF/DOC/DOCX/XLSX/XLS, max 25 MB)
  - Client-seitige Groessenvalidierung
  - Blob-Download via Axios (Auth-Header werden mitgesendet)

Backend-Briefing in INSIGHT-CRM.md:
- contract_files Tabelle + Prisma-Modell
- Datei-Speicherung (/app/uploads/contracts/{tenantId}/{contractId}/{uuid}-{name})
- 4 Endpoints (POST/GET/GET-download/DELETE)
- Sicherheitshinweise (Path Traversal, Tenant-Isolation, DSGVO)
- Docker-Volume Konfiguration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 21:46:32 +01:00
Thomas Reitz
0e6565e210 feat(crm): Vertraege-Modul Frontend — ContractsCard vollstaendig implementiert
- ContractsCard.tsx: CRUD-Card ersetzt Platzhalter (Liste, Create, Edit, Delete)
- types.ts: CreateContractPayload, UpdateContractPayload, ContractsQueryParams ergaenzt
- api.ts: contractsApi mit 4 Methoden (nested /crm/companies/:id/contracts)
- hooks.ts: crmKeys.contracts + useContracts, useCreate/Update/DeleteContract
- CompanyDetailPage.tsx: contractCount-Prop entfernt (Card laedt via API)
- docs/INSIGHT-CRM.md + Summarize.md aktualisiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 21:32:51 +01:00
Thomas Reitz
27507f1372 feat(crm): move Import-Wizard into CRM-Settings tab
Import ist kein eigener Nav-Eintrag mehr, sondern ein Tab
in den CRM-Einstellungen. /crm/import redirectet auf /crm/settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 21:00:19 +01:00
Thomas Reitz
dda672d41e fix(crm): align Phase 2.2–2.4 frontend to actual backend contracts
- Import: convert mapping Record→Array, entityType PERSON→contact
- Enrichment: handle suggestions as Record<field,{current,suggested,source}>
- Types: add availableTargetFields to ImportPreviewResponse

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:55:47 +01:00
Thomas Reitz
219863d538 feat(crm): remove Lexware section from ContactDetailPage
Lexware contact linking is not relevant for individual contacts,
only for companies. Removed LexwareSection component and the
two-column topRow grid layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:49:36 +01:00
Thomas Reitz
b746fc987d fix(crm): Kontaktdaten-Card zeigt alle Allgemein-Felder konstant
E-Mail, Telefon, Mobil, LinkedIn, Unternehmen, Position, Abteilung
werden immer angezeigt — leere Felder mit '—' Platzhalter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:45:01 +01:00
Thomas Reitz
bff4419c27 fix(crm): ContactDetailPage Layout-Anpassungen
- Name (Unternehmen) in Klammern in der Überschrift
- Grüner Punkt → "● Aktiv"/"● Inaktiv" Badge
- Position als Subtitle unterhalb des Namens
- Lexware-Card rechts neben Kontaktdaten (260px)
- Aktivitäten als volle Breite unterhalb der Kontaktdaten

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:39:18 +01:00
Thomas Reitz
ec9f3ea364 feat(crm): UI/UX Redesign – Kontakt Eingabemaske & Detailansicht
- Neue Drawer-Komponente (right-side slide-in, sticky footer, Portal)
- ContactFormModal: Modal → Drawer, Tab 1 Allgemein / Tab 2 Details & Adresse
- Checkbox "Adresse vom Unternehmen übernehmen" (blendet Adressfelder aus)
- Typ-Feld entfernt (Kontakte immer PERSON)
- ContactDetailPage: Subtitle "Position @ Unternehmen" im Header
- Kontaktdaten-Card: 2 Sub-Spalten, kein Firma-Duplikat, Mobil tel:-Link, LinkedIn-Icon
- Neue Card "Notizen & Tags" (Badges + Notizen-Text + Custom Fields)
- Aktivitäten-Spalte auf minmax(380px, 40%) verbreitert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:20:30 +01:00
Thomas Reitz
fdab2d5bcb feat(crm): Phase 2.2-2.4 frontend — Forecast, Import, Enrichment
Phase 2.3 Forecast:
- probability field on PipelineStage (types, edit UI, add-stage form)
- ForecastPage with pipeline filter, period selector, summary cards, table
- forecastApi + useForecast hook
- /crm/forecast route + "Prognose" nav link

Phase 2.2 CSV/Excel Import:
- 3-step wizard ImportPage (Upload → Mapping → Result)
- Entity type selection, auto-mapping, duplicate strategy, preview table
- importApi (preview + execute) + useImportPreview/useImportExecute hooks
- /crm/import route + "Import" nav link

Phase 2.4 Data Enrichment:
- "Anreichern" button on CompanyDetailPage with suggestions modal
- Per-field accept with PATCH update
- enrichmentApi (enrich, getConfig, setConfig) + hooks
- NorthDataConfig in CRM Settings "Integrationen" tab
- API-Key management UI

All changes pass TypeScript strict mode (npx tsc --noEmit).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 19:37:54 +01:00
Thomas Reitz
aaedf68085 feat(crm): Phase 2.1 Custom Fields — backend + frontend integration
Backend (CRM expert): Custom field definitions CRUD, bulk value upsert,
7 endpoints, Prisma schema with CustomFieldDef + CustomFieldValue tables.

Frontend: Types, API, hooks, admin settings page with field management,
CustomFieldsDisplay for detail pages, CustomFieldsForm for edit modals.
Also fix Vite allowedHosts for insight.xinion.lan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:22:57 +01:00
Thomas Reitz
405ab5f038 feat: add SSL/Domain admin page for custom HTTPS configuration
- Backend: 4 new endpoints in SettingsController (GET/POST/DELETE /settings/ssl, POST /settings/ssl/check-dns)
- Certificate validation via Node.js crypto.X509Certificate (PEM format, expiry, SAN match)
- DNS resolution check via dns.promises.resolve4
- Auto-generates Traefik dynamic config (ssl-domain.yml) with custom domain routing + HTTP->HTTPS redirect
- Frontend: AdminSslPage with DNS name input, cert/key upload, status display
- Docker: Core-service gets access to traefik-certs volume and dynamic config directory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:13:49 +01:00
Thomas Reitz
48df3c3144 feat(crm): Phase 1 backend schema expansion + frontend integration
Backend (CRM-Expert Phase 1):
- New enums: ContactSource, EntityStatus, CompanySize, OwnerRole,
  LostReason, EmailType, PhoneType
- Contact: add linkedinUrl, birthday, source, department, status
- Company: add vatId, taxId, tradeRegisterNumber, registerCourt,
  companySize, deliveryAddress, dataEnrichedAt/Source, status
- Deal: add lostReason + lostReasonText (required when status=LOST)
- Multi-value emails/phones tables (contact_emails, contact_phones)
- Owner m:n model (contact_owners, company_owners, deal_owners)
- Redis Pub/Sub CRM events (crm.contact.created, crm.deal.won, etc.)
- Activity due_soon scheduler (cron every 15 min)
- SQL migration with data migration for existing records

Frontend integration:
- types.ts: all new enums, interfaces, label maps
- api.ts: owner CRUD endpoints (add/remove for contacts/companies/deals)
- hooks.ts: 6 new owner mutation hooks
- ContactFormModal: LinkedIn, birthday, source, department, status fields
- ContactDetailPage: display new fields (LinkedIn, department, birthday,
  source, status badge)
- CompanyDetailPage: display vatId, taxId, trade register, company size,
  delivery address, data enrichment info
- DealFormModal: lost reason dropdown + text (shown when status=LOST)
- DealDetailPage: display lost reason with label
- CompaniesPage: EntityStatus-aware status dots (ACTIVE/INACTIVE/BLOCKED)
- ActivityType: add FOLLOWUP to all label maps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:56:41 +01:00
Thomas Reitz
a85634a906 feat: add trade event (Messe-Timer) feature with admin CRUD and dashboard tiles
Backend: TradeEvent Prisma model, NestJS CRUD module with date validation
and tenant isolation. Frontend: Admin Events page with create/edit/delete
modals, dashboard countdown tiles showing upcoming/ongoing/ended events
with progress bars, and useEventCountdown hook for live timer updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:33:19 +01:00
Thomas Reitz
923e6bc127 feat(frontend): redesign CompanyDetailPage with tabbed layout
Replace 3-column grid with 4-tab layout:
- Tab 1 (Unternehmen): 2-column grid with master data + Lexware status (left), contacts + relationships (right)
- Tab 2 (Aktivitäten): ActivityFeed component at full width
- Tab 3 (Vorgänge): Combined CRM deals + Lexware vouchers table with source badges, filters, and color-coded Lexware rows
- Tab 4 (Verträge): ContractsCard placeholder at full width

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:08:53 +01:00
Thomas Reitz
dbbde6c4fa style(crm): indent and color-highlight contact sub-rows
Sub-rows now have an indigo left border, tinted background and stronger
indent to visually separate them from company rows. Dark mode variants
included. Expanded company row also gets a primary-colored left border.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 20:15:19 +01:00
Thomas Reitz
72fd57740b feat(crm): expandable contact sub-rows in companies table
Companies with linked contacts now show an expand arrow. Clicking it
lazy-loads and displays contacts as indented sub-rows with name,
position, email, phone, type badge and status. Backend gains companyId
filter on GET /contacts for efficient per-company querying.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 20:03:26 +01:00
Thomas Reitz
00f6b9842e fix(crm): link imported Ansprechpartner to CRM company via companyId
When importing contact persons from Lexware, if the parent company
is already in the CRM (detected via linkedMap), the companyId is now
passed to createContact so the contact is properly associated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:20:07 +01:00
Thomas Reitz
332a623d6f fix(crm): fix Lexware import duplicate detection — pageSize 500 exceeded backend max 100
The backend DTO validation enforces @Max(100) on pageSize. The ImportTab
was requesting pageSize=500, causing a 400 Bad Request. This left the
linkedMap empty and showed import buttons for already-imported contacts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:12:32 +01:00
Thomas Reitz
6e77bf43b0 feat(crm): prevent duplicate Lexware imports — show linked status in import list
Import tab now loads all CRM companies/contacts and cross-references
lexwareContactId to detect already-imported entries. Linked contacts show
a green badge and "Öffnen" link instead of import buttons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:48:32 +01:00
Thomas Reitz
0ed1e77844 feat(crm): add company detail overhaul with industries, account types, relationship types
- Backend: new CRUD services/controllers for Industries, AccountTypes,
  RelationshipTypes, CompanyRelationships with Prisma schema migration
- Frontend: new hooks, API functions, and types for all config entities
- CompanyDetailPage redesign with ActivityFeed, RelationshipsCard
- CompanyFormModal extended with industry, account type, owner fields
- Activities service now supports companyId filter + includeContacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:21:57 +01:00