refactor external orders
This commit is contained in:
@@ -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'} · {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'} · {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 */}
|
||||
{/* ══════════════════════════════════════════════════════════════════════ */}
|
||||
|
||||
Reference in New Issue
Block a user