Files
dashboard/frontend/src/services/incidents.ts
Matthias Hochmeister b7b883649c rework vehicle handling
2026-02-28 14:13:56 +01:00

315 lines
8.7 KiB
TypeScript

import { api } from './api';
// ---------------------------------------------------------------------------
// SHARED TYPES (mirrors backend models, kept lean for frontend consumption)
// ---------------------------------------------------------------------------
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 / 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];
// ---------------------------------------------------------------------------
// API RESPONSE SHAPES
// ---------------------------------------------------------------------------
export interface EinsatzListItem {
id: string;
einsatz_nr: string;
alarm_time: string; // ISO 8601 string from JSON
einsatz_art: EinsatzArt;
einsatz_stichwort: string | null;
ort: string | null;
strasse: string | null;
status: EinsatzStatus;
einsatzleiter_name: string | null;
hilfsfrist_min: number | null;
dauer_min: number | null;
personal_count: number;
}
export interface EinsatzPersonal {
einsatz_id: string;
user_id: string;
funktion: EinsatzFunktion;
alarm_time: string | null;
ankunft_time: string | null;
assigned_at: string;
name: string | null;
email: string;
given_name: string | null;
family_name: string | null;
}
export interface EinsatzFahrzeug {
einsatz_id: string;
fahrzeug_id: string;
ausrueck_time: string | null;
einrueck_time: string | null;
assigned_at: string;
kennzeichen: string;
bezeichnung: string;
fahrzeug_typ: string | null;
}
export interface EinsatzDetail {
id: string;
einsatz_nr: string;
alarm_time: string;
ausrueck_time: string | null;
ankunft_time: string | null;
einrueck_time: string | null;
einsatz_art: EinsatzArt;
einsatz_stichwort: string | null;
strasse: string | null;
hausnummer: string | null;
ort: string | null;
bericht_kurz: string | null;
bericht_text: string | null; // undefined/null for roles below Kommandant
einsatzleiter_id: string | null;
einsatzleiter_name: string | null;
alarmierung_art: string;
status: EinsatzStatus;
created_by: string | null;
created_at: string;
updated_at: string;
hilfsfrist_min: number | null;
dauer_min: number | null;
fahrzeuge: EinsatzFahrzeug[];
personal: EinsatzPersonal[];
}
export interface MonthlyStatRow {
monat: number;
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;
haeufigste_art: EinsatzArt | null;
monthly: MonthlyStatRow[];
by_art: EinsatzArtStatRow[];
prev_year_monthly: MonthlyStatRow[];
}
export interface IncidentListResponse {
items: EinsatzListItem[];
total: number;
limit: number;
offset: number;
}
// ---------------------------------------------------------------------------
// REQUEST SHAPES
// ---------------------------------------------------------------------------
export interface IncidentFilters {
dateFrom?: string;
dateTo?: string;
einsatzArt?: EinsatzArt;
status?: EinsatzStatus;
limit?: number;
offset?: number;
}
export interface CreateEinsatzPayload {
alarm_time: string;
ausrueck_time?: string | null;
ankunft_time?: string | null;
einrueck_time?: string | null;
einsatz_art: EinsatzArt;
einsatz_stichwort?: string | null;
strasse?: string | null;
hausnummer?: string | null;
ort?: string | null;
bericht_kurz?: string | null;
bericht_text?: string | null;
einsatzleiter_id?: string | null;
alarmierung_art?: string;
status?: EinsatzStatus;
}
export type UpdateEinsatzPayload = Partial<CreateEinsatzPayload>;
export interface AssignPersonnelPayload {
user_id: string;
funktion?: EinsatzFunktion;
alarm_time?: string | null;
ankunft_time?: string | null;
}
export interface AssignVehiclePayload {
fahrzeug_id: string;
ausrueck_time?: string | null;
einrueck_time?: string | null;
}
// ---------------------------------------------------------------------------
// API CALLS
// ---------------------------------------------------------------------------
export const incidentsApi = {
/**
* Fetch paginated incident list with optional filters.
*/
async getAll(filters: IncidentFilters = {}): Promise<IncidentListResponse> {
const params = new URLSearchParams();
if (filters.dateFrom) params.set('dateFrom', filters.dateFrom);
if (filters.dateTo) params.set('dateTo', filters.dateTo);
if (filters.einsatzArt) params.set('einsatzArt', filters.einsatzArt);
if (filters.status) params.set('status', filters.status);
if (filters.limit !== undefined) params.set('limit', String(filters.limit));
if (filters.offset !== undefined) params.set('offset', String(filters.offset));
const response = await api.get<{ success: boolean; data: IncidentListResponse }>(
`/api/incidents?${params.toString()}`
);
if (response.data?.data === undefined || response.data?.data === null) {
throw new Error('Invalid API response');
}
return response.data.data;
},
/**
* Fetch aggregated statistics for a given year.
*/
async getStats(year?: number): Promise<EinsatzStats> {
const params = year ? `?year=${year}` : '';
const response = await api.get<{ success: boolean; data: EinsatzStats }>(
`/api/incidents/stats${params}`
);
if (response.data?.data === undefined || response.data?.data === null) {
throw new Error('Invalid API response');
}
return response.data.data;
},
/**
* Fetch a single incident with full detail.
*/
async getById(id: string): Promise<EinsatzDetail> {
const response = await api.get<{ success: boolean; data: EinsatzDetail }>(
`/api/incidents/${id}`
);
if (response.data?.data === undefined || response.data?.data === null) {
throw new Error('Invalid API response');
}
return response.data.data;
},
/**
* Create a new incident. Returns the created Einsatz row.
*/
async create(payload: CreateEinsatzPayload): Promise<EinsatzDetail> {
const response = await api.post<{ success: boolean; data: EinsatzDetail }>(
'/api/incidents',
payload
);
if (response.data?.data === undefined || response.data?.data === null) {
throw new Error('Invalid API response');
}
return response.data.data;
},
/**
* Partially update an incident.
*/
async update(id: string, payload: UpdateEinsatzPayload): Promise<EinsatzDetail> {
const response = await api.patch<{ success: boolean; data: EinsatzDetail }>(
`/api/incidents/${id}`,
payload
);
if (response.data?.data === undefined || response.data?.data === null) {
throw new Error('Invalid API response');
}
return response.data.data;
},
/**
* Soft-delete (archive) an incident.
*/
async delete(id: string): Promise<void> {
await api.delete(`/api/incidents/${id}`);
},
/**
* Assign a member to an incident.
*/
async assignPersonnel(einsatzId: string, payload: AssignPersonnelPayload): Promise<void> {
await api.post(`/api/incidents/${einsatzId}/personnel`, payload);
},
/**
* Remove a member from an incident.
*/
async removePersonnel(einsatzId: string, userId: string): Promise<void> {
await api.delete(`/api/incidents/${einsatzId}/personnel/${userId}`);
},
/**
* Assign a vehicle to an incident.
*/
async assignVehicle(einsatzId: string, payload: AssignVehiclePayload): Promise<void> {
await api.post(`/api/incidents/${einsatzId}/vehicles`, payload);
},
/**
* Remove a vehicle from an incident.
*/
async removeVehicle(einsatzId: string, fahrzeugId: string): Promise<void> {
await api.delete(`/api/incidents/${einsatzId}/vehicles/${fahrzeugId}`);
},
};