From 337c847d793ece4684035243f5a149ce1fbb8c2f Mon Sep 17 00:00:00 2001 From: dnviti Date: Sat, 29 Nov 2025 20:32:22 +0100 Subject: [PATCH] - --- DEVELOPMENT.md | 14 +++ frontend/src/App.tsx | 32 ++--- frontend/src/components/Layout.tsx | 4 + frontend/src/components/SettingsSelector.tsx | 116 +++++++++++++++++++ frontend/src/contexts/LanguageContext.tsx | 88 ++++++++++++++ frontend/src/contexts/ThemeContext.tsx | 64 ++++++++++ frontend/src/index.css | 69 +---------- src/Apollinare.API/apollinare.db-shm | Bin 32768 -> 32768 bytes src/Apollinare.API/apollinare.db-wal | Bin 177192 -> 0 bytes 9 files changed, 300 insertions(+), 87 deletions(-) create mode 100644 frontend/src/components/SettingsSelector.tsx create mode 100644 frontend/src/contexts/LanguageContext.tsx create mode 100644 frontend/src/contexts/ThemeContext.tsx 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 ( + <> + + + + + + + + + {mode === 'dark' ? : } + + + {mode === 'dark' ? t('light') : t('dark')} + + + + + + + + + {language === 'it' ? 'English' : 'Italiano'} + + + + + ); +}; 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 99ddfc3898d9bda2b095ce2fd55ce271faa95461..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 100644 GIT binary patch delta 84 zcmZo@U}|V!;+1%$%K!t6lMNZAMJ?DR*i0wi=ERT@fyuzs{|ADJIWn6a8232>iCwk; Dr2G?5 literal 32768 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