Commit graph

156 commits

Author SHA1 Message Date
Thomas Reitz
1bba4abac3 fix: Buttons in ExpertProfile nicht mehr vollbreit (align-self: flex-start)
Buttons in sectionHeader (flex-direction: column) streckten sich auf
volle Containerbreite. align-self: flex-start begrenzt die Breite auf
min-width + Textinhalt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 14:34:24 +01:00
Thomas Reitz
da4a036e8a fix: PDF-Grünton dunkler + Buttons gleichbreit
- darkenColor() Funktion: extrahierte Logo-Farbe um 30% abdunkeln
  (gilt für PDF und DOCX Export) → kräftigerer, druckfreundlicher Ton
- ExpertProfileTab: min-width: 130px für btnPrimary und btnSecondary
  → alle Aktions-Buttons haben einheitliche Mindestbreite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 14:29:00 +01:00
Thomas Reitz
dabd36349e fix: PDF Leerseite, Button-Größen und Primärfarbe
- PDF-Export: doc.flushPages() vor doc.end() verhindert leere
  Schlussseite (PDFKit bufferPages-Bug nach Footer-Loop)
- ExpertProfileTab: height: 32px für btnPrimary/Secondary/Danger
  sowie chipInput- und headerForm-Inputs → einheitliche Höhe
- Primärfarbe #1a56db → #1040bb (dunkler, besser zum Logo passend)
- LoginPage CSS-Fallback-Gradient ebenfalls auf neue Primärfarbe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 14:07:20 +01:00
Thomas Reitz
1608f4e936 fix: PDF export — tab chars garbled, unconditional bullets, encrypted PDF attachments
Root causes identified via DB hex-dump and server logs:

1. Tab character in budget lines: DB stores e.g. `**Budget Verantwortung:**\t750.000 EUR`
   (byte 0x09 between `:**` and the amount). PDFKit can't render \t in WinAnsiEncoding,
   producing garbage output like `"sSãUU`. Fix: `.replace(/\t/g, ' ')` in cleaned text.

2. Unconditional bullet: `\u2022 ${sanitize(hasBullet ? cleaned : cleaned)}` always
   prepended `•` — the ternary was a no-op. Fix: only add `•` when hasBullet is true;
   `**...**` header lines now render as Helvetica-Bold without a bullet.

3. ITIL4 Foundation Cert.pdf is owner-password-encrypted. pdf-lib threw
   "Input document is encrypted" → cert was silently skipped.
   Fix: `PdfLib.load(attBuffer, { ignoreEncryption: true })`.

Applies to both PDF and DOCX export paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 13:56:57 +01:00
Thomas Reitz
98e7f48ce2 fix: make GET /settings/branding public to break login loading loop
LoginPage calls /settings/branding to load branding config (logo, colors).
Without @Public(), the JWT guard returns 401, which triggered the axios
response interceptor to attempt a silent refresh, fail, and call
window.location.href = '/login' — creating an infinite reload loop on
the login page itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:44:50 +01:00
Thomas Reitz
ad3a580d0b fix: resolve login loading loop caused by Vite HMR reconnects + rate limiting
- Add @SkipThrottle() to POST /auth/refresh so repeated silent-refresh calls
  from page reloads no longer exhaust the rate limit (HTTP 429)
- Configure Vite HMR explicitly with host/clientPort/protocol=wss so the
  WebSocket connects correctly through Traefik instead of reconnecting every ~1s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:35:18 +01:00
Thomas Reitz
79ad5e4be3 fix(core): PDF-Anhänge korrekt einbetten via pdf-lib + Zeichenbereinigung verbessert
- pdf-lib installiert und importiert
- PDF-Anhänge werden nicht mehr als Platzhalter-Seite angezeigt, sondern
  alle Seiten des Anhang-PDFs direkt in das Export-PDF eingebettet (pdf-lib merge)
- Bild-Anhänge bleiben weiterhin per PDFKit eingebettet
- sanitizePdfText() erweitert: vollständige Windows-1252 U+0080-U+009F Mapping-Tabelle
  für mis-enkodierte Zeichen (€, Anführungszeichen, Gedankenstriche, TM usw.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:23:03 +01:00
Thomas Reitz
d08bedecf7 fix(frontend): Section-Header Titel über Eingabefelder + kompaktere Darstellung
- sectionHeader auf flex-column umgestellt: Titel immer oberhalb der
  Eingabe-Felder statt nebeneinander (Skills / Sprachen / Erfahrung)
- Section-Padding reduziert (1.5rem → 1rem/1.125rem) für kompaktere 3-Spalten
- chipInput und headerForm: flex: 1 + min-width: 0 für sauberes Stretching
- Max-Width-Beschränkungen entfernt damit Inputs die volle Spaltenbreite nutzen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:16:53 +01:00
Thomas Reitz
9d7dcaaaea fix(core): PDF-Export Seitenumbruch und Sonderzeichen repariert
- pageBottom von 800 auf 740 reduziert: verhindert PDFKit-Auto-Seitenumbrüche
  mitten in Projekt-Einträgen (bisher: Datum/Rolle/Firma je auf eigener Seite)
- Vor jedem Projekt-Eintrag Header-Höhe (Datum+Rolle+Firma) vorberechnen und
  Seitenumbruch proaktiv auslösen, bevor der Header gezeichnet wird
- sanitizePdfText() Hilfsmethode: ersetzt €→EUR sowie Zeichen außerhalb Latin-1
  die Helvetica (WinAnsiEncoding) nicht rendern kann (bisher: Zeichensalat)
- sanitizePdfText auf Projekt-Texte und Zertifizierungs-Texte angewendet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 11:08:56 +01:00
Thomas Reitz
90a0388b22 feat(core+frontend): Anhänge im Export + neues 3-Spalten-Layout im Profil-Reiter
- PDF-Export: alle Anhänge als zusätzliche Seiten (Bilder als Vorschau, andere mit Hinweis)
- DOCX-Export: Bild-Anhänge als zusätzliche Sections (je eine Seite pro Bild)
- ExpertProfileTab: Skills/Sprachen/Erfahrungen nebeneinander (3 Spalten)
- ExpertProfileTab: Zertifizierungen und Profilanlagen nebeneinander (2 Spalten)
- threeColumnRow CSS-Klasse hinzugefügt (responsive auf 1 Spalte bei <900px)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:59:04 +01:00
Thomas Reitz
c8b25321e7 feat(core+frontend): Profilzugriff-Gruppen für Admin mit delegierten Berechtigungen
- Neue Prisma-Modelle ProfileAccessGroup + ProfileAccessGroupMember mit canView/canExport/canEdit
- Manuelle Migration 20260314_profile_access_groups
- ProfileAccessModule: CRUD-Endpoints für Gruppen und Mitglieder (nur PLATFORM_ADMIN)
- Neue Admin-Endpoints in ExpertProfileService/-Controller für alle Profil-Mutationen
- verifyOwnership mit skipCheck-Parameter für Admin-Bypass
- ExpertProfileTab + alle Section-Komponenten erhalten apiBase-Prop für Wiederverwendung
- AdminProfileAccessPage: Gruppen-Tab (CRUD) + Profile-Tab (alle User mit Aktionen)
- AdminProfileDetailPage: Profil eines beliebigen Users im Admin-Kontext bearbeiten
- Route /admin/profile-access + /admin/profiles/:userId + Nav-Tab Profilzugriff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:47:36 +01:00
Thomas Reitz
f7736a6fca fix(core): Word-Export — Abstand zwischen linker und rechter Spalte
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:16:45 +01:00
Thomas Reitz
1a222edb46 fix(core): Word-Export — Logo-Seitenverhältnis korrekt (tatsächliche Pixel-Dimensionen)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 10:12:09 +01:00
Thomas Reitz
8fc894c74c fix(core): Word-Export — jobTitle, Logo, Akzentfarbe und Firmenfußzeile
- Jobtitel aus Profil statt experiences[0].area unter dem Namen
- Platform-Logo aus Redis über Avatar einfügen
- Dominante Akzentfarbe dynamisch aus Logo extrahieren
- Firmen-Fußzeile aus Redis-Settings als DOCX-Footer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 09:43:29 +01:00
Thomas Reitz
196515daa8 fix(core): PDF-Export — Titel aus jobTitle-Feld statt erster Erfahrung
- getExportData(): jobTitle zu den selektierten User-Feldern hinzugefuegt
- ExportData-Interface: jobTitle: string | null ergaenzt
- PDF: Unter dem Namen wird jetzt data.jobTitle angezeigt statt
  profile.experiences[0].area

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 09:35:45 +01:00
Thomas Reitz
382beab9c3 feat(core): PDF-Export — Akzentfarbe dynamisch aus Branding-Logo extrahiert
- extractDominantColor(): 20x20 Resize via sharp, Alpha gegen Weiss flatten,
  alle gesaettigten Pixel (nicht weiss/schwarz/grau, range > 35) mitteln
- Ergebnis wird als accentColor fuer Timeline-Linien, Ueberschriften,
  Skill-Chips usw. verwendet
- Fallback auf #009688 wenn kein Logo hinterlegt oder keine Farbe extrahierbar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 09:27:41 +01:00
Thomas Reitz
f5d83dc1c3 fix(core): PDF-Export — Footer-Leerseite behoben und Logo ueber Profilfoto
- Footer: doc.page.margins.bottom temporaer auf 0 gesetzt beim Footer-Zeichnen
  verhindert PDFKit Auto-Pagination (footerTextY > maxY wuerde sonst
  eine leere zweite Seite erzeugen)
- Logo: Platform-Branding-Logo (aus Redis platform_branding_logo) wird oben
  in der linken Spalte ueber dem Profilfoto gerendert (fit 180x50px)
- Firmendaten + Branding parallel via Promise.all aus Redis geladen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 09:20:25 +01:00
Thomas Reitz
46ced98bf4 feat(core+frontend): Firmendaten im Admin + PDF-Fusszeile
Backend:
- GET/POST /settings/company (Redis-Key platform_company_settings)
  Felder: name, street, postalCode, city, phone, email, website
- ProfileExportService: RedisService injiziert, laedt Firmendaten vor PDF-Erzeugung
- PDF-Footer: Trennlinie + kompakte Zeile mit allen Firmendaten auf jeder Seite
  (bufferPages=true, switchToPage-Loop vor doc.end())

Frontend:
- AdminCompanyPage: Formular mit Vorschau der Fusszeile
- AdminLayout: neuer Tab 'Firmendaten'
- App.tsx: Route /admin/company

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 09:07:42 +01:00
Thomas Reitz
1d4894b637 fix(core): PDF-Export — Zertifizierungen in linke Spalte verschoben
- ZERTIFIZIERUNGEN von rechter Spalte in linke Spalte verschoben
  (nach ERFAHRUNG, vor FÄHIGKEITEN)
- Textbreite auf leftColWidth (certContentWidth = leftColWidth - 14) angepasst
- Timeline-Linie dynamisch (certEntryStartY gespeichert, Linie nach Content)
- Alte rechte-Spalte-Sektion entfernt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 08:49:25 +01:00
Thomas Reitz
f8aed00645 fix(core): PDF-Export — Abschnittslinien bis zum Spaltenende und Titel angepasst
- pdfSectionTitle: Linie zieht sich jetzt ueber die volle Spaltenbreite (width-Parameter)
  statt nur bis zum Textende
- Titel 'BERUFSERFAHRUNG' umbenannt in 'BERUFSERFAHRUNG / PROJEKTE'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 08:46:19 +01:00
Thomas Reitz
ed24c061c4 fix(core): PDF-Export — Timeline-Linien korrekt und Faehigkeiten in linke Spalte
- BERUFSERFAHRUNG: Timeline-Linie wird jetzt NACH dem Content gezeichnet
  (entryStartY gespeichert, Linie von entryStartY+8 bis yRight-4)
  Damit stimmt die Laenge exakt mit der tatsaechlichen Eintraghoehe ueberein
- Seitenumbruch-Flag (pageBreakOccurred): Linie wird nicht gezeichnet wenn
  der Content ueber eine Seite hinausgeht
- FAEHIGKEITEN: aus dem full-width Bereich am Seitenende entfernt und in die
  linke Spalte nach ERFAHRUNG verschoben (kleinere Chips: 7pt, 16px, 5px Pad)
- Alte full-width FAEHIGKEITEN-Sektion entfernt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 22:08:18 +01:00
Thomas Reitz
41d944312c fix(core): PDF-Export Icons kleiner (12->9px) und besser positioniert
- iconSize 12 -> 9px
- iconTextOffset 20 -> 14px (kompakter)
- Alle Icons gleichmaessig bei y+1 positioniert (sauberes vertikales Alignment mit 8pt Text)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 21:39:33 +01:00
Thomas Reitz
927de0a809 fix(core): PDF-Export Zeitraum groesser und fetter (8pt -> 10pt)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 21:35:38 +01:00
Thomas Reitz
3d486e0541 fix(core): PDF-Export — Bullets, Zertifizierungen, Bild-Anhaenge
- Tasks: Bullet-Praefix nur fuer Zeilen mit echtem Aufzaehlungszeichen (kein spurious Bullet bei Plaintext)
- Zertifizierungen: Schriftgroesse reduziert (Titel 10->9pt, Aussteller 9->8pt) und Timeline-Linie gekuerzt
- Anhaenge: Bild-Anhaenge werden als zusaetzliche Seiten ans PDF angehaengt
- ExportData-Interface + getExportData() um attachments[] erweitert
- Gleiche Bullet-Fix-Logik im DOCX-Export

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 21:33:01 +01:00
Thomas Reitz
a37942b37d fix: PDF-Export — Dateiname, Fettschrift, Zeichen, Abstaende, Zertifizierungen rechts
5 Korrekturen am Experten-Profil PDF-Export:
1. Dateiname: Vorname_Nachname_CV.pdf/.docx (RFC 5987 Umlaut-sicher)
2. Fettschrift: Zeitraum, Firmenname und Branche in Berufserfahrung
3. Zeichen-Darstellung: Markdown-Marker (**bold**/*italic*/__u__)
   werden aus Tasks-Text entfernt, doppelte Bullet-Praefix-Normalisierung
4. Abstaende: Sprachen 14->11px, Erfahrung 14->11px, Gap 8->4px
5. Zertifizierungen in rechte Spalte verschoben (nach Berufserfahrung)
   statt als Vollbreite-Sektion am Ende

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 21:20:43 +01:00
Thomas Reitz
c333cbfa4b feat: Login-Screen-Branding im Global Admin (Hintergrund + Logo)
Im Bereich Anpassungen (AdminCustomizePage) kann der Platform-Admin
nun den Login-Screen individuell gestalten:
- Hintergrundtyp: Farbverlauf, Einfarbig oder Hintergrundbild
- Farbverlauf: zwei Farbpicker (Von/Bis) mit Hex-Eingabe
- Hintergrundbild: Datei-Upload max. 2MB, Live-Vorschau
- Logo auf Login-Screen: wird automatisch aus dem Sidebar-Logo uebernommen

Backend: settings.controller.ts GET/POST /settings/branding um
loginBgType, loginBgColor1, loginBgColor2, loginBgImage erweitert.
LoginPage laedt Branding per oeffentlichem Endpoint (kein Auth), leitet
containerStyle per useMemo ab und zeigt Logo-Bild statt Hardcode-Text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 21:09:32 +01:00
Thomas Reitz
b872b7e708 feat(frontend): bullet-editor B/I/U-Formatierung + Aufgaben-Anzeige in Projektliste
BulletEditor (ProjectModal.tsx):
- blWrapFormat(): wrap/unwrap Selektion mit Marker-Paar (**/*/__),
  leere Selektion → leeres Marker-Paar mit Cursor dazwischen
- Buttons: B (Fett **), I (Kursiv *), U (Unterstrichen __)
- Shortcuts: Strg/Cmd + B/I/U in handleKeyDown

ProjectsSection.tsx:
- renderInline(): regex-basierter Inline-Markdown-Renderer ohne
  dangerouslySetInnerHTML — wandelt **bold**, *italic*, __underline__ um
- RichText-Komponente: rendert Aufgaben-Text mit Bullets, Nummernlisten,
  Einrueckung und Inline-Formatierung
- Projektliste zeigt Aufgaben unterhalb der Taetigkeitszeile an
  (nur wenn vorhanden, mit border-top als optischem Trenner)
- Layout-Anpassung: entryItemExpanded + entryItemRow fuer vertikales Layout

CSS: bulletBtnBold/Italic/Underline, entryItemExpanded/Row,
entryTasks, richText/Line/Bullet/Num/Blank

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 20:47:07 +01:00
Thomas Reitz
4f141b94e5 feat(frontend): bullet-editor – nummerierte Liste + Tab/Shift+Tab Einrueckung
- Neue Funktion blLineAt() auf Modul-Ebene: parst Zeile an Cursor-Position
  (Einrueckung, Bullet/Nummeriert, Zahl, Zeileninhalt) ohne Closure-Probleme
- Neuer Toolbar-Button "1. Liste": toggled nummerierte Liste (1./2./3.);
  wandelt Bullet→Nummeriert und Nummeriert→Bullet automatisch um
- Tab-Taste: fuegt 2 Leerzeichen am Zeilenanfang ein (Einrueckung)
- Shift+Tab: entfernt bis zu 2 Leerzeichen (Ausrueckung)
- Enter in nummerierter Liste: setzt naechste Zeile mit N+1 fort
- Enter auf leerem Listenelement: beendet die Liste (Bullet + Nummeriert)
- Enter beruecksichtigt Einrueckung bei Bullets
- CSS: bulletToolbarSep (Trennlinie) + bulletToolbarHint (Keyboard-Hinweis)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 20:40:50 +01:00
Thomas Reitz
a4013d4356 feat(frontend): bullet-editor fuer Projektaufgaben + Popup-Backdrop deaktiviert
- Modal.tsx + Drawer.tsx: onClick={onClose} vom Backdrop entfernt — alle
  Popups schliessen sich jetzt nur noch ueber Speichern/Abbrechen-Buttons
- ProjectModal.tsx: Textarea "Aufgaben" durch BulletEditor-Komponente
  ersetzt (Toolbar-Button toggled Aufzaehlungspunkt, Enter setzt Bullet
  auf naechster Zeile fort, leere Bullet-Zeile + Enter beendet die Liste)
- ExpertProfileTab.module.css: CSS fuer bulletEditor, bulletToolbar,
  bulletBtn, bulletEditorTextarea hinzugefuegt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 20:32:00 +01:00
Thomas Reitz
2078a90fba fix(expert-profile): Sprachniveau-Validierung mit Frontend-Werten synchronisieren
Backend @IsIn erlaubte nur CEFR-Codes (C1/C2/B2…), Frontend schickte
aber deutsche Bezeichnungen (Verhandlungssicher/Fließend/Gut).
Alle 4 Frontend-Level + CEFR-Codes für Rückwärtskompatibilität aufgenommen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 20:02:10 +01:00
Thomas Reitz
6c51eb5e83 feat(crm): Kontakt-Detailseite – Breite, Outlook Daten, Outlook-Push
- max-width 960px auf Kontakt-Detailseite
- M365-Sektion umbenannt zu "Outlook Daten", default eingeklappt
- Aufgaben-Tab entfernt (nur noch E-Mails + Kalender)
- "In Outlook speichern"-Button: pusht/aktualisiert Kontakt in Outlook-Kontakte via MS Graph POST/PATCH /me/contacts
- Kontaktdaten: Typ, Status immer sichtbar, Bundesland (state) in Adresse
- Backend: GraphService exportiert, pushContactToOutlook-Methode, POST /crm/contacts/:id/push-to-outlook

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 19:57:07 +01:00
Thomas Reitz
b197660ac8 feat(crm): Dediziertes Projektanfrage-Formular + Button in Vorgänge-Liste
- ProjectRequestFormModal: Eigenständiges Formular mit Projektdetails oben
  (Beschreibung, Auslastung/Start, Laufzeit/Vorort-Anteil, Stundensätze)
  und Standard-Vorgangsdaten darunter (Titel, Pipeline/Stage, Kontakt, etc.)
  Auto-Select bei genau einem isProjectType-Typ; Warnung wenn nicht konfiguriert
- DealsPage: Neuer outlined Button "Neue Projektanfrage" neben "Neuer Vorgang"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 18:33:33 +01:00
Thomas Reitz
4c739945f0 feat(crm): Projektanfrage-Vorgangstyp – isProjectType + ProjectRequestDetails
Backend:
- DealType.isProjectType Boolean-Flag (Admin-konfigurierbar in CRM Settings)
- Neue 1:1-Tabelle project_request_details (ON DELETE CASCADE):
  notes, workload, startDate, duration, onsitePercent, rateRemote, rateOnsite
- Migration 20260313_project_request
- ProjectRequestDto mit Validierung (0-100% fuer Auslastung/Vorort-Anteil)
- Deals-Service: nested create + upsert fuer projectRequest

Frontend:
- DealFormModal: Vorgangsart-Dropdown an Anfang verschoben;
  Projektanfrage-Sektion erscheint conditional bei isProjectType=true
  (Beschreibung, Auslastung/Start, Laufzeit/Vorort-Anteil, Stundensaetze)
- CrmSettingsPage: DealTypesConfig mit Projektanfrage-Checkbox + Tabellenspalte
- types.ts: ProjectRequestDetails, CreateProjectRequestPayload, Deal.projectRequest

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 17:17:40 +01:00
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