Commit graph

75 commits

Author SHA1 Message Date
Thomas Reitz
3f919340b5 feat: Stammdaten, CRM Reporting, Hilfesystem (hohe Priorität)
Stammdaten (Kapitel 14):
- 5 neue Prisma-Modelle: Department, Location, CostCenter, JobTitle, SkillCategory
- MasterDataModule (Core Service): vollständiges CRUD + öffentliche Dropdown-Endpoints
- Admin-UI /admin/master-data mit 5 Tabs, Inline-Edit, Farbwahl (Skill-Kategorien)

CRM Reporting (Kapitel 22.9):
- recharts ^2.12.0 installiert
- Deals: GET /deals/stats (Win/Loss-Rate, Umsatz, Trend, Verlustgründe)
- Aktivitäten: GET /activities/stats (nach Typ, Completion-Rate, offene Tasks)
- Reports-Seite /crm/reports: LineChart, PieChart, BarChart mit Zeitraum-Filter

Hilfesystem (Kapitel 16):
- @anthropic-ai/sdk installiert; ANTHROPIC_API_KEY optional in .env
- HelpModule (Core Service): POST /help/chat via Claude Haiku
- HelpTooltip-Komponente: Hover-Tooltip für Formularfelder
- HelpPanel: seitlicher Drawer mit Seitenkontext + KI-Chat
- -Button im Topbar (AppLayout), pageKey aus Route abgeleitet

Migration erforderlich: prisma migrate deploy (core-service)
Deployment: core rebuild, crm rebuild, frontend rebuild

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 10:39:30 +01:00
Thomas Reitz
69305a0b0b feat: Firmendaten um Geschäftsführer, Amtsgericht, Handelsregister erweitert
- Backend: CompanySettings Interface + GET/POST um managingDirector,
  localCourt, commercialRegister ergänzt (Redis-Storage)
- Frontend AdminCompanyPage: neue Sektion „Rechtliche Angaben" mit 3
  Feldern, Footer-Vorschau zeigt alle Angaben inkl. HR-Nummer
- Export-Service: PDF- und DOCX-Fußzeile enthält jetzt Geschäftsführer
  und Handelsregistereintrag (HRB + Amtsgericht kombiniert)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 09:28:06 +01:00
Thomas Reitz
bfaf718596 feat: Primärfarbe (Button-/Akzentfarbe) im Branding konfigurierbar
- Backend: primaryColor-Feld in GET/POST /settings/branding (Redis, Hex-Validierung)
- Frontend AppLayout: --color-primary/--color-primary-hover/--color-primary-light
  CSS-Variablen dynamisch aus Branding-Einstellungen setzen
- AdminCustomizePage: neuer Card-Block „Button-/Primärfarbe" mit 6 Farbpresets,
  Color-Picker, Hex-Input und Live-Vorschau (Solid + Outline Button)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 09:14:41 +01:00
Thomas Reitz
de4af77c5c feat: CRM Berechtigungsmodell — konfigurierbares Sichtbarkeitsmodell (OWN/TEAM/ALL)
Implementiert pro-Entity Sichtbarkeitssteuerung für Companies, Contacts, Deals
und Activities mit Rollen-basierter Zugriffskontrolle (ADMIN sieht alles,
TEAM_LEAD mindestens Team-Sicht, READONLY nur Lesezugriff).

- JWT Payload um tenantRole + department erweitert (Core + CRM)
- Team-Members-Endpoint im Core Service (GET /users/team-members)
- VisibilityModule mit Redis-Cache (CRM Service)
- ReadonlyGuard als globaler Guard (CRM Service)
- buildVisibilityFilter Utility für Prisma WHERE-Filterung
- Admin-Einstellungsseite /admin/crm-settings (Frontend)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 22:20:53 +01:00
Thomas Reitz
c987ce87c0 fix: Timeline-Linie auf Fortsetzungsseite bei Seitenüberlauf zeichnen
Wenn ein Projekteintrag auf eine neue Seite überläuft (Tasks zu lang),
wird jetzt trotzdem eine pendingLine vom Seitenanfang der neuen Seite
bis zum Gap vor dem nächsten Eintrag vorgemerkt. Dadurch entsteht eine
durchgehende Timeline-Linie auf der Fortsetzungsseite, die den
überlaufenden Inhalt visuell mit dem nächsten Eintrag verbindet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 20:16:42 +01:00
Thomas Reitz
a7cf59ae20 fix: Timeline-Linie am Anfang der nächsten Iteration zeichnen (Deferred-Draw)
Vorherige Ansätze berechneten die Ziel-Header-Höhe am Ende des Eintrags
neu (fehleranfällig durch doppelte Font-State-Operationen). Neuer Ansatz:
Linie für Entry i wird am ANFANG von Entry i+1 gezeichnet, BEVOR der
Seitenumbruch-Check läuft — mit demselben headerH der bereits berechnet
wurde. Eine einzige Bedingung entscheidet konsistent ob Linie gezeichnet
wird UND ob ein Seitenumbruch folgt, ohne Redundanz oder State-Probleme.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 15:39:10 +01:00
Thomas Reitz
fb57f5a4dc fix: exakte Header-Höhe für Timeline-Linie prüfen statt fester 40px-Schwelle
Berechnet die tatsächliche Mindesthöhe des nächsten Projekteintrags
(Datum + Rolle + Firma) identisch zur Seitenumbruch-Logik. Verhindert
hängende Linien wenn der nächste Header > 40px hoch ist und eine neue
Seite benötigt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 15:02:24 +01:00
Thomas Reitz
bec770c6ba fix: verhindere hängende Timeline-Linie bei Seitenumbruch im PDF-Export
Wenn ein Projekteintrag nahe am Seitenende endet und der nächste
Eintrag auf einer neuen Seite beginnt, wird keine Verbindungslinie
mehr gezeichnet. Vorher entstand ein hängender Strich ohne Ziel-Punkt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 14:54:07 +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
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
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
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
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
22af0375ea feat(core): Scopes für M365-Integration auf ReadWrite erweitern
Calendars.ReadWrite + Contacts.ReadWrite (Delegiert) für
bidirektionale CRM↔Office365 Synchronisation (Kontakte + Termine).
Contacts.ReadWrite neu hinzugefügt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:06:43 +01:00
Thomas Reitz
82e6a03bb9 fix(ms365): HTTPS-Protokoll für Integration-Redirect-URI erzwingen
Traefik leitet x-forwarded-proto nicht korrekt weiter, sodass der
Controller http:// statt https:// generierte — Azure lehnt nicht-HTTPS
Redirect-URIs für nicht-localhost ab (AADSTS50011).

Protokoll wird jetzt aus der konfigurierten SSO-Redirect-URI abgeleitet
(immer HTTPS), der Host bleibt dynamisch (IP oder DNS).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:26:10 +01:00
Thomas Reitz
1f6e59d362 fix(ms365): direkte OAuth2 URL-Konstruktion statt MSAL für Integration-Flow
MSAL-node v5 erzeugt bei getAuthCodeUrl mit reinen Graph-API-Scopes
(ohne openid) einen fehlerhaften Authorize-URL → AADSTS900561.

getIntegrationAuthUrl und handleIntegrationCallback verwenden jetzt
direkte fetch-Aufrufe (analog zu refreshIntegrationToken) ohne MSAL,
was den Fehler umgeht und denselben Standard-OAuth2-Flow garantiert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:17:38 +01:00
Thomas Reitz
254d00c106 fix(ms365): dynamische Redirect-URI aus Request-Host + Azure-Kompatibilität
Problem: Redirect-URI wurde falsch aus SSO-URI abgeleitet, und unterstützte
nur eine feste URL statt sowohl IP als auch DNS-Name.

Lösung:
- initM365Integration: Host aus x-forwarded-host/host Header lesen,
  korrekte Redirect-URI bauen (proto://host/api/v1/auth/integrations/...)
- Redis-State speichert jetzt {userId, redirectUri} als JSON
- handleIntegrationCallback: gespeicherte redirectUri aus State verwenden
- getIntegrationAuthUrl/handleIntegrationCallback: optionaler redirectUri-Parameter
- Fallback-Derivation: base URL aus SSO-URI + fester Integrations-Pfad

Beide URIs müssen in Azure registriert sein:
- http://172.20.10.59/api/v1/auth/integrations/microsoft-365/callback
- http://insight.xinion.lan/api/v1/auth/integrations/microsoft-365/callback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:01:09 +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
05ccabfdb4 fix(core): export EntraIdService from AuthModule for IntegrationsModule DI
IntegrationsService benötigt EntraIdService (Token-Refresh), der in
AuthModule als Provider registriert aber nicht exportiert war.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:52:56 +01:00
Thomas Reitz
28f6ba84b0 feat(core): Microsoft 365 OAuth-Integration — UserIntegration + IntegrationsModule
- Neues Prisma-Model UserIntegration (AES-256-GCM verschluesselte Tokens)
- Migration: 20260312_user_integrations
- EntraIdService: getIntegrationAuthUrl(), handleIntegrationCallback(), refreshIntegrationToken()
  — erweiterte Scopes: Mail.Read, Calendars.Read, Tasks.ReadWrite, offline_access
- IntegrationsModule mit Controller + Service:
  GET  /auth/integrations/microsoft-365          → OAuth-Flow starten
  GET  /auth/integrations/microsoft-365/callback → Token speichern
  GET  /users/me/integrations                    → Verbindungen auflisten
  DELETE /users/me/integrations/microsoft-365    → Verbindung trennen
  GET  /users/me/integrations/microsoft-365/token → Token fuer CRM-Service
- Env: AZURE_INTEGRATION_REDIRECT_URI, INTEGRATION_ENCRYPTION_KEY

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:36:03 +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
833bc44acd Update 2026-03-11 10:21:14 +01:00
Thomas Reitz
3bedda2b9d feat: dark mode, collapsible sidebar, branding customization
- Add Light/Dark/System theme toggle with ThemeContext and CSS variables
- Sidebar fully collapsible (icons-only mode, persisted in localStorage)
- Anwendungen section collapsible with chevron toggle
- Admin "Anpassungen" page: logo upload, sidebar color picker with presets
- Backend branding endpoints (GET/POST /settings/branding) stored in Redis
- Optional custom icon upload for external links (click icon field)
- Backend favicon proxy with HTML parsing for reliable icon loading
- Dark mode CSS variables for all components
- Login page SSO button and error styles use CSS variables

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:47:51 +01:00
Thomas Reitz
0f9b3d4f36 feat: backend favicon proxy with HTML parsing, collapsible sidebar sections
- Add GET /settings/favicon?url= endpoint that parses HTML for <link rel="icon"> tags
- Falls back to /favicon.ico if no icon link found in HTML
- Caches favicon URLs in Redis (24h TTL)
- Frontend uses backend proxy for reliable favicon loading (fixes Atlassian etc.)
- Anwendungen section in sidebar is now collapsible with chevron toggle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:29:19 +01:00
Thomas Reitz
65c5c7b7dd feat: use website favicons instead of manual icon upload
External links now automatically show the favicon of the target website
using Google's favicon service. No manual icon upload needed — just
enter label and URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:19:34 +01:00
Thomas Reitz
878f8be45c fix: replace crypto.randomUUID with HTTP-compatible alternative
crypto.randomUUID() is only available in secure contexts (HTTPS).
Since the app runs over HTTP in development, this caused a blank page
crash on the external links admin page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:08:21 +01:00
Thomas Reitz
1a87356048 feat: restructure admin area with separate layout, external links management
- Admin section moved to dedicated area with horizontal tab navigation
- Sidebar now shows gear icon link to Administration (PLATFORM_ADMIN only)
- External links management page for configuring sidebar shortcuts
- External links displayed in sidebar for all authenticated users
- Backend: Redis-based CRUD endpoints for external link configuration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:04:43 +01:00
Thomas Reitz
bc156e1657 feat: dynamic SSO configuration via Admin UI
SSO config (Tenant ID, Client ID, Client Secret, Redirect URI) can now
be managed entirely from the Admin SSO page. Config is stored in Redis
(persistent) and the MSAL client is reinitialized on save — no server
restart or console access required.

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