new features
This commit is contained in:
@@ -26,7 +26,7 @@ import {
|
||||
Tooltip,
|
||||
Autocomplete,
|
||||
} from '@mui/material';
|
||||
import { Add as AddIcon, Edit as EditIcon, Delete as DeleteIcon } from '@mui/icons-material';
|
||||
import { Add as AddIcon, Edit as EditIcon, Delete as DeleteIcon, RemoveCircleOutline as RemoveIcon } from '@mui/icons-material';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
@@ -35,7 +35,7 @@ import { useNotification } from '../contexts/NotificationContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { bestellungApi } from '../services/bestellung';
|
||||
import { BESTELLUNG_STATUS_LABELS, BESTELLUNG_STATUS_COLORS } from '../types/bestellung.types';
|
||||
import type { BestellungStatus, BestellungFormData, LieferantFormData, Lieferant } from '../types/bestellung.types';
|
||||
import type { BestellungStatus, BestellungFormData, BestellpositionFormData, LieferantFormData, Lieferant } from '../types/bestellung.types';
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
@@ -61,8 +61,9 @@ const ALL_STATUSES: BestellungStatus[] = ['entwurf', 'erstellt', 'bestellt', 'te
|
||||
|
||||
// ── Empty form data ──
|
||||
|
||||
const emptyOrderForm: BestellungFormData = { bezeichnung: '', lieferant_id: undefined, besteller_id: '', budget: undefined, notizen: '' };
|
||||
const emptyOrderForm: BestellungFormData = { bezeichnung: '', lieferant_id: undefined, besteller_id: '', budget: undefined, notizen: '', positionen: [] };
|
||||
const emptyVendorForm: LieferantFormData = { name: '', kontakt_name: '', email: '', telefon: '', adresse: '', website: '', notizen: '' };
|
||||
const emptyPosition: BestellpositionFormData = { bezeichnung: '', menge: 1, einheit: 'Stk' };
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// Component
|
||||
@@ -330,12 +331,13 @@ export default function Bestellungen() {
|
||||
</TabPanel>
|
||||
|
||||
{/* ── Create Order Dialog ── */}
|
||||
<Dialog open={orderDialogOpen} onClose={() => setOrderDialogOpen(false)} maxWidth="sm" fullWidth>
|
||||
<Dialog open={orderDialogOpen} onClose={() => setOrderDialogOpen(false)} maxWidth="md" fullWidth>
|
||||
<DialogTitle>Neue Bestellung</DialogTitle>
|
||||
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: '16px !important' }}>
|
||||
<TextField
|
||||
label="Bezeichnung"
|
||||
required
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={orderForm.bezeichnung}
|
||||
onChange={(e) => setOrderForm((f) => ({ ...f, bezeichnung: e.target.value }))}
|
||||
/>
|
||||
@@ -378,13 +380,6 @@ export default function Bestellungen() {
|
||||
onChange={(_e, v) => setOrderForm((f) => ({ ...f, besteller_id: v?.id || '' }))}
|
||||
renderInput={(params) => <TextField {...params} label="Besteller" />}
|
||||
/>
|
||||
<TextField
|
||||
label="Budget"
|
||||
type="number"
|
||||
value={orderForm.budget ?? ''}
|
||||
onChange={(e) => setOrderForm((f) => ({ ...f, budget: e.target.value ? Number(e.target.value) : undefined }))}
|
||||
inputProps={{ min: 0, step: 0.01 }}
|
||||
/>
|
||||
<TextField
|
||||
label="Notizen"
|
||||
multiline
|
||||
@@ -392,6 +387,69 @@ export default function Bestellungen() {
|
||||
value={orderForm.notizen || ''}
|
||||
onChange={(e) => setOrderForm((f) => ({ ...f, notizen: e.target.value }))}
|
||||
/>
|
||||
|
||||
{/* ── Dynamic Items List ── */}
|
||||
<Typography variant="subtitle2" sx={{ mt: 1 }}>Positionen</Typography>
|
||||
{(orderForm.positionen || []).map((pos, idx) => (
|
||||
<Box key={idx} sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||
<TextField
|
||||
label="Bezeichnung"
|
||||
size="small"
|
||||
required
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={pos.bezeichnung}
|
||||
onChange={(e) => {
|
||||
const next = [...(orderForm.positionen || [])];
|
||||
next[idx] = { ...next[idx], bezeichnung: e.target.value };
|
||||
setOrderForm((f) => ({ ...f, positionen: next }));
|
||||
}}
|
||||
sx={{ flexGrow: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
label="Menge"
|
||||
size="small"
|
||||
type="number"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={pos.menge}
|
||||
onChange={(e) => {
|
||||
const next = [...(orderForm.positionen || [])];
|
||||
next[idx] = { ...next[idx], menge: Math.max(1, Number(e.target.value) || 1) };
|
||||
setOrderForm((f) => ({ ...f, positionen: next }));
|
||||
}}
|
||||
sx={{ width: 90 }}
|
||||
inputProps={{ min: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
label="Einheit"
|
||||
size="small"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={pos.einheit || 'Stk'}
|
||||
onChange={(e) => {
|
||||
const next = [...(orderForm.positionen || [])];
|
||||
next[idx] = { ...next[idx], einheit: e.target.value };
|
||||
setOrderForm((f) => ({ ...f, positionen: next }));
|
||||
}}
|
||||
sx={{ width: 100 }}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => {
|
||||
const next = (orderForm.positionen || []).filter((_, i) => i !== idx);
|
||||
setOrderForm((f) => ({ ...f, positionen: next }));
|
||||
}}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
))}
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setOrderForm((f) => ({ ...f, positionen: [...(f.positionen || []), { ...emptyPosition }] }))}
|
||||
>
|
||||
Position hinzufügen
|
||||
</Button>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOrderDialogOpen(false)}>Abbrechen</Button>
|
||||
|
||||
@@ -653,11 +653,12 @@ export default function Shop() {
|
||||
};
|
||||
|
||||
const tabIndex = useMemo(() => {
|
||||
const map: Record<string, number> = { katalog: 0 };
|
||||
let next = 1;
|
||||
const map: Record<string, number> = {};
|
||||
let next = 0;
|
||||
if (canCreate) { map.meine = next; next++; }
|
||||
if (canApprove) { map.alle = next; next++; }
|
||||
if (canViewOverview) { map.uebersicht = next; }
|
||||
if (canViewOverview) { map.uebersicht = next; next++; }
|
||||
map.katalog = next;
|
||||
return map;
|
||||
}, [canCreate, canApprove, canViewOverview]);
|
||||
|
||||
@@ -675,17 +676,17 @@ export default function Shop() {
|
||||
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
|
||||
<Tabs value={activeTab} onChange={handleTabChange} variant="scrollable" scrollButtons="auto">
|
||||
<Tab label="Katalog" />
|
||||
{canCreate && <Tab label="Meine Anfragen" />}
|
||||
{canApprove && <Tab label="Alle Anfragen" />}
|
||||
{canViewOverview && <Tab label="Übersicht" />}
|
||||
<Tab label="Katalog" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{activeTab === tabIndex.katalog && <KatalogTab />}
|
||||
{canCreate && activeTab === tabIndex.meine && <MeineAnfragenTab />}
|
||||
{canApprove && activeTab === tabIndex.alle && <AlleAnfragenTab />}
|
||||
{canViewOverview && activeTab === tabIndex.uebersicht && <UebersichtTab />}
|
||||
{activeTab === tabIndex.katalog && <KatalogTab />}
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user