import type { AnstehendArt } from "./dashboard-filter"; import type { EmploymentStatus, HistoryEventType, NoteCategory } from "./types"; // Die Initialen stehen in Weiss darauf, jeder Ton braucht also 4.5:1 gegen // Weiss — nachgerechnet liegt der schwaechste bei 6.11:1. Die Reihe beginnt // bei den beiden Markenfarben (Blau direkt, Rosa in der dunklen Stufe, weil // #F69686 selbst nur 2.18:1 traegt) und faechert von dort auf, damit zwei // Personen nebeneinander nicht denselben Kreis bekommen. const AVATAR_PALETTE = [ "#164194", // Manner Blau "#0d2857", "#a8402e", // dunkles Manner Rosa "#5c2e91", "#0e700e", "#835b00", "#00666d", "#8a2740", ]; // Stable per-employee avatar color when employees.avatar_color isn't set. export function avatarColorFor(seed: string): string { let hash = 0; for (let i = 0; i < seed.length; i++) { hash = (hash << 5) - hash + seed.charCodeAt(i); hash |= 0; } return AVATAR_PALETTE[Math.abs(hash) % AVATAR_PALETTE.length]; } export const STATUS_STYLES: Record = { Aktiv: "bg-success-bg text-success-text", Karenz: "bg-warning-bg text-warning-text", // Rosa statt Blau: „Geplant" ist kein Hinweis und keine Warnung, sondern // der Normalfall ohne besondere Faerbung — und dafuer ist die Markenfarbe // die richtige. Blau wuerde hier nach Bedienelement aussehen. Geplant: "bg-accent-200 text-brand-700", Ausgetreten: "bg-danger-bg text-danger-text", }; type ColorCategory = "success" | "danger" | "warning" | "info" | "purple" | "brand"; export const CATEGORY_STYLES: Record = { success: "bg-success-bg text-success-text", danger: "bg-danger-bg text-danger-text", warning: "bg-warning-bg text-warning-text", info: "bg-info-bg text-info-text", purple: "bg-purple-bg text-purple-text", // Der Chip ohne eigene Bedeutung. Auf accent-200 statt accent-100, weil // die hellere Stufe dem Fehler-Chip zu nahe kommt (dE 5.6 gegen 13.7). brand: "bg-accent-200 text-brand-700", }; /** Der Punkt vor einer Zeile: dieselbe Kategorie, nur als Flaeche. */ const CATEGORY_DOT: Record = { success: "bg-success-text", danger: "bg-danger-text", warning: "bg-warning-text", info: "bg-info-text", purple: "bg-purple-text", brand: "bg-accent-600", }; /** * Die Ereignisse der Personalakte — die Sprache von employee_history. * * Getrennt von ACTION_CATEGORY darunter, und das ist der Punkt: die beiden * sind zwei Vokabulare fuer dasselbe Geschehen. Das Protokoll schreibt * „Neueinstellung", die Historie schreibt „Eintritt"; „Wiedereinstellung" * steht dort als „Wiedereintritt". Die Uebersicht zeigt Historienereignisse, * holte ihre Farbe aber aus der Protokolltabelle — und genau diese zwei * standen nicht darin und bekamen den neutralen Chip. In „Letzte * Aktivitaeten" war „Eintritt" deshalb rosa, waehrend die Karte daneben ihn * laengst gruen zeigte. * * `Record` statt `Record`: kaeme ein * zwoelftes Ereignis dazu, liesse der Typpruefer es nicht durch, ohne dass * jemand eine Farbe dafuer bestimmt. Ein Nachschlagen mit Rueckfall haette * auch dann wieder still etwas Plausibles geliefert. */ export const EVENT_CATEGORY: Record = { Eintritt: "success", Wiedereintritt: "success", Austritt: "danger", // Violett und nicht gruen: auf der Uebersicht steht die Rueckkehr neben dem // Eintritt, und zwei Gruentoene nebeneinander sind keine zwei Dinge. Die // Begruendung samt Messung steht bei ANSTEHEND_STYLES. Rückkehr: "purple", Versetzung: "info", Beförderung: "purple", Reorganisation: "purple", Karenz: "warning", Vertragsänderung: "warning", Stammdatenänderung: "warning", Gehaltsanpassung: "warning", }; export function eventBadgeStyle(event: HistoryEventType): string { return CATEGORY_STYLES[EVENT_CATEGORY[event]]; } export function eventDotStyle(event: HistoryEventType): string { return CATEGORY_DOT[EVENT_CATEGORY[event]]; } // Audit-log action -> badge color, per the action list in dem Kommentar an // audit_log in der ersten Migration. // // Hier ist der Rueckfall richtig und bleibt: die Aktionen schreiben die // SQL-Funktionen als freien Text, und eine neue kann jederzeit dazukommen. // Ihr neutraler Chip ist dann eine ehrliche Aussage. Fuer die Historie gilt // das nicht — deren Werte sind eine geschlossene Aufzaehlung, und dort war // derselbe Rueckfall ein Fehler. const ACTION_CATEGORY: Record = { Neueinstellung: "success", Wiedereinstellung: "success", Rückkehr: "success", Austritt: "danger", Versetzung: "info", Ausschreibung: "info", "Interne Besetzung": "info", Beförderung: "purple", Reorganisation: "purple", "Reorganisation rückgängig": "purple", Karenz: "warning", Vertragsänderung: "warning", Stammdatenänderung: "warning", Gehaltsanpassung: "warning", }; export function actionBadgeStyle(action: string): string { return CATEGORY_STYLES[ACTION_CATEGORY[action] ?? "brand"]; } /** * Die vier Arten auf der Karte „Anstehend". * * Eigene Zuordnung statt actionBadgeStyle(): das war der Fehler, den sie * ersetzt. Dort wird ueber den **Beschriftungstext** in die Tabelle der * Protokoll-Aktionen nachgeschlagen, und die kennt eine andere Sprache — * „Neueinstellung", nicht „Eintritt". Zwei der vier Beschriftungen standen * gar nicht darin und fielen auf den neutralen Rosa-Chip zurueck. Ein * Nachschlagen, das bei einem Fehlschlag still etwas Plausibles liefert, * faellt eben nicht auf. * * Deshalb hier: nach dem **Wert** verschluesselt, nicht nach der Beschriftung * — eine umbenannte Beschriftung kann die Farben so nicht mehr stillschweigend * verstellen. Und vollstaendig ueber `Record`: eine fuenfte * Art muesste sich hier eintragen, der Typpruefer laesst sie sonst nicht durch. * * Die Farben selbst, nebeneinander gemessen (dE im CIELAB, Kontrast nach * WCAG — schwaechstes Paar 14.2, schwaechster Kontrast 5.49:1): * * Eintritt gruen jemand kommt * Austritt rot jemand geht * Rueckkehr violett weder das eine noch das andere — eine eigene Klasse * Wiedervorlage gelb ein Termin, an dem etwas zu tun ist * * Tuerkis fuer die Rueckkehr waere die naheliegendere Lesart gewesen, kam * gegen das Gruen des Eintritts aber nur auf dE 13.0. Violett steht mit 30.8 * eindeutig daneben, und die vier sind das, was sie sein sollen: vier Dinge, * die man im Vorbeigehen auseinanderhaelt. */ export const ANSTEHEND_STYLES: Record = { // Aus EVENT_CATEGORY abgeleitet, wo es dasselbe meint: auf der Uebersicht // stehen die beiden Karten nebeneinander, und ein Eintritt darf links nicht // anders aussehen als rechts. Genau das war zu sehen, bevor die Historie // ihre eigene Zuordnung bekam. hire: CATEGORY_STYLES[EVENT_CATEGORY.Eintritt], exit: CATEGORY_STYLES[EVENT_CATEGORY.Austritt], return: CATEGORY_STYLES[EVENT_CATEGORY.Rückkehr], // Die Wiedervorlage ist kein Ereignis der Personalakte, sondern ein Termin // aus den Notizen — sie hat in EVENT_CATEGORY nichts verloren. note: CATEGORY_STYLES.warning, }; export const NOTE_CATEGORY_STYLES: Record = { Vertraulich: CATEGORY_STYLES.purple, "Personalgespräch": CATEGORY_STYLES.info, Wiedervorlage: CATEGORY_STYLES.warning, "Lob / Anerkennung": CATEGORY_STYLES.success, Allgemein: "bg-surface text-ink-muted", };