feat(ausruestung): catalog-driven item tracking, im_haus in overview, order quantity override, fix stale queries

This commit is contained in:
Matthias Hochmeister
2026-04-15 10:20:36 +02:00
parent 633a75cb0b
commit 279cc03b6b
10 changed files with 195 additions and 114 deletions

View File

@@ -12,6 +12,7 @@ import { PageHeader } from '../components/templates';
import { useNotification } from '../contexts/NotificationContext';
import { usePermissionContext } from '../contexts/PermissionContext';
import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
import { personalEquipmentApi } from '../services/personalEquipment';
import { vehiclesApi } from '../services/vehicles';
import { membersService } from '../services/members';
import type { AusruestungAnfragePosition } from '../types/ausruestungsanfrage.types';
@@ -24,8 +25,6 @@ interface PositionAssignment {
standort?: string;
userId?: string;
benutzerName?: string;
groesse?: string;
kategorie?: string;
}
function getUnassignedPositions(positions: AusruestungAnfragePosition[]): AusruestungAnfragePosition[] {
@@ -92,6 +91,12 @@ export default function AusruestungsanfrageZuweisung() {
name: v.bezeichnung ?? v.kurzname,
}));
const { data: allPersonalItems = [] } = useQuery({
queryKey: ['persoenliche-ausruestung', 'all-for-count'],
queryFn: () => personalEquipmentApi.getAll(),
staleTime: 2 * 60 * 1000,
});
const [submitting, setSubmitting] = useState(false);
const updateAssignment = (posId: number, patch: Partial<PositionAssignment>) => {
@@ -137,12 +142,12 @@ export default function AusruestungsanfrageZuweisung() {
standort: a.typ === 'ausruestung' ? a.standort : undefined,
userId: a.typ === 'persoenlich' ? a.userId : undefined,
benutzerName: a.typ === 'persoenlich' ? (a.benutzerName || anfrage.fuer_benutzer_name || anfrage.anfrager_name) : undefined,
groesse: a.typ === 'persoenlich' ? a.groesse : undefined,
kategorie: a.typ === 'persoenlich' ? a.kategorie : undefined,
}));
await ausruestungsanfrageApi.assignItems(anfrageId, payload);
showSuccess('Gegenstände zugewiesen');
navigate(`/ausruestungsanfrage/${id}`);
queryClient.invalidateQueries({ queryKey: ['persoenliche-ausruestung'] });
queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] });
} catch {
showError('Fehler beim Zuweisen');
} finally {
@@ -233,11 +238,10 @@ export default function AusruestungsanfrageZuweisung() {
size="small"
onChange={(_e, val) => val && updateAssignment(pos.id, { typ: val })}
sx={{ mb: 1.5 }}
disabled={!pos.artikel_id}
>
<ToggleButton value="ausruestung">Ausrüstung</ToggleButton>
<ToggleButton value="persoenlich">Persönlich</ToggleButton>
<ToggleButton value="keine">Nicht erfassen</ToggleButton>
<ToggleButton value="ausruestung" disabled={!pos.artikel_id}>Ausrüstung</ToggleButton>
<ToggleButton value="persoenlich" disabled={!pos.artikel_id}>Persönlich</ToggleButton>
<ToggleButton value="keine">Nicht verfolgt</ToggleButton>
</ToggleButtonGroup>
{a.typ === 'ausruestung' && (
@@ -262,7 +266,7 @@ export default function AusruestungsanfrageZuweisung() {
)}
{a.typ === 'persoenlich' && (
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
<Autocomplete
size="small"
options={memberOptions}
@@ -278,20 +282,19 @@ export default function AusruestungsanfrageZuweisung() {
)}
sx={{ minWidth: 200, flex: 1 }}
/>
<TextField
size="small"
label="Größe"
value={a.groesse ?? ''}
onChange={(e) => updateAssignment(pos.id, { groesse: e.target.value })}
sx={{ minWidth: 100 }}
/>
<TextField
size="small"
label="Kategorie"
value={a.kategorie ?? ''}
onChange={(e) => updateAssignment(pos.id, { kategorie: e.target.value })}
sx={{ minWidth: 140 }}
/>
{(() => {
if (!a.userId || !pos.artikel_id) return null;
const count = allPersonalItems.filter(i => i.user_id === a.userId && i.artikel_id === pos.artikel_id).length;
if (count === 0) return null;
return <Typography variant="caption" color="text.secondary">Hat bereits {count} Stk.</Typography>;
})()}
{pos.eigenschaften && pos.eigenschaften.length > 0 && (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mb: 1 }}>
{pos.eigenschaften.map(e => (
<Chip key={e.eigenschaft_id} label={`${e.eigenschaft_name}: ${e.wert}`} size="small" variant="outlined" />
))}
</Box>
)}
</Box>
)}
</Box>