262 lines
7.9 KiB
TypeScript
262 lines
7.9 KiB
TypeScript
import { z } from 'zod';
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// ENUMS (as const arrays for runtime use + TypeScript types)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const EINSATZ_ARTEN = [
|
||
'Brand',
|
||
'THL',
|
||
'ABC',
|
||
'BMA',
|
||
'Hilfeleistung',
|
||
'Fehlalarm',
|
||
'Brandsicherheitswache',
|
||
] as const;
|
||
|
||
export type EinsatzArt = (typeof EINSATZ_ARTEN)[number];
|
||
|
||
export const EINSATZ_ART_LABELS: Record<EinsatzArt, string> = {
|
||
Brand: 'Brand',
|
||
THL: 'Technische Hilfeleistung',
|
||
ABC: 'ABC-Einsatz / Gefahrgut',
|
||
BMA: 'Brandmeldeanlage',
|
||
Hilfeleistung: 'Hilfeleistung',
|
||
Fehlalarm: 'Fehlalarm',
|
||
Brandsicherheitswache: 'Brandsicherheitswache',
|
||
};
|
||
|
||
export const EINSATZ_STATUS = ['aktiv', 'abgeschlossen', 'archiviert'] as const;
|
||
|
||
export type EinsatzStatus = (typeof EINSATZ_STATUS)[number];
|
||
|
||
export const EINSATZ_STATUS_LABELS: Record<EinsatzStatus, string> = {
|
||
aktiv: 'Aktiv',
|
||
abgeschlossen: 'Abgeschlossen',
|
||
archiviert: 'Archiviert',
|
||
};
|
||
|
||
export const EINSATZ_FUNKTIONEN = [
|
||
'Einsatzleiter',
|
||
'Gruppenführer',
|
||
'Maschinist',
|
||
'Atemschutz',
|
||
'Sicherheitstrupp',
|
||
'Melder',
|
||
'Wassertrupp',
|
||
'Angriffstrupp',
|
||
'Mannschaft',
|
||
'Sonstiges',
|
||
] as const;
|
||
|
||
export type EinsatzFunktion = (typeof EINSATZ_FUNKTIONEN)[number];
|
||
|
||
export const ALARMIERUNG_ARTEN = [
|
||
'ILS',
|
||
'DME',
|
||
'Telefon',
|
||
'Vor_Ort',
|
||
'Sonstiges',
|
||
] as const;
|
||
|
||
export type AlarmierungArt = (typeof ALARMIERUNG_ARTEN)[number];
|
||
|
||
export const ALARMIERUNG_ART_LABELS: Record<AlarmierungArt, string> = {
|
||
ILS: 'ILS (Integrierte Leitstelle)',
|
||
DME: 'Digitaler Meldeempfänger',
|
||
Telefon: 'Telefon',
|
||
Vor_Ort: 'Vor Ort',
|
||
Sonstiges: 'Sonstiges',
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// DATABASE ROW INTERFACES
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** Raw database row for einsaetze table */
|
||
export interface Einsatz {
|
||
id: string;
|
||
einsatz_nr: string;
|
||
|
||
alarm_time: Date;
|
||
ausrueck_time: Date | null;
|
||
ankunft_time: Date | null;
|
||
einrueck_time: Date | null;
|
||
|
||
einsatz_art: EinsatzArt;
|
||
einsatz_stichwort: string | null;
|
||
|
||
strasse: string | null;
|
||
hausnummer: string | null;
|
||
ort: string | null;
|
||
koordinaten: { x: number; y: number } | null; // pg returns POINT as {x,y}
|
||
|
||
bericht_kurz: string | null;
|
||
bericht_text: string | null;
|
||
|
||
einsatzleiter_id: string | null;
|
||
alarmierung_art: AlarmierungArt;
|
||
status: EinsatzStatus;
|
||
|
||
created_by: string | null;
|
||
created_at: Date;
|
||
updated_at: Date;
|
||
}
|
||
|
||
/** Assigned vehicle row */
|
||
export interface EinsatzFahrzeug {
|
||
einsatz_id: string;
|
||
fahrzeug_id: string;
|
||
ausrueck_time: Date | null;
|
||
einrueck_time: Date | null;
|
||
assigned_at: Date;
|
||
// Joined fields — aliased to 'kennzeichen' and 'fahrzeug_typ' in SQL query
|
||
// to match the aliased SELECT in incident.service.ts
|
||
kennzeichen?: string | null; // aliased from amtliches_kennzeichen
|
||
bezeichnung?: string;
|
||
fahrzeug_typ?: string | null; // aliased from typ_schluessel
|
||
}
|
||
|
||
/** Assigned personnel row */
|
||
export interface EinsatzPersonal {
|
||
einsatz_id: string;
|
||
user_id: string;
|
||
funktion: EinsatzFunktion;
|
||
alarm_time: Date | null;
|
||
ankunft_time: Date | null;
|
||
assigned_at: Date;
|
||
// Joined fields
|
||
name?: string | null;
|
||
email?: string;
|
||
given_name?: string | null;
|
||
family_name?: string | null;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// EXTENDED TYPES (joins / computed)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** Full incident with all related data — used by detail endpoint */
|
||
export interface EinsatzWithDetails extends Einsatz {
|
||
einsatzleiter_name: string | null;
|
||
fahrzeuge: EinsatzFahrzeug[];
|
||
personal: EinsatzPersonal[];
|
||
/** Hilfsfrist in minutes (alarm → arrival), null if ankunft_time not set */
|
||
hilfsfrist_min: number | null;
|
||
/** Total duration in minutes (alarm → return), null if einrueck_time not set */
|
||
dauer_min: number | null;
|
||
}
|
||
|
||
/** Lightweight row for the list view DataGrid */
|
||
export interface EinsatzListItem {
|
||
id: string;
|
||
einsatz_nr: string;
|
||
alarm_time: Date;
|
||
einsatz_art: EinsatzArt;
|
||
einsatz_stichwort: string | null;
|
||
ort: string | null;
|
||
strasse: string | null;
|
||
status: EinsatzStatus;
|
||
einsatzleiter_name: string | null;
|
||
/** Hilfsfrist in minutes, null if ankunft_time not set */
|
||
hilfsfrist_min: number | null;
|
||
/** Total duration in minutes, null if einrueck_time not set */
|
||
dauer_min: number | null;
|
||
personal_count: number;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// STATISTICS TYPES
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export interface MonthlyStatRow {
|
||
monat: number; // 1–12
|
||
anzahl: number;
|
||
avg_hilfsfrist_min: number | null;
|
||
avg_dauer_min: number | null;
|
||
}
|
||
|
||
export interface EinsatzArtStatRow {
|
||
einsatz_art: EinsatzArt;
|
||
anzahl: number;
|
||
avg_hilfsfrist_min: number | null;
|
||
}
|
||
|
||
export interface EinsatzStats {
|
||
jahr: number;
|
||
gesamt: number;
|
||
abgeschlossen: number;
|
||
aktiv: number;
|
||
avg_hilfsfrist_min: number | null;
|
||
/** Einsatzart with the highest count */
|
||
haeufigste_art: EinsatzArt | null;
|
||
monthly: MonthlyStatRow[];
|
||
by_art: EinsatzArtStatRow[];
|
||
/** Previous year monthly for chart overlay */
|
||
prev_year_monthly: MonthlyStatRow[];
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// ZOD VALIDATION SCHEMAS (Zod v4)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const CreateEinsatzSchema = z.object({
|
||
alarm_time: z.string().datetime({ offset: true }),
|
||
ausrueck_time: z.string().datetime({ offset: true }).optional().nullable(),
|
||
ankunft_time: z.string().datetime({ offset: true }).optional().nullable(),
|
||
einrueck_time: z.string().datetime({ offset: true }).optional().nullable(),
|
||
|
||
einsatz_art: z.enum(EINSATZ_ARTEN),
|
||
einsatz_stichwort: z.string().max(30).optional().nullable(),
|
||
|
||
strasse: z.string().max(150).optional().nullable(),
|
||
hausnummer: z.string().max(20).optional().nullable(),
|
||
ort: z.string().max(100).optional().nullable(),
|
||
|
||
bericht_kurz: z.string().max(255).optional().nullable(),
|
||
bericht_text: z.string().optional().nullable(),
|
||
|
||
einsatzleiter_id: z.string().uuid().optional().nullable(),
|
||
alarmierung_art: z.enum(ALARMIERUNG_ARTEN).optional().default('ILS'),
|
||
status: z.enum(EINSATZ_STATUS).optional().default('aktiv'),
|
||
});
|
||
|
||
export type CreateEinsatzData = z.infer<typeof CreateEinsatzSchema>;
|
||
|
||
export const UpdateEinsatzSchema = CreateEinsatzSchema.partial().omit({
|
||
alarm_time: true, // alarm_time can be updated but is handled explicitly
|
||
}).extend({
|
||
// alarm_time is allowed to be updated but must remain valid if provided
|
||
alarm_time: z.string().datetime({ offset: true }).optional(),
|
||
});
|
||
|
||
export type UpdateEinsatzData = z.infer<typeof UpdateEinsatzSchema>;
|
||
|
||
export const AssignPersonnelSchema = z.object({
|
||
user_id: z.string().uuid(),
|
||
funktion: z.enum(EINSATZ_FUNKTIONEN).optional().default('Mannschaft'),
|
||
alarm_time: z.string().datetime({ offset: true }).optional().nullable(),
|
||
ankunft_time: z.string().datetime({ offset: true }).optional().nullable(),
|
||
});
|
||
|
||
export type AssignPersonnelData = z.infer<typeof AssignPersonnelSchema>;
|
||
|
||
export const AssignVehicleSchema = z.object({
|
||
fahrzeug_id: z.string().uuid(),
|
||
ausrueck_time: z.string().datetime({ offset: true }).optional().nullable(),
|
||
einrueck_time: z.string().datetime({ offset: true }).optional().nullable(),
|
||
});
|
||
|
||
export type AssignVehicleData = z.infer<typeof AssignVehicleSchema>;
|
||
|
||
export const IncidentFiltersSchema = z.object({
|
||
dateFrom: z.string().datetime({ offset: true }).optional(),
|
||
dateTo: z.string().datetime({ offset: true }).optional(),
|
||
einsatzArt: z.enum(EINSATZ_ARTEN).optional(),
|
||
status: z.enum(EINSATZ_STATUS).optional(),
|
||
limit: z.coerce.number().int().min(1).max(200).optional().default(50),
|
||
offset: z.coerce.number().int().min(0).optional().default(0),
|
||
});
|
||
|
||
export type IncidentFilters = z.infer<typeof IncidentFiltersSchema>;
|