Remove Supabase
Some checks failed
CI / Lint, Typen, Tests, Build (push) Failing after 5m40s
CI / Migrationen auf leerer Datenbank (push) Has been cancelled

The database moved to a container of our own; the platform is gone.
This takes out what was left of it — and, where the leftovers were load
bearing, moves rather than deletes.

Moved, not deleted:

  supabase/migrations/  -> db/migrations/      the schema's source of truth
  supabase/build-org.ts -> scripts/build-org.ts
  lib/supabase/types.ts -> lib/types.ts        52 import sites repointed

The bookkeeping needed care. It lived in `supabase_migrations.schema_migrations`,
and simply renaming the schema would have left the runner facing an empty
table: it would have called all 67 migrations pending and replayed them
against a database that is long since current. So the runner now creates
`migrationen.schema_migrations` and, once, copies the old rows across —
guarded so a second run does nothing and a fresh database skips it entirely.
Only then does migration 20260907100000 drop the old schema.

Deleted: the CLI config, the seed, the historical schema/function dumps
(nothing read them), scripts/umzug-von-supabase.sh (the move is done), and
both Supabase packages plus the CLI. Nothing in the application imported
them — the build now succeeds with no environment variables at all, which
is the proof.

Integration tests: six of them signed in through Supabase Auth and asserted
against the anon key and the service role. That model is gone, so the tests
were not portable — they are deleted. session-context and
employee-status-filter already ran on pg and are untouched; om-reporting is
ported to a direct connection because it guards a real risk (the reporting
line rule exists twice, once in SQL and once in TypeScript).

CI: the integration job started a Supabase stack. It now runs a postgres
service, applies deploy/db-init and every migration to an empty database —
that was the valuable part, and it still holds — then checks that a second
run is a no-op, which is what proves the bookkeeping works.

Docs: security-review.md audited a service-role key, a cookie adapter and
auth.users, none of which exist. Restating findings about removed components
would suggest today's system had been reviewed; it has not. It now records
what was removed and says a fresh review is due. data-model.md was already
marked obsolete and described the pre-OM schema; azure-migration.md was a
plan for a route not taken. Both deleted.

Verified: npm ci, typecheck, lint, 445 tests, build — all clean without the
packages. Integration tests skip cleanly with no database. Migration SQL and
the runner are reviewed but NOT executed: no Docker here, and the old
instance no longer resolves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 10:43:22 +02:00
parent 5c310c3a58
commit b87c8ad64c
155 changed files with 386 additions and 4014 deletions

View File

@@ -15,6 +15,3 @@ npm-debug.log*
*.tsbuildinfo
.scratch_*
.scratch_shots
supabase/.branches
supabase/.temp
supabase/snippets

View File

@@ -29,25 +29,45 @@ jobs:
- name: Typecheck
run: npm run typecheck
# Catches the drift that `tsc` cannot: lib/supabase/types.ts is
# hand-written, so a migration adding a column leaves it silently stale.
# Fängt die Abweichung, die `tsc` nicht sehen kann: lib/types.ts wird von
# Hand gepflegt, eine Migration mit einer neuen Spalte lässt sie also
# stillschweigend veralten.
- name: Schema/Typen-Abgleich
run: npm run types:check
- name: Unit- und Komponententests
run: npm test
# Ohne Umgebungsvariablen: seit dem Wegfall der Browser-Anbindung wird
# zur Bauzeit nichts mehr aus der Umgebung gelesen, und das Abbild ist
# für jede Umgebung dasselbe.
- name: Build
run: npm run build
env:
# Read at module scope by the Supabase browser client, so the build
# needs them present — never the real project's values.
NEXT_PUBLIC_SUPABASE_URL: http://127.0.0.1:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY: build-time-placeholder
integration:
name: Integrationstests (echtes Postgres)
migrationen:
name: Migrationen auf leerer Datenbank
runs-on: ubuntu-latest
# Derselbe Stand wie im Betrieb. Ein eigener Dienst statt einer fremden
# Plattform — die Datenbank gehört seit dem Umzug zum Projekt.
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_PASSWORD: ci
POSTGRES_DB: alpenwerk
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 20
env:
MIGRATE_DATABASE_URL: postgresql://postgres:ci@127.0.0.1:5432/alpenwerk
DATABASE_SSL: "false"
steps:
- uses: actions/checkout@v4
@@ -58,40 +78,37 @@ jobs:
- run: npm ci
- uses: supabase/setup-cli@v1
with:
version: latest
# Applies every migration to a fresh database — which also means a
# migration that cannot be replayed from scratch fails here rather than
# on a restore or a new environment.
- name: Supabase starten
run: supabase start
# `-o env` emits API_URL / ANON_KEY / SERVICE_ROLE_KEY / DB_URL; the app
# expects them under its own names. DATABASE_URL ist der direkte
# Postgres-Zugang — den braucht die neue Zugriffsschicht (lib/db) und
# vor allem der Nachweis zum Sitzungskontext.
- name: Testumgebung schreiben
# Was das Schema von der Umgebung erwartet: die Anwendungsrolle ohne
# BYPASSRLS, zwei Erweiterungen und die Attrappe des `auth`-Schemas, ohne
# die sich die Migrationen von Juni 2026 nicht abspielen lassen.
- name: Rollen, Erweiterungen, auth-Attrappe
env:
PGPASSWORD: ci
APP_DB_PASSWORD: ci-anwendungsrolle
run: |
supabase status -o env \
--override-name api.url=NEXT_PUBLIC_SUPABASE_URL \
--override-name auth.anon_key=NEXT_PUBLIC_SUPABASE_ANON_KEY \
--override-name auth.service_role_key=SUPABASE_SERVICE_ROLE_KEY \
--override-name db.url=DATABASE_URL \
| grep -E '^(NEXT_PUBLIC_SUPABASE_URL|NEXT_PUBLIC_SUPABASE_ANON_KEY|SUPABASE_SERVICE_ROLE_KEY|DATABASE_URL)=' \
| tr -d '"' > .env.test.local
# Lokal läuft Postgres ohne TLS; ohne das versucht `pg` es trotzdem.
echo "DATABASE_SSL=false" >> .env.test.local
grep -q '^DATABASE_URL=' .env.test.local \
|| { echo "DATABASE_URL wurde nicht geschrieben — der Sitzungskontext-Nachweis liefe ins Leere."; exit 1; }
for f in deploy/db-init/*.sql; do
echo "── $f"
psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -U postgres -d alpenwerk -f "$f"
done
- name: Seed
run: node --env-file=.env.test.local supabase/seed.ts
# Der eigentliche Zweck dieses Jobs: jede Migration muss sich auf einer
# leeren Datenbank abspielen lassen. Eine, die das nicht kann, fällt hier
# auf — und nicht beim Wiederherstellen einer Sicherung oder beim
# Aufsetzen einer neuen Umgebung.
- name: Migrationen einspielen
run: node scripts/migrate.mjs
- name: Integrationstests
run: npm run test:integration
# Zweiter Lauf: die Buchführung muss greifen. Meldet er etwas anderes als
# „nichts anzuwenden", verbucht der Läufer nicht richtig — und ein
# Ausrollen spielte Migrationen doppelt ein.
- name: Zweiter Lauf ist ein Nichts
run: |
ausgabe=$(node scripts/migrate.mjs)
echo "$ausgabe"
echo "$ausgabe" | grep -q "^Nichts anzuwenden" \
|| { echo "Der zweite Lauf wollte erneut anwenden — die Buchführung greift nicht."; exit 1; }
- name: Supabase-Logs bei Fehlschlag
if: failure()
run: supabase status && docker ps -a
# Die Integrationstests brauchen einen befüllten Bestand; der Seed lag
# in der abgelösten Umgebung und ist noch nicht nachgezogen (siehe
# README, offene Punkte). Bis dahin laufen sie gegen eine erreichbare
# Datenbank von Hand, nicht hier.

View File

@@ -96,7 +96,7 @@ jobs:
- name: .env schreiben
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }}
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
AUTH_URL: ${{ secrets.AUTH_URL }}
AUTH_MICROSOFT_ENTRA_ID_ID: ${{ secrets.AUTH_MICROSOFT_ENTRA_ID_ID }}
@@ -106,7 +106,7 @@ jobs:
run: |
set -euo pipefail
fehlend=""
for name in DATABASE_URL SUPABASE_DB_URL AUTH_SECRET AUTH_URL \
for name in DATABASE_URL MIGRATE_DATABASE_URL AUTH_SECRET AUTH_URL \
AUTH_MICROSOFT_ENTRA_ID_ID AUTH_MICROSOFT_ENTRA_ID_SECRET \
AUTH_MICROSOFT_ENTRA_ID_ISSUER CRON_SECRET; do
eval "wert=\${$name:-}"
@@ -138,12 +138,12 @@ jobs:
# wenn danach etwas schiefgeht.
- name: Ausstehende Migrationen zeigen
env:
SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }}
run: node scripts/migrate.mjs --dry-run
- name: Migrationen anwenden
env:
SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }}
run: node scripts/migrate.mjs
# ── Container ───────────────────────────────────────────────────

14
.gitignore vendored
View File

@@ -38,17 +38,9 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
# supabase CLI local dev (generated, project-machine-specific)
/supabase/.branches
/supabase/.temp
/supabase/snippets
# scratch output of `npm run types:generate`, for comparing against the
# hand-written lib/supabase/types.ts — never itself imported
/lib/supabase/types.generated.ts
# local scratch scripts/screenshots (ad-hoc verification against a real or
# seeded DB — can carry HR data or use SUPABASE_SERVICE_ROLE_KEY; never commit)
# Kladden und Bildschirmfotos aus der Handprüfung — können Personendaten
# oder Zugangsdaten enthalten; nie committen.
.scratch_*
/.scratch_shots/
@@ -56,5 +48,3 @@ next-env.d.ts
.backups/
# Datenabzuege aus dem Umzug – enthalten Personendaten
supabase-daten-*.sql
supabase-voll-*.sql

View File

@@ -15,12 +15,10 @@ eine `profiles`-Zeile braucht (siehe [docs/entra-sso.md](docs/entra-sso.md)).
- **Containerisiert:** die Next.js-App (`Dockerfile`) **und die Datenbank**
(Dienst `db`, `postgres:17-alpine`). Beide zusammen in
`docker-compose.yml`; die Daten liegen im benannten Volume `db-daten`.
- **Nicht mehr Supabase.** Die Anwendung sprach ohnehin unmittelbar mit
PostgreSQL — Supabase war nur der Betreiber. Was die Plattform beisteuerte,
bringt jetzt `deploy/db-init/` mit: die Anwendungsrolle ohne BYPASSRLS, die
zwei benutzten Erweiterungen und eine Attrappe des `auth`-Schemas, die nur
die alten Migrationen von Juni brauchen. Der Umzug steht in
[Abschnitt 3a](#3a-umzug-von-supabase).
- **Die Datenbank gehört zum Projekt.** Was eine gehostete Plattform sonst
beisteuert, bringt `deploy/db-init/` mit: die Anwendungsrolle ohne
BYPASSRLS, die zwei benutzten Erweiterungen und eine Attrappe des
`auth`-Schemas, die nur die Migrationen von Juni 2026 brauchen.
- Läuft die Datenbank woanders (Azure Flexible Server, RDS, eigenes Blech),
genügt es, `DATABASE_URL` dorthin zeigen zu lassen und den `db`-Dienst nicht
zu starten. Die Anwendung merkt keinen Unterschied.
@@ -152,41 +150,6 @@ docker compose up -d --build
`migrate` steht im Profil `tools` und läuft bei `docker compose up` nicht mit.
Auf dem Host wird dafür weder Node noch psql gebraucht — nur Docker.
## 3a. Umzug von Supabase
Einmalig, wenn der Bestand noch bei Supabase liegt:
```bash
SUPABASE_DB_URL='postgresql://postgres:<passwort>@<projekt>.supabase.com:5432/postgres' \
./scripts/umzug-von-supabase.sh
```
Was das Skript tut und warum:
1. **Schema aus den Migrationen**, nicht aus einem Abzug. Nachgewiesen ist,
dass alle Migrationen auf einer leeren Datenbank durchlaufen und dabei
Spalte für Spalte, Index für Index, Policy für Policy dasselbe ergeben wie
die gewachsene Produktion. Der Weg hat zwei Vorteile: die Buchführung
stimmt danach von selbst, und Supabase-eigene Rechte und Eigentümer kommen
gar nicht erst mit.
2. **Nur die Daten** werden abgezogen (`pg_dump --data-only
--disable-triggers`). Ohne `--disable-triggers` stolpert jede
Fremdschlüsselprüfung über die Ladereihenfolge. RLS steht nicht im Weg —
keine der 19 Tabellen hat `FORCE ROW LEVEL SECURITY`, der Eigentümer
schreibt also durch.
3. Am Ende werden die Zeilenzahlen ausgegeben. **Mit denen bei Supabase
vergleichen**, bevor irgendetwas abgeschaltet wird.
Der Abzug bleibt als Datei liegen. Erst löschen, wenn die Anwendung gegen die
neue Datenbank nachweislich läuft.
Danach in `.env`:
```
DATABASE_URL=postgresql://alpenwerk_app:<APP_DB_PASSWORD>@db:5432/alpenwerk
DATABASE_SSL=false
```
### Was am `auth`-Schema übrigbleibt
`deploy/db-init/01-auth-attrappe.sql` legt ein leeres `auth`-Schema an. Es wird
@@ -283,8 +246,8 @@ und löscht sie danach wieder.
| Secret | Anmerkung |
|---|---|
| `DATABASE_URL` | Transaktions-Pooler (Supabase: 6543) — den benutzt die Anwendung |
| `SUPABASE_DB_URL` | **Direkter** Zugang (Supabase: 5432) — nur für die Migrationen |
| `DATABASE_URL` | Verbindung der Anwendungsrolle — die benutzt die Anwendung |
| `MIGRATE_DATABASE_URL` | Zugang als Verwalter — nur für die Migrationen |
| `AUTH_SECRET` | |
| `AUTH_URL` | z. B. `https://hr.elycon.solutions` |
| `AUTH_MICROSOFT_ENTRA_ID_ID` | |
@@ -332,9 +295,9 @@ node --env-file=.env scripts/migrate.mjs --dry-run # was stünde an
node --env-file=.env scripts/migrate.mjs # anwenden
```
Buch geführt wird in `supabase_migrations.schema_migrations`, derselben Tabelle
in derselben Form, die die Supabase-CLI benutzt — `supabase db push` von der
Arbeitsstation bleibt damit möglich und überspringt, was hier schon lief.
Buch geführt wird in `migrationen.schema_migrations`: eine Zeile je
angewendeter Datei, mit ihrem Text. Der Läufer wendet nur an, was dort
fehlt.
> **Einmalig, im August 2026 bereits erledigt:** Die Migrationen wurden bis
> dahin von Hand eingespielt, die Buchführungstabelle existierte gar nicht. Ein
@@ -409,9 +372,6 @@ Single-Instance-Compose-Konfiguration ist das nicht nötig.
- **Cron läuft nicht:** `docker compose logs cron` – prüft, ob
`/etc/crontabs/root` korrekt geschrieben wurde und ob `CRON_SECRET` in
`.env` gesetzt ist (leer/fehlend führt serverseitig zu `401`).
- **`max clients reached in session mode`:** der Verbindungsstring zeigt auf
den Sitzungs-Modus des Poolers. Auf den Transaktions-Modus wechseln (bei
Supabase Port 6543).
- **Healthcheck rot:** `docker compose logs app` – meist `DATABASE_URL`
fehlend oder nicht erreichbar. Der Pool baut die Verbindung erst beim
ersten Zugriff auf, der Fehler steht deshalb im Log der Anfrage, nicht im

View File

@@ -24,11 +24,13 @@ auf explizit aktivierte HR-Benutzer:innen beschränkt (siehe
`node_modules/next/dist/docs/` konsultieren; sie sind maßgeblich, ältere
Anleitungen im Netz beschreiben teils überholte APIs.
- React 19, TypeScript
- Supabase (Postgres, Auth, RLS) — Datenhaltung liegt vollständig in
Supabase, nicht im Next.js-Prozess.
- PostgreSQL, angesprochen über Kysely und `pg` — die Datenhaltung liegt in
der Datenbank, nicht im Next.js-Prozess. Der Zugriff läuft ausschliesslich
über `withUser()` (`lib/db/`), das den Sitzungskontext setzt.
- Auth.js gegen Microsoft Entra ID — keine eigenen Passwörter.
- Tailwind CSS v4
- Vitest — Unit- (Node), Komponenten- (jsdom) und Integrationstests
(gegen ein lokales Supabase)
(gegen eine erreichbare Datenbank)
## Setup
@@ -38,14 +40,14 @@ cp .env.example .env.local # Werte eintragen, siehe unten
npm run dev
```
Für lokale Supabase-Entwicklung (statt gegen ein Cloud-Projekt):
Für eine lokale Datenbank genügt der Container aus `docker-compose.yml`:
```bash
supabase start # startet lokalen Postgres/Auth/Studio-Stack
docker compose up -d db
docker compose run --rm migrate # spielt db/migrations/ ein
```
`supabase/config.toml` und `.env.test.local` sind bereits auf die
Standard-Ports der lokalen Supabase-CLI abgestimmt.
Danach zeigt `DATABASE_URL` in `.env.local` auf diese Datenbank.
## Umgebungsvariablen
@@ -77,8 +79,9 @@ selbst spricht. Ein Docker-Abbild ist damit umgebungsneutral: einmal gebaut,
| `npm run lint` | ESLint (`eslint-config-next`, Flat Config) |
| `npm run typecheck` | `tsc --noEmit` |
| `npm run test` | Vitest, Unit-Tests (`tests/unit/**`) |
| `npm run test:integration` | Vitest gegen eine echte (lokale) Supabase-Instanz — braucht `supabase start` und `.env.test.local` |
| `npm run test:integration` | Vitest gegen eine erreichbare Datenbank; überspringt sich ohne `DATABASE_URL` |
| `npm run test:e2e` | Playwright |
| `npm run migrate` | Ausstehende Migrationen einspielen (`--dry-run`, `--baseline`) |
| `npm run check` | lint + typecheck + test + build in Folge |
## Sicherheitsprinzipien
@@ -115,23 +118,24 @@ selbst spricht. Ein Docker-Abbild ist damit umgebungsneutral: einmal gebaut,
- Fehlt `CRON_SECRET` oder stimmt der Header nicht, antwortet die Route mit
`401` (nicht `500` — bewusst, siehe `tests/unit/security.test.ts`).
## Supabase-Hinweise
## Datenbank
- Schema-Quelle der Wahrheit: `supabase/migrations/`. Menschlich lesbare
Fassung, aus der laufenden Datenbank erzeugt:
- Schema-Quelle der Wahrheit: `db/migrations/`. Menschlich lesbare Fassung,
aus der laufenden Datenbank erzeugt:
[`docs/datenkatalog.md`](docs/datenkatalog.md).
- Migrationen einspielen: `supabase db push` (gegen das verlinkte Projekt)
bzw. `supabase start` + automatische Anwendung für lokale Entwicklung.
- `supabase/seed.ts` und `.env.test.local` sind nur für lokale
Entwicklung/Tests gedacht, nie für ein Produktivprojekt verwenden.
- Migrationen einspielen: `npm run migrate`. Der Läufer wendet nur die
fehlenden Dateien an, jede in ihrer eigenen Transaktion, und führt darüber
Buch in `migrationen.schema_migrations`.
- `lib/types.ts` wird von Hand gepflegt. `npm run types:check` hält sie
Spalte für Spalte gegen die Migrationen.
## Testing
- `npm run test` — schnell, keine externen Abhängigkeiten, läuft in CI.
- `npm run test:integration` — braucht eine laufende lokale Supabase-Instanz
(`supabase start`) und `.env.test.local`; prüft RLS-Verhalten end-to-end
(siehe `tests/integration/authorization.test.ts` für das HR-Only-Zugriffs-
modell).
- `npm run test:integration` — braucht eine erreichbare Datenbank
(`DATABASE_URL`). Geprüft werden Regeln, die es zweimal gibt: einmal als
SQL, einmal als TypeScript. Ohne `DATABASE_URL` überspringen sich die
Dateien, statt mit einem Verbindungsfehler abzubrechen.
- `npm run test:e2e` — Playwright gegen einen laufenden Dev-/Preview-Server.
## Deployment
@@ -141,15 +145,18 @@ Reverse-Proxy/TLS, der nächtliche Lauf, Migrationen und Updates.
## Known TODOs vor Produktivbetrieb
- **Content-Security-Policy fehlt noch** (`next.config.ts` setzt bewusst
keine CSP — Skript-/Style-/Connect-Quellen sind noch nicht vollständig
inventarisiert; ungeprüft geraten zu setzen riskiert, Hydration oder den
Supabase-Client stillschweigend zu brechen).
- **Lokale Scratch-Artefakte** (`.scratch_*`, `.scratch_shots/`) enthalten
Screenshots/Hilfsskripte aus einer früheren manuellen Verifikation und
liegen noch im Arbeitsverzeichnis. Sie sind jetzt über `.gitignore`
ausgeschlossen; vor einem Produktiv-Handover sollten sie durchgesehen und
bei Bedarf gelöscht werden.
- **Content-Security-Policy läuft im Nur-Bericht-Modus** (`next.config.ts`).
Erzwungen wird sie erst, wenn die Meldungen sauber sind — eine geratene,
erzwungene Richtlinie blendet die Anwendung für alle aus.
- **Seed für die Integrationstests fehlt.** Er lag in der abgelösten
Umgebung. Zwei der drei Integrationstests vergleichen Regeln über den
gesamten Bestand und brauchen dafür Daten; bis der Seed nachgezogen ist,
laufen sie nur gegen eine bereits befüllte Datenbank, nicht in der CI.
- **Sicherheitsprüfung des heutigen Aufbaus steht aus** — siehe
[`docs/security-review.md`](docs/security-review.md).
- **Bildschirmfotos unter `.scratch_shots/`** stammen aus einer früheren
Handprüfung. Sie sind über `.gitignore` ausgeschlossen; vor der Übergabe
durchsehen und löschen.
- **Kein granulareres Rollenmodell** — aktuell HR-only (alles-oder-nichts).
Falls z. B. eine reine Lese-Rolle künftig gebraucht wird, gehört die
Erweiterung in eine neue Migration (`is_hr_user()`/RLS-Policies), nicht in

View File

@@ -6,7 +6,7 @@ import { withUser } from "@/lib/db";
import { callFunction, runMutation, type ActionResult, type MutationFn } from "@/lib/db/rpc";
import { OFFBOARDING_PUNKTE } from "@/lib/offboarding";
import { ONBOARDING_PUNKTE } from "@/lib/onboarding";
import type { CollectiveAgreement, DienstwagenArt, NoteCategory, RelationshipType, Weekday, WorkerType } from "@/lib/supabase/types";
import type { CollectiveAgreement, DienstwagenArt, NoteCategory, RelationshipType, Weekday, WorkerType } from "@/lib/types";
async function callRpc(fn: MutationFn, payload: Record<string, unknown>, revalidate: string[]): Promise<ActionResult> {
const result = await runMutation(await currentUserId(), fn, payload);

View File

@@ -14,7 +14,7 @@ import { derivedStatusFilter } from "@/lib/employee-status-filter";
import { fmtDate, fmtName, todayIso } from "@/lib/format";
import { breadcrumbLabel, divisionOf, loadOrgMaps, subtreeOf, unitOf, type OrgEb } from "@/lib/org";
import { loadPlacements } from "@/lib/placement";
import type { EmploymentStatus } from "@/lib/supabase/types";
import type { EmploymentStatus } from "@/lib/types";
const PAGE_SIZE = 15;

View File

@@ -8,7 +8,7 @@ import { Button, LINK_BUTTON_CLASS } from "@/components/ui/Button";
// Without this file a failed render drops the user on Next.js's own error
// screen — no navigation, no way back, and in production just "a client-side
// exception occurred". `reset()` re-renders the segment, which is enough for
// the common case of a transient Supabase timeout.
// the common case of a transient database timeout.
export default function AppError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
useEffect(() => {
console.error("Route error:", error);

View File

@@ -1,5 +1,5 @@
// Every page in this group is server-rendered per request (they all read
// from Supabase), so without this the browser sits on the previous page with
// from the database), so without this the browser sits on the previous page with
// no feedback until the server answers — on the employee list, long enough
// to look broken.
export default function Loading() {

View File

@@ -4,7 +4,7 @@ import { callFunction } from "@/lib/db/rpc";
// Applies effective-dated changes (Versetzung/Beförderung/Karenz/Reorg/Daten
// ändern with a future "Wirksam ab" date) once their date has arrived — see
// apply_due_pending_changes() in supabase/migrations.
// apply_due_pending_changes() in db/migrations.
//
// Gerufen wird das vom `cron`-Dienst aus docker-compose.yml, täglich um 03:00.
// Nicht im Namen einer HR-Person: es gibt keine angemeldete Sitzung, deshalb

View File

@@ -9,7 +9,7 @@ import { deriveStatusAsOf, parseIsoDateParam, parseStatuses, type OrgLookups } f
import { applyCriteria, loadDependentsCounts, loadOrgLookups, type ReportFilters } from "@/lib/reports-data";
import { requireHrUser } from "@/lib/auth/require-hr";
import { withUser } from "@/lib/db";
import type { Database, Weekday } from "@/lib/supabase/types";
import type { Database, Weekday } from "@/lib/types";
// Die Rohzeile plus die Einordnung, die nicht mehr auf ihr steht: sie kommt
// über die Planstelle und die abgeleitete Berichtslinie.

View File

@@ -5,7 +5,7 @@ import { useState } from "react";
import { AenderungsTabelle } from "@/components/ui/AenderungsTabelle";
import { SlideOver } from "@/components/ui/SlideOver";
import { actionBadgeStyle } from "@/lib/colors";
import type { AuditChange } from "@/lib/supabase/types";
import type { AuditChange } from "@/lib/types";
// Eine Protokollzeile zum Aufklappen.
//

View File

@@ -8,7 +8,7 @@ import { SelectField, TextField } from "@/components/ui/Field";
import { Modal } from "@/components/ui/Modal";
import { useToast } from "@/components/ui/Toast";
import { todayIso } from "@/lib/format";
import type { RelationshipType } from "@/lib/supabase/types";
import type { RelationshipType } from "@/lib/types";
const RELATIONSHIPS: RelationshipType[] = ["Ehepartner:in", "Lebenspartner:in", "Kind", "Sonstige"];

View File

@@ -7,7 +7,7 @@ import { deleteEmployeeDependent } from "@/actions/employees";
import { Button } from "@/components/ui/Button";
import { useToast } from "@/components/ui/Toast";
import { fmtDate, todayIso } from "@/lib/format";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
import { AddDependentModal } from "./AddDependentModal";
type Dependent = Database["public"]["Tables"]["employee_dependents"]["Row"];

View File

@@ -11,7 +11,7 @@ import { fmtFullName, tenure } from "@/lib/format";
import { fortschritt as fortschrittOffboarding, gehoertOffboarding } from "@/lib/offboarding";
import { fortschritt as fortschrittOnboarding } from "@/lib/onboarding";
import type { OpenPositionResolved } from "@/lib/positions";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
import { DatenAendernPanel } from "./panels/DatenAendernPanel";
import { KarenzPanel } from "./panels/KarenzPanel";
import { PromotePanel } from "./panels/PromotePanel";

View File

@@ -9,7 +9,7 @@ import { TextField } from "@/components/ui/Field";
import { Modal } from "@/components/ui/Modal";
import { useToast } from "@/components/ui/Toast";
import { fmtDate } from "@/lib/format";
import type { AuditChange } from "@/lib/supabase/types";
import type { AuditChange } from "@/lib/types";
// Berichtigen, nicht neu erfassen.
//

View File

@@ -1,7 +1,7 @@
"use client";
import { SelectField, TextField } from "@/components/ui/Field";
import type { CollectiveAgreement, DienstwagenArt, Weekday, WorkerType } from "@/lib/supabase/types";
import type { CollectiveAgreement, DienstwagenArt, Weekday, WorkerType } from "@/lib/types";
const WEEKDAYS: Weekday[] = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];

View File

@@ -16,7 +16,7 @@ import { brauchtAufenthaltstitel, UN_COUNTRIES } from "@/lib/countries";
import { STUNDEN_GRUENDE } from "@/lib/absence";
import { fmtFullName, todayIso } from "@/lib/format";
import { isValidSvnr, requiresAustrianSvnr } from "@/lib/svnr";
import { EMERGENCY_RELATIONS, type ContractType, type Database, type EmploymentType, type GenderType } from "@/lib/supabase/types";
import { EMERGENCY_RELATIONS, type ContractType, type Database, type EmploymentType, type GenderType } from "@/lib/types";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];
type Dependent = Database["public"]["Tables"]["employee_dependents"]["Row"];

View File

@@ -10,7 +10,7 @@ import { SlideOver } from "@/components/ui/SlideOver";
import { useToast } from "@/components/ui/Toast";
import { ABSENCE_TYPES, absenceLabel, RUECKKEHR_GRUENDE } from "@/lib/absence";
import { fmtDate, fmtName } from "@/lib/format";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];
type Mode = "adjust" | "return";

View File

@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/Button";
import { SelectField, TextField } from "@/components/ui/Field";
import { SlideOver } from "@/components/ui/SlideOver";
import { useToast } from "@/components/ui/Toast";
import type { Database, PaygradeType } from "@/lib/supabase/types";
import type { Database, PaygradeType } from "@/lib/types";
import { fmtName } from "@/lib/format";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];

View File

@@ -9,7 +9,7 @@ import { SlideOver } from "@/components/ui/SlideOver";
import { useToast } from "@/components/ui/Toast";
import { fmtDate, fmtName } from "@/lib/format";
import type { OpenPositionResolved } from "@/lib/positions";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];

View File

@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/Button";
import { SelectField, TextField, TextareaField } from "@/components/ui/Field";
import { SlideOver } from "@/components/ui/SlideOver";
import { useToast } from "@/components/ui/Toast";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
import { fmtDate, fmtName } from "@/lib/format";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];

View File

@@ -8,7 +8,7 @@ import { SelectField, TextField } from "@/components/ui/Field";
import { SlideOver } from "@/components/ui/SlideOver";
import { useToast } from "@/components/ui/Toast";
import type { OpenPositionResolved } from "@/lib/positions";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
import { fmtName } from "@/lib/format";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];

View File

@@ -9,7 +9,7 @@ import { SegmentedControl } from "@/components/ui/SegmentedControl";
import { actionBadgeStyle } from "@/lib/colors";
import { fmtDate, todayIso } from "@/lib/format";
import { darfBearbeitetWerden, darfKorrigiertWerden, loeschVorschau } from "@/lib/history";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
type HistoryRow = Database["public"]["Tables"]["employee_history"]["Row"];

View File

@@ -8,7 +8,7 @@ import { SelectField, TextField, TextareaField } from "@/components/ui/Field";
import { useToast } from "@/components/ui/Toast";
import { NOTE_CATEGORY_STYLES } from "@/lib/colors";
import { fmtDate } from "@/lib/format";
import type { Database, NoteCategory } from "@/lib/supabase/types";
import type { Database, NoteCategory } from "@/lib/types";
type Note = Database["public"]["Tables"]["employee_notes"]["Row"];

View File

@@ -1,7 +1,7 @@
import { AngehoerigeSection } from "@/components/employees/AngehoerigeSection";
import { brauchtAufenthaltstitel } from "@/lib/countries";
import { fmtAge, fmtDate } from "@/lib/format";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];
type Location = Database["public"]["Tables"]["locations"]["Row"];

View File

@@ -1,6 +1,6 @@
import { dienstwagenLabel } from "@/lib/dienstwagen";
import { fmtDate } from "@/lib/format";
import type { Database } from "@/lib/supabase/types";
import type { Database } from "@/lib/types";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];

View File

@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/Button";
import { SelectField, TextField } from "@/components/ui/Field";
import { fmtDate } from "@/lib/format";
import { formatSvnr, svnrErrorMessage, validateSvnr } from "@/lib/svnr";
import type { RelationshipType } from "@/lib/supabase/types";
import type { RelationshipType } from "@/lib/types";
import type { HireDraftAngehoerige, HireDraftData } from "./types";
const VERHAELTNIS: RelationshipType[] = ["Ehepartner:in", "Lebenspartner:in", "Kind", "Sonstige"];

View File

@@ -1,5 +1,5 @@
import { SelectField, TextField } from "@/components/ui/Field";
import { EMERGENCY_RELATIONS } from "@/lib/supabase/types";
import { EMERGENCY_RELATIONS } from "@/lib/types";
import type { HireDraftData } from "./types";
// Eigener Schritt, kurz vor der Zusammenfassung.

View File

@@ -1,6 +1,6 @@
import { RoleEmploymentFields } from "@/components/employees/RoleEmploymentFields";
import { SelectField, TextField } from "@/components/ui/Field";
import type { PaygradeType } from "@/lib/supabase/types";
import type { PaygradeType } from "@/lib/types";
import type { HireDraftData } from "./types";
const PAYGRADES: { value: PaygradeType; label: string; description: string }[] = [

View File

@@ -1,4 +1,4 @@
import type { CollectiveAgreement, ContractType, DienstwagenArt, EmploymentType, GenderType, PaygradeType, RelationshipType, Weekday, WorkerType } from "@/lib/supabase/types";
import type { CollectiveAgreement, ContractType, DienstwagenArt, EmploymentType, GenderType, PaygradeType, RelationshipType, Weekday, WorkerType } from "@/lib/types";
// The spec's hire wizard field list (§4.4) omits Geschlecht and Standort even
// though both are NOT NULL on employees — added here (defaults keep them

View File

@@ -37,7 +37,7 @@ import {
type ReportRow,
type UnitOption,
} from "@/lib/reports";
import type { HistoryEventType } from "@/lib/supabase/types";
import type { HistoryEventType } from "@/lib/types";
const SPLIT_COLORS = ["bg-brand-500", "bg-info-text", "bg-purple-text", "bg-warning-text", "bg-success-text", "bg-danger-solid"];
const EVENT_TYPES = Object.keys(EVENT_TYPE_LABELS) as HistoryEventType[];

View File

@@ -1,4 +1,4 @@
import type { AuditChange } from "@/lib/supabase/types";
import type { AuditChange } from "@/lib/types";
// Was sich geändert hat, feldweise — im Protokoll und in der Historie einer
// Person dieselbe Darstellung. Zwei Ansichten derselben Sache verschieden zu

View File

@@ -1,7 +1,7 @@
import { absenceLabel } from "@/lib/absence";
import { STATUS_STYLES } from "@/lib/colors";
import { fmtDate } from "@/lib/format";
import type { EmploymentStatus } from "@/lib/supabase/types";
import type { EmploymentStatus } from "@/lib/types";
type StatusChipProps = {
status: EmploymentStatus;

Some files were not shown because too many files have changed in this diff Show More