add features
This commit is contained in:
299
frontend/src/services/incidents.ts
Normal file
299
frontend/src/services/incidents.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
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()}`
|
||||
);
|
||||
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}`
|
||||
);
|
||||
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}`
|
||||
);
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
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}`);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user