refactor: move equipment type assignment from detail page to settings page

This commit is contained in:
Matthias Hochmeister
2026-03-28 17:37:01 +01:00
parent bccb0745b8
commit a52bb2a57c
2 changed files with 124 additions and 106 deletions

View File

@@ -1,8 +1,10 @@
import { useState } from 'react';
import {
Alert,
Autocomplete,
Box,
Button,
Chip,
CircularProgress,
Container,
Dialog,
@@ -10,6 +12,7 @@ import {
DialogContent,
DialogContentText,
DialogTitle,
Divider,
IconButton,
Paper,
Table,
@@ -27,6 +30,7 @@ import { useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import DashboardLayout from '../components/dashboard/DashboardLayout';
import { ausruestungTypenApi, AusruestungTyp } from '../services/ausruestungTypen';
import { equipmentApi } from '../services/equipment';
import { usePermissions } from '../hooks/usePermissions';
import { useNotification } from '../contexts/NotificationContext';
@@ -302,9 +306,129 @@ function AusruestungEinstellungen() {
</Button>
</DialogActions>
</Dialog>
<Divider sx={{ my: 4 }} />
<EquipmentTypeAssignment allTypes={typen} />
</Container>
</DashboardLayout>
);
}
export default AusruestungEinstellungen;
// ── Per-equipment type assignment ──────────────────────────────────────────────
function EquipmentTypeAssignment({ allTypes }: { allTypes: AusruestungTyp[] }) {
const { showSuccess, showError } = useNotification();
const { data: equipment = [], isLoading } = useQuery({
queryKey: ['equipment'],
queryFn: equipmentApi.getAll,
});
const [assignDialog, setAssignDialog] = useState<{ equipmentId: string; equipmentName: string } | null>(null);
const [selected, setSelected] = useState<AusruestungTyp[]>([]);
const [saving, setSaving] = useState(false);
const [equipmentTypesMap, setEquipmentTypesMap] = useState<Record<string, AusruestungTyp[]>>({});
const openAssign = async (equipmentId: string, equipmentName: string) => {
let current = equipmentTypesMap[equipmentId];
if (!current) {
try { current = await ausruestungTypenApi.getTypesForEquipment(equipmentId); }
catch { current = []; }
setEquipmentTypesMap((m) => ({ ...m, [equipmentId]: current }));
}
setSelected(current);
setAssignDialog({ equipmentId, equipmentName });
};
const handleSave = async () => {
if (!assignDialog) return;
try {
setSaving(true);
await ausruestungTypenApi.setTypesForEquipment(assignDialog.equipmentId, selected.map((t) => t.id));
setEquipmentTypesMap((m) => ({ ...m, [assignDialog.equipmentId]: selected }));
setAssignDialog(null);
showSuccess('Typen gespeichert');
} catch {
showError('Fehler beim Speichern');
} finally {
setSaving(false);
}
};
return (
<>
<Typography variant="h6" gutterBottom>Typzuweisung je Gerät</Typography>
{isLoading ? (
<CircularProgress size={24} />
) : (
<Paper variant="outlined">
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Gerät</TableCell>
<TableCell>Zugewiesene Typen</TableCell>
<TableCell align="right">Aktionen</TableCell>
</TableRow>
</TableHead>
<TableBody>
{equipment.map((e) => {
const types = equipmentTypesMap[e.id];
return (
<TableRow key={e.id} hover>
<TableCell>{e.bezeichnung}</TableCell>
<TableCell>
{types === undefined ? (
<Typography variant="body2" color="text.disabled"></Typography>
) : types.length === 0 ? (
<Typography variant="body2" color="text.disabled">Keine</Typography>
) : (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
{types.map((t) => <Chip key={t.id} label={t.name} size="small" variant="outlined" />)}
</Box>
)}
</TableCell>
<TableCell align="right">
<Tooltip title="Typen zuweisen">
<IconButton size="small" onClick={() => openAssign(e.id, e.bezeichnung)}>
<Edit fontSize="small" />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Paper>
)}
<Dialog open={!!assignDialog} onClose={() => setAssignDialog(null)} maxWidth="sm" fullWidth>
<DialogTitle sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
Typen für {assignDialog?.equipmentName}
<IconButton size="small" onClick={() => setAssignDialog(null)}><Close /></IconButton>
</DialogTitle>
<DialogContent sx={{ mt: 1 }}>
<Autocomplete
multiple
options={allTypes}
getOptionLabel={(o) => o.name}
value={selected}
onChange={(_e, val) => setSelected(val)}
isOptionEqualToValue={(a, b) => a.id === b.id}
renderInput={(params) => <TextField {...params} label="Ausrüstungstypen" />}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setAssignDialog(null)}>Abbrechen</Button>
<Button variant="contained" onClick={handleSave} disabled={saving}
startIcon={saving ? <CircularProgress size={16} /> : <Save />}>
Speichern
</Button>
</DialogActions>
</Dialog>
</>
);
}