Die Übersicht beantwortet vier Fragen, die man morgens hat: woran arbeite ich,
was ist fällig, wo hat jemand geschrieben, was hat sich getan. Jede Liste
läuft durch visibleCaseWhere - das ist der Ort, an dem eine unachtsame
Abfrage am ehesten eine Akte verrät. Ein Test prüft jede einzeln gegen eine zu
hoch eingestufte Akte. Der bisherige Inhalt ("Dein Zugang") steht jetzt unter
/zugang.
Beobachten ist eine Merkliste, kein Recht: nur was sichtbar ist, lässt sich
merken, und steigt eine Akte später über die eigene Stufe, verschwindet sie
aus der Liste, ohne dass jemand aufräumt.
Das helle Thema tauscht nur die semantische Ebene; die Grundpalette bleibt.
Die Klassifizierungsfarben sind für hellen Grund eine Spur dunkler, im Farbton
aber dieselben - eine Stufe muss in beiden Themen wiedererkennbar sein. Drei
Zustände, "wie das System" ist der Auslieferungszustand, und ein Skript im
Kopf setzt die Wahl, bevor das erste Mal gemalt wird.
Die Messung misst nicht den Nutzer. web-vitals schickt Zahlen und das
Routenmuster, keine Kennung und keinen konkreten Pfad: wer wann welche Akte
geöffnet hat, steht im Zugriffsprotokoll, und eine zweite Spur daneben wäre
eine Überwachungsfunktion ohne Auftrag. Ausgewertet wird Median und 75.
Perzentil, nicht der Mittelwert.
Mobil: die breiten Tabellen scrollen in sich, statt die Seite breiter zu
machen. Ein e2e-Test hält fest, dass keine Seite waagerecht überläuft.
scripts/restore-test.sh spielt eine Sicherung in eine Wegwerf-Datenbank und
prüft ausdrücklich, dass Regeln und Trigger mitkommen: eine Wiederherstellung
ohne entry_no_delete wäre eine Datenbank ohne die Zusicherung, um derentwillen
das System gebaut wird. Einmal durchgespielt, alles angekommen.
186 Tests im Web-Paket, 17 in der Datenbank, 7 im Worker, 54 e2e.
Grösste Route 116,2 von 120 kB.
188 lines
6.1 KiB
TypeScript
188 lines
6.1 KiB
TypeScript
/**
|
|
* Rahmen der Anwendung: Name, Navigation, Anmeldung, Übersicht, Zustand.
|
|
*
|
|
* Nach Bereichen getrennt, weil eine **Client-Komponente die ganze Datei ins
|
|
* Bündel zieht**, in der ihr Text steht. Lägen alle Texte in einer, trüge jede
|
|
* Ansicht die der ganzen Anwendung mit sich herum — das Budget aus
|
|
* docs/09-performance-und-ux.md wäre nach wenigen Phasen aufgebraucht.
|
|
*
|
|
* Serverkomponenten nehmen weiter `de` aus `lib/i18n/de.ts`: was dorthin
|
|
* gezogen wird, verlässt den Server nicht.
|
|
*/
|
|
|
|
export const appText = {
|
|
name: 'AfiS-Aktensystem',
|
|
shortName: 'AfiS',
|
|
description: 'Fallakten, Personalakten und Ermittlungen der Abteilung für innere Sicherheit',
|
|
} as const
|
|
|
|
export const navText = {
|
|
dashboard: 'Übersicht',
|
|
cases: 'Fallakten',
|
|
personnel: 'Personalakten',
|
|
search: 'Suche',
|
|
admin: 'Verwaltung',
|
|
skipToContent: 'Zum Inhalt springen',
|
|
} as const
|
|
|
|
export const authText = {
|
|
signIn: 'Mit Discord anmelden',
|
|
signOut: 'Abmelden',
|
|
signedInAs: 'Angemeldet als',
|
|
intro:
|
|
'Die Anmeldung läuft über Discord. Zugriff haben Mitglieder der Abteilung für innere Sicherheit.',
|
|
reasons: {
|
|
'keine-mitgliedschaft': {
|
|
title: 'Du bist nicht auf dem AfiS-Discord',
|
|
body: 'Die Anmeldung setzt eine Mitgliedschaft auf dem Discord-Server voraus. Wende dich an die Abteilungsleitung.',
|
|
},
|
|
'keine-afis-rolle': {
|
|
title: 'Keine Berechtigung für das Aktensystem',
|
|
body: 'Du bist auf dem Server, trägst aber keine Rolle der Abteilung. Wende dich an die Abteilungsleitung.',
|
|
},
|
|
'discord-nicht-erreichbar': {
|
|
title: 'Discord antwortet nicht',
|
|
body: 'Die Mitgliedschaft war nicht prüfbar. In einigen Minuten erneut versuchen.',
|
|
},
|
|
} as Record<string, { title: string; body: string }>,
|
|
sessionEnded: {
|
|
title: 'Sitzung beendet',
|
|
body: 'Deine Rollen haben sich geändert. Melde dich neu an.',
|
|
},
|
|
error: {
|
|
title: 'Anmeldung nicht möglich',
|
|
body: 'Der Anmeldevorgang wurde abgebrochen. Erneut versuchen.',
|
|
},
|
|
backToSignIn: 'Zurück zur Anmeldung',
|
|
} as const
|
|
|
|
export const dashboardText = {
|
|
heading: 'Übersicht',
|
|
mine: 'Meine Fälle',
|
|
mineEmpty: 'Du bist an keiner offenen Akte beteiligt.',
|
|
due: 'Fristen',
|
|
dueEmpty: 'Nichts steht an.',
|
|
dueAt: (datum: string) => `fällig ${datum}`,
|
|
overdue: 'überfällig',
|
|
unread: 'Ungelesen',
|
|
unreadEmpty: 'Nichts Neues.',
|
|
unreadCount: (n: number) => (n === 1 ? '1 Nachricht' : `${n} Nachrichten`),
|
|
watched: 'Beobachtet',
|
|
watchedEmpty: 'Keine Akte beobachtet.',
|
|
recent: 'Zuletzt geändert',
|
|
recentEmpty: 'Noch keine Akte.',
|
|
access: 'Dein Zugang',
|
|
} as const
|
|
|
|
export const watchText = {
|
|
watch: 'Beobachten',
|
|
unwatch: 'Nicht mehr beobachten',
|
|
hint: 'Beobachtete Akten stehen auf deiner Übersicht und melden sich, auch ohne Beteiligung.',
|
|
} as const
|
|
|
|
export const overviewText = {
|
|
heading: 'Dein Zugang',
|
|
intro: 'Was das System über dich weiss und was daraus folgt.',
|
|
person: 'Person',
|
|
discordId: 'Discord-Kennung',
|
|
roles: 'Rollen auf dem Discord',
|
|
clearance: 'Errechnete Freigabestufe',
|
|
permissions: 'Rechte',
|
|
noPermissions: 'Keine Rechte. Die Abteilungsleitung vergibt sie in der Rollenverwaltung.',
|
|
noRoles: 'Keine Rollen.',
|
|
bootstrapAdmin: 'Notfallzugang aus der Konfiguration, unabhängig vom Rollen-Mapping',
|
|
unmapped: 'nicht zugeordnet',
|
|
} as const
|
|
|
|
export const healthText = {
|
|
heading: 'Systemzustand',
|
|
service: 'Dienst',
|
|
state: 'Zustand',
|
|
checkedAt: (when: string) => `Zustand der Dienste, abgefragt ${when}`,
|
|
ok: 'Betriebsbereit',
|
|
degraded: 'Eingeschränkt',
|
|
down: 'Gestört',
|
|
components: {
|
|
database: 'Datenbank',
|
|
discordGateway: 'Discord-Gateway',
|
|
clamav: 'Virenprüfung',
|
|
} as Record<string, string>,
|
|
} as const
|
|
|
|
export const notifyText = {
|
|
heading: 'Benachrichtigungen',
|
|
intro:
|
|
'Discord erfährt nie den Inhalt einer Akte. Bei Akten ab Stufe 3 auch nicht den Titel — nur, dass sich etwas getan hat, und einen Link.',
|
|
mode: 'Was soll dich erreichen?',
|
|
save: 'Übernehmen',
|
|
saved: 'Übernommen',
|
|
modes: {
|
|
ALL: 'Alles, was ich sehen darf',
|
|
OWN_CASES: 'Nur meine Fälle',
|
|
MENTIONS: 'Nur Erwähnungen',
|
|
OFF: 'Nichts',
|
|
} as Record<string, string>,
|
|
} as const
|
|
|
|
export const searchText = {
|
|
heading: 'Suche',
|
|
placeholder: 'Aktenzeichen, Titel, Inhalt',
|
|
kinds: {
|
|
case: 'Fallakten',
|
|
document: 'Dokumente',
|
|
personnel: 'Personalakten',
|
|
} as Record<string, string>,
|
|
filters: 'Filter',
|
|
reset: 'Filter zurücksetzen',
|
|
empty: 'Kein Treffer. Anderen Begriff oder weniger Filter versuchen.',
|
|
emptyNoQuery: 'Begriff eingeben oder einen Filter wählen.',
|
|
countLabel: (n: number) => (n === 1 ? '1 Treffer' : `${n} Treffer`),
|
|
more: 'Weitere laden',
|
|
truncated:
|
|
'Sehr viele Treffer. Angezeigt werden die besten; mit einem genaueren Begriff wird die Liste vollständig.',
|
|
views: 'Ansichten',
|
|
saveView: 'Ansicht speichern',
|
|
saveViewName: 'Name der Ansicht',
|
|
deleteView: 'Ansicht löschen',
|
|
builtin: {
|
|
mine: 'Meine Fälle',
|
|
dueThisWeek: 'Wiedervorlage diese Woche',
|
|
idle: 'Unbearbeitet seit 14 Tagen',
|
|
open: 'Offene Fälle',
|
|
sinceLastVisit: 'Seit deinem letzten Besuch geändert',
|
|
} as Record<string, string>,
|
|
} as const
|
|
|
|
export const paletteText = {
|
|
open: 'Springen',
|
|
placeholder: 'Aktenzeichen, Akte oder Seite',
|
|
hint: 'Strg+K',
|
|
empty: 'Nichts gefunden.',
|
|
close: 'Schliessen',
|
|
sections: {
|
|
cases: 'Fallakten',
|
|
personnel: 'Personalakten',
|
|
pages: 'Seiten',
|
|
} as Record<string, string>,
|
|
/**
|
|
* Die Sprungziele der Palette. Sie stehen hier und nicht in der Komponente,
|
|
* weil sie Oberflächentext sind — die Adressen daneben sind es nicht.
|
|
*/
|
|
pages: [
|
|
{ href: '/uebersicht', label: 'Übersicht' },
|
|
{ href: '/faelle', label: 'Fallakten' },
|
|
{ href: '/faelle/neu', label: 'Neue Fallakte' },
|
|
{ href: '/personalakten', label: 'Personalakten' },
|
|
{ href: '/kanaele', label: 'Kanäle' },
|
|
{ href: '/suche', label: 'Suche' },
|
|
{ href: '/einstellungen', label: 'Benachrichtigungen' },
|
|
],
|
|
} as const
|
|
|
|
export const themeText = {
|
|
label: 'Thema',
|
|
system: 'Wie das System',
|
|
dark: 'Dunkel',
|
|
light: 'Hell',
|
|
} as const
|