new features
This commit is contained in:
@@ -20,9 +20,18 @@ import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { shopApi } from '../services/shop';
|
||||
import { bestellungApi } from '../services/bestellung';
|
||||
import { SHOP_STATUS_LABELS, SHOP_STATUS_COLORS } from '../types/shop.types';
|
||||
import type { ShopArtikel, ShopArtikelFormData, ShopAnfrageFormItem, ShopAnfrageDetailResponse, ShopAnfrageStatus } from '../types/shop.types';
|
||||
import type { ShopArtikel, ShopArtikelFormData, ShopAnfrageFormItem, ShopAnfrageDetailResponse, ShopAnfrageStatus, ShopAnfrage, ShopOverview } from '../types/shop.types';
|
||||
import type { Bestellung } from '../types/bestellung.types';
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function formatOrderId(r: ShopAnfrage): string {
|
||||
if (r.bestell_jahr && r.bestell_nummer) {
|
||||
return `${r.bestell_jahr}/${String(r.bestell_nummer).padStart(3, '0')}`;
|
||||
}
|
||||
return `#${r.id}`;
|
||||
}
|
||||
|
||||
// ─── Catalog Tab ────────────────────────────────────────────────────────────
|
||||
|
||||
interface DraftItem {
|
||||
@@ -291,7 +300,7 @@ function MeineAnfragenTab() {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width={40} />
|
||||
<TableCell>#</TableCell>
|
||||
<TableCell>Anfrage</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Positionen</TableCell>
|
||||
<TableCell>Erstellt am</TableCell>
|
||||
@@ -303,9 +312,9 @@ function MeineAnfragenTab() {
|
||||
<>
|
||||
<TableRow key={r.id} hover sx={{ cursor: 'pointer' }} onClick={() => setExpandedId(prev => prev === r.id ? null : r.id)}>
|
||||
<TableCell>{expandedId === r.id ? <ExpandLess fontSize="small" /> : <ExpandMore fontSize="small" />}</TableCell>
|
||||
<TableCell>{r.id}</TableCell>
|
||||
<TableCell>{formatOrderId(r)}</TableCell>
|
||||
<TableCell><Chip label={SHOP_STATUS_LABELS[r.status]} color={SHOP_STATUS_COLORS[r.status]} size="small" /></TableCell>
|
||||
<TableCell>{r.items_count ?? '-'}</TableCell>
|
||||
<TableCell>{r.positionen_count ?? r.items_count ?? '-'}</TableCell>
|
||||
<TableCell>{new Date(r.erstellt_am).toLocaleDateString('de-AT')}</TableCell>
|
||||
<TableCell>{r.admin_notizen || '-'}</TableCell>
|
||||
</TableRow>
|
||||
@@ -424,7 +433,7 @@ function AlleAnfragenTab() {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width={40} />
|
||||
<TableCell>#</TableCell>
|
||||
<TableCell>Anfrage</TableCell>
|
||||
<TableCell>Anfrager</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Positionen</TableCell>
|
||||
@@ -437,10 +446,10 @@ function AlleAnfragenTab() {
|
||||
<>
|
||||
<TableRow key={r.id} hover sx={{ cursor: 'pointer' }} onClick={() => setExpandedId(prev => prev === r.id ? null : r.id)}>
|
||||
<TableCell>{expandedId === r.id ? <ExpandLess fontSize="small" /> : <ExpandMore fontSize="small" />}</TableCell>
|
||||
<TableCell>{r.id}</TableCell>
|
||||
<TableCell>{formatOrderId(r)}</TableCell>
|
||||
<TableCell>{r.anfrager_name || r.anfrager_id}</TableCell>
|
||||
<TableCell><Chip label={SHOP_STATUS_LABELS[r.status]} color={SHOP_STATUS_COLORS[r.status]} size="small" /></TableCell>
|
||||
<TableCell>{r.items_count ?? '-'}</TableCell>
|
||||
<TableCell>{r.positionen_count ?? r.items_count ?? '-'}</TableCell>
|
||||
<TableCell>{new Date(r.erstellt_am).toLocaleDateString('de-AT')}</TableCell>
|
||||
<TableCell onClick={e => e.stopPropagation()}>
|
||||
<Box sx={{ display: 'flex', gap: 0.5 }}>
|
||||
@@ -558,6 +567,68 @@ function AlleAnfragenTab() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Overview Tab ────────────────────────────────────────────────────────────
|
||||
|
||||
function UebersichtTab() {
|
||||
const { data: overview, isLoading } = useQuery<ShopOverview>({
|
||||
queryKey: ['shop', 'overview'],
|
||||
queryFn: () => shopApi.getOverview(),
|
||||
});
|
||||
|
||||
if (isLoading) return <Typography color="text.secondary">Lade Übersicht...</Typography>;
|
||||
if (!overview) return <Typography color="text.secondary">Keine Daten verfügbar.</Typography>;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Grid container spacing={2} sx={{ mb: 3 }}>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Paper variant="outlined" sx={{ p: 2, textAlign: 'center' }}>
|
||||
<Typography variant="h4" fontWeight={700}>{overview.pending_count}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">Offene Anfragen</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Paper variant="outlined" sx={{ p: 2, textAlign: 'center' }}>
|
||||
<Typography variant="h4" fontWeight={700}>{overview.approved_count}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">Genehmigte Anfragen</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Paper variant="outlined" sx={{ p: 2, textAlign: 'center' }}>
|
||||
<Typography variant="h4" fontWeight={700}>{overview.total_items}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">Artikel insgesamt</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{overview.items.length === 0 ? (
|
||||
<Typography color="text.secondary">Keine offenen/genehmigten Anfragen vorhanden.</Typography>
|
||||
) : (
|
||||
<TableContainer component={Paper} variant="outlined">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Artikel</TableCell>
|
||||
<TableCell align="right">Gesamtmenge</TableCell>
|
||||
<TableCell align="right">Anfragen</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{overview.items.map(item => (
|
||||
<TableRow key={item.bezeichnung}>
|
||||
<TableCell>{item.bezeichnung}</TableCell>
|
||||
<TableCell align="right">{item.total_menge}</TableCell>
|
||||
<TableCell align="right">{item.anfrage_count}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Main Page ──────────────────────────────────────────────────────────────
|
||||
|
||||
export default function Shop() {
|
||||
@@ -567,8 +638,9 @@ export default function Shop() {
|
||||
const canView = hasPermission('shop:view');
|
||||
const canCreate = hasPermission('shop:create_request');
|
||||
const canApprove = hasPermission('shop:approve_requests');
|
||||
const canViewOverview = hasPermission('shop:view_overview');
|
||||
|
||||
const tabCount = 1 + (canCreate ? 1 : 0) + (canApprove ? 1 : 0);
|
||||
const tabCount = 1 + (canCreate ? 1 : 0) + (canApprove ? 1 : 0) + (canViewOverview ? 1 : 0);
|
||||
|
||||
const [activeTab, setActiveTab] = useState(() => {
|
||||
const t = Number(searchParams.get('tab'));
|
||||
@@ -584,9 +656,10 @@ export default function Shop() {
|
||||
const map: Record<string, number> = { katalog: 0 };
|
||||
let next = 1;
|
||||
if (canCreate) { map.meine = next; next++; }
|
||||
if (canApprove) { map.alle = next; }
|
||||
if (canApprove) { map.alle = next; next++; }
|
||||
if (canViewOverview) { map.uebersicht = next; }
|
||||
return map;
|
||||
}, [canCreate, canApprove]);
|
||||
}, [canCreate, canApprove, canViewOverview]);
|
||||
|
||||
if (!canView) {
|
||||
return (
|
||||
@@ -605,12 +678,14 @@ export default function Shop() {
|
||||
<Tab label="Katalog" />
|
||||
{canCreate && <Tab label="Meine Anfragen" />}
|
||||
{canApprove && <Tab label="Alle Anfragen" />}
|
||||
{canViewOverview && <Tab label="Übersicht" />}
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{activeTab === tabIndex.katalog && <KatalogTab />}
|
||||
{canCreate && activeTab === tabIndex.meine && <MeineAnfragenTab />}
|
||||
{canApprove && activeTab === tabIndex.alle && <AlleAnfragenTab />}
|
||||
{canViewOverview && activeTab === tabIndex.uebersicht && <UebersichtTab />}
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user