diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 29beede..1fca904 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -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:**
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 4c9de96..77e25df 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -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 (
-
-
-
+
+
@@ -121,8 +105,8 @@ function App() {
-
-
+
+
);
}
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index 9645f68..ec0e331 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -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 */}
+
+ {/* Settings Selector */}
+
diff --git a/frontend/src/components/SettingsSelector.tsx b/frontend/src/components/SettingsSelector.tsx
new file mode 100644
index 0000000..98bebbc
--- /dev/null
+++ b/frontend/src/components/SettingsSelector.tsx
@@ -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);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event: React.MouseEvent) => {
+ 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 (
+ <>
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/frontend/src/contexts/LanguageContext.tsx b/frontend/src/contexts/LanguageContext.tsx
new file mode 100644
index 0000000..385527f
--- /dev/null
+++ b/frontend/src/contexts/LanguageContext.tsx
@@ -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({
+ language: 'it',
+ setLanguage: () => { },
+ t: (key: string) => key,
+});
+
+export const useLanguage = () => useContext(LanguageContext);
+
+// Simple translation dictionary for demo purposes
+const translations: Record> = {
+ 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(() => {
+ 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 (
+
+
+ {children}
+
+
+ );
+};
diff --git a/frontend/src/contexts/ThemeContext.tsx b/frontend/src/contexts/ThemeContext.tsx
new file mode 100644
index 0000000..55a51e8
--- /dev/null
+++ b/frontend/src/contexts/ThemeContext.tsx
@@ -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({
+ mode: 'light',
+ toggleTheme: () => { },
+});
+
+export const useThemeContext = () => useContext(ThemeContext);
+
+export const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [mode, setMode] = useState(() => {
+ 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 (
+
+
+
+ {children}
+
+
+ );
+};
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 08a3ac9..ed913e8 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -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%;
}
diff --git a/src/Apollinare.API/apollinare.db-shm b/src/Apollinare.API/apollinare.db-shm
index 99ddfc3..fe9ac28 100644
Binary files a/src/Apollinare.API/apollinare.db-shm and b/src/Apollinare.API/apollinare.db-shm differ
diff --git a/src/Apollinare.API/apollinare.db-wal b/src/Apollinare.API/apollinare.db-wal
index 856b57f..e69de29 100644
Binary files a/src/Apollinare.API/apollinare.db-wal and b/src/Apollinare.API/apollinare.db-wal differ