diff --git a/backend/src/controllers/personalEquipment.controller.ts b/backend/src/controllers/personalEquipment.controller.ts index 341026a..c032fe4 100644 --- a/backend/src/controllers/personalEquipment.controller.ts +++ b/backend/src/controllers/personalEquipment.controller.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { z } from 'zod'; import personalEquipmentService from '../services/personalEquipment.service'; +import settingsService from '../services/settings.service'; import logger from '../utils/logger'; const uuidString = z.string().regex( @@ -13,7 +14,7 @@ const isoDate = z.string().regex( 'Erwartet ISO-Datum im Format YYYY-MM-DD', ); -const ZustandEnum = z.enum(['gut', 'beschaedigt', 'abgaengig', 'verloren']); +const ZustandEnum = z.string().min(1).max(50); const EigenschaftInput = z.object({ eigenschaft_id: z.number().int().positive().nullable().optional(), @@ -180,6 +181,42 @@ class PersonalEquipmentController { res.status(500).json({ success: false, message: 'Persönliche Ausrüstung konnte nicht gelöscht werden' }); } } + + async getZustandOptions(_req: Request, res: Response): Promise { + try { + const setting = await settingsService.get('personal_equipment_zustand_options'); + const options = Array.isArray(setting?.value) ? setting!.value : [ + { key: 'gut', label: 'Gut', color: 'success' }, + { key: 'beschaedigt', label: 'Beschädigt', color: 'warning' }, + { key: 'abgaengig', label: 'Abgängig', color: 'error' }, + { key: 'verloren', label: 'Verloren', color: 'default' }, + ]; + res.status(200).json({ success: true, data: options }); + } catch (error) { + logger.error('personalEquipment.getZustandOptions error', { error }); + res.status(500).json({ success: false, message: 'Zustände konnten nicht geladen werden' }); + } + } + + async updateZustandOptions(req: Request, res: Response): Promise { + try { + const schema = z.array(z.object({ + key: z.string().min(1).max(50), + label: z.string().min(1).max(100), + color: z.enum(['success', 'warning', 'error', 'default', 'primary', 'secondary', 'info']), + })).min(1); + const parsed = schema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ success: false, message: 'Validierungsfehler', errors: parsed.error.flatten() }); + return; + } + await settingsService.set('personal_equipment_zustand_options', parsed.data, req.user!.id); + res.status(200).json({ success: true, data: parsed.data }); + } catch (error) { + logger.error('personalEquipment.updateZustandOptions error', { error }); + res.status(500).json({ success: false, message: 'Zustände konnten nicht gespeichert werden' }); + } + } } export default new PersonalEquipmentController(); diff --git a/backend/src/database/migrations/091_personal_equipment_configurable_zustand.sql b/backend/src/database/migrations/091_personal_equipment_configurable_zustand.sql new file mode 100644 index 0000000..7b40101 --- /dev/null +++ b/backend/src/database/migrations/091_personal_equipment_configurable_zustand.sql @@ -0,0 +1,17 @@ +-- Migration: 091_personal_equipment_configurable_zustand +-- Makes the zustand field on persoenliche_ausruestung admin-configurable +-- by removing the hard-coded CHECK constraint and seeding default options +-- into app_settings. + +-- 1. Drop the hard-coded CHECK constraint +ALTER TABLE persoenliche_ausruestung DROP CONSTRAINT IF EXISTS persoenliche_ausruestung_zustand_check; + +-- 2. Seed default zustand options into app_settings +INSERT INTO app_settings (key, value, updated_by, updated_at) +VALUES ( + 'personal_equipment_zustand_options', + '[{"key":"gut","label":"Gut","color":"success"},{"key":"beschaedigt","label":"Beschädigt","color":"warning"},{"key":"abgaengig","label":"Abgängig","color":"error"},{"key":"verloren","label":"Verloren","color":"default"}]', + 'system', + NOW() +) +ON CONFLICT (key) DO NOTHING; diff --git a/backend/src/routes/personalEquipment.routes.ts b/backend/src/routes/personalEquipment.routes.ts index fd62e4d..e7ad5c9 100644 --- a/backend/src/routes/personalEquipment.routes.ts +++ b/backend/src/routes/personalEquipment.routes.ts @@ -24,6 +24,10 @@ router.get('/user/:userId', authenticate, async (req, res, next) => { res.status(403).json({ success: false, message: 'Keine Berechtigung' }); }, personalEquipmentController.getByUser.bind(personalEquipmentController)); +// Zustand options — GET (all authenticated users), PUT (admin:write) +router.get('/zustand-options', authenticate, personalEquipmentController.getZustandOptions.bind(personalEquipmentController)); +router.put('/zustand-options', authenticate, requirePermission('admin:write'), personalEquipmentController.updateZustandOptions.bind(personalEquipmentController)); + // Single item router.get('/:id', authenticate, personalEquipmentController.getById.bind(personalEquipmentController)); diff --git a/backend/src/services/personalEquipment.service.ts b/backend/src/services/personalEquipment.service.ts index 85cea4d..df8a897 100644 --- a/backend/src/services/personalEquipment.service.ts +++ b/backend/src/services/personalEquipment.service.ts @@ -40,10 +40,12 @@ interface UpdatePersonalEquipmentData { const BASE_SELECT = ` SELECT pa.*, COALESCE(u.given_name || ' ' || u.family_name, u.name) AS user_display_name, - aa.bezeichnung AS artikel_bezeichnung + aa.bezeichnung AS artikel_bezeichnung, + akk.name AS artikel_kategorie_name FROM persoenliche_ausruestung pa LEFT JOIN users u ON u.id = pa.user_id LEFT JOIN ausruestung_artikel aa ON aa.id = pa.artikel_id + LEFT JOIN ausruestung_kategorien_katalog akk ON akk.id = aa.kategorie_id WHERE pa.geloescht_am IS NULL `; diff --git a/frontend/src/components/dashboard/PersoenlicheAusruestungWidget.tsx b/frontend/src/components/dashboard/PersoenlicheAusruestungWidget.tsx index 8aacece..1b3a0d4 100644 --- a/frontend/src/components/dashboard/PersoenlicheAusruestungWidget.tsx +++ b/frontend/src/components/dashboard/PersoenlicheAusruestungWidget.tsx @@ -3,7 +3,7 @@ import CheckroomIcon from '@mui/icons-material/Checkroom'; import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { personalEquipmentApi } from '../../services/personalEquipment'; -import { ZUSTAND_LABELS, ZUSTAND_COLORS } from '../../types/personalEquipment.types'; +import type { ZustandOption } from '../../types/personalEquipment.types'; import { WidgetCard } from '../templates/WidgetCard'; import { ItemListSkeleton } from '../templates/SkeletonPresets'; @@ -17,6 +17,15 @@ function PersoenlicheAusruestungWidget() { retry: 1, }); + const { data: zustandOptions = [] } = useQuery({ + queryKey: ['persoenliche-ausruestung', 'zustand-options'], + queryFn: () => personalEquipmentApi.getZustandOptions(), + staleTime: 5 * 60 * 1000, + }); + + const getZustandLabel = (key: string) => zustandOptions.find(o => o.key === key)?.label ?? key; + const getZustandColor = (key: string) => zustandOptions.find(o => o.key === key)?.color ?? 'default'; + const displayItems = (items ?? []).slice(0, 5); return ( @@ -43,13 +52,13 @@ function PersoenlicheAusruestungWidget() { ([]); + const { data: zustandOptionsData } = useQuery({ + queryKey: ['persoenliche-ausruestung', 'zustand-options'], + queryFn: () => personalEquipmentApi.getZustandOptions(), + enabled: canAccess, + }); + useEffect(() => { + if (zustandOptionsData) setZustandOptions(zustandOptionsData); + }, [zustandOptionsData]); + const zustandMutation = useMutation({ + mutationFn: (options: ZustandOption[]) => personalEquipmentApi.updateZustandOptions(options), + onSuccess: () => { + showSuccess('Zustandsoptionen gespeichert'); + queryClient.invalidateQueries({ queryKey: ['persoenliche-ausruestung', 'zustand-options'] }); + }, + onError: () => { + showError('Fehler beim Speichern der Zustandsoptionen'); + }, + }); + if (!canAccess) { return ; } @@ -572,7 +596,98 @@ function AdminSettings() { - {/* Section 5: Info */} + {/* Section 5: Zustandsoptionen (Persönliche Ausrüstung) */} + + + + + Zustandsoptionen — Persönliche Ausrüstung + + + + Konfigurierbare Zustandswerte für die persönliche Ausrüstung. Schlüssel wird intern gespeichert, Label wird angezeigt. + + + {zustandOptions.map((opt, idx) => ( + + + setZustandOptions((prev) => + prev.map((o, i) => (i === idx ? { ...o, key: e.target.value } : o)) + ) + } + size="small" + sx={{ flex: 1 }} + /> + + setZustandOptions((prev) => + prev.map((o, i) => (i === idx ? { ...o, label: e.target.value } : o)) + ) + } + size="small" + sx={{ flex: 1 }} + /> + + Farbe + + + + setZustandOptions((prev) => prev.filter((_, i) => i !== idx)) + } + aria-label="Option entfernen" + size="small" + > + + + + ))} + + + + + + + + + {/* Section 6: Info */} diff --git a/frontend/src/pages/AusruestungsanfrageNeu.tsx b/frontend/src/pages/AusruestungsanfrageNeu.tsx index c2d9681..ea46d8a 100644 --- a/frontend/src/pages/AusruestungsanfrageNeu.tsx +++ b/frontend/src/pages/AusruestungsanfrageNeu.tsx @@ -11,8 +11,7 @@ import { useNotification } from '../contexts/NotificationContext'; import { usePermissionContext } from '../contexts/PermissionContext'; import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage'; import { personalEquipmentApi } from '../services/personalEquipment'; -import { ZUSTAND_LABELS, ZUSTAND_COLORS } from '../types/personalEquipment.types'; -import type { PersoenlicheAusruestungZustand } from '../types/personalEquipment.types'; +import type { ZustandOption } from '../types/personalEquipment.types'; import type { AusruestungAnfrageFormItem, AusruestungEigenschaft, @@ -97,6 +96,15 @@ export default function AusruestungsanfrageNeu() { queryFn: () => ausruestungsanfrageApi.getItems({ aktiv: true }), }); + const { data: zustandOptions = [] } = useQuery({ + queryKey: ['persoenliche-ausruestung', 'zustand-options'], + queryFn: () => personalEquipmentApi.getZustandOptions(), + staleTime: 5 * 60 * 1000, + }); + + const getZustandLabel = (key: string) => zustandOptions.find(o => o.key === key)?.label ?? key; + const getZustandColor = (key: string) => zustandOptions.find(o => o.key === key)?.color ?? 'default'; + const { data: orderUsers = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'orderUsers'], queryFn: () => ausruestungsanfrageApi.getOrderUsers(), @@ -367,8 +375,8 @@ export default function AusruestungsanfrageNeu() { )} {!!assignedSelections[item.id] && ( @@ -380,8 +388,8 @@ export default function AusruestungsanfrageNeu() { value={assignedSelections[item.id]} onChange={(e) => setAssignedSelections(prev => ({ ...prev, [item.id]: e.target.value }))} > - {Object.entries(ZUSTAND_LABELS).map(([key, label]) => ( - {label} + {zustandOptions.map((opt) => ( + {opt.label} ))} )} diff --git a/frontend/src/pages/MitgliedDetail.tsx b/frontend/src/pages/MitgliedDetail.tsx index 0931cc4..1a94670 100644 --- a/frontend/src/pages/MitgliedDetail.tsx +++ b/frontend/src/pages/MitgliedDetail.tsx @@ -44,8 +44,7 @@ import { usePermissionContext } from '../contexts/PermissionContext'; import { membersService } from '../services/members'; import { atemschutzApi } from '../services/atemschutz'; import { personalEquipmentApi } from '../services/personalEquipment'; -import { ZUSTAND_LABELS, ZUSTAND_COLORS } from '../types/personalEquipment.types'; -import type { PersoenlicheAusruestung } from '../types/personalEquipment.types'; +import type { PersoenlicheAusruestung, ZustandOption } from '../types/personalEquipment.types'; import { toGermanDate, fromGermanDate } from '../utils/dateInput'; import { MemberWithProfile, @@ -233,6 +232,7 @@ function MitgliedDetail() { // Personal equipment data const [personalEquipment, setPersonalEquipment] = useState([]); const [personalEquipmentLoading, setPersonalEquipmentLoading] = useState(false); + const [zustandOptions, setZustandOptions] = useState([]); // FDISK-synced sub-section data const [befoerderungen, setBefoerderungen] = useState([]); @@ -291,6 +291,9 @@ function MitgliedDetail() { .then(setPersonalEquipment) .catch(() => setPersonalEquipment([])) .finally(() => setPersonalEquipmentLoading(false)); + personalEquipmentApi.getZustandOptions() + .then(setZustandOptions) + .catch(() => {}); }, [userId]); // Load FDISK-synced sub-section data @@ -976,8 +979,8 @@ function MitgliedDetail() { )} o.key === item.zustand)?.label ?? item.zustand} + color={(zustandOptions.find(o => o.key === item.zustand)?.color ?? 'default') as any} size="small" variant="outlined" /> diff --git a/frontend/src/pages/PersoenlicheAusruestung.tsx b/frontend/src/pages/PersoenlicheAusruestung.tsx index 2a67427..c6e3381 100644 --- a/frontend/src/pages/PersoenlicheAusruestung.tsx +++ b/frontend/src/pages/PersoenlicheAusruestung.tsx @@ -23,13 +23,7 @@ import { usePermissionContext } from '../contexts/PermissionContext'; import ChatAwareFab from '../components/shared/ChatAwareFab'; import { PageHeader } from '../components/templates'; import { KatalogTab } from '../components/shared/KatalogTab'; -import { - ZUSTAND_LABELS, - ZUSTAND_COLORS, -} from '../types/personalEquipment.types'; -import type { PersoenlicheAusruestungZustand } from '../types/personalEquipment.types'; - -const ZUSTAND_OPTIONS = Object.entries(ZUSTAND_LABELS) as [PersoenlicheAusruestungZustand, string][]; +import type { ZustandOption } from '../types/personalEquipment.types'; function PersoenlicheAusruestungPage() { const navigate = useNavigate(); @@ -52,6 +46,15 @@ function PersoenlicheAusruestungPage() { staleTime: 2 * 60 * 1000, }); + const { data: zustandOptions = [] } = useQuery({ + queryKey: ['persoenliche-ausruestung', 'zustand-options'], + queryFn: () => personalEquipmentApi.getZustandOptions(), + staleTime: 5 * 60 * 1000, + }); + + const getZustandLabel = (key: string) => zustandOptions.find(o => o.key === key)?.label ?? key; + const getZustandColor = (key: string) => zustandOptions.find(o => o.key === key)?.color ?? 'default'; + const { data: membersList } = useQuery({ queryKey: ['members-list-compact'], queryFn: () => membersService.getMembers({ pageSize: 500 }), @@ -126,8 +129,8 @@ function PersoenlicheAusruestungPage() { sx={{ minWidth: 140 }} > Alle - {ZUSTAND_OPTIONS.map(([key, label]) => ( - {label} + {zustandOptions.map((opt) => ( + {opt.label} ))} {canSeeAll && ( @@ -178,15 +181,13 @@ function PersoenlicheAusruestungPage() { Bezeichnung Kategorie {canSeeAll && Benutzer} - Größe Zustand - Anschaffung {isLoading ? ( - + Lade Daten… @@ -194,7 +195,7 @@ function PersoenlicheAusruestungPage() { ) : filtered.length === 0 ? ( - + @@ -230,7 +231,7 @@ function PersoenlicheAusruestungPage() { )} - {item.kategorie ?? '—'} + {item.artikel_kategorie_name ?? item.kategorie ?? '—'} {canSeeAll && ( @@ -239,24 +240,14 @@ function PersoenlicheAusruestungPage() { )} - - {item.groesse ?? '—'} - - - - {item.anschaffung_datum - ? new Date(item.anschaffung_datum).toLocaleDateString('de-AT') - : '—'} - - )) )} diff --git a/frontend/src/pages/PersoenlicheAusruestungDetail.tsx b/frontend/src/pages/PersoenlicheAusruestungDetail.tsx index cdc01d9..87bd556 100644 --- a/frontend/src/pages/PersoenlicheAusruestungDetail.tsx +++ b/frontend/src/pages/PersoenlicheAusruestungDetail.tsx @@ -12,8 +12,7 @@ import { PageHeader } from '../components/templates'; import { usePermissionContext } from '../contexts/PermissionContext'; import { useNotification } from '../contexts/NotificationContext'; import { personalEquipmentApi } from '../services/personalEquipment'; -import { ZUSTAND_LABELS, ZUSTAND_COLORS } from '../types/personalEquipment.types'; -import type { PersoenlicheAusruestungZustand } from '../types/personalEquipment.types'; +import type { ZustandOption } from '../types/personalEquipment.types'; export default function PersoenlicheAusruestungDetail() { const { id } = useParams<{ id: string }>(); @@ -30,6 +29,15 @@ export default function PersoenlicheAusruestungDetail() { enabled: !!id, }); + const { data: zustandOptions = [] } = useQuery({ + queryKey: ['persoenliche-ausruestung', 'zustand-options'], + queryFn: () => personalEquipmentApi.getZustandOptions(), + staleTime: 5 * 60 * 1000, + }); + + const getZustandLabel = (key: string) => zustandOptions.find(o => o.key === key)?.label ?? key; + const getZustandColor = (key: string) => zustandOptions.find(o => o.key === key)?.color ?? 'default'; + const canEdit = hasPermission('persoenliche_ausruestung:edit'); const canDelete = hasPermission('persoenliche_ausruestung:delete'); @@ -61,14 +69,14 @@ export default function PersoenlicheAusruestungDetail() { {/* Status + actions row */} @@ -92,10 +100,8 @@ export default function PersoenlicheAusruestungDetail() { {([ ['Benutzer', item.user_display_name || item.benutzer_name], - ['Größe', item.groesse], ['Seriennummer', item.seriennummer], ['Inventarnummer', item.inventarnummer], - ['Anschaffungsdatum', item.anschaffung_datum ? new Date(item.anschaffung_datum).toLocaleDateString('de-AT') : null], ['Erstellt am', new Date(item.erstellt_am).toLocaleDateString('de-AT')], ] as [string, string | null | undefined][]).map(([label, value]) => value ? ( diff --git a/frontend/src/pages/PersoenlicheAusruestungEdit.tsx b/frontend/src/pages/PersoenlicheAusruestungEdit.tsx index 8c8e475..1854b23 100644 --- a/frontend/src/pages/PersoenlicheAusruestungEdit.tsx +++ b/frontend/src/pages/PersoenlicheAusruestungEdit.tsx @@ -21,14 +21,11 @@ import { membersService } from '../services/members'; import { usePermissionContext } from '../contexts/PermissionContext'; import { useNotification } from '../contexts/NotificationContext'; import { PageHeader } from '../components/templates'; -import { ZUSTAND_LABELS } from '../types/personalEquipment.types'; import type { - PersoenlicheAusruestungZustand, + ZustandOption, UpdatePersoenlicheAusruestungPayload, } from '../types/personalEquipment.types'; -const ZUSTAND_OPTIONS = Object.entries(ZUSTAND_LABELS) as [PersoenlicheAusruestungZustand, string][]; - interface EigenschaftRow { id?: number; eigenschaft_id?: number | null; @@ -72,14 +69,19 @@ export default function PersoenlicheAusruestungEdit() { staleTime: 5 * 60 * 1000, }); + const { data: zustandOptions = [] } = useQuery({ + queryKey: ['persoenliche-ausruestung', 'zustand-options'], + queryFn: () => personalEquipmentApi.getZustandOptions(), + staleTime: 5 * 60 * 1000, + }); + const [catalogEigenschaftValues, setCatalogEigenschaftValues] = useState>({}); // Form state const [bezeichnung, setBezeichnung] = useState(''); const [seriennummer, setSeriennummer] = useState(''); const [inventarnummer, setInventarnummer] = useState(''); - const [anschaffungDatum, setAnschaffungDatum] = useState(''); - const [zustand, setZustand] = useState('gut'); + const [zustand, setZustand] = useState('gut'); const [notizen, setNotizen] = useState(''); const [userId, setUserId] = useState<{ id: string; name: string } | null>(null); const [eigenschaften, setEigenschaften] = useState([]); @@ -90,7 +92,6 @@ export default function PersoenlicheAusruestungEdit() { setBezeichnung(item.bezeichnung); setSeriennummer(item.seriennummer ?? ''); setInventarnummer(item.inventarnummer ?? ''); - setAnschaffungDatum(item.anschaffung_datum ? item.anschaffung_datum.split('T')[0] : ''); setZustand(item.zustand); setNotizen(item.notizen ?? ''); if (item.eigenschaften) { @@ -139,7 +140,6 @@ export default function PersoenlicheAusruestungEdit() { groesse: null, seriennummer: seriennummer || null, inventarnummer: inventarnummer || null, - anschaffung_datum: anschaffungDatum || null, zustand, notizen: notizen || null, eigenschaften: item.artikel_id @@ -231,24 +231,15 @@ export default function PersoenlicheAusruestungEdit() { onChange={(e) => setInventarnummer(e.target.value)} /> - setAnschaffungDatum(e.target.value)} - InputLabelProps={{ shrink: true }} - /> - setZustand(e.target.value as PersoenlicheAusruestungZustand)} + onChange={(e) => setZustand(e.target.value)} > - {ZUSTAND_OPTIONS.map(([key, label]) => ( - {label} + {zustandOptions.map((opt) => ( + {opt.label} ))} diff --git a/frontend/src/pages/PersoenlicheAusruestungNeu.tsx b/frontend/src/pages/PersoenlicheAusruestungNeu.tsx index 07b5b53..bab6f36 100644 --- a/frontend/src/pages/PersoenlicheAusruestungNeu.tsx +++ b/frontend/src/pages/PersoenlicheAusruestungNeu.tsx @@ -17,15 +17,12 @@ import { membersService } from '../services/members'; import { usePermissionContext } from '../contexts/PermissionContext'; import { useNotification } from '../contexts/NotificationContext'; import { PageHeader } from '../components/templates'; -import { ZUSTAND_LABELS } from '../types/personalEquipment.types'; import type { - PersoenlicheAusruestungZustand, + ZustandOption, CreatePersoenlicheAusruestungPayload, } from '../types/personalEquipment.types'; import type { AusruestungArtikel } from '../types/ausruestungsanfrage.types'; -const ZUSTAND_OPTIONS = Object.entries(ZUSTAND_LABELS) as [PersoenlicheAusruestungZustand, string][]; - export default function PersoenlicheAusruestungNeu() { const navigate = useNavigate(); const queryClient = useQueryClient(); @@ -37,7 +34,7 @@ export default function PersoenlicheAusruestungNeu() { const [formArtikel, setFormArtikel] = useState(null); const [formUserId, setFormUserId] = useState<{ id: string; name: string } | null>(null); const [formBenutzerName, setFormBenutzerName] = useState(''); - const [formZustand, setFormZustand] = useState('gut'); + const [formZustand, setFormZustand] = useState('gut'); const [formNotizen, setFormNotizen] = useState(''); const [eigenschaftValues, setEigenschaftValues] = useState>({}); @@ -47,6 +44,12 @@ export default function PersoenlicheAusruestungNeu() { staleTime: 10 * 60 * 1000, }); + const { data: zustandOptions = [] } = useQuery({ + queryKey: ['persoenliche-ausruestung', 'zustand-options'], + queryFn: () => personalEquipmentApi.getZustandOptions(), + staleTime: 5 * 60 * 1000, + }); + const { data: artikelEigenschaften = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'eigenschaften', formArtikel?.id], queryFn: () => ausruestungsanfrageApi.getArtikelEigenschaften(formArtikel!.id), @@ -169,10 +172,10 @@ export default function PersoenlicheAusruestungNeu() { select size="small" value={formZustand} - onChange={(e) => setFormZustand(e.target.value as PersoenlicheAusruestungZustand)} + onChange={(e) => setFormZustand(e.target.value)} > - {ZUSTAND_OPTIONS.map(([key, label]) => ( - {label} + {zustandOptions.map((opt) => ( + {opt.label} ))} diff --git a/frontend/src/services/personalEquipment.ts b/frontend/src/services/personalEquipment.ts index 1b27d3b..60ed1d5 100644 --- a/frontend/src/services/personalEquipment.ts +++ b/frontend/src/services/personalEquipment.ts @@ -3,6 +3,7 @@ import type { PersoenlicheAusruestung, CreatePersoenlicheAusruestungPayload, UpdatePersoenlicheAusruestungPayload, + ZustandOption, } from '../types/personalEquipment.types'; async function unwrap( @@ -61,4 +62,12 @@ export const personalEquipmentApi = { async delete(id: string): Promise { await api.delete(`/api/persoenliche-ausruestung/${id}`); }, + + async getZustandOptions(): Promise { + return unwrap(api.get('/api/persoenliche-ausruestung/zustand-options')); + }, + + async updateZustandOptions(options: ZustandOption[]): Promise { + return unwrap(api.put('/api/persoenliche-ausruestung/zustand-options', { options })); + }, }; diff --git a/frontend/src/types/personalEquipment.types.ts b/frontend/src/types/personalEquipment.types.ts index 11c5b77..6f97c5e 100644 --- a/frontend/src/types/personalEquipment.types.ts +++ b/frontend/src/types/personalEquipment.types.ts @@ -1,20 +1,10 @@ // Personal Equipment (Persönliche Ausrüstung) — Frontend Types -export type PersoenlicheAusruestungZustand = 'gut' | 'beschaedigt' | 'abgaengig' | 'verloren'; - -export const ZUSTAND_LABELS: Record = { - gut: 'Gut', - beschaedigt: 'Beschädigt', - abgaengig: 'Abgängig', - verloren: 'Verloren', -}; - -export const ZUSTAND_COLORS: Record = { - gut: 'success', - beschaedigt: 'warning', - abgaengig: 'error', - verloren: 'default', -}; +export interface ZustandOption { + key: string; + label: string; + color: string; +} export interface PersoenlicheAusruestung { id: string; @@ -22,6 +12,7 @@ export interface PersoenlicheAusruestung { kategorie?: string; artikel_id?: number; artikel_bezeichnung?: string; + artikel_kategorie_name?: string; user_id?: string; user_display_name?: string; benutzer_name?: string; @@ -29,7 +20,7 @@ export interface PersoenlicheAusruestung { seriennummer?: string; inventarnummer?: string; anschaffung_datum?: string; - zustand: PersoenlicheAusruestungZustand; + zustand: string; notizen?: string; anfrage_id?: number; anfrage_position_id?: number; @@ -48,7 +39,7 @@ export interface CreatePersoenlicheAusruestungPayload { seriennummer?: string; inventarnummer?: string; anschaffung_datum?: string; - zustand?: PersoenlicheAusruestungZustand; + zustand?: string; notizen?: string; eigenschaften?: { eigenschaft_id?: number; name: string; wert: string }[]; } @@ -63,7 +54,7 @@ export interface UpdatePersoenlicheAusruestungPayload { seriennummer?: string | null; inventarnummer?: string | null; anschaffung_datum?: string | null; - zustand?: PersoenlicheAusruestungZustand; + zustand?: string; notizen?: string | null; eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[] | null; }