diff --git a/docs/development/ZENTRAL.md b/docs/development/ZENTRAL.md index 158767d..f59ab41 100644 --- a/docs/development/ZENTRAL.md +++ b/docs/development/ZENTRAL.md @@ -36,3 +36,5 @@ File riassuntivo dello stato di sviluppo di Zentral. - Rimozione tab interne e header dal modulo Magazzino per uniformità con la UI principale. - [2025-12-05 Live Data Alignment](./devlog/2025-12-05-230000_live_data_alignment.md) - **Completato** - Implementazione `SchemaDiscoveryService` per allineamento automatico dataset report con strutture dati live. +- [2025-12-06 Sidebar Collapsible](./devlog/2025-12-06-010500_sidebar_collapsible.md) - **Completato** + - Reso il menu laterale collassabile (manuale e responsive) con visualizzazione a sole icone. diff --git a/docs/development/devlog/2025-12-06-010500_sidebar_collapsible.md b/docs/development/devlog/2025-12-06-010500_sidebar_collapsible.md new file mode 100644 index 0000000..b0895f5 --- /dev/null +++ b/docs/development/devlog/2025-12-06-010500_sidebar_collapsible.md @@ -0,0 +1,30 @@ +# Sidebar Collapsible and Responsive + +## Obiettivo +Rendere il menu laterale (Sidebar) collassabile manualmente e automaticamente responsive (si chiude se la finestra si riduce). + +## Stato +Completato. + +## Modifiche Apportate +### Frontend +- **`src/frontend/src/components/Layout.tsx`**: + - Aggiunto stato `isCollapsed`. + - Aggiunto hook `useMediaQuery` per rilevare la larghezza dello schermo (`md` breakpoint). + - Implementata logica `useEffect` per collassare automaticamente la sidebar su schermi medi (tra `sm` e `md`). + - Aggiornato il calcolo della larghezza dinamica (`currentDrawerWidth`) per `AppBar`, `Drawer` e `Main Content`. + - Aggiunta transizione CSS per un'animazione fluida. + +- **`src/frontend/src/components/Sidebar.tsx`**: + - Aggiunto pulsante di toggle (freccia sinistra/destra) nell'header della sidebar. + - Implementata la modalità "collassata": + - Nasconde i testi (`ListItemText`). + - Nasconde le icone di espansione (`ExpandLess`/`ExpandMore`). + - Centra le icone (`ListItemIcon`). + - Aggiunge `Tooltip` al passaggio del mouse per mostrare l'etichetta del menu. + - Gestione click in modalità collassata: se si clicca una voce con sottomenu, la sidebar si espande automaticamente e apre il sottomenu. + +## Verifica +- Testato il toggle manuale. +- Testato il comportamento responsive (simulato tramite logica breakpoint). +- Verificato che i tooltip appaiano correttamente in modalità collassata. diff --git a/src/frontend/src/components/Layout.tsx b/src/frontend/src/components/Layout.tsx index 44002ea..193ea63 100644 --- a/src/frontend/src/components/Layout.tsx +++ b/src/frontend/src/components/Layout.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Outlet, useLocation } from "react-router-dom"; import { Box, @@ -19,6 +19,7 @@ import SearchBar from "./SearchBar"; import TabsBar from "./TabsBar"; const DRAWER_WIDTH = 280; // Increased width for better readability +const COLLAPSED_DRAWER_WIDTH = 72; export default function Layout() { const [mobileOpen, setMobileOpen] = useState(false); @@ -27,6 +28,19 @@ export default function Layout() { // Breakpoints const isMobile = useMediaQuery(theme.breakpoints.down("sm")); // < 600px + const isMdUp = useMediaQuery(theme.breakpoints.up("md")); // >= 900px + + const [isCollapsed, setIsCollapsed] = useState(false); + + useEffect(() => { + setIsCollapsed(!isMdUp); + }, [isMdUp]); + + const handleCollapseToggle = () => { + setIsCollapsed(!isCollapsed); + }; + + const currentDrawerWidth = isCollapsed ? COLLAPSED_DRAWER_WIDTH : DRAWER_WIDTH; const handleDrawerToggle = () => { setMobileOpen(!mobileOpen); @@ -37,8 +51,8 @@ export default function Layout() { theme.zIndex.drawer + 1, }} @@ -69,7 +83,7 @@ export default function Layout() { @@ -96,12 +110,17 @@ export default function Layout() { display: { xs: "none", sm: "block" }, "& .MuiDrawer-paper": { boxSizing: "border-box", - width: DRAWER_WIDTH, + width: currentDrawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + overflowX: 'hidden', }, }} open > - + @@ -112,7 +131,7 @@ export default function Layout() { flexGrow: 1, width: { xs: "100vw", - sm: `calc(100vw - ${DRAWER_WIDTH}px)`, + sm: `calc(100vw - ${currentDrawerWidth}px)`, }, height: "100vh", display: "flex", diff --git a/src/frontend/src/components/Sidebar.tsx b/src/frontend/src/components/Sidebar.tsx index 281ef1f..46d1f7b 100644 --- a/src/frontend/src/components/Sidebar.tsx +++ b/src/frontend/src/components/Sidebar.tsx @@ -9,6 +9,8 @@ import { Box, Typography, Divider, + Tooltip, + IconButton, } from '@mui/material'; import { ExpandLess, @@ -36,6 +38,8 @@ import { Category as CategoryIcon, AttachMoney as AttachMoneyIcon, Receipt as ReceiptIcon, + ChevronLeft, + ChevronRight, } from '@mui/icons-material'; import { useLocation } from 'react-router-dom'; import { useLanguage } from '../contexts/LanguageContext'; @@ -52,7 +56,13 @@ interface MenuItem { appCode?: string; } -export default function Sidebar({ onClose }: { onClose?: () => void }) { +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(); @@ -73,6 +83,12 @@ export default function Sidebar({ onClose }: { onClose?: () => void }) { }; 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); if (onClose) onClose(); @@ -187,37 +203,43 @@ export default function Sidebar({ onClose }: { onClose?: () => void }) { return ( - handleItemClick(item)} - selected={isSelected} - sx={{ - minHeight: 48, - justifyContent: 'initial', - px: 2.5, - pl: level * 2 + 2.5, - }} - > - + handleItemClick(item)} + selected={isSelected} sx={{ - minWidth: 0, - mr: 3, - justifyContent: 'center', - color: isSelected ? 'primary.main' : 'inherit', + minHeight: 48, + justifyContent: isCollapsed ? 'center' : 'initial', + px: 2.5, + pl: isCollapsed ? 2.5 : level * 2 + 2.5, }} > - {item.icon} - - - {hasChildren ? (isOpen ? : ) : null} - + + {item.icon} + + {!isCollapsed && ( + <> + + {hasChildren ? (isOpen ? : ) : null} + + )} + + - {hasChildren && ( + {!isCollapsed && hasChildren && ( {item.children?.map((child) => renderMenuItem(child, level + 1))} @@ -229,14 +251,21 @@ export default function Sidebar({ onClose }: { onClose?: () => void }) { }; return ( - - - - Zentral - + + + {!isCollapsed && ( + + Zentral + + )} + {onToggleCollapse && ( + + {isCollapsed ? : } + + )} - + {menuStructure.map((item) => renderMenuItem(item))}