This commit is contained in:
2025-11-29 20:32:22 +01:00
parent 5a8740269b
commit 337c847d79
9 changed files with 300 additions and 87 deletions

View File

@@ -52,6 +52,20 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve
**Lavoro completato nell'ultima sessione:**
- **NUOVA FEATURE: Supporto Tema Scuro e Multilingua** - COMPLETATO
- **Obiettivo:** Permettere all'utente di cambiare tema (Chiaro/Scuro) e lingua (Italiano/Inglese) con persistenza
- **Frontend implementato:**
- `ThemeContext.tsx` - Gestione stato tema e integrazione MUI ThemeProvider
- `LanguageContext.tsx` - Gestione stato lingua e integrazione dayjs/MUI LocalizationProvider
- `SettingsSelector.tsx` - Componente UI per cambio impostazioni nella toolbar
- Integrazione in `App.tsx` e `Layout.tsx`
- **Funzionalità:**
- Cambio tema istantaneo (Light/Dark)
- Cambio lingua istantaneo (IT/EN)
- Persistenza impostazioni nel browser (localStorage)
- Adattamento automatico componenti MUI
- **NUOVA FEATURE: Gestione Inventario (Frontend)** - COMPLETATO
- **Obiettivo:** Interfaccia utente per la gestione completa degli inventari fisici
- **Frontend implementato:**

View File

@@ -1,10 +1,9 @@
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import CssBaseline from "@mui/material/CssBaseline";
import "dayjs/locale/it";
import { AppThemeProvider } from "./contexts/ThemeContext";
import { AppLanguageProvider } from "./contexts/LanguageContext";
import Layout from "./components/Layout";
import Dashboard from "./pages/Dashboard";
@@ -46,26 +45,11 @@ function RealTimeProvider({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
const theme = createTheme({
palette: {
primary: {
main: "#1976d2",
},
secondary: {
main: "#dc004e",
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="it">
<CssBaseline />
<AppLanguageProvider>
<AppThemeProvider>
<BrowserRouter>
<ModuleProvider>
<CollaborationProvider>
@@ -121,8 +105,8 @@ function App() {
</CollaborationProvider>
</ModuleProvider>
</BrowserRouter>
</LocalizationProvider>
</ThemeProvider>
</AppThemeProvider>
</AppLanguageProvider>
</QueryClientProvider>
);
}

View File

@@ -33,6 +33,7 @@ import {
} from "@mui/icons-material";
import CollaborationIndicator from "./collaboration/CollaborationIndicator";
import { useModules } from "../contexts/ModuleContext";
import { SettingsSelector } from "./SettingsSelector";
const DRAWER_WIDTH = 240;
const DRAWER_WIDTH_COLLAPSED = 64;
@@ -181,6 +182,9 @@ export default function Layout() {
{/* Collaboration Indicator */}
<CollaborationIndicator compact={isMobile} />
{/* Settings Selector */}
<SettingsSelector />
</Toolbar>
</AppBar>

View File

@@ -0,0 +1,116 @@
import React, { useState } from 'react';
import {
IconButton,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
Divider,
Tooltip,
} from '@mui/material';
import {
Settings as SettingsIcon,
Brightness4 as DarkModeIcon,
Brightness7 as LightModeIcon,
Language as LanguageIcon,
} from '@mui/icons-material';
import { useThemeContext } from '../contexts/ThemeContext';
import { useLanguage } from '../contexts/LanguageContext';
export const SettingsSelector: React.FC = () => {
const { mode, toggleTheme } = useThemeContext();
const { language, setLanguage, t } = useLanguage();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleToggleTheme = () => {
toggleTheme();
// Keep menu open or close it? Let's keep it open for now or close it.
// Usually toggling theme is a one-off action, but maybe user wants to change language too.
// Let's close it for simplicity, or keep it open if it feels better.
// I'll keep it open to allow multiple changes.
};
const handleLanguageChange = () => {
setLanguage(language === 'it' ? 'en' : 'it');
handleClose();
};
return (
<>
<Tooltip title={t('settings')}>
<IconButton
onClick={handleClick}
size="small"
sx={{ ml: 2 }}
aria-controls={open ? 'settings-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
>
<SettingsIcon />
</IconButton>
</Tooltip>
<Menu
anchorEl={anchorEl}
id="settings-menu"
open={open}
onClose={handleClose}
onClick={handleClose}
PaperProps={{
elevation: 0,
sx: {
overflow: 'visible',
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
mt: 1.5,
'& .MuiAvatar-root': {
width: 32,
height: 32,
ml: -0.5,
mr: 1,
},
'&:before': {
content: '""',
display: 'block',
position: 'absolute',
top: 0,
right: 14,
width: 10,
height: 10,
bgcolor: 'background.paper',
transform: 'translateY(-50%) rotate(45deg)',
zIndex: 0,
},
},
}}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
<MenuItem onClick={handleToggleTheme}>
<ListItemIcon>
{mode === 'dark' ? <LightModeIcon fontSize="small" /> : <DarkModeIcon fontSize="small" />}
</ListItemIcon>
<ListItemText>
{mode === 'dark' ? t('light') : t('dark')}
</ListItemText>
</MenuItem>
<Divider />
<MenuItem onClick={handleLanguageChange}>
<ListItemIcon>
<LanguageIcon fontSize="small" />
</ListItemIcon>
<ListItemText>
{language === 'it' ? 'English' : 'Italiano'}
</ListItemText>
</MenuItem>
</Menu>
</>
);
};

View File

@@ -0,0 +1,88 @@
import React, { createContext, useState, useContext, useEffect } from 'react';
import dayjs from 'dayjs';
import 'dayjs/locale/it';
import 'dayjs/locale/en';
import { LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
type Language = 'it' | 'en';
interface LanguageContextType {
language: Language;
setLanguage: (lang: Language) => void;
t: (key: string) => string;
}
const LanguageContext = createContext<LanguageContextType>({
language: 'it',
setLanguage: () => { },
t: (key: string) => key,
});
export const useLanguage = () => useContext(LanguageContext);
// Simple translation dictionary for demo purposes
const translations: Record<Language, Record<string, string>> = {
it: {
'settings': 'Impostazioni',
'theme': 'Tema',
'language': 'Lingua',
'dark': 'Scuro',
'light': 'Chiaro',
'logout': 'Esci',
'dashboard': 'Dashboard',
'calendar': 'Calendario',
'events': 'Eventi',
'clients': 'Clienti',
'location': 'Location',
'articles': 'Articoli',
'resources': 'Risorse',
'warehouse': 'Magazzino',
'reports': 'Report',
'modules': 'Moduli',
'autoCodes': 'Codici Auto',
},
en: {
'settings': 'Settings',
'theme': 'Theme',
'language': 'Language',
'dark': 'Dark',
'light': 'Light',
'logout': 'Logout',
'dashboard': 'Dashboard',
'calendar': 'Calendar',
'events': 'Events',
'clients': 'Clients',
'location': 'Location',
'articles': 'Articles',
'resources': 'Resources',
'warehouse': 'Warehouse',
'reports': 'Reports',
'modules': 'Modules',
'autoCodes': 'Auto Codes',
}
};
export const AppLanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [language, setLanguage] = useState<Language>(() => {
const savedLang = localStorage.getItem('language');
return (savedLang as Language) || 'it';
});
useEffect(() => {
localStorage.setItem('language', language);
dayjs.locale(language);
}, [language]);
const t = (key: string) => {
return translations[language][key] || key;
};
return (
<LanguageContext.Provider value={{ language, setLanguage, t }}>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={language}>
{children}
</LocalizationProvider>
</LanguageContext.Provider>
);
};

View File

@@ -0,0 +1,64 @@
import React, { createContext, useState, useMemo, useEffect, useContext } from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
type ThemeMode = 'light' | 'dark';
interface ThemeContextType {
mode: ThemeMode;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType>({
mode: 'light',
toggleTheme: () => { },
});
export const useThemeContext = () => useContext(ThemeContext);
export const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [mode, setMode] = useState<ThemeMode>(() => {
const savedMode = localStorage.getItem('themeMode');
return (savedMode as ThemeMode) || 'light';
});
useEffect(() => {
localStorage.setItem('themeMode', mode);
}, [mode]);
const toggleTheme = () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
};
const theme = useMemo(
() =>
createTheme({
palette: {
mode,
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
background: {
default: mode === 'light' ? '#f5f5f5' : '#121212',
paper: mode === 'light' ? '#ffffff' : '#1e1e1e',
}
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
},
}),
[mode]
);
return (
<ThemeContext.Provider value={{ mode, toggleTheme }}>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</ThemeContext.Provider>
);
};

View File

@@ -1,68 +1,11 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
padding: 0;
width: 100%;
height: 100%;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
#root {
width: 100%;
height: 100%;
}

Binary file not shown.

Binary file not shown.