rework from modal to page
This commit is contained in:
@@ -22,6 +22,8 @@ import {
|
||||
CardContent,
|
||||
LinearProgress,
|
||||
Checkbox,
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ArrowBack,
|
||||
@@ -34,6 +36,7 @@ import {
|
||||
Alarm,
|
||||
History,
|
||||
Upload as UploadIcon,
|
||||
ArrowDropDown,
|
||||
} from '@mui/icons-material';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
@@ -62,13 +65,15 @@ const formatFileSize = (bytes?: number) => {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
};
|
||||
|
||||
// Status flow
|
||||
const STATUS_FLOW: BestellungStatus[] = ['entwurf', 'erstellt', 'bestellt', 'teillieferung', 'vollstaendig', 'abgeschlossen'];
|
||||
|
||||
function getNextStatus(current: BestellungStatus): BestellungStatus | null {
|
||||
const idx = STATUS_FLOW.indexOf(current);
|
||||
return idx >= 0 && idx < STATUS_FLOW.length - 1 ? STATUS_FLOW[idx + 1] : null;
|
||||
}
|
||||
// Valid status transitions (must match backend VALID_STATUS_TRANSITIONS)
|
||||
const STATUS_TRANSITIONS: Record<BestellungStatus, BestellungStatus[]> = {
|
||||
entwurf: ['erstellt', 'bestellt'],
|
||||
erstellt: ['bestellt'],
|
||||
bestellt: ['teillieferung', 'vollstaendig'],
|
||||
teillieferung: ['vollstaendig'],
|
||||
vollstaendig: ['abgeschlossen'],
|
||||
abgeschlossen: [],
|
||||
};
|
||||
|
||||
// Empty line item form
|
||||
const emptyItem: BestellpositionFormData = { bezeichnung: '', artikelnummer: '', menge: 1, einheit: 'Stk', einzelpreis: undefined };
|
||||
@@ -91,7 +96,8 @@ export default function BestellungDetail() {
|
||||
const [newItem, setNewItem] = useState<BestellpositionFormData>({ ...emptyItem });
|
||||
const [editingItemId, setEditingItemId] = useState<number | null>(null);
|
||||
const [editingItemData, setEditingItemData] = useState<Partial<BestellpositionFormData>>({});
|
||||
const [statusConfirmOpen, setStatusConfirmOpen] = useState(false);
|
||||
const [statusConfirmTarget, setStatusConfirmTarget] = useState<BestellungStatus | null>(null);
|
||||
const [statusMenuAnchor, setStatusMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
const [deleteItemTarget, setDeleteItemTarget] = useState<number | null>(null);
|
||||
const [deleteFileTarget, setDeleteFileTarget] = useState<number | null>(null);
|
||||
|
||||
@@ -113,7 +119,7 @@ export default function BestellungDetail() {
|
||||
const historie = data?.historie ?? [];
|
||||
|
||||
const canEdit = hasPermission('bestellungen:create');
|
||||
const nextStatus = bestellung ? getNextStatus(bestellung.status) : null;
|
||||
const validTransitions = bestellung ? STATUS_TRANSITIONS[bestellung.status] : [];
|
||||
|
||||
// ── Mutations ──
|
||||
|
||||
@@ -122,7 +128,7 @@ export default function BestellungDetail() {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['bestellung', orderId] });
|
||||
showSuccess('Status aktualisiert');
|
||||
setStatusConfirmOpen(false);
|
||||
setStatusConfirmTarget(null);
|
||||
},
|
||||
onError: () => showError('Fehler beim Aktualisieren des Status'),
|
||||
});
|
||||
@@ -322,11 +328,40 @@ export default function BestellungDetail() {
|
||||
</Grid>
|
||||
|
||||
{/* ── Status Action ── */}
|
||||
{canEdit && nextStatus && (
|
||||
{canEdit && validTransitions.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Button variant="contained" onClick={() => setStatusConfirmOpen(true)}>
|
||||
Status ändern: {BESTELLUNG_STATUS_LABELS[nextStatus]}
|
||||
</Button>
|
||||
{validTransitions.length === 1 ? (
|
||||
<Button variant="contained" onClick={() => setStatusConfirmTarget(validTransitions[0])}>
|
||||
Status ändern: {BESTELLUNG_STATUS_LABELS[validTransitions[0]]}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
endIcon={<ArrowDropDown />}
|
||||
onClick={(e) => setStatusMenuAnchor(e.currentTarget)}
|
||||
>
|
||||
Status ändern
|
||||
</Button>
|
||||
<Menu
|
||||
anchorEl={statusMenuAnchor}
|
||||
open={Boolean(statusMenuAnchor)}
|
||||
onClose={() => setStatusMenuAnchor(null)}
|
||||
>
|
||||
{validTransitions.map((s) => (
|
||||
<MenuItem
|
||||
key={s}
|
||||
onClick={() => {
|
||||
setStatusMenuAnchor(null);
|
||||
setStatusConfirmTarget(s);
|
||||
}}
|
||||
>
|
||||
{BESTELLUNG_STATUS_LABELS[s]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -630,17 +665,17 @@ export default function BestellungDetail() {
|
||||
{/* ══════════════════════════════════════════════════════════════════════ */}
|
||||
|
||||
{/* Status Confirmation */}
|
||||
<Dialog open={statusConfirmOpen} onClose={() => setStatusConfirmOpen(false)}>
|
||||
<Dialog open={statusConfirmTarget != null} onClose={() => setStatusConfirmTarget(null)}>
|
||||
<DialogTitle>Status ändern</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Status von <strong>{BESTELLUNG_STATUS_LABELS[bestellung.status]}</strong> auf{' '}
|
||||
<strong>{nextStatus ? BESTELLUNG_STATUS_LABELS[nextStatus] : ''}</strong> ändern?
|
||||
<strong>{statusConfirmTarget ? BESTELLUNG_STATUS_LABELS[statusConfirmTarget] : ''}</strong> ändern?
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setStatusConfirmOpen(false)}>Abbrechen</Button>
|
||||
<Button variant="contained" onClick={() => nextStatus && updateStatus.mutate(nextStatus)} disabled={updateStatus.isPending}>
|
||||
<Button onClick={() => setStatusConfirmTarget(null)}>Abbrechen</Button>
|
||||
<Button variant="contained" onClick={() => statusConfirmTarget && updateStatus.mutate(statusConfirmTarget)} disabled={updateStatus.isPending}>
|
||||
Bestätigen
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
Reference in New Issue
Block a user