add features
This commit is contained in:
194
frontend/src/types/member.types.ts
Normal file
194
frontend/src/types/member.types.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
// ----------------------------------------------------------------
|
||||
// Frontend mirror of backend/src/models/member.model.ts
|
||||
// Keep in sync when the model changes.
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
export const DIENSTGRAD_VALUES = [
|
||||
'Feuerwehranwärter',
|
||||
'Feuerwehrmann',
|
||||
'Feuerwehrfrau',
|
||||
'Oberfeuerwehrmann',
|
||||
'Oberfeuerwehrfrau',
|
||||
'Hauptfeuerwehrmann',
|
||||
'Hauptfeuerwehrfrau',
|
||||
'Löschmeister',
|
||||
'Oberlöschmeister',
|
||||
'Hauptlöschmeister',
|
||||
'Brandmeister',
|
||||
'Oberbrandmeister',
|
||||
'Hauptbrandmeister',
|
||||
'Brandinspektor',
|
||||
'Oberbrandinspektor',
|
||||
'Brandoberinspektor',
|
||||
'Brandamtmann',
|
||||
] as const;
|
||||
|
||||
export const STATUS_VALUES = [
|
||||
'aktiv',
|
||||
'passiv',
|
||||
'ehrenmitglied',
|
||||
'jugendfeuerwehr',
|
||||
'anwärter',
|
||||
'ausgetreten',
|
||||
] as const;
|
||||
|
||||
export const FUNKTION_VALUES = [
|
||||
'Kommandant',
|
||||
'Stellv. Kommandant',
|
||||
'Gruppenführer',
|
||||
'Truppführer',
|
||||
'Gerätewart',
|
||||
'Kassier',
|
||||
'Schriftführer',
|
||||
'Atemschutzwart',
|
||||
'Ausbildungsbeauftragter',
|
||||
] as const;
|
||||
|
||||
export const TSHIRT_GROESSE_VALUES = [
|
||||
'XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL',
|
||||
] as const;
|
||||
|
||||
export type DienstgradEnum = typeof DIENSTGRAD_VALUES[number];
|
||||
export type StatusEnum = typeof STATUS_VALUES[number];
|
||||
export type FunktionEnum = typeof FUNKTION_VALUES[number];
|
||||
export type TshirtGroesseEnum = typeof TSHIRT_GROESSE_VALUES[number];
|
||||
|
||||
export interface MitgliederProfile {
|
||||
id: string;
|
||||
user_id: string;
|
||||
mitglieds_nr: string | null;
|
||||
dienstgrad: DienstgradEnum | null;
|
||||
dienstgrad_seit: string | null; // ISO date string from API
|
||||
funktion: FunktionEnum[];
|
||||
status: StatusEnum;
|
||||
eintrittsdatum: string | null;
|
||||
austrittsdatum: string | null;
|
||||
geburtsdatum: string | null; // null when redacted by server
|
||||
_age?: number; // synthesised when geburtsdatum is redacted
|
||||
telefon_mobil: string | null;
|
||||
telefon_privat: string | null;
|
||||
notfallkontakt_name: string | null;
|
||||
notfallkontakt_telefon: string | null;
|
||||
fuehrerscheinklassen: string[];
|
||||
tshirt_groesse: TshirtGroesseEnum | null;
|
||||
schuhgroesse: string | null;
|
||||
bemerkungen: string | null;
|
||||
bild_url: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface DienstgradVerlaufEntry {
|
||||
id: string;
|
||||
user_id: string;
|
||||
dienstgrad_neu: string;
|
||||
dienstgrad_alt: string | null;
|
||||
datum: string;
|
||||
durch_user_id: string | null;
|
||||
durch_user_name?: string | null;
|
||||
bemerkung: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface MemberWithProfile {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
given_name: string | null;
|
||||
family_name: string | null;
|
||||
preferred_username: string | null;
|
||||
profile_picture_url: string | null;
|
||||
is_active: boolean;
|
||||
last_login_at: string | null;
|
||||
created_at: string;
|
||||
profile: MitgliederProfile | null;
|
||||
dienstgrad_verlauf?: DienstgradVerlaufEntry[];
|
||||
}
|
||||
|
||||
export interface MemberListItem {
|
||||
id: string;
|
||||
name: string | null;
|
||||
given_name: string | null;
|
||||
family_name: string | null;
|
||||
email: string;
|
||||
profile_picture_url: string | null;
|
||||
is_active: boolean;
|
||||
profile_id: string | null;
|
||||
mitglieds_nr: string | null;
|
||||
dienstgrad: DienstgradEnum | null;
|
||||
funktion: FunktionEnum[];
|
||||
status: StatusEnum | null;
|
||||
eintrittsdatum: string | null;
|
||||
telefon_mobil: string | null;
|
||||
}
|
||||
|
||||
export interface MemberFilters {
|
||||
search?: string;
|
||||
status?: StatusEnum[];
|
||||
dienstgrad?: DienstgradEnum[];
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export type CreateMemberProfileData = Partial<Omit<MitgliederProfile, 'id' | 'user_id' | 'created_at' | 'updated_at'>>;
|
||||
export type UpdateMemberProfileData = CreateMemberProfileData;
|
||||
|
||||
export interface MemberStats {
|
||||
total: number;
|
||||
aktiv: number;
|
||||
passiv: number;
|
||||
ehrenmitglied: number;
|
||||
jugendfeuerwehr: number;
|
||||
'anwärter': number;
|
||||
ausgetreten: number;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Display helpers
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/** Returns the display name built from given/family name or email fallback */
|
||||
export function getMemberDisplayName(
|
||||
member: Pick<MemberWithProfile | MemberListItem, 'given_name' | 'family_name' | 'name' | 'email'>
|
||||
): string {
|
||||
if (member.given_name || member.family_name) {
|
||||
return [member.given_name, member.family_name].filter(Boolean).join(' ');
|
||||
}
|
||||
return member.name || member.email;
|
||||
}
|
||||
|
||||
/** Format a German phone number for display.
|
||||
* Stored raw; displayed with spaces for readability.
|
||||
* e.g. "+436641234567" → "+43 664 123 4567"
|
||||
* Falls back to raw value for unrecognised formats. */
|
||||
export function formatPhone(raw: string | null | undefined): string {
|
||||
if (!raw) return '—';
|
||||
const digits = raw.replace(/\s/g, '');
|
||||
// Austrian mobile: +43 6xx xxx xxxx
|
||||
const atMobile = digits.match(/^(\+43)(6\d{2})(\d{3,4})(\d{4})$/);
|
||||
if (atMobile) return `${atMobile[1]} ${atMobile[2]} ${atMobile[3]} ${atMobile[4]}`;
|
||||
// German mobile: +49 1xx xxx xxxxx
|
||||
const deMobile = digits.match(/^(\+49)(1\d{2})(\d{3,4})(\d{4,5})$/);
|
||||
if (deMobile) return `${deMobile[1]} ${deMobile[2]} ${deMobile[3]} ${deMobile[4]}`;
|
||||
return raw;
|
||||
}
|
||||
|
||||
/** Returns a human-readable status label */
|
||||
export const STATUS_LABELS: Record<StatusEnum, string> = {
|
||||
aktiv: 'Aktiv',
|
||||
passiv: 'Passiv',
|
||||
ehrenmitglied: 'Ehrenmitglied',
|
||||
jugendfeuerwehr: 'Jugendfeuerwehr',
|
||||
anwärter: 'Anwärter',
|
||||
ausgetreten: 'Ausgetreten',
|
||||
};
|
||||
|
||||
/** MUI Chip color for each status */
|
||||
export const STATUS_COLORS: Record<StatusEnum, 'success' | 'warning' | 'error' | 'info' | 'default'> = {
|
||||
aktiv: 'success',
|
||||
passiv: 'warning',
|
||||
ehrenmitglied: 'info',
|
||||
jugendfeuerwehr: 'info',
|
||||
anwärter: 'default',
|
||||
ausgetreten: 'error',
|
||||
};
|
||||
115
frontend/src/types/training.types.ts
Normal file
115
frontend/src/types/training.types.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frontend training types — mirrors backend/src/models/training.model.ts
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const UEBUNG_TYPEN = [
|
||||
'Übungsabend',
|
||||
'Lehrgang',
|
||||
'Sonderdienst',
|
||||
'Versammlung',
|
||||
'Gemeinschaftsübung',
|
||||
'Sonstiges',
|
||||
] as const;
|
||||
|
||||
export type UebungTyp = (typeof UEBUNG_TYPEN)[number];
|
||||
|
||||
export const TEILNAHME_STATUSES = [
|
||||
'zugesagt',
|
||||
'abgesagt',
|
||||
'erschienen',
|
||||
'entschuldigt',
|
||||
'unbekannt',
|
||||
] as const;
|
||||
|
||||
export type TeilnahmeStatus = (typeof TEILNAHME_STATUSES)[number];
|
||||
|
||||
export interface Uebung {
|
||||
id: string;
|
||||
titel: string;
|
||||
beschreibung?: string | null;
|
||||
typ: UebungTyp;
|
||||
datum_von: string; // ISO string from JSON
|
||||
datum_bis: string;
|
||||
ort?: string | null;
|
||||
treffpunkt?: string | null;
|
||||
pflichtveranstaltung: boolean;
|
||||
mindest_teilnehmer?: number | null;
|
||||
max_teilnehmer?: number | null;
|
||||
angelegt_von?: string | null;
|
||||
erstellt_am: string;
|
||||
aktualisiert_am: string;
|
||||
abgesagt: boolean;
|
||||
absage_grund?: string | null;
|
||||
}
|
||||
|
||||
export interface Teilnahme {
|
||||
uebung_id: string;
|
||||
user_id: string;
|
||||
status: TeilnahmeStatus;
|
||||
antwort_am?: string | null;
|
||||
erschienen_erfasst_am?: string | null;
|
||||
erschienen_erfasst_von?: string | null;
|
||||
bemerkung?: string | null;
|
||||
user_name?: string | null;
|
||||
user_email?: string | null;
|
||||
}
|
||||
|
||||
export interface AttendanceCounts {
|
||||
gesamt_eingeladen: number;
|
||||
anzahl_zugesagt: number;
|
||||
anzahl_abgesagt: number;
|
||||
anzahl_erschienen: number;
|
||||
anzahl_entschuldigt: number;
|
||||
anzahl_unbekannt: number;
|
||||
}
|
||||
|
||||
export interface UebungWithAttendance extends Uebung, AttendanceCounts {
|
||||
teilnahmen?: Teilnahme[];
|
||||
eigener_status?: TeilnahmeStatus;
|
||||
angelegt_von_name?: string | null;
|
||||
}
|
||||
|
||||
export interface UebungListItem {
|
||||
id: string;
|
||||
titel: string;
|
||||
typ: UebungTyp;
|
||||
datum_von: string;
|
||||
datum_bis: string;
|
||||
ort?: string | null;
|
||||
pflichtveranstaltung: boolean;
|
||||
abgesagt: boolean;
|
||||
anzahl_zugesagt: number;
|
||||
anzahl_erschienen: number;
|
||||
gesamt_eingeladen: number;
|
||||
eigener_status?: TeilnahmeStatus;
|
||||
}
|
||||
|
||||
export interface MemberParticipationStats {
|
||||
userId: string;
|
||||
name: string;
|
||||
totalUebungen: number;
|
||||
attended: number;
|
||||
attendancePercent: number;
|
||||
pflichtGesamt: number;
|
||||
pflichtErschienen: number;
|
||||
uebungsabendQuotePct: number;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Form data types (sent to API)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface CreateUebungData {
|
||||
titel: string;
|
||||
beschreibung?: string | null;
|
||||
typ: UebungTyp;
|
||||
datum_von: string; // ISO-8601 with offset
|
||||
datum_bis: string;
|
||||
ort?: string | null;
|
||||
treffpunkt?: string | null;
|
||||
pflichtveranstaltung: boolean;
|
||||
mindest_teilnehmer?: number | null;
|
||||
max_teilnehmer?: number | null;
|
||||
}
|
||||
|
||||
export type UpdateUebungData = Partial<CreateUebungData>;
|
||||
205
frontend/src/types/vehicle.types.ts
Normal file
205
frontend/src/types/vehicle.types.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
// =============================================================================
|
||||
// Vehicle Fleet Management — Frontend Type Definitions
|
||||
// Mirror of backend/src/models/vehicle.model.ts (transport layer shapes)
|
||||
// =============================================================================
|
||||
|
||||
export enum FahrzeugStatus {
|
||||
Einsatzbereit = 'einsatzbereit',
|
||||
AusserDienstWartung = 'ausser_dienst_wartung',
|
||||
AusserDienstSchaden = 'ausser_dienst_schaden',
|
||||
InLehrgang = 'in_lehrgang',
|
||||
}
|
||||
|
||||
export const FahrzeugStatusLabel: Record<FahrzeugStatus, string> = {
|
||||
[FahrzeugStatus.Einsatzbereit]: 'Einsatzbereit',
|
||||
[FahrzeugStatus.AusserDienstWartung]: 'Außer Dienst (Wartung)',
|
||||
[FahrzeugStatus.AusserDienstSchaden]: 'Außer Dienst (Schaden)',
|
||||
[FahrzeugStatus.InLehrgang]: 'In Lehrgang',
|
||||
};
|
||||
|
||||
export enum PruefungArt {
|
||||
HU = 'HU',
|
||||
AU = 'AU',
|
||||
UVV = 'UVV',
|
||||
Leiter = 'Leiter',
|
||||
Kran = 'Kran',
|
||||
Seilwinde = 'Seilwinde',
|
||||
Sonstiges = 'Sonstiges',
|
||||
}
|
||||
|
||||
export const PruefungArtLabel: Record<PruefungArt, string> = {
|
||||
[PruefungArt.HU]: 'Hauptuntersuchung (TÜV)',
|
||||
[PruefungArt.AU]: 'Abgasuntersuchung',
|
||||
[PruefungArt.UVV]: 'UVV-Prüfung (BGV D29)',
|
||||
[PruefungArt.Leiter]: 'Leiternprüfung (DLK)',
|
||||
[PruefungArt.Kran]: 'Kranprüfung',
|
||||
[PruefungArt.Seilwinde]: 'Seilwindenprüfung',
|
||||
[PruefungArt.Sonstiges]: 'Sonstige Prüfung',
|
||||
};
|
||||
|
||||
export type PruefungErgebnis =
|
||||
| 'bestanden'
|
||||
| 'bestanden_mit_maengeln'
|
||||
| 'nicht_bestanden'
|
||||
| 'ausstehend';
|
||||
|
||||
export type WartungslogArt =
|
||||
| 'Inspektion'
|
||||
| 'Reparatur'
|
||||
| 'Kraftstoff'
|
||||
| 'Reifenwechsel'
|
||||
| 'Hauptuntersuchung'
|
||||
| 'Reinigung'
|
||||
| 'Sonstiges';
|
||||
|
||||
// ── API Response Shapes ───────────────────────────────────────────────────────
|
||||
|
||||
export interface FahrzeugListItem {
|
||||
id: string;
|
||||
bezeichnung: string;
|
||||
kurzname: string | null;
|
||||
amtliches_kennzeichen: string | null;
|
||||
baujahr: number | null;
|
||||
hersteller: string | null;
|
||||
besatzung_soll: string | null;
|
||||
status: FahrzeugStatus;
|
||||
status_bemerkung: string | null;
|
||||
bild_url: string | null;
|
||||
hu_faellig_am: string | null; // ISO date string from API
|
||||
hu_tage_bis_faelligkeit: number | null;
|
||||
au_faellig_am: string | null;
|
||||
au_tage_bis_faelligkeit: number | null;
|
||||
uvv_faellig_am: string | null;
|
||||
uvv_tage_bis_faelligkeit: number | null;
|
||||
leiter_faellig_am: string | null;
|
||||
leiter_tage_bis_faelligkeit: number | null;
|
||||
naechste_pruefung_tage: number | null;
|
||||
}
|
||||
|
||||
export interface PruefungStatus {
|
||||
pruefung_id: string | null;
|
||||
faellig_am: string | null;
|
||||
tage_bis_faelligkeit: number | null;
|
||||
ergebnis: PruefungErgebnis | null;
|
||||
}
|
||||
|
||||
export interface FahrzeugPruefung {
|
||||
id: string;
|
||||
fahrzeug_id: string;
|
||||
pruefung_art: PruefungArt;
|
||||
faellig_am: string;
|
||||
durchgefuehrt_am: string | null;
|
||||
ergebnis: PruefungErgebnis | null;
|
||||
naechste_faelligkeit: string | null;
|
||||
pruefende_stelle: string | null;
|
||||
kosten: number | null;
|
||||
dokument_url: string | null;
|
||||
bemerkung: string | null;
|
||||
erfasst_von: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface FahrzeugWartungslog {
|
||||
id: string;
|
||||
fahrzeug_id: string;
|
||||
datum: string;
|
||||
art: WartungslogArt | null;
|
||||
beschreibung: string;
|
||||
km_stand: number | null;
|
||||
kraftstoff_liter: number | null;
|
||||
kosten: number | null;
|
||||
externe_werkstatt: string | null;
|
||||
erfasst_von: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface FahrzeugDetail {
|
||||
id: string;
|
||||
bezeichnung: string;
|
||||
kurzname: string | null;
|
||||
amtliches_kennzeichen: string | null;
|
||||
fahrgestellnummer: string | null;
|
||||
baujahr: number | null;
|
||||
hersteller: string | null;
|
||||
typ_schluessel: string | null;
|
||||
besatzung_soll: string | null;
|
||||
status: FahrzeugStatus;
|
||||
status_bemerkung: string | null;
|
||||
standort: string;
|
||||
bild_url: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
pruefstatus: {
|
||||
hu: PruefungStatus;
|
||||
au: PruefungStatus;
|
||||
uvv: PruefungStatus;
|
||||
leiter: PruefungStatus;
|
||||
};
|
||||
naechste_pruefung_tage: number | null;
|
||||
pruefungen: FahrzeugPruefung[];
|
||||
wartungslog: FahrzeugWartungslog[];
|
||||
}
|
||||
|
||||
export interface VehicleStats {
|
||||
total: number;
|
||||
einsatzbereit: number;
|
||||
ausserDienst: number;
|
||||
inLehrgang: number;
|
||||
inspectionsDue: number;
|
||||
inspectionsOverdue: number;
|
||||
}
|
||||
|
||||
export interface InspectionAlert {
|
||||
fahrzeugId: string;
|
||||
bezeichnung: string;
|
||||
kurzname: string | null;
|
||||
pruefungId: string;
|
||||
pruefungArt: PruefungArt;
|
||||
faelligAm: string;
|
||||
tage: number;
|
||||
}
|
||||
|
||||
// ── Request Payload Types ─────────────────────────────────────────────────────
|
||||
|
||||
export interface CreateFahrzeugPayload {
|
||||
bezeichnung: string;
|
||||
kurzname?: string;
|
||||
amtliches_kennzeichen?: string;
|
||||
fahrgestellnummer?: string;
|
||||
baujahr?: number;
|
||||
hersteller?: string;
|
||||
typ_schluessel?: string;
|
||||
besatzung_soll?: string;
|
||||
status?: FahrzeugStatus;
|
||||
status_bemerkung?: string;
|
||||
standort?: string;
|
||||
bild_url?: string;
|
||||
}
|
||||
|
||||
export type UpdateFahrzeugPayload = Partial<CreateFahrzeugPayload>;
|
||||
|
||||
export interface UpdateStatusPayload {
|
||||
status: FahrzeugStatus;
|
||||
bemerkung?: string;
|
||||
}
|
||||
|
||||
export interface CreatePruefungPayload {
|
||||
pruefung_art: PruefungArt;
|
||||
faellig_am: string;
|
||||
durchgefuehrt_am?: string;
|
||||
ergebnis?: PruefungErgebnis;
|
||||
pruefende_stelle?: string;
|
||||
kosten?: number;
|
||||
dokument_url?: string;
|
||||
bemerkung?: string;
|
||||
}
|
||||
|
||||
export interface CreateWartungslogPayload {
|
||||
datum: string;
|
||||
art?: WartungslogArt;
|
||||
beschreibung: string;
|
||||
km_stand?: number;
|
||||
kraftstoff_liter?: number;
|
||||
kosten?: number;
|
||||
externe_werkstatt?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user