301 lines
12 KiB
TypeScript
301 lines
12 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
List,
|
|
ListItem,
|
|
ListItemButton,
|
|
ListItemIcon,
|
|
ListItemText,
|
|
Collapse,
|
|
Box,
|
|
Typography,
|
|
Divider,
|
|
Tooltip,
|
|
IconButton,
|
|
} from '@mui/material';
|
|
import {
|
|
ExpandLess,
|
|
ExpandMore,
|
|
Dashboard as DashboardIcon,
|
|
Event as EventIcon,
|
|
People as PeopleIcon,
|
|
Place as PlaceIcon,
|
|
CalendarMonth as CalendarIcon,
|
|
Print as PrintIcon,
|
|
Extension as ModulesIcon,
|
|
Warehouse as WarehouseIcon,
|
|
Code as AutoCodeIcon,
|
|
ShoppingCart as ShoppingCartIcon,
|
|
Sell as SellIcon,
|
|
Factory as ProductionIcon,
|
|
Settings as SettingsIcon,
|
|
Storage as StorageIcon,
|
|
SwapHoriz as SwapIcon,
|
|
Assignment as AssignmentIcon,
|
|
ListAlt as ListAltIcon,
|
|
Build as BuildIcon,
|
|
Timeline as TimelineIcon,
|
|
PrecisionManufacturing as ManufacturingIcon,
|
|
Category as CategoryIcon,
|
|
Folder as FolderIcon,
|
|
AttachMoney as AttachMoneyIcon,
|
|
Receipt as ReceiptIcon,
|
|
ChevronLeft,
|
|
ChevronRight,
|
|
Email as EmailIcon,
|
|
School as SchoolIcon,
|
|
} from '@mui/icons-material';
|
|
import { useLocation } from 'react-router-dom';
|
|
import { useLanguage } from '../contexts/LanguageContext';
|
|
import { useApps } from '../contexts/AppContext';
|
|
import { useTabs } from '../contexts/TabContext';
|
|
|
|
interface MenuItem {
|
|
id: string;
|
|
label: string;
|
|
tabLabel?: string;
|
|
icon?: React.ReactNode;
|
|
path?: string;
|
|
children?: MenuItem[];
|
|
appCode?: string;
|
|
translationKey?: string;
|
|
}
|
|
|
|
interface SidebarProps {
|
|
onClose?: () => void;
|
|
isCollapsed?: boolean;
|
|
onToggleCollapse?: () => void;
|
|
}
|
|
|
|
export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse }: SidebarProps) {
|
|
const { t } = useLanguage();
|
|
const { activeApps } = useApps();
|
|
const { openTab } = useTabs();
|
|
const location = useLocation();
|
|
const [openItems, setOpenItems] = useState<Record<string, boolean>>({
|
|
core: true,
|
|
warehouse: false,
|
|
purchases: false,
|
|
sales: false,
|
|
production: false,
|
|
events: false,
|
|
hr: false,
|
|
training: false,
|
|
admin: false,
|
|
});
|
|
|
|
const handleToggle = (id: string) => {
|
|
setOpenItems((prev) => ({ ...prev, [id]: !prev[id] }));
|
|
};
|
|
|
|
const handleItemClick = (item: MenuItem) => {
|
|
if (isCollapsed && item.children && onToggleCollapse) {
|
|
onToggleCollapse();
|
|
setOpenItems((prev) => ({ ...prev, [item.id]: true }));
|
|
return;
|
|
}
|
|
|
|
if (item.path) {
|
|
openTab(item.path, item.tabLabel || item.label, true, item.translationKey);
|
|
if (onClose) onClose();
|
|
} else if (item.children) {
|
|
handleToggle(item.id);
|
|
}
|
|
};
|
|
|
|
const menuStructure: MenuItem[] = [
|
|
{
|
|
id: 'dashboard',
|
|
label: t('menu.dashboard'),
|
|
icon: <DashboardIcon />,
|
|
path: '/',
|
|
translationKey: 'menu.dashboard',
|
|
},
|
|
{
|
|
id: 'warehouse',
|
|
label: t('menu.warehouse'),
|
|
icon: <WarehouseIcon />,
|
|
appCode: 'warehouse',
|
|
translationKey: 'menu.warehouse',
|
|
children: [
|
|
{ id: 'wh-dashboard', label: t('menu.dashboard'), tabLabel: t('menu.warehouse'), icon: <DashboardIcon />, path: '/warehouse', translationKey: 'menu.warehouse' },
|
|
{ id: 'wh-articles', label: t('menu.articles'), icon: <CategoryIcon />, path: '/warehouse/articles', translationKey: 'menu.articles' },
|
|
{ id: 'wh-categories', label: t('menu.categories'), icon: <FolderIcon />, path: '/warehouse/categories', translationKey: 'menu.categories' },
|
|
{ id: 'wh-locations', label: t('menu.location'), icon: <PlaceIcon />, path: '/warehouse/locations', translationKey: 'menu.location' },
|
|
{ id: 'wh-movements', label: t('menu.movements'), icon: <SwapIcon />, path: '/warehouse/movements', translationKey: 'menu.movements' },
|
|
{ id: 'wh-stock', label: t('menu.stock'), icon: <StorageIcon />, path: '/warehouse/stock', translationKey: 'menu.stock' },
|
|
{ id: 'wh-inventory', label: t('menu.inventory'), icon: <AssignmentIcon />, path: '/warehouse/inventory', translationKey: 'menu.inventory' },
|
|
],
|
|
},
|
|
{
|
|
id: 'purchases',
|
|
label: t('menu.purchases'),
|
|
icon: <ShoppingCartIcon />,
|
|
appCode: 'purchases',
|
|
translationKey: 'menu.purchases',
|
|
children: [
|
|
{ id: 'pur-suppliers', label: t('menu.suppliers'), icon: <PeopleIcon />, path: '/purchases/suppliers', translationKey: 'menu.suppliers' },
|
|
{ id: 'pur-orders', label: t('menu.purchaseOrders'), icon: <ListAltIcon />, path: '/purchases/orders', translationKey: 'menu.purchaseOrders' },
|
|
],
|
|
},
|
|
{
|
|
id: 'sales',
|
|
label: t('menu.sales'),
|
|
icon: <SellIcon />,
|
|
appCode: 'sales',
|
|
translationKey: 'menu.sales',
|
|
children: [
|
|
{ id: 'sal-orders', label: t('menu.salesOrders'), icon: <ListAltIcon />, path: '/sales/orders', translationKey: 'menu.salesOrders' },
|
|
],
|
|
},
|
|
{
|
|
id: 'production',
|
|
label: t('menu.production'),
|
|
icon: <ProductionIcon />,
|
|
appCode: 'production',
|
|
translationKey: 'menu.production',
|
|
children: [
|
|
{ id: 'prod-dashboard', label: t('menu.dashboard'), tabLabel: t('menu.production'), icon: <DashboardIcon />, path: '/production', translationKey: 'menu.production' },
|
|
{ id: 'prod-orders', label: t('menu.productionOrders'), icon: <ListAltIcon />, path: '/production/orders', translationKey: 'menu.productionOrders' },
|
|
{ id: 'prod-bom', label: t('menu.bom'), icon: <AssignmentIcon />, path: '/production/bom', translationKey: 'menu.bom' },
|
|
{ id: 'prod-workcenters', label: t('menu.workCenters'), icon: <BuildIcon />, path: '/production/work-centers', translationKey: 'menu.workCenters' },
|
|
{ id: 'prod-cycles', label: t('menu.cycles'), icon: <TimelineIcon />, path: '/production/cycles', translationKey: 'menu.cycles' },
|
|
{ id: 'prod-mrp', label: t('menu.mrp'), icon: <ManufacturingIcon />, path: '/production/mrp', translationKey: 'menu.mrp' },
|
|
],
|
|
},
|
|
{
|
|
id: 'events',
|
|
label: t('menu.events'),
|
|
icon: <EventIcon />,
|
|
appCode: 'events',
|
|
translationKey: 'menu.events',
|
|
children: [
|
|
{ id: 'ev-list', label: t('menu.events'), icon: <EventIcon />, path: '/events/list', translationKey: 'menu.events' },
|
|
{ id: 'ev-calendar', label: t('menu.calendar'), icon: <CalendarIcon />, path: '/events/calendar', translationKey: 'menu.calendar' },
|
|
{ id: 'ev-locations', label: t('menu.location'), icon: <PlaceIcon />, path: '/events/locations', translationKey: 'menu.location' },
|
|
],
|
|
},
|
|
{
|
|
id: 'hr',
|
|
label: t('apps.hr.title'),
|
|
icon: <PeopleIcon />,
|
|
appCode: 'hr',
|
|
translationKey: 'apps.hr.title',
|
|
children: [
|
|
{ id: 'hr-dipendenti', label: t('apps.hr.dipendenti'), icon: <PeopleIcon />, path: '/hr/dipendenti', translationKey: 'apps.hr.dipendenti' },
|
|
{ id: 'hr-contratti', label: t('apps.hr.contratti'), icon: <AssignmentIcon />, path: '/hr/contratti', translationKey: 'apps.hr.contratti' },
|
|
{ id: 'hr-assenze', label: t('apps.hr.assenze'), icon: <EventIcon />, path: '/hr/assenze', translationKey: 'apps.hr.assenze' },
|
|
{ id: 'hr-pagamenti', label: t('apps.hr.pagamenti'), icon: <AttachMoneyIcon />, path: '/hr/pagamenti', translationKey: 'apps.hr.pagamenti' },
|
|
{ id: 'hr-rimborsi', label: t('apps.hr.rimborsi'), icon: <ReceiptIcon />, path: '/hr/rimborsi', translationKey: 'apps.hr.rimborsi' },
|
|
],
|
|
},
|
|
{
|
|
id: 'training',
|
|
label: t('apps.training.title'),
|
|
icon: <SchoolIcon />,
|
|
appCode: 'training',
|
|
translationKey: 'apps.training.title',
|
|
children: [
|
|
{ id: 'tr-dashboard', label: t('apps.training.dashboard'), tabLabel: t('apps.training.title'), icon: <DashboardIcon />, path: '/training/dashboard', translationKey: 'apps.training.dashboard' },
|
|
{ id: 'tr-registry', label: t('apps.training.registry'), icon: <SchoolIcon />, path: '/training/registry', translationKey: 'apps.training.registry' },
|
|
{ id: 'tr-matrix', label: t('apps.training.matrix'), icon: <AssignmentIcon />, path: '/training/matrix', translationKey: 'apps.training.matrix' },
|
|
],
|
|
},
|
|
{
|
|
id: 'admin',
|
|
label: t('menu.administration'),
|
|
icon: <SettingsIcon />,
|
|
translationKey: 'menu.administration',
|
|
children: [
|
|
{ id: 'apps', label: t('menu.apps'), icon: <ModulesIcon />, path: '/apps', translationKey: 'menu.apps' },
|
|
{ id: 'autocodes', label: t('menu.autoCodes'), icon: <AutoCodeIcon />, path: '/admin/auto-codes', translationKey: 'menu.autoCodes' },
|
|
{ id: 'customfields', label: t('menu.customFields'), icon: <AutoCodeIcon />, path: '/admin/custom-fields', translationKey: 'menu.customFields' },
|
|
{ id: 'reports', label: t('menu.reports'), icon: <PrintIcon />, path: '/report-designer', appCode: 'report-designer', translationKey: 'menu.reports' },
|
|
{ id: 'email-config', label: t('menu.emailConfig'), icon: <EmailIcon />, path: '/admin/email-config', translationKey: 'menu.emailConfig' },
|
|
],
|
|
},
|
|
];
|
|
|
|
const activeAppCodes = activeApps.map((m) => m.code);
|
|
|
|
const renderMenuItem = (item: MenuItem, level: number = 0) => {
|
|
// Filter by module
|
|
if (item.appCode && !activeAppCodes.includes(item.appCode)) {
|
|
return null;
|
|
}
|
|
|
|
const hasChildren = item.children && item.children.length > 0;
|
|
const isOpen = openItems[item.id];
|
|
const isSelected = item.path ? location.pathname === item.path : false;
|
|
|
|
return (
|
|
<React.Fragment key={item.id}>
|
|
<ListItem disablePadding sx={{ display: 'block' }}>
|
|
<Tooltip title={isCollapsed ? item.label : ''} placement="right">
|
|
<ListItemButton
|
|
onClick={() => handleItemClick(item)}
|
|
selected={isSelected}
|
|
sx={{
|
|
minHeight: 48,
|
|
justifyContent: isCollapsed ? 'center' : 'initial',
|
|
px: 2.5,
|
|
pl: isCollapsed ? 2.5 : level * 2 + 2.5,
|
|
}}
|
|
>
|
|
<ListItemIcon
|
|
sx={{
|
|
minWidth: 0,
|
|
mr: isCollapsed ? 0 : 3,
|
|
justifyContent: 'center',
|
|
color: isSelected ? 'primary.main' : 'inherit',
|
|
}}
|
|
>
|
|
{item.icon}
|
|
</ListItemIcon>
|
|
{!isCollapsed && (
|
|
<>
|
|
<ListItemText
|
|
primary={item.label}
|
|
primaryTypographyProps={{
|
|
fontWeight: isSelected ? 'bold' : 'medium',
|
|
fontSize: level === 0 ? '0.95rem' : '0.9rem',
|
|
}}
|
|
/>
|
|
{hasChildren ? (isOpen ? <ExpandLess /> : <ExpandMore />) : null}
|
|
</>
|
|
)}
|
|
</ListItemButton>
|
|
</Tooltip>
|
|
</ListItem>
|
|
{!isCollapsed && hasChildren && (
|
|
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
|
<List component="div" disablePadding>
|
|
{item.children?.map((child) => renderMenuItem(child, level + 1))}
|
|
</List>
|
|
</Collapse>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Box sx={{ overflow: 'hidden', display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
<Box sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: isCollapsed ? 'center' : 'space-between', minHeight: 64 }}>
|
|
{!isCollapsed && (
|
|
<Typography variant="h6" component="div" sx={{ fontWeight: 'bold' }}>
|
|
Zentral
|
|
</Typography>
|
|
)}
|
|
{onToggleCollapse && (
|
|
<IconButton onClick={onToggleCollapse}>
|
|
{isCollapsed ? <ChevronRight /> : <ChevronLeft />}
|
|
</IconButton>
|
|
)}
|
|
</Box>
|
|
<Divider />
|
|
<List sx={{ overflowY: 'auto', flex: 1 }}>
|
|
{menuStructure.map((item) => renderMenuItem(item))}
|
|
</List>
|
|
</Box>
|
|
);
|
|
}
|