315 lines
8.7 KiB
TypeScript
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}`);
|
|
},
|
|
};
|