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}`);
|
||||
},
|
||||
};
|
||||
113
frontend/src/services/members.ts
Normal file
113
frontend/src/services/members.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { api } from './api';
|
||||
import {
|
||||
MemberListItem,
|
||||
MemberWithProfile,
|
||||
MemberFilters,
|
||||
MemberStats,
|
||||
CreateMemberProfileData,
|
||||
UpdateMemberProfileData,
|
||||
} from '../types/member.types';
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Response envelope shapes
|
||||
// ----------------------------------------------------------------
|
||||
interface ApiListResponse<T> {
|
||||
success: boolean;
|
||||
data: T[];
|
||||
meta: { total: number; page: number };
|
||||
}
|
||||
|
||||
interface ApiItemResponse<T> {
|
||||
success: boolean;
|
||||
data: T;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Service
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Builds a URLSearchParams object from the filter object so query
|
||||
* strings like ?status[]=aktiv&status[]=passiv are sent correctly.
|
||||
*/
|
||||
function buildParams(filters?: MemberFilters): URLSearchParams {
|
||||
const params = new URLSearchParams();
|
||||
if (!filters) return params;
|
||||
|
||||
if (filters.search) params.append('search', filters.search);
|
||||
if (filters.page) params.append('page', String(filters.page));
|
||||
if (filters.pageSize) params.append('pageSize', String(filters.pageSize));
|
||||
|
||||
filters.status?.forEach((s) => params.append('status[]', s));
|
||||
filters.dienstgrad?.forEach((d) => params.append('dienstgrad[]', d));
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export const membersService = {
|
||||
/**
|
||||
* Fetches a paginated, optionally filtered list of members.
|
||||
*/
|
||||
async getMembers(
|
||||
filters?: MemberFilters
|
||||
): Promise<{ items: MemberListItem[]; total: number; page: number }> {
|
||||
const params = buildParams(filters);
|
||||
const response = await api.get<ApiListResponse<MemberListItem>>(
|
||||
`/api/members?${params.toString()}`
|
||||
);
|
||||
return {
|
||||
items: response.data.data,
|
||||
total: response.data.meta.total,
|
||||
page: response.data.meta.page,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches a single member with their full profile and rank history.
|
||||
*/
|
||||
async getMember(userId: string): Promise<MemberWithProfile> {
|
||||
const response = await api.get<ApiItemResponse<MemberWithProfile>>(
|
||||
`/api/members/${userId}`
|
||||
);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new member profile for an existing auth user.
|
||||
* Restricted to Kommandant/Admin (enforced server-side).
|
||||
*/
|
||||
async createMemberProfile(
|
||||
userId: string,
|
||||
data: CreateMemberProfileData
|
||||
): Promise<MemberWithProfile> {
|
||||
const response = await api.post<ApiItemResponse<MemberWithProfile>>(
|
||||
`/api/members/${userId}/profile`,
|
||||
data
|
||||
);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Partially updates a member profile.
|
||||
* Kommandant/Admin: full update.
|
||||
* Own profile: limited fields only (enforced server-side).
|
||||
*/
|
||||
async updateMember(
|
||||
userId: string,
|
||||
data: UpdateMemberProfileData
|
||||
): Promise<MemberWithProfile> {
|
||||
const response = await api.patch<ApiItemResponse<MemberWithProfile>>(
|
||||
`/api/members/${userId}`,
|
||||
data
|
||||
);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches aggregate counts for the dashboard KPI widget.
|
||||
*/
|
||||
async getMemberStats(): Promise<MemberStats> {
|
||||
const response = await api.get<ApiItemResponse<MemberStats>>('/api/members/stats');
|
||||
return response.data.data;
|
||||
},
|
||||
};
|
||||
131
frontend/src/services/training.ts
Normal file
131
frontend/src/services/training.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { api } from './api';
|
||||
import { API_URL } from '../utils/config';
|
||||
import type {
|
||||
Uebung,
|
||||
UebungWithAttendance,
|
||||
UebungListItem,
|
||||
MemberParticipationStats,
|
||||
CreateUebungData,
|
||||
UpdateUebungData,
|
||||
} from '../types/training.types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Response shapes from the backend
|
||||
// ---------------------------------------------------------------------------
|
||||
interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data: T;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: iCal subscribe URL
|
||||
// ---------------------------------------------------------------------------
|
||||
export function buildIcalUrl(token: string): string {
|
||||
const base = API_URL.replace(/\/$/, '');
|
||||
return `${base}/api/training/calendar.ics?token=${token}`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Training API service
|
||||
// ---------------------------------------------------------------------------
|
||||
export const trainingApi = {
|
||||
// -------------------------------------------------------------------------
|
||||
// Event listing
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** Upcoming events (dashboard widget, list view) */
|
||||
getUpcoming(limit = 10): Promise<UebungListItem[]> {
|
||||
return api
|
||||
.get<ApiResponse<UebungListItem[]>>('/api/training', { params: { limit } })
|
||||
.then((r) => r.data.data);
|
||||
},
|
||||
|
||||
/** Events in a date range for the month calendar view */
|
||||
getCalendarRange(from: Date, to: Date): Promise<UebungListItem[]> {
|
||||
return api
|
||||
.get<ApiResponse<UebungListItem[]>>('/api/training/calendar', {
|
||||
params: {
|
||||
from: from.toISOString(),
|
||||
to: to.toISOString(),
|
||||
},
|
||||
})
|
||||
.then((r) => r.data.data);
|
||||
},
|
||||
|
||||
/** Full event detail with attendance data */
|
||||
getById(id: string): Promise<UebungWithAttendance> {
|
||||
return api
|
||||
.get<ApiResponse<UebungWithAttendance>>(`/api/training/${id}`)
|
||||
.then((r) => r.data.data);
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// CRUD
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
createEvent(data: CreateUebungData): Promise<Uebung> {
|
||||
return api
|
||||
.post<ApiResponse<Uebung>>('/api/training', data)
|
||||
.then((r) => r.data.data);
|
||||
},
|
||||
|
||||
updateEvent(id: string, data: Partial<UpdateUebungData>): Promise<Uebung> {
|
||||
return api
|
||||
.patch<ApiResponse<Uebung>>(`/api/training/${id}`, data)
|
||||
.then((r) => r.data.data);
|
||||
},
|
||||
|
||||
cancelEvent(id: string, absage_grund: string): Promise<void> {
|
||||
return api
|
||||
.delete(`/api/training/${id}`, { data: { absage_grund } })
|
||||
.then(() => undefined);
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Attendance / RSVP
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** Member updates own RSVP */
|
||||
updateRsvp(
|
||||
uebungId: string,
|
||||
status: 'zugesagt' | 'abgesagt',
|
||||
bemerkung?: string
|
||||
): Promise<void> {
|
||||
return api
|
||||
.patch(`/api/training/${uebungId}/attendance`, { status, bemerkung })
|
||||
.then(() => undefined);
|
||||
},
|
||||
|
||||
/** Gruppenführer bulk-marks attendance */
|
||||
markAttendance(uebungId: string, userIds: string[]): Promise<void> {
|
||||
return api
|
||||
.post(`/api/training/${uebungId}/attendance/mark`, { userIds })
|
||||
.then(() => undefined);
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Stats
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
getMemberStats(year?: number): Promise<MemberParticipationStats[]> {
|
||||
return api
|
||||
.get<ApiResponse<MemberParticipationStats[]>>('/api/training/stats', {
|
||||
params: { year: year ?? new Date().getFullYear() },
|
||||
})
|
||||
.then((r) => r.data.data);
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// iCal
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** Get the user's personal calendar subscribe URL */
|
||||
getCalendarToken(): Promise<{ token: string; subscribeUrl: string; instructions: string }> {
|
||||
return api
|
||||
.get<ApiResponse<{ token: string; subscribeUrl: string; instructions: string }>>(
|
||||
'/api/training/calendar-token'
|
||||
)
|
||||
.then((r) => r.data.data);
|
||||
},
|
||||
};
|
||||
115
frontend/src/services/vehicles.ts
Normal file
115
frontend/src/services/vehicles.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { api } from './api';
|
||||
import type {
|
||||
FahrzeugListItem,
|
||||
FahrzeugDetail,
|
||||
FahrzeugPruefung,
|
||||
FahrzeugWartungslog,
|
||||
VehicleStats,
|
||||
InspectionAlert,
|
||||
CreateFahrzeugPayload,
|
||||
UpdateFahrzeugPayload,
|
||||
UpdateStatusPayload,
|
||||
CreatePruefungPayload,
|
||||
CreateWartungslogPayload,
|
||||
} from '../types/vehicle.types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal: unwrap the standard { success, data } envelope
|
||||
// ---------------------------------------------------------------------------
|
||||
async function unwrap<T>(promise: ReturnType<typeof api.get<{ success: boolean; data: T }>>): Promise<T> {
|
||||
const response = await promise;
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vehicle API Service
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const vehiclesApi = {
|
||||
|
||||
// ── Fleet overview ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Fetch all vehicles with their next inspection badge data */
|
||||
async getAll(): Promise<FahrzeugListItem[]> {
|
||||
return unwrap(api.get<{ success: boolean; data: FahrzeugListItem[] }>('/api/vehicles'));
|
||||
},
|
||||
|
||||
/** Dashboard KPI stats */
|
||||
async getStats(): Promise<VehicleStats> {
|
||||
return unwrap(api.get<{ success: boolean; data: VehicleStats }>('/api/vehicles/stats'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Upcoming and overdue inspection alerts.
|
||||
* @param daysAhead How many days to look ahead (default 30, max 365).
|
||||
*/
|
||||
async getAlerts(daysAhead = 30): Promise<InspectionAlert[]> {
|
||||
return unwrap(
|
||||
api.get<{ success: boolean; data: InspectionAlert[] }>(
|
||||
`/api/vehicles/alerts?daysAhead=${daysAhead}`
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
// ── Vehicle detail ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Full vehicle detail including inspection history and maintenance log */
|
||||
async getById(id: string): Promise<FahrzeugDetail> {
|
||||
return unwrap(api.get<{ success: boolean; data: FahrzeugDetail }>(`/api/vehicles/${id}`));
|
||||
},
|
||||
|
||||
// ── CRUD ────────────────────────────────────────────────────────────────────
|
||||
|
||||
async create(payload: CreateFahrzeugPayload): Promise<FahrzeugDetail> {
|
||||
const response = await api.post<{ success: boolean; data: FahrzeugDetail }>(
|
||||
'/api/vehicles',
|
||||
payload
|
||||
);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async update(id: string, payload: UpdateFahrzeugPayload): Promise<FahrzeugDetail> {
|
||||
const response = await api.patch<{ success: boolean; data: FahrzeugDetail }>(
|
||||
`/api/vehicles/${id}`,
|
||||
payload
|
||||
);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
/** Live status change — Socket.IO event is emitted server-side in Tier 3 */
|
||||
async updateStatus(id: string, payload: UpdateStatusPayload): Promise<void> {
|
||||
await api.patch(`/api/vehicles/${id}/status`, payload);
|
||||
},
|
||||
|
||||
// ── Inspections ─────────────────────────────────────────────────────────────
|
||||
|
||||
async getPruefungen(id: string): Promise<FahrzeugPruefung[]> {
|
||||
return unwrap(
|
||||
api.get<{ success: boolean; data: FahrzeugPruefung[] }>(`/api/vehicles/${id}/pruefungen`)
|
||||
);
|
||||
},
|
||||
|
||||
async addPruefung(id: string, payload: CreatePruefungPayload): Promise<FahrzeugPruefung> {
|
||||
const response = await api.post<{ success: boolean; data: FahrzeugPruefung }>(
|
||||
`/api/vehicles/${id}/pruefungen`,
|
||||
payload
|
||||
);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
// ── Maintenance log ─────────────────────────────────────────────────────────
|
||||
|
||||
async getWartungslog(id: string): Promise<FahrzeugWartungslog[]> {
|
||||
return unwrap(
|
||||
api.get<{ success: boolean; data: FahrzeugWartungslog[] }>(`/api/vehicles/${id}/wartung`)
|
||||
);
|
||||
},
|
||||
|
||||
async addWartungslog(id: string, payload: CreateWartungslogPayload): Promise<FahrzeugWartungslog> {
|
||||
const response = await api.post<{ success: boolean; data: FahrzeugWartungslog }>(
|
||||
`/api/vehicles/${id}/wartung`,
|
||||
payload
|
||||
);
|
||||
return response.data.data;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user