refactor external orders

This commit is contained in:
Matthias Hochmeister
2026-03-25 14:26:41 +01:00
parent 561334791b
commit 5add6590e5
10 changed files with 740 additions and 259 deletions

View File

@@ -24,6 +24,9 @@ import {
Checkbox,
Menu,
MenuItem,
Accordion,
AccordionSummary,
AccordionDetails,
} from '@mui/material';
import {
ArrowBack,
@@ -38,6 +41,7 @@ import {
Upload as UploadIcon,
ArrowDropDown,
MoreVert,
ExpandMore as ExpandMoreIcon,
} from '@mui/icons-material';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useParams, useNavigate } from 'react-router-dom';
@@ -104,6 +108,8 @@ export default function BestellungDetail() {
const [deleteItemTarget, setDeleteItemTarget] = useState<number | null>(null);
const [deleteFileTarget, setDeleteFileTarget] = useState<number | null>(null);
const [editMode, setEditMode] = useState(false);
const [reminderForm, setReminderForm] = useState<ErinnerungFormData>({ faellig_am: '', nachricht: '' });
const [reminderFormOpen, setReminderFormOpen] = useState(false);
const [deleteReminderTarget, setDeleteReminderTarget] = useState<number | null>(null);
@@ -124,6 +130,7 @@ export default function BestellungDetail() {
const canCreate = hasPermission('bestellungen:create');
const canDelete = hasPermission('bestellungen:delete');
const canManageReminders = hasPermission('bestellungen:manage_reminders');
const canManageOrders = hasPermission('bestellungen:manage_orders');
const validTransitions = bestellung ? STATUS_TRANSITIONS[bestellung.status] : [];
// All statuses except current, for force override
@@ -322,25 +329,19 @@ export default function BestellungDetail() {
{/* ── Info Cards ── */}
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6} md={3}>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Lieferant</Typography>
<Typography>{bestellung.lieferant_name || ''}</Typography>
</CardContent></Card>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Besteller</Typography>
<Typography>{bestellung.besteller_name || ''}</Typography>
</CardContent></Card>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Budget</Typography>
<Typography>{formatCurrency(bestellung.budget)}</Typography>
</CardContent></Card>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Erstellt am</Typography>
<Typography>{formatDate(bestellung.erstellt_am)}</Typography>
@@ -349,7 +350,7 @@ export default function BestellungDetail() {
</Grid>
{/* ── Status Action ── */}
{canCreate && (
{canManageOrders && (
<Box sx={{ mb: 3, display: 'flex', gap: 1, alignItems: 'center' }}>
{validTransitions.length === 1 ? (
<Button variant="contained" onClick={() => { setStatusForce(false); setStatusConfirmTarget(validTransitions[0]); }}>
@@ -440,7 +441,19 @@ export default function BestellungDetail() {
{/* Positionen */}
{/* ══════════════════════════════════════════════════════════════════════ */}
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" sx={{ mb: 2 }}>Positionen</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Typography variant="h6" sx={{ flexGrow: 1 }}>Positionen</Typography>
{canCreate && (
<Button
size="small"
variant={editMode ? 'outlined' : 'text'}
startIcon={editMode ? <CloseIcon /> : <EditIcon />}
onClick={() => setEditMode((m) => !m)}
>
{editMode ? 'Abbrechen' : 'Bearbeiten'}
</Button>
)}
</Box>
<TableContainer>
<Table size="small">
<TableHead>
@@ -457,7 +470,7 @@ export default function BestellungDetail() {
</TableHead>
<TableBody>
{positionen.map((p) =>
editingItemId === p.id ? (
editMode && editingItemId === p.id ? (
<TableRow key={p.id}>
<TableCell>
<TextField size="small" value={editingItemData.bezeichnung || ''} onChange={(e) => setEditingItemData((d) => ({ ...d, bezeichnung: e.target.value }))} />
@@ -490,7 +503,7 @@ export default function BestellungDetail() {
<TableCell align="right">{formatCurrency(p.einzelpreis)}</TableCell>
<TableCell align="right">{formatCurrency((p.einzelpreis ?? 0) * p.menge)}</TableCell>
<TableCell align="right">
{canCreate ? (
{canManageOrders ? (
<TextField
size="small"
type="number"
@@ -505,8 +518,8 @@ export default function BestellungDetail() {
</TableCell>
{(canCreate || canDelete) && (
<TableCell align="right">
{canCreate && <IconButton size="small" onClick={() => startEditItem(p)}><EditIcon fontSize="small" /></IconButton>}
{canDelete && <IconButton size="small" color="error" onClick={() => setDeleteItemTarget(p.id)}><DeleteIcon fontSize="small" /></IconButton>}
{editMode && canCreate && <IconButton size="small" onClick={() => startEditItem(p)}><EditIcon fontSize="small" /></IconButton>}
{editMode && canDelete && <IconButton size="small" color="error" onClick={() => setDeleteItemTarget(p.id)}><DeleteIcon fontSize="small" /></IconButton>}
</TableCell>
)}
</TableRow>
@@ -514,7 +527,7 @@ export default function BestellungDetail() {
)}
{/* ── Add Item Row ── */}
{canCreate && (
{editMode && canCreate && (
<TableRow>
<TableCell>
<TextField size="small" placeholder="Bezeichnung" value={newItem.bezeichnung} onChange={(e) => setNewItem((f) => ({ ...f, bezeichnung: e.target.value }))} />
@@ -553,7 +566,7 @@ export default function BestellungDetail() {
<TableCell colSpan={5} align="right">
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5 }}>
MwSt.
{canCreate ? (
{editMode && canCreate ? (
<TextField
size="small"
type="number"
@@ -715,38 +728,6 @@ export default function BestellungDetail() {
)}
</Paper>
{/* ══════════════════════════════════════════════════════════════════════ */}
{/* Historie */}
{/* ══════════════════════════════════════════════════════════════════════ */}
<Paper sx={{ p: 2, mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<History sx={{ mr: 1 }} />
<Typography variant="h6">Historie</Typography>
</Box>
{historie.length === 0 ? (
<Typography variant="body2" color="text.secondary">Keine Einträge</Typography>
) : (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{historie.map((h) => (
<Box key={h.id} sx={{ display: 'flex', gap: 1 }}>
<Box sx={{ width: 6, minHeight: '100%', borderRadius: 3, bgcolor: 'divider', flexShrink: 0 }} />
<Box>
<Typography variant="body2">{h.aktion}</Typography>
<Typography variant="caption" color="text.secondary">
{h.erstellt_von_name || 'System'} &middot; {formatDateTime(h.erstellt_am)}
</Typography>
{h.details && (
<Typography variant="caption" display="block" color="text.secondary">
{Object.entries(h.details).map(([k, v]) => `${k}: ${v}`).join(', ')}
</Typography>
)}
</Box>
</Box>
))}
</Box>
)}
</Paper>
{/* ── Notizen ── */}
{bestellung.notizen && (
<Paper sx={{ p: 2, mb: 3 }}>
@@ -755,6 +736,42 @@ export default function BestellungDetail() {
</Paper>
)}
{/* ══════════════════════════════════════════════════════════════════════ */}
{/* Historie */}
{/* ══════════════════════════════════════════════════════════════════════ */}
<Accordion defaultExpanded={false} sx={{ mb: 3 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<History />
<Typography variant="h6">Historie ({historie.length} Einträge)</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
{historie.length === 0 ? (
<Typography variant="body2" color="text.secondary">Keine Einträge</Typography>
) : (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{historie.map((h) => (
<Box key={h.id} sx={{ display: 'flex', gap: 1 }}>
<Box sx={{ width: 6, minHeight: '100%', borderRadius: 3, bgcolor: 'divider', flexShrink: 0 }} />
<Box>
<Typography variant="body2">{h.aktion}</Typography>
<Typography variant="caption" color="text.secondary">
{h.erstellt_von_name || 'System'} &middot; {formatDateTime(h.erstellt_am)}
</Typography>
{h.details && (
<Typography variant="caption" display="block" color="text.secondary">
{Object.entries(h.details).map(([k, v]) => `${k}: ${v}`).join(', ')}
</Typography>
)}
</Box>
</Box>
))}
</Box>
)}
</AccordionDetails>
</Accordion>
{/* ══════════════════════════════════════════════════════════════════════ */}
{/* Dialogs */}
{/* ══════════════════════════════════════════════════════════════════════ */}