93fe8cfd73db6f0a85b20906862ce8ca6d6d7b15
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
87759a8a24 |
feat(ui): drei Farbwelten je hell und dunkel, gewaehlt pro Benutzer
Neben dem Design des Hauses ("perinco") gibt es "Pixel Art" und
"Outdoor", jedes in hell und dunkel. Die Wahl steht neben dem
Hell/Dunkel-Schalter im Kopf der App.
Sie gehoert dem BENUTZER und nicht dem Browser: crm_users.theme und
crm_users.theme_mode, gesetzt ueber PATCH /api/auth/me, das jeder fuer
sich selbst darf. Wer sich an einem anderen Rechner anmeldet, findet sein
Design wieder, und zwei Benutzer an derselben Maschine kommen sich nicht
in die Quere. Der localStorage bleibt als Zwischenspeicher daneben, damit
das Inline-Skript in index.html die Klassen vor dem ersten Frame setzen
kann - ohne das blitzt beim Laden das Standarddesign auf. Das hat eine
sichtbare Folge: melden sich zwei Leute an demselben Rechner an, sieht
der zweite fuer einen Augenblick das Design des ersten.
Der ThemeProvider steht deshalb in main.tsx jetzt unter dem AuthProvider
- er liest den Benutzer.
Keine Komponente wurde umgefaerbt. Im Markup steht nirgends eine feste
Farbe, sondern durchgaengig ink-*, brand-*, canvas und surface; Tailwind
v4 loest auch --radius-*, --shadow-* und die Schriften ueber Variablen
auf. Ein Theme tauscht also nur Werte, und deshalb kann Pixel Art alle
380 abgerundeten Ecken eckig machen, ohne eine Klasse anzufassen.
Zwei Dinge waren Voraussetzung:
- Der helle Block eines Themes braucht :not(.dark). html.theme-x hat
eine Klasse mehr in der Spezifitaet als .dark und wuerde den
Dunkelmodus sonst ueberstimmen.
- Auf brand-500/600 steht 69 Mal weisse Schrift. Jedes Theme braucht
dort einen ausreichend dunklen Ton - ein helles Pastell waere
unlesbar.
Die Pixelschrift (Silkscreen) sitzt nur in --font-display, also in den
Ueberschriften und den Feldbeschriftungen; labelClass tragt dafuer die
neue Klasse "field-label". Der Fliesstext bleibt Nunito Sans: die Listen
haben bis zu acht Spalten bei 11-13 px, und eine Pixelschrift waere darin
nicht mehr zu lesen. Outdoor bringt Cabin fuer beides mit.
server/test/userFields.test.mjs prueft die Werte und dass die Liste der
Farbwelten in userFields.js und src/types.ts nicht auseinanderlaeuft -
sonst zeigt die Auswahl ein Design an, dessen Speichern das Backend
abweist, und niemand weiss warum.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
62dce64739 |
feat(akquise): mehrere Berater je Eintrag, je Berater eine Opportunity
Ein gefundenes Projekt passt oft zu mehr als einem Berater, und fuer jeden geht eine eigene Bewerbung heraus. Das Feld "Berater" liess bisher nur einen Namen zu. Gespeichert wird eine kommagetrennte Liste in crm_acquisitions.consultant (auf VARCHAR(500) erweitert, Migration in db.js) und keine eigene Zuordnungstabelle: die Namen sind reiner Text, die Auswahlliste in crm_acquisition_options ist eine Pflegehilfe und kein Fremdschluessel. Suche, Sortierung und Anzeige der Liste arbeiten damit weiter auf einem Feld. Die Funktionen zum Zerlegen stehen in acquisitionRules.js, das Gegenstueck fuer das Frontend in src/types.ts, geprueft von server/test/acquisition.test.mjs. Zerlegt wird an vier Stellen: - Uebernahme: je Berater eine Opportunity, alle in einer Transaktion. Ihre Titel tragen den Namen in Klammern, weil die Opportunity kein eigenes Feld fuer den Berater hat und in Pipeline und Liste sonst identische Zeilen stehen; bei einem einzigen Berater bleibt der Titel unveraendert. In den Notizen steht neben dem eigenen Berater auch, mit wem er sich das Projekt teilt. Der Eintrag merkt sich die erste Opportunity - sie entscheidet nur darueber, ob er wieder im Eingang auftaucht. - Verlauf (crm_acquisition_history): ein Berater je Zeile. Ein Eintrag mit drei Beratern sind drei Bewerbungen, "Nico, Lisa" waere sonst ein eigener Schluessel neben "Nico" und "Lisa". - Dashboard: dieselbe Zaehlung. Die Summe der Balken liegt damit ueber der Gesamtzahl "Bewerbungen", die weiter die Eintraege zaehlt. - Auswahlliste pflegen: Umbenennen ersetzt den Namen innerhalb der Liste, Loeschen zaehlt auch, wer nur einer von mehreren ist. Ein Gleichheitsvergleich uebersah beides. In der Oberflaeche haengt das Auswahlfeld jeweils einen Namen an, die Gewaehlten stehen als Plaketten darunter und lassen sich einzeln entfernen; ein bereits gewaehlter Berater verschwindet aus der Liste. Bewusst kein <select multiple>: Strg-Klick ist mit der Maus unauffindbar und mit einer Beruehrung gar nicht bedienbar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6fd713497d |
feat(postfach): crm@perinco.de lesend anbinden, Kontakte aus Mails
Spiegelt das Vertriebspostfach ueber Microsoft Graph ins CRM. Aus jeder Mail entstehen Kontakt-Vorschlaege, und jede Mail laesst sich an Opportunities und Kontakte haengen. Uebernommen ist die Idee aus dem Altsystem KeepTrack, nicht die Umsetzung: - Delta Query statt "$filter=isRead eq false" mit anschliessendem PATCH. Damit genuegt die Anwendungsberechtigung Mail.Read, es braucht keinen Schreibzugriff auf das Postfach, und eine vorher in Outlook geoeffnete Mail geht nicht verloren. Der Fortschritt liegt als delta_link in der Datenbank und ueberlebt Neustarts; ein Lauf ist gedeckelt und fortsetzbar. - Tenant- und Client-ID kommen aus der .env, nicht aus dem Browser. Im Altsystem konnte jeder Angemeldete das Server-Secret gegen einen fremden Tenant verwenden. - Kontaktdaten werden aus den Graph-Feldern und der Signatur gelesen, ohne Sprachmodell. Mailinhalte verlassen den Server nicht. - Ein Vorschlag ohne E-Mail-Adresse wird nicht mit einer erfundenen Adresse angelegt, sondern bleibt zur Durchsicht liegen. Ist die Adresse schon als Kontakt vorhanden, werden nur leere Felder ergaenzt. - Mails haengen als eigene Verknuepfung an Opportunity und Kontakt, nicht als Text im Notizfeld - loesbar und mehrfach moeglich. - Fremdes Mail-HTML wird serverseitig entschaerft und in einem iframe mit sandbox="" gezeigt. Externe Bilder sind per CSP gesperrt, damit das Oeffnen dem Absender nichts meldet. - Der Verbindungstest prueft Anmeldung und tatsaechlichen Postfachzugriff und benennt den wahrscheinlichen Grund. Der alte Test holte nur ein Token. Fehlen die MS_*-Werte, startet das CRM normal; die Einstellungen nennen die fehlende Variable. Neue Tabellen: crm_mail_messages, crm_mail_links, crm_mail_contact_candidates, crm_mail_sync_state, crm_settings. Uebernommene Kontakte tragen source = 'Mail'; die Kontaktliste hat dafuer einen Herkunftsfilter. Nicht enthalten: Senden, Antworten, Weiterleiten, Anhaenge und die automatische Zuordnung zu Opportunities. npm test im Verzeichnis server prueft Kontaktextraktion und Graph-Client (47 Pruefungen, ohne Framework und ohne Datenbank). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |