This commit is contained in:
2025-11-29 17:01:20 +01:00
parent 7e27aa9aad
commit 8cba8db549
11 changed files with 989 additions and 59 deletions

View File

@@ -0,0 +1,101 @@
import { useState, useEffect } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import { Box, Tabs, Tab, Paper, Typography, Breadcrumbs, Link } from "@mui/material";
import {
Dashboard as DashboardIcon,
Inventory as ArticleIcon,
Place as LocationIcon,
SwapHoriz as MovementIcon,
Assessment as StockIcon,
FactCheck as InventoryIcon,
} from "@mui/icons-material";
const navItems = [
{ label: "Dashboard", path: "/warehouse", icon: <DashboardIcon fontSize="small" /> },
{ label: "Articoli", path: "/warehouse/articles", icon: <ArticleIcon fontSize="small" /> },
{ label: "Magazzini", path: "/warehouse/locations", icon: <LocationIcon fontSize="small" /> },
{ label: "Movimenti", path: "/warehouse/movements", icon: <MovementIcon fontSize="small" /> },
{ label: "Giacenze", path: "/warehouse/stock", icon: <StockIcon fontSize="small" /> },
{ label: "Inventario", path: "/warehouse/inventory", icon: <InventoryIcon fontSize="small" /> },
];
export default function WarehouseLayout() {
const location = useLocation();
const navigate = useNavigate();
const [value, setValue] = useState(0);
useEffect(() => {
// Find the matching tab based on current path
const index = navItems.findIndex((item) => {
if (item.path === "/warehouse") {
return location.pathname === "/warehouse";
}
return location.pathname.startsWith(item.path);
});
if (index !== -1) {
setValue(index);
} else {
// If no match (e.g. sub-pages), keep the closest parent or default
// Logic could be improved here but keeping it simple for now
if (location.pathname.includes("articles")) setValue(1);
else if (location.pathname.includes("locations")) setValue(2);
else if (location.pathname.includes("movements")) setValue(3);
else if (location.pathname.includes("stock")) setValue(4);
else if (location.pathname.includes("inventory")) setValue(5);
else setValue(0);
}
}, [location.pathname]);
const handleChange = (_event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
navigate(navItems[newValue].path);
};
return (
<Box sx={{ display: "flex", flexDirection: "column", height: "100%", gap: 2 }}>
{/* Header & Navigation */}
<Paper sx={{ px: 2, pt: 2, pb: 0 }}>
<Box sx={{ mb: 2 }}>
<Typography variant="h5" component="h1" fontWeight="bold" gutterBottom>
Gestione Magazzino
</Typography>
<Breadcrumbs aria-label="breadcrumb">
<Link underline="hover" color="inherit" href="/">
Home
</Link>
<Typography color="text.primary">Magazzino</Typography>
{navItems[value]?.label !== "Dashboard" && (
<Typography color="text.primary">{navItems[value]?.label}</Typography>
)}
</Breadcrumbs>
</Box>
<Tabs
value={value}
onChange={handleChange}
variant="scrollable"
scrollButtons="auto"
aria-label="warehouse navigation tabs"
sx={{ borderBottom: 1, borderColor: "divider" }}
>
{navItems.map((item, index) => (
<Tab
key={item.path}
label={item.label}
icon={item.icon}
iconPosition="start"
id={`warehouse-tab-${index}`}
aria-controls={`warehouse-tabpanel-${index}`}
/>
))}
</Tabs>
</Paper>
{/* Content Area */}
<Box sx={{ flex: 1, minHeight: 0, overflow: "auto" }}>
<Outlet />
</Box>
</Box>
);
}

View File

@@ -104,15 +104,15 @@ export function useWarehouseNavigation() {
// Inventory
const goToInventories = useCallback(() => {
navigate('/warehouse/inventories');
navigate('/warehouse/inventory');
}, [navigate]);
const goToInventory = useCallback((id: number) => {
navigate(`/warehouse/inventories/${id}`);
navigate(`/warehouse/inventory/${id}`);
}, [navigate]);
const goToNewInventory = useCallback(() => {
navigate('/warehouse/inventories/new');
navigate('/warehouse/inventory/new');
}, [navigate]);
// Dashboard

View File

@@ -0,0 +1,333 @@
import { useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
Box,
Typography,
Button,
Paper,
Grid,
Chip,
Card,
CardContent,
Alert,
CircularProgress,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
} from "@mui/material";
import {
DataGrid,
GridColDef,
GridRenderCellParams,
GridToolbar,
GridRowModel,
} from "@mui/x-data-grid";
import {
PlayArrow as StartIcon,
CheckCircle as CompleteIcon,
DoneAll as ConfirmIcon,
ArrowBack as ArrowBackIcon,
} from "@mui/icons-material";
import { inventoryService } from "../services/warehouseService";
import { InventoryStatus, InventoryCountLineDto } from "../types";
import dayjs from "dayjs";
export default function InventoryCountPage() {
const { id } = useParams();
const navigate = useNavigate();
const queryClient = useQueryClient();
const inventoryId = Number(id);
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
const { data: inventory, isLoading } = useQuery({
queryKey: ["inventory", inventoryId],
queryFn: () => inventoryService.getById(inventoryId),
});
const startMutation = useMutation({
mutationFn: () => inventoryService.start(inventoryId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["inventory", inventoryId] });
},
});
const completeMutation = useMutation({
mutationFn: () => inventoryService.complete(inventoryId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["inventory", inventoryId] });
},
});
const confirmMutation = useMutation({
mutationFn: () => inventoryService.confirm(inventoryId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["inventory", inventoryId] });
setConfirmDialogOpen(false);
navigate("/warehouse/inventory");
},
});
const updateLineMutation = useMutation({
mutationFn: ({
lineId,
quantity,
}: {
lineId: number;
quantity: number;
}) => inventoryService.updateLine(lineId, quantity),
onSuccess: () => {
// Optimistic update or invalidate
queryClient.invalidateQueries({ queryKey: ["inventory", inventoryId] });
},
});
const handleProcessRowUpdate = async (newRow: GridRowModel) => {
const line = newRow as InventoryCountLineDto;
if (line.countedQuantity !== undefined && line.countedQuantity !== null) {
await updateLineMutation.mutateAsync({
lineId: line.id,
quantity: Number(line.countedQuantity),
});
}
return newRow;
};
if (isLoading || !inventory) {
return (
<Box sx={{ display: "flex", justifyContent: "center", p: 4 }}>
<CircularProgress />
</Box>
);
}
const isEditable = inventory.status === InventoryStatus.InProgress;
const columns: GridColDef[] = [
{ field: "articleCode", headerName: "Codice Articolo", width: 150 },
{
field: "articleDescription",
headerName: "Descrizione",
flex: 1,
minWidth: 200,
},
{ field: "batchNumber", headerName: "Lotto", width: 120 },
{ field: "locationCode", headerName: "Ubicazione", width: 120 },
{
field: "theoreticalQuantity",
headerName: "Qta Teorica",
width: 120,
type: "number",
valueFormatter: (value) => (value ? Number(value).toFixed(2) : "0"),
},
{
field: "countedQuantity",
headerName: "Qta Contata",
width: 150,
type: "number",
editable: isEditable,
cellClassName: isEditable ? "editable-cell" : "",
valueFormatter: (value) =>
value !== undefined && value !== null
? Number(value).toFixed(2)
: "-",
},
{
field: "difference",
headerName: "Differenza",
width: 120,
type: "number",
valueGetter: (_value, row) => {
if (
row.countedQuantity === undefined ||
row.countedQuantity === null
)
return null;
return row.countedQuantity - row.theoreticalQuantity;
},
renderCell: (params: GridRenderCellParams) => {
if (params.value === null || params.value === undefined) return "-";
const val = Number(params.value);
return (
<Typography
color={val === 0 ? "text.primary" : val < 0 ? "error" : "success"}
fontWeight={val !== 0 ? "bold" : "normal"}
>
{val > 0 ? "+" : ""}
{val.toFixed(2)}
</Typography>
);
},
},
];
return (
<Box sx={{ p: 3 }}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 3,
}}
>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Button
startIcon={<ArrowBackIcon />}
onClick={() => navigate("/warehouse/inventory")}
>
Indietro
</Button>
<Typography variant="h4">
Inventario: {inventory.description}
</Typography>
<Chip
label={inventory.status}
color={
inventory.status === InventoryStatus.Confirmed
? "success"
: inventory.status === InventoryStatus.InProgress
? "primary"
: "default"
}
/>
</Box>
<Box sx={{ display: "flex", gap: 2 }}>
{inventory.status === InventoryStatus.Draft && (
<Button
variant="contained"
color="primary"
startIcon={<StartIcon />}
onClick={() => startMutation.mutate()}
>
Avvia Inventario
</Button>
)}
{inventory.status === InventoryStatus.InProgress && (
<Button
variant="contained"
color="warning"
startIcon={<CompleteIcon />}
onClick={() => completeMutation.mutate()}
>
Completa Conteggio
</Button>
)}
{inventory.status === InventoryStatus.Completed && (
<Button
variant="contained"
color="success"
startIcon={<ConfirmIcon />}
onClick={() => setConfirmDialogOpen(true)}
>
Conferma e Rettifica
</Button>
)}
</Box>
</Box>
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid size={{ xs: 12, md: 3 }}>
<Card>
<CardContent>
<Typography color="text.secondary" gutterBottom>
Data Inventario
</Typography>
<Typography variant="h6">
{dayjs(inventory.inventoryDate).format("DD/MM/YYYY")}
</Typography>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 3 }}>
<Card>
<CardContent>
<Typography color="text.secondary" gutterBottom>
Magazzino
</Typography>
<Typography variant="h6">{inventory.warehouseName}</Typography>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 3 }}>
<Card>
<CardContent>
<Typography color="text.secondary" gutterBottom>
Righe Totali
</Typography>
<Typography variant="h6">{inventory.lineCount}</Typography>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 3 }}>
<Card>
<CardContent>
<Typography color="text.secondary" gutterBottom>
Righe Contate
</Typography>
<Typography variant="h6">
{inventory.lines.filter((l) => l.countedQuantity !== null).length}
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
{inventory.status === InventoryStatus.Completed && (
<Alert severity="warning" sx={{ mb: 3 }}>
L'inventario è completato. Verifica le differenze prima di confermare.
La conferma genererà automaticamente i movimenti di rettifica.
</Alert>
)}
<Paper sx={{ height: 600, width: "100%" }}>
<DataGrid
rows={inventory.lines}
columns={columns}
processRowUpdate={handleProcessRowUpdate}
onProcessRowUpdateError={(error) => console.error(error)}
slots={{ toolbar: GridToolbar }}
slotProps={{
toolbar: {
showQuickFilter: true,
},
}}
disableRowSelectionOnClick
sx={{
"& .editable-cell": {
backgroundColor: "#f0f8ff",
},
}}
/>
</Paper>
<Dialog
open={confirmDialogOpen}
onClose={() => setConfirmDialogOpen(false)}
>
<DialogTitle>Conferma Inventario</DialogTitle>
<DialogContent>
<DialogContentText>
Sei sicuro di voler confermare l'inventario? Questa operazione è
irreversibile e genererà i movimenti di rettifica per le differenze
riscontrate.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setConfirmDialogOpen(false)}>Annulla</Button>
<Button
onClick={() => confirmMutation.mutate()}
color="success"
variant="contained"
autoFocus
>
Conferma
</Button>
</DialogActions>
</Dialog>
</Box>
);
}

View File

@@ -0,0 +1,258 @@
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
Box,
Typography,
Button,
Paper,
Grid,
TextField,
MenuItem,
FormControl,
InputLabel,
Select,
Breadcrumbs,
Link,
CircularProgress,
} from "@mui/material";
import { Save as SaveIcon, ArrowBack as ArrowBackIcon } from "@mui/icons-material";
import {
inventoryService,
warehouseLocationService,
categoryService,
} from "../services/warehouseService";
import {
CreateInventoryCountDto,
InventoryType,
} from "../types";
import dayjs from "dayjs";
export default function InventoryFormPage() {
const { id } = useParams();
const navigate = useNavigate();
const queryClient = useQueryClient();
const isEditing = !!id;
const [formData, setFormData] = useState<CreateInventoryCountDto>({
description: "",
inventoryDate: dayjs().format("YYYY-MM-DD"),
type: InventoryType.Full,
notes: "",
});
const { data: warehouses = [] } = useQuery({
queryKey: ["warehouse-locations"],
queryFn: () => warehouseLocationService.getAll(),
});
const { data: categories = [] } = useQuery({
queryKey: ["categories"],
queryFn: () => categoryService.getAll(),
});
const { data: inventory, isLoading: isLoadingInventory } = useQuery({
queryKey: ["inventory", id],
queryFn: () => inventoryService.getById(Number(id)),
enabled: isEditing,
});
useEffect(() => {
if (inventory) {
setFormData({
description: inventory.description,
inventoryDate: dayjs(inventory.inventoryDate).format("YYYY-MM-DD"),
warehouseId: inventory.warehouseId,
categoryId: inventory.categoryId,
type: inventory.type,
notes: inventory.notes,
});
}
}, [inventory]);
const createMutation = useMutation({
mutationFn: (data: CreateInventoryCountDto) => inventoryService.create(data),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["inventory-counts"] });
navigate(`/warehouse/inventory/${data.id}/count`);
},
});
// TODO: Add update mutation if needed, currently only creation is critical
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (isEditing) {
// Implement update logic if needed
} else {
createMutation.mutate(formData);
}
};
if (isEditing && isLoadingInventory) {
return (
<Box sx={{ display: "flex", justifyContent: "center", p: 4 }}>
<CircularProgress />
</Box>
);
}
return (
<Box sx={{ p: 3 }}>
<Breadcrumbs sx={{ mb: 2 }}>
<Link
color="inherit"
href="#"
onClick={(e) => {
e.preventDefault();
navigate("/warehouse/inventory");
}}
>
Inventari
</Link>
<Typography color="text.primary">
{isEditing ? "Modifica Inventario" : "Nuovo Inventario"}
</Typography>
</Breadcrumbs>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 3,
}}
>
<Typography variant="h4">
{isEditing ? `Inventario ${inventory?.code}` : "Nuovo Inventario"}
</Typography>
<Button
startIcon={<ArrowBackIcon />}
onClick={() => navigate("/warehouse/inventory")}
>
Indietro
</Button>
</Box>
<Paper sx={{ p: 3 }}>
<form onSubmit={handleSubmit}>
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 6 }}>
<TextField
label="Descrizione"
fullWidth
required
value={formData.description}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<TextField
label="Data Inventario"
type="date"
fullWidth
required
InputLabelProps={{ shrink: true }}
value={formData.inventoryDate}
onChange={(e) =>
setFormData({ ...formData, inventoryDate: e.target.value })
}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<FormControl fullWidth required>
<InputLabel>Magazzino</InputLabel>
<Select
value={formData.warehouseId || ""}
label="Magazzino"
onChange={(e) =>
setFormData({
...formData,
warehouseId: Number(e.target.value),
})
}
disabled={isEditing} // Cannot change warehouse after creation usually
>
{warehouses.map((w) => (
<MenuItem key={w.id} value={w.id}>
{w.name} ({w.code})
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<FormControl fullWidth>
<InputLabel>Categoria (Opzionale)</InputLabel>
<Select
value={formData.categoryId || ""}
label="Categoria (Opzionale)"
onChange={(e) =>
setFormData({
...formData,
categoryId: e.target.value ? Number(e.target.value) : undefined,
})
}
>
<MenuItem value="">
<em>Tutte</em>
</MenuItem>
{categories.map((c) => (
<MenuItem key={c.id} value={c.id}>
{c.name}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<FormControl fullWidth required>
<InputLabel>Tipo Inventario</InputLabel>
<Select
value={formData.type}
label="Tipo Inventario"
onChange={(e) =>
setFormData({
...formData,
type: Number(e.target.value) as InventoryType,
})
}
>
<MenuItem value={InventoryType.Full}>Completo</MenuItem>
<MenuItem value={InventoryType.Partial}>Parziale</MenuItem>
<MenuItem value={InventoryType.Cyclic}>Ciclico</MenuItem>
<MenuItem value={InventoryType.Sample}>A Campione</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12 }}>
<TextField
label="Note"
fullWidth
multiline
rows={4}
value={formData.notes || ""}
onChange={(e) =>
setFormData({ ...formData, notes: e.target.value })
}
/>
</Grid>
<Grid size={{ xs: 12 }} sx={{ display: "flex", justifyContent: "flex-end" }}>
<Button
type="submit"
variant="contained"
size="large"
startIcon={<SaveIcon />}
disabled={createMutation.isPending}
>
{isEditing ? "Salva Modifiche" : "Crea e Inizia"}
</Button>
</Grid>
</Grid>
</form>
</Paper>
</Box>
);
}

View File

@@ -0,0 +1,204 @@
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import {
Box,
Typography,
Button,
Paper,
Chip,
IconButton,
Tooltip,
} from "@mui/material";
import {
DataGrid,
GridColDef,
GridRenderCellParams,
GridToolbar,
} from "@mui/x-data-grid";
import {
Add as AddIcon,
Visibility as ViewIcon,
PlayArrow as StartIcon,
Cancel as CancelIcon,
} from "@mui/icons-material";
import { inventoryService } from "../services/warehouseService";
import { InventoryCountDto, InventoryStatus } from "../types";
import dayjs from "dayjs";
export default function InventoryListPage() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const [statusFilter] = useState<InventoryStatus | undefined>(
undefined
);
const { data: inventories = [], isLoading } = useQuery({
queryKey: ["inventory-counts", statusFilter],
queryFn: () => inventoryService.getAll(statusFilter),
});
const cancelMutation = useMutation({
mutationFn: (id: number) => inventoryService.cancel(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["inventory-counts"] });
},
});
const handleCreate = () => {
navigate("/warehouse/inventory/new");
};
const handleView = (id: number) => {
navigate(`/warehouse/inventory/${id}`);
};
const handleStart = (id: number) => {
navigate(`/warehouse/inventory/${id}/count`);
};
const getStatusChip = (status: InventoryStatus) => {
switch (status) {
case InventoryStatus.Draft:
return <Chip label="Bozza" size="small" />;
case InventoryStatus.InProgress:
return <Chip label="In Corso" color="primary" size="small" />;
case InventoryStatus.Completed:
return <Chip label="Completato" color="info" size="small" />;
case InventoryStatus.Confirmed:
return <Chip label="Confermato" color="success" size="small" />;
case InventoryStatus.Cancelled:
return <Chip label="Annullato" color="error" size="small" />;
default:
return <Chip label={status} size="small" />;
}
};
const columns: GridColDef[] = [
{ field: "code", headerName: "Codice", width: 120 },
{ field: "description", headerName: "Descrizione", flex: 1, minWidth: 200 },
{
field: "inventoryDate",
headerName: "Data Inventario",
width: 150,
valueFormatter: (value) =>
value ? dayjs(value).format("DD/MM/YYYY") : "",
},
{ field: "warehouseName", headerName: "Magazzino", width: 180 },
{ field: "categoryName", headerName: "Categoria", width: 150 },
{
field: "status",
headerName: "Stato",
width: 120,
renderCell: (params: GridRenderCellParams<InventoryCountDto>) =>
getStatusChip(params.row.status),
},
{
field: "progress",
headerName: "Progresso",
width: 150,
valueGetter: (_value, row) => {
if (!row.lineCount) return "0%";
const percentage = Math.round(
(row.countedLineCount / row.lineCount) * 100
);
return `${row.countedLineCount}/${row.lineCount} (${percentage}%)`;
},
},
{
field: "actions",
headerName: "Azioni",
width: 180,
sortable: false,
renderCell: (params: GridRenderCellParams<InventoryCountDto>) => (
<Box>
<Tooltip title="Dettaglio">
<IconButton size="small" onClick={() => handleView(params.row.id)}>
<ViewIcon />
</IconButton>
</Tooltip>
{params.row.status === InventoryStatus.Draft && (
<Tooltip title="Avvia Conteggio">
<IconButton
size="small"
color="primary"
onClick={() => handleStart(params.row.id)}
>
<StartIcon />
</IconButton>
</Tooltip>
)}
{params.row.status === InventoryStatus.InProgress && (
<Tooltip title="Continua Conteggio">
<IconButton
size="small"
color="primary"
onClick={() => handleStart(params.row.id)}
>
<StartIcon />
</IconButton>
</Tooltip>
)}
{params.row.status === InventoryStatus.Draft && (
<Tooltip title="Annulla">
<IconButton
size="small"
color="error"
onClick={() => {
if (confirm("Sei sicuro di voler annullare questo inventario?")) {
cancelMutation.mutate(params.row.id);
}
}}
>
<CancelIcon />
</IconButton>
</Tooltip>
)}
</Box>
),
},
];
return (
<Box sx={{ p: 3 }}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 3,
}}
>
<Typography variant="h4">Inventari Fisici</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={handleCreate}
>
Nuovo Inventario
</Button>
</Box>
<Paper sx={{ height: 600, width: "100%" }}>
<DataGrid
rows={inventories}
columns={columns}
loading={isLoading}
slots={{ toolbar: GridToolbar }}
slotProps={{
toolbar: {
showQuickFilter: true,
},
}}
disableRowSelectionOnClick
initialState={{
pagination: { paginationModel: { pageSize: 25 } },
sorting: {
sortModel: [{ field: "inventoryDate", sort: "desc" }],
},
}}
/>
</Paper>
</Box>
);
}

View File

@@ -174,38 +174,30 @@ export default function WarehouseDashboard() {
return (
<Box>
{/* Header */}
{/* Actions */}
<Box
sx={{
mb: 4,
display: "flex",
justifyContent: "space-between",
justifyContent: "flex-end",
alignItems: "center",
gap: 1
}}
>
<Box>
<Typography variant="h4" fontWeight="bold">
Magazzino
</Typography>
<Typography variant="body1" color="text.secondary">
Dashboard e panoramica giacenze
</Typography>
</Box>
<Box sx={{ display: "flex", gap: 1 }}>
<Button
variant="outlined"
startIcon={<AddIcon />}
onClick={nav.goToNewInbound}
>
Nuovo Carico
</Button>
<Button
variant="contained"
startIcon={<AssessmentIcon />}
onClick={nav.goToStockLevels}
>
Giacenze
</Button>
</Box>
<Button
variant="outlined"
startIcon={<AddIcon />}
onClick={nav.goToNewInbound}
>
Nuovo Carico
</Button>
<Button
variant="contained"
startIcon={<AssessmentIcon />}
onClick={nav.goToStockLevels}
>
Giacenze
</Button>
</Box>
{/* Stats Cards */}
@@ -313,11 +305,11 @@ export default function WarehouseDashboard() {
size="small"
color={
getMovementTypeColor(movement.type) as
| "success"
| "error"
| "info"
| "warning"
| "default"
| "success"
| "error"
| "info"
| "warning"
| "default"
}
/>
</Box>

View File

@@ -16,3 +16,8 @@ export { default as TransferMovementPage } from './TransferMovementPage';
// Stock
export { default as StockLevelsPage } from './StockLevelsPage';
// Inventory
export { default as InventoryListPage } from './InventoryListPage';
export { default as InventoryFormPage } from './InventoryFormPage';
export { default as InventoryCountPage } from './InventoryCountPage';

View File

@@ -10,42 +10,55 @@ import {
OutboundMovementPage,
TransferMovementPage,
StockLevelsPage,
InventoryListPage,
InventoryFormPage,
InventoryCountPage,
} from "./pages";
import WarehouseLayout from "./components/WarehouseLayout";
export default function WarehouseRoutes() {
return (
<WarehouseProvider>
<Routes>
{/* Dashboard */}
<Route index element={<WarehouseDashboard />} />
<Route element={<WarehouseLayout />}>
{/* Dashboard */}
<Route index element={<WarehouseDashboard />} />
{/* Articles */}
<Route path="articles" element={<ArticlesPage />} />
<Route path="articles/new" element={<ArticleFormPage />} />
<Route path="articles/:id" element={<ArticleFormPage />} />
<Route path="articles/:id/edit" element={<ArticleFormPage />} />
{/* Articles */}
<Route path="articles" element={<ArticlesPage />} />
<Route path="articles/new" element={<ArticleFormPage />} />
<Route path="articles/:id" element={<ArticleFormPage />} />
<Route path="articles/:id/edit" element={<ArticleFormPage />} />
{/* Warehouse Locations */}
<Route path="locations" element={<WarehouseLocationsPage />} />
{/* Warehouse Locations */}
<Route path="locations" element={<WarehouseLocationsPage />} />
{/* Movements */}
<Route path="movements" element={<MovementsPage />} />
<Route path="movements/:id" element={<MovementsPage />} />
<Route path="movements/inbound/new" element={<InboundMovementPage />} />
<Route
path="movements/outbound/new"
element={<OutboundMovementPage />}
/>
<Route
path="movements/transfer/new"
element={<TransferMovementPage />}
/>
{/* Movements */}
<Route path="movements" element={<MovementsPage />} />
<Route path="movements/:id" element={<MovementsPage />} />
<Route path="movements/inbound/new" element={<InboundMovementPage />} />
<Route
path="movements/outbound/new"
element={<OutboundMovementPage />}
/>
<Route
path="movements/transfer/new"
element={<TransferMovementPage />}
/>
{/* Stock */}
<Route path="stock" element={<StockLevelsPage />} />
{/* Stock */}
<Route path="stock" element={<StockLevelsPage />} />
{/* Fallback */}
<Route path="*" element={<WarehouseDashboard />} />
{/* Inventory */}
<Route path="inventory" element={<InventoryListPage />} />
<Route path="inventory/new" element={<InventoryFormPage />} />
<Route path="inventory/:id" element={<InventoryFormPage />} />
<Route path="inventory/:id/count" element={<InventoryCountPage />} />
{/* Fallback */}
<Route path="*" element={<WarehouseDashboard />} />
</Route>
</Routes>
</WarehouseProvider>
);