Files
dashboard/frontend/src/pages/PersoenlicheAusruestungNeu.tsx

205 lines
7.1 KiB
TypeScript

import { useState, useMemo } from 'react';
import {
Autocomplete,
Box,
Container,
MenuItem,
Stack,
TextField,
Button,
} from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import DashboardLayout from '../components/dashboard/DashboardLayout';
import { personalEquipmentApi } from '../services/personalEquipment';
import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
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,
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();
const { hasPermission } = usePermissionContext();
const { showSuccess, showError } = useNotification();
const canViewAll = hasPermission('persoenliche_ausruestung:view_all');
const [formArtikel, setFormArtikel] = useState<AusruestungArtikel | null>(null);
const [formUserId, setFormUserId] = useState<{ id: string; name: string } | null>(null);
const [formBenutzerName, setFormBenutzerName] = useState('');
const [formZustand, setFormZustand] = useState<PersoenlicheAusruestungZustand>('gut');
const [formNotizen, setFormNotizen] = useState('');
const [eigenschaftValues, setEigenschaftValues] = useState<Record<number, string>>({});
const { data: catalogItems } = useQuery({
queryKey: ['ausruestungsanfrage-items-catalog'],
queryFn: () => ausruestungsanfrageApi.getItems(),
staleTime: 10 * 60 * 1000,
});
const { data: artikelEigenschaften = [] } = useQuery({
queryKey: ['ausruestungsanfrage', 'eigenschaften', formArtikel?.id],
queryFn: () => ausruestungsanfrageApi.getArtikelEigenschaften(formArtikel!.id),
enabled: !!formArtikel?.id,
staleTime: 5 * 60 * 1000,
});
const { data: membersList } = useQuery({
queryKey: ['members-list-compact'],
queryFn: () => membersService.getMembers({ pageSize: 500 }),
staleTime: 5 * 60 * 1000,
enabled: canViewAll,
});
const memberOptions = useMemo(() => {
return (membersList?.items ?? []).map((m) => ({
id: m.id,
name: [m.given_name, m.family_name].filter(Boolean).join(' ') || m.email,
}));
}, [membersList]);
const createMutation = useMutation({
mutationFn: (data: CreatePersoenlicheAusruestungPayload) => personalEquipmentApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['persoenliche-ausruestung'] });
showSuccess('Persönliche Ausrüstung erstellt');
navigate('/persoenliche-ausruestung');
},
onError: () => {
showError('Fehler beim Erstellen');
},
});
const handleCreate = () => {
const bezeichnung = formArtikel?.bezeichnung ?? '';
if (!bezeichnung.trim()) return;
const payload: CreatePersoenlicheAusruestungPayload = {
bezeichnung: bezeichnung.trim(),
artikel_id: formArtikel?.id,
user_id: formUserId?.id || undefined,
benutzer_name: formBenutzerName || undefined,
zustand: formZustand,
notizen: formNotizen || undefined,
eigenschaften: Object.entries(eigenschaftValues)
.filter(([, v]) => v.trim())
.map(([id, wert]) => ({
eigenschaft_id: Number(id),
name: artikelEigenschaften.find(e => e.id === Number(id))?.name ?? '',
wert,
})),
};
createMutation.mutate(payload);
};
return (
<DashboardLayout>
<Container maxWidth="sm">
<PageHeader
title="Neue Ausrüstung zuweisen"
backTo="/persoenliche-ausruestung"
/>
<Stack spacing={2}>
<Autocomplete
options={catalogItems ?? []}
getOptionLabel={(o) => o.bezeichnung}
value={formArtikel}
onChange={(_e, v) => {
setFormArtikel(v);
setEigenschaftValues({});
}}
renderInput={(params) => (
<TextField {...params} label="Bezeichnung" required size="small" />
)}
size="small"
/>
{artikelEigenschaften.map(e =>
e.typ === 'options' && e.optionen?.length ? (
<TextField key={e.id} select size="small" label={e.name} required={e.pflicht}
value={eigenschaftValues[e.id] ?? ''}
onChange={ev => setEigenschaftValues(prev => ({ ...prev, [e.id]: ev.target.value }))}
>
<MenuItem value=""></MenuItem>
{e.optionen.map(opt => <MenuItem key={opt} value={opt}>{opt}</MenuItem>)}
</TextField>
) : (
<TextField key={e.id} size="small" label={e.name} required={e.pflicht}
value={eigenschaftValues[e.id] ?? ''}
onChange={ev => setEigenschaftValues(prev => ({ ...prev, [e.id]: ev.target.value }))}
/>
)
)}
{canViewAll && (
<Autocomplete
options={memberOptions}
getOptionLabel={(o) => o.name}
value={formUserId}
onChange={(_e, v) => setFormUserId(v)}
renderInput={(params) => (
<TextField {...params} label="Benutzer" size="small" />
)}
size="small"
/>
)}
{!canViewAll && (
<TextField
label="Benutzer (Name)"
size="small"
value={formBenutzerName}
onChange={(e) => setFormBenutzerName(e.target.value)}
/>
)}
<TextField
label="Zustand"
select
size="small"
value={formZustand}
onChange={(e) => setFormZustand(e.target.value as PersoenlicheAusruestungZustand)}
>
{ZUSTAND_OPTIONS.map(([key, label]) => (
<MenuItem key={key} value={key}>{label}</MenuItem>
))}
</TextField>
<TextField
label="Notizen"
size="small"
multiline
rows={2}
value={formNotizen}
onChange={(e) => setFormNotizen(e.target.value)}
/>
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'flex-end', mt: 1 }}>
<Button onClick={() => navigate('/persoenliche-ausruestung')}>
Abbrechen
</Button>
<Button
variant="contained"
onClick={handleCreate}
disabled={createMutation.isPending || !formArtikel}
>
Erstellen
</Button>
</Box>
</Stack>
</Container>
</DashboardLayout>
);
}