Commit Graph

5 Commits

Author SHA1 Message Date
3987e8f54f Rosa fuehrt, Blau bedient
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m1s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m1s
Die Flaechenverteilung war verkehrt herum. Blau lag auf dem Groessten, was die
Anmeldeseite zu vergeben hat, und Rosa nur in Andeutungen daneben — bei einer
Marke, deren Schriftzug auf Rosa steht und deren Manual Rosa zuerst nennt.

Sichtbar wurde es am Logo. Es bringt sein rosa Feld selbst mit, weil §1.2 den
Schriftzug nur zur Gaenze auf Rosa zulaesst. Auf einer blauen Flaeche steht
dieses Feld als ausgeschnittenes Rechteck darauf. Auf Weiss ist es dagegen
richtig — §4.1 verlangt fuer weissen Untergrund genau das, ein rechteckiges
rosa Feld. Falsch war also nicht das Logo, sondern die blaue Flaeche darunter.

Rosa bekommen jetzt: das Panel der Anmeldeseite und das Band ueber der
Anwendung aus Kopfzeile und Logokopf der Seitenleiste. Beide zusammen sind ein
durchgehender Streifen, in dem das Logo aufgeht statt darauf zu liegen — was
§3.1 mit "in einem grossflaechigen rosa Umfeld eingebettet" meint.

Blau bleibt, was es ist: Knopf, Link, Fokusring, ausgewaehlter Eintrag. Das ist
dasselbe Verhaeltnis, aus dem der Schriftzug besteht — blaue Lettern auf rosa
Feld — und es ist das einzige, das traegt. Weiss auf Rosa sind 2.18:1.

Auf den rosa Flaechen steht deshalb nichts Weisses und nichts Gedaempftes mehr.
ink kommt auf 7.47:1, brand-700 auf 6.60:1; ink-muted lag bei 2.33:1 und ist
aus Kopfzeile, Glocke und Logokopf verschwunden. Gestuft wird ueber Groesse und
Gewicht statt ueber Transparenz — eine aufgehellte Schrift ist genau das, was
auf dieser Flaeche durchfaellt.

Das Raster auf dem Panel ist von Weiss auf ink gewechselt: auf Rosa war es
nicht mehr zu sehen.

Lint, Typen, Schemaabgleich, 552 Tests und der Build sind sauber. Im Browser
nicht gesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:47:26 +02:00
f7a5c48615 Die Oberflaeche auf das Manner-CD umstellen
Some checks failed
CI / Lint, Typen, Tests, Build (push) Successful in 11m45s
CI / Migrationen auf leerer Datenbank (push) Has been cancelled
Farben, Schrift, Logo und Symbole folgen jetzt dem CD Manual 2022. An der
Logik aendert sich nichts: keine Migration, keine Abfrage, keine Berechtigung.

Die zwei Markenfarben stehen in §1.1 als #F69686 (Rosa) und #164194 (Blau).
Welche davon was traegt, ist nicht gewaehlt, sondern nachgerechnet: Weiss auf
Rosa kommt auf 2.18:1 und faellt damit auch fuer Grossschrift durch, Blau auf
Rosa auf 4.34:1 und reicht fuer das Logo, nicht fuer Text. Schwarz auf Rosa
sind 7.47:1, Weiss auf Blau 9.47:1. Rosa ist deshalb Flaeche, Blau ist
Interaktion — zwei Skalen, brand-* und accent-*, statt einer geteilten.

Von den 26 Stellen mit brand-50/100/200 sind nur sieben auf accent gewandert.
Der Rest meint einen Zustand und keinen Markenton: die Zeile unter dem Zeiger,
der gewaehlte Listeneintrag, der aktive Menuepunkt. Gegen die warme
Seitenflaeche ist ein kuehler Ton dort deutlicher, und Blau heisst in dieser
Oberflaeche ab jetzt "reagiert auf dich". Nach accent gingen die Faelle ohne
eigene Bedeutung — "Geplant", "Offen", der neutrale Protokoll-Chip — und die
offene Planstelle im Organigramm, die vorher ein blasses Blau war und damit
wie eine schwaechere Person aussah.

Die Funktionsfarben bleiben, was sie sind. Das Manual regelt die Identitaet,
nicht die Rueckmeldung: Rot heisst Fehler, weil die Benutzerin das mitbringt.
`info` bleibt bewusst tuerkis — Blau saehe ab jetzt bedienbar aus, und gemessen
kaeme ein blaues info dem violetten Chip auf dE 6.8 nahe, also nicht
unterscheidbar. Violett ist dabei nachgezogen: gegen den Fehler-Chip stand es
bei dE 8.0, "Austritt" und "Befoerderung" waren im Vorbeigehen dieselbe blasse
Flaeche. Jetzt dE 15.0.

Die Schrift ist Barlow. Nachgezaehlt ist das Manual zu 95 % in DIN gesetzt
(Regular 79 %, Bold 16 %); Helvetica Neue steht nur in den Visitenkarten und im
Claim. DIN laesst sich nicht ausliefern — eine Drucklizenz deckt keinen Webfont
—, und die freien Nachbauten der DIN 1451 sind Schilderschriften, bei 14 px in
einer langen Tabelle schlechter lesbar als das, was sie ersetzen. Die Variable
heisst --font-din und nicht --font-barlow: liegt eines Tages eine Web-Lizenz
vor, ist der Wechsel diese eine Deklaration.

Nebenbei zwei Dinge repariert, die vorher schon falsch waren. Die Umrandung von
Eingabefeldern, Knoepfen und Suchfeldern lag bei 1.30:1 und damit unter den 3:1,
die WCAG 1.4.11 fuer Bedienelemente verlangt; border-strong bringt 3.56:1. Und
das mitgelieferte favicon.ico liess sich gar nicht bauen: eingebettet waren
24-Bit-RGB-PNG, waehrend der Kopf 32 bpp behauptete. Alle Rastersymbole liegen
jetzt als RGBA vor, und ihr Blau ist auf den CD-Wert gezogen — samt der
kantengeglaetteten Raender, indem je Pixel der Blauanteil bestimmt und neu
gemischt wurde.

Das Logo ist das Markenlogo (§4.1), nicht das Unternehmenslogo, das §3.2 fuer
eine Anwendung mit der AG als Absender vorsaehe — es liegt nicht vor. Der
Freiraum X/3 steckt im Bauteil selbst und nicht in den Aufrufstellen, sonst
haengt seine Einhaltung daran, dass jede einzelne daran denkt.

docs/farbschema.html zeigt Token, Kontraste und Bauteile nebeneinander und
laesst sich ohne Server oeffnen.

Nicht im Browser gesehen: Anmeldung laeuft ueber das Firmenkonto und die
Datenbank ist von hier nicht erreichbar. Lint, Typen, Schemaabgleich, 524 Tests
und der Build sind sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:26:21 +02:00
3926f1bb80 Load a whole organisation from a file, or none of it
Second half of the mass import: the transactional loader, the /import page
and a template generated from the same schema the validation uses.

Everything happens in one transaction. A half-loaded organisation — areas
without departments, positions without people — is worse than none, because
it looks like data. The dry run is the same code path with a rollback at the
end, so the report is built against the real current state rather than a
copy, and nothing is cached between checking and committing: the file is
sent twice. That costs one upload and avoids server-side state that can
expire, fill up, or be confused between two people.

Personnel numbers are taken from the file, not reassigned. personnel_number
is GENERATED ALWAYS AS IDENTITY, so this needs OVERRIDING SYSTEM VALUE and a
hand-written insert — worth it, because the number is on payslips, in files
and on badges. An import that reissues it is not a migration. The identity
counter is advanced afterwards; without that the next hire draws a number
the import already used, and the unique index refuses it weeks later, far
from the cause.

Three defects the first real run against the database exposed, none of which
typecheck, lint or 231 tests could have found:

  - weekly_hours is bound to employment type by a CHECK constraint: full time
    is exactly 38.5. The import reached the insert and was rolled back. Now
    it is a finding with a row number.
  - Titles are restricted to a fixed list by another CHECK. Same treatment.
  - setval() needs UPDATE on the sequence, which `usage, select` does not
    grant. Migration 20260803120000 adds it; until it is applied, an import
    containing people will fail at the last step and take itself back.

I also had exit_date > entry_date where the database has >=. Someone who
never starts enters and leaves the same day; the stricter rule would have
rejected a real case.

Verified against the live database through the actual route and session: a
file with four deliberate faults produced exactly four findings, each with
sheet, row and column, and the rollback left nothing behind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:40:33 +02:00
79f0e19bf8 Org assignment history, mobile support, and a correctness pass
Data model
- employee_assignments records org placement over time (valid_from/valid_to),
  written by a trigger on `employees` rather than inside each RPC: ~70
  `update employees` statements spread over fifteen migrations mean per-call
  bookkeeping would miss paths today and again with every future RPC. A
  partial unique index enforces the one-open-interval invariant the trigger
  relies on when closing the current row.
- The Organigramm gains a Stichtag (default today). Membership comes from
  entry/exit/karenz, past placement from the new history, future placement
  projected from pending_org_changes. Placements predating the migration are
  backfilled with today's values and flagged as such in the UI, since
  employee_history only ever stored free text and cannot be reconstructed.

Correctness
- Reports and exports silently truncated at PostgREST's 1000-row cap
  (db.max_rows); employee_history is already past it at ~800 staff. Every
  whole-table read now pages explicitly.
- XLSX date cells were a day early: ExcelJS converts a Date to an Excel
  serial straight off getTime(), so a Date built at local midnight lands on
  the previous day's serial in any positive-offset zone.
- Date handling is pinned to Europe/Vienna throughout, and date-only strings
  are formatted without a Date round-trip. The dashboard's YTD window was
  built by round-tripping a local Date through toISOString(), which shifted
  it a day early and dropped 31 December entirely.
- Export routes parsed measure/group/split/eventType with unchecked `as`
  casts, so an unknown value reached column headers as `undefined` and the
  Content-Disposition filename. Parsed against the label maps now, with the
  filename slugged as a backstop.
- toXlsx keyed columns by header text, silently dropping the second of any
  two columns sharing a name — split columns take their header from data.
- The org chart tree walks had no cycle guard; nothing in the schema forbids
  a manager_id cycle, and one would hang the tab rather than misreport.
- The login page reflected ?error= verbatim, letting anyone put arbitrary
  text on the real sign-in screen; messages are looked up by code now.
- React Flow needs elementsSelectable on, or it sets pointer-events:none on
  the whole node and the expand control stops responding.

UI
- Mobile: the shell was unusable below lg — a fixed 236px margin pushed
  content off-screen with no mobile navigation at all. The sidebar is now a
  drawer, dvh replaces vh, safe-area insets are honoured, inputs are 16px so
  iOS stops zooming on focus, and form grids stack.
- Org chart nodes redesigned: per-kind accent stripes and icons, vacant
  roles called out, expand control moved to the bottom edge carrying the
  child count.
- Pagination is windowed; it previously rendered one link per page (54 for
  the employee list, unbounded for the audit log).
- Positions page reduced to open positions with a single "Besetzen" action.
- The employee Organisation tab links into the org chart focused on that
  person, reusing the chart's existing search-match highlighting.

Also included, uncommitted until now
- Dependants, HR notes, academic titles, split address fields, position
  validity and role/employment fields, with their migrations and UI.
- Docker/compose deployment setup, data-model and security-review docs.
2026-07-24 23:38:10 +02:00
ef9852b09c Phase 1: project foundation for Alpenwerk HR
Scaffolds the Next.js 16 / TypeScript strict / Tailwind v3 app per
NEXTJS_REBUILD_SUPERPROMPT.md, and implements the Foundation slice from
the Phase 1 plan:

- Corrected Supabase schema (supabase/schema.sql): org units, employees,
  history, positions, hire drafts, saved reports, audit log, reorg
  scenarios, role-based profiles, salary-masking view, RLS policies,
  auto-derivation triggers, position-number generator.
- Seed script (supabase/seed.ts): ~800 realistic Austrian employees across
  9 divisions / 16 departments / 35 teams, history, 8 open positions, and
  hr_admin/manager test accounts.
- Supabase clients (lib/supabase/*), design tokens (tailwind.config.ts),
  format/color helpers (lib/format.ts, lib/colors.ts).
- Shared UI kit (components/ui): Avatar, StatusChip, Toast, Modal,
  SlideOver, SegmentedControl, Lookup.
- Auth (login page, Server Actions) and proxy.ts (Next 16's replacement
  for middleware) guarding the authenticated route group.
- Shell (Sidebar, Topbar, NewHireButton stub) and the Dashboard page,
  reading live data via employees_directory.

Employees list/detail, hire wizard, action panels, org chart, positions,
reports, and audit log are deferred to later phases per the plan.
2026-07-13 21:44:28 +02:00