import { useState, useCallback, useRef } from 'react'; import { Box, Typography, Paper, Button, TextField, IconButton, Autocomplete, Divider, MenuItem, } from '@mui/material'; import { ArrowBack, Add as AddIcon, Delete as DeleteIcon } from '@mui/icons-material'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import DashboardLayout from '../components/dashboard/DashboardLayout'; import { useNotification } from '../contexts/NotificationContext'; import { usePermissionContext } from '../contexts/PermissionContext'; import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage'; import type { AusruestungAnfrageFormItem, AusruestungEigenschaft, } from '../types/ausruestungsanfrage.types'; // ── EigenschaftFields ── interface EigenschaftFieldsProps { eigenschaften: AusruestungEigenschaft[]; values: Record; onChange: (eigenschaftId: number, wert: string) => void; } function EigenschaftFields({ eigenschaften, values, onChange }: EigenschaftFieldsProps) { if (eigenschaften.length === 0) return null; return ( {eigenschaften.map(e => ( {e.typ === 'options' && e.optionen && e.optionen.length > 0 ? ( onChange(e.id, ev.target.value)} required={e.pflicht} sx={{ minWidth: 160 }} > {e.optionen.map(opt => ( {opt} ))} ) : ( onChange(e.id, ev.target.value)} required={e.pflicht} sx={{ minWidth: 160 }} /> )} ))} ); } // ══════════════════════════════════════════════════════════════════════════════ // Component // ══════════════════════════════════════════════════════════════════════════════ export default function AusruestungsanfrageNeu() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { showSuccess, showError } = useNotification(); const { hasPermission } = usePermissionContext(); const canOrderForUser = hasPermission('ausruestungsanfrage:order_for_user'); // ── Form state ── const [bezeichnung, setBezeichnung] = useState(''); const [notizen, setNotizen] = useState(''); const [fuerBenutzer, setFuerBenutzer] = useState<{ id: string; name: string } | string | null>(null); const [catalogItems, setCatalogItems] = useState([{ bezeichnung: '', menge: 1 }]); const [freeItems, setFreeItems] = useState<{ bezeichnung: string; menge: number }[]>([]); // Eigenschaften state const [itemEigenschaften, setItemEigenschaften] = useState>({}); const itemEigenschaftenRef = useRef(itemEigenschaften); itemEigenschaftenRef.current = itemEigenschaften; const [itemEigenschaftValues, setItemEigenschaftValues] = useState>>({}); // ── Queries ── const { data: katalogArtikel = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'items-for-create'], queryFn: () => ausruestungsanfrageApi.getItems({ aktiv: true }), }); const { data: orderUsers = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'orderUsers'], queryFn: () => ausruestungsanfrageApi.getOrderUsers(), enabled: canOrderForUser, }); // ── Mutations ── const createMut = useMutation({ mutationFn: (args: { items: AusruestungAnfrageFormItem[]; notizen?: string; bezeichnung?: string; fuer_benutzer_id?: string; fuer_benutzer_name?: string }) => ausruestungsanfrageApi.createRequest(args.items, args.notizen, args.bezeichnung, args.fuer_benutzer_id, args.fuer_benutzer_name), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Anfrage erstellt'); navigate('/ausruestungsanfrage'); }, onError: () => showError('Fehler beim Erstellen der Anfrage'), }); // ── Eigenschaft loading ── const loadEigenschaften = useCallback(async (artikelId: number) => { if (itemEigenschaftenRef.current[artikelId]) return; try { const eigs = await ausruestungsanfrageApi.getArtikelEigenschaften(artikelId); if (eigs && eigs.length > 0) { setItemEigenschaften(prev => ({ ...prev, [artikelId]: eigs })); } } catch (err) { console.warn('Failed to load eigenschaften for artikel', artikelId, err); } }, []); // ── Submit ── const handleSubmit = () => { // Catalog items with eigenschaften const catalogValidItems = catalogItems.filter(i => i.bezeichnung.trim() && i.artikel_id).map((item, idx) => { const vals = itemEigenschaftValues[idx] || {}; const eigenschaften = Object.entries(vals) .filter(([, wert]) => wert.trim()) .map(([eid, wert]) => ({ eigenschaft_id: Number(eid), wert })); return { ...item, eigenschaften: eigenschaften.length > 0 ? eigenschaften : undefined }; }); // Free-text items const freeValidItems = freeItems .filter(i => i.bezeichnung.trim()) .map(i => ({ bezeichnung: i.bezeichnung, menge: i.menge })); const allItems = [...catalogValidItems, ...freeValidItems]; if (allItems.length === 0) return; // Check required eigenschaften for catalog items for (let idx = 0; idx < catalogItems.length; idx++) { const item = catalogItems[idx]; if (!item.bezeichnung.trim() || !item.artikel_id) continue; if (itemEigenschaften[item.artikel_id]) { for (const e of itemEigenschaften[item.artikel_id]) { if (e.pflicht && !(itemEigenschaftValues[idx]?.[e.id]?.trim())) { showError(`Pflichtfeld "${e.name}" für "${item.bezeichnung}" fehlt`); return; } } } } createMut.mutate({ items: allItems, notizen: notizen || undefined, bezeichnung: bezeichnung || undefined, fuer_benutzer_id: typeof fuerBenutzer === 'object' && fuerBenutzer ? fuerBenutzer.id : undefined, fuer_benutzer_name: typeof fuerBenutzer === 'string' ? fuerBenutzer : undefined, }); }; const hasValidItems = catalogItems.some(i => !!i.artikel_id) || freeItems.some(i => i.bezeichnung.trim()); return ( {/* Header */} navigate('/ausruestungsanfrage')}> Neue Bestellung setBezeichnung(e.target.value)} fullWidth /> {canOrderForUser && ( typeof o === 'string' ? o : o.name} value={fuerBenutzer} onChange={(_, v) => setFuerBenutzer(v)} onInputChange={(_, value, reason) => { if (reason === 'input') { const match = orderUsers.find(u => u.name === value); if (!match && value) { setFuerBenutzer(value); } else if (!value) { setFuerBenutzer(null); } } }} isOptionEqualToValue={(option, value) => { if (typeof option === 'string' || typeof value === 'string') return option === value; return option.id === value.id; }} renderInput={params => } /> )} setNotizen(e.target.value)} multiline rows={2} fullWidth /> Aus Katalog {catalogItems.map((item, idx) => ( typeof o === 'string' ? o : o.bezeichnung} value={item.artikel_id ? katalogArtikel.find(c => c.id === item.artikel_id) || null : null} onChange={(_, v) => { if (v && typeof v !== 'string') { setCatalogItems(prev => prev.map((it, i) => i === idx ? { ...it, artikel_id: v.id, bezeichnung: v.bezeichnung } : it)); loadEigenschaften(v.id); } else { setCatalogItems(prev => prev.map((it, i) => i === idx ? { ...it, artikel_id: undefined, bezeichnung: '' } : it)); setItemEigenschaftValues(prev => { const n = { ...prev }; delete n[idx]; return n; }); } }} renderInput={params => } sx={{ flexGrow: 1 }} /> setCatalogItems(prev => prev.map((it, i) => i === idx ? { ...it, menge: Math.max(1, Number(e.target.value)) } : it))} sx={{ width: 90 }} inputProps={{ min: 1 }} /> setCatalogItems(prev => prev.filter((_, i) => i !== idx))} disabled={catalogItems.length <= 1}> {item.artikel_id && itemEigenschaften[item.artikel_id] && itemEigenschaften[item.artikel_id].length > 0 && ( setItemEigenschaftValues(prev => ({ ...prev, [idx]: { ...(prev[idx] || {}), [eid]: wert }, }))} /> )} ))} Freitext-Positionen {freeItems.length === 0 ? ( Keine Freitext-Positionen. ) : ( freeItems.map((item, idx) => ( setFreeItems(prev => prev.map((it, i) => i === idx ? { ...it, bezeichnung: e.target.value } : it))} sx={{ flexGrow: 1 }} /> setFreeItems(prev => prev.map((it, i) => i === idx ? { ...it, menge: Math.max(1, Number(e.target.value)) } : it))} sx={{ width: 90 }} inputProps={{ min: 1 }} /> setFreeItems(prev => prev.filter((_, i) => i !== idx))}> )) )} ); }