|
diff --git a/src/frontend/src/apps/report-designer/components/reportEditor/PreviewDialog.tsx b/src/frontend/src/apps/report-designer/components/reportEditor/PreviewDialog.tsx
index cb78917..3ebbab1 100644
--- a/src/frontend/src/apps/report-designer/components/reportEditor/PreviewDialog.tsx
+++ b/src/frontend/src/apps/report-designer/components/reportEditor/PreviewDialog.tsx
@@ -43,6 +43,7 @@ import {
Close as CloseIcon,
ArrowBack as BackIcon,
} from "@mui/icons-material";
+import { useTranslation } from "react-i18next";
import { useQueries } from "@tanstack/react-query";
import { reportGeneratorService } from "../../../../services/reportService";
import type {
@@ -66,6 +67,7 @@ export default function PreviewDialog({
onGeneratePreview,
isGenerating,
}: PreviewDialogProps) {
+ const { t } = useTranslation();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
@@ -268,7 +270,7 @@ export default function PreviewDialog({
{isSelected && (
-
+
{
@@ -327,7 +329,7 @@ export default function PreviewDialog({
setMobileShowList(true)}>
- Seleziona
+ {t('reports.preview.select')}
)}
@@ -357,7 +359,7 @@ export default function PreviewDialog({
{/* Ricerca */}
{searchTerms[activeDataset || ""]
- ? "Nessun risultato trovato"
- : "Nessuna entità disponibile"}
+ ? t('reports.preview.noResults')
+ : t('reports.preview.noEntities')}
) : (
@@ -482,7 +484,7 @@ export default function PreviewDialog({
}}
>
- {filteredEntities.length} risultati
+ {filteredEntities.length} {t('reports.preview.results')}
@@ -511,7 +513,7 @@ export default function PreviewDialog({
- Anteprima Report
+ {t('reports.preview.title')}
- Anteprima Report
+ {t('reports.preview.title')}
- Seleziona un'entità per ogni dataset da utilizzare nell'anteprima
+ {t('reports.preview.instruction')}
)}
@@ -545,15 +547,14 @@ export default function PreviewDialog({
{hasError && (
- Errore nel caricamento dei dati disponibili
+ {t('reports.preview.errorLoading')}
)}
{selectedDatasets.length === 0 ? (
- Non ci sono dataset selezionati per questo template. Aggiungi
- almeno un dataset per poter generare l'anteprima.
+ {t('reports.preview.noDatasets')}
) : isMobile ? (
@@ -577,7 +578,7 @@ export default function PreviewDialog({
}}
>
- Seleziona un'entità per ogni dataset
+ {t('reports.preview.selectEntityInstruction')}
{renderDatasetList()}
@@ -613,7 +614,7 @@ export default function PreviewDialog({
diff --git a/src/frontend/src/components/SearchBar.tsx b/src/frontend/src/components/SearchBar.tsx
index c8b274c..ee23955 100644
--- a/src/frontend/src/components/SearchBar.tsx
+++ b/src/frontend/src/components/SearchBar.tsx
@@ -51,6 +51,7 @@ interface SearchOption {
label: string;
path: string;
category: string;
+ translationKey?: string;
}
export default function SearchBar() {
@@ -63,55 +64,55 @@ export default function SearchBar() {
const options = useMemo(() => {
const opts: SearchOption[] = [
// Core
- { label: t('menu.dashboard'), path: '/', category: 'Zentral' },
- { label: t('menu.calendar'), path: '/calendario', category: 'Zentral' },
- { label: t('menu.events'), path: '/eventi', category: 'Zentral' },
- { label: t('menu.clients'), path: '/clienti', category: 'Zentral' },
- { label: t('menu.location'), path: '/location', category: 'Zentral' },
- { label: t('menu.articles'), path: '/articoli', category: 'Zentral' },
- { label: t('menu.resources'), path: '/risorse', category: 'Zentral' },
- { label: t('menu.reports'), path: '/report-templates', category: 'Zentral' },
+ { label: t('menu.dashboard'), path: '/', category: 'Zentral', translationKey: 'menu.dashboard' },
+ { label: t('menu.calendar'), path: '/calendario', category: 'Zentral', translationKey: 'menu.calendar' },
+ { label: t('menu.events'), path: '/eventi', category: 'Zentral', translationKey: 'menu.events' },
+ { label: t('menu.clients'), path: '/clienti', category: 'Zentral', translationKey: 'menu.clients' },
+ { label: t('menu.location'), path: '/location', category: 'Zentral', translationKey: 'menu.location' },
+ { label: t('menu.articles'), path: '/articoli', category: 'Zentral', translationKey: 'menu.articles' },
+ { label: t('menu.resources'), path: '/risorse', category: 'Zentral', translationKey: 'menu.resources' },
+ { label: t('menu.reports'), path: '/report-templates', category: 'Zentral', translationKey: 'menu.reports' },
];
if (activeAppCodes.includes('warehouse')) {
opts.push(
- { label: t('menu.warehouse') + ' Dashboard', path: '/warehouse', category: t('menu.warehouse') },
- { label: t('menu.articles'), path: '/warehouse/articles', category: t('menu.warehouse') },
- { label: t('menu.location'), path: '/warehouse/locations', category: t('menu.warehouse') },
- { label: 'Movimenti', path: '/warehouse/movements', category: t('menu.warehouse') },
- { label: 'Giacenze', path: '/warehouse/stock', category: t('menu.warehouse') },
- { label: 'Inventario', path: '/warehouse/inventory', category: t('menu.warehouse') }
+ { label: t('menu.warehouse') + ' ' + t('menu.dashboard'), path: '/warehouse', category: t('menu.warehouse'), translationKey: 'menu.warehouse' },
+ { label: t('menu.articles'), path: '/warehouse/articles', category: t('menu.warehouse'), translationKey: 'menu.articles' },
+ { label: t('menu.location'), path: '/warehouse/locations', category: t('menu.warehouse'), translationKey: 'menu.location' },
+ { label: t('menu.movements'), path: '/warehouse/movements', category: t('menu.warehouse'), translationKey: 'menu.movements' },
+ { label: t('menu.stock'), path: '/warehouse/stock', category: t('menu.warehouse'), translationKey: 'menu.stock' },
+ { label: t('menu.inventory'), path: '/warehouse/inventory', category: t('menu.warehouse'), translationKey: 'menu.inventory' }
);
}
if (activeAppCodes.includes('purchases')) {
opts.push(
- { label: 'Fornitori', path: '/purchases/suppliers', category: t('menu.purchases') },
- { label: 'Ordini Acquisto', path: '/purchases/orders', category: t('menu.purchases') }
+ { label: t('menu.suppliers'), path: '/purchases/suppliers', category: t('menu.purchases'), translationKey: 'menu.suppliers' },
+ { label: t('menu.purchaseOrders'), path: '/purchases/orders', category: t('menu.purchases'), translationKey: 'menu.purchaseOrders' }
);
}
if (activeAppCodes.includes('sales')) {
opts.push(
- { label: 'Ordini Vendita', path: '/sales/orders', category: t('menu.sales') }
+ { label: t('menu.salesOrders'), path: '/sales/orders', category: t('menu.sales'), translationKey: 'menu.salesOrders' }
);
}
if (activeAppCodes.includes('production')) {
opts.push(
- { label: t('menu.production') + ' Dashboard', path: '/production', category: t('menu.production') },
- { label: 'Ordini Produzione', path: '/production/orders', category: t('menu.production') },
- { label: 'Distinte Base', path: '/production/bom', category: t('menu.production') },
- { label: 'Centri di Lavoro', path: '/production/work-centers', category: t('menu.production') },
- { label: 'Cicli', path: '/production/cycles', category: t('menu.production') },
- { label: 'MRP', path: '/production/mrp', category: t('menu.production') }
+ { label: t('menu.production') + ' ' + t('menu.dashboard'), path: '/production', category: t('menu.production'), translationKey: 'menu.production' },
+ { label: t('menu.productionOrders'), path: '/production/orders', category: t('menu.production'), translationKey: 'menu.productionOrders' },
+ { label: t('menu.bom'), path: '/production/bom', category: t('menu.production'), translationKey: 'menu.bom' },
+ { label: t('menu.workCenters'), path: '/production/work-centers', category: t('menu.production'), translationKey: 'menu.workCenters' },
+ { label: t('menu.cycles'), path: '/production/cycles', category: t('menu.production'), translationKey: 'menu.cycles' },
+ { label: t('menu.mrp'), path: '/production/mrp', category: t('menu.production'), translationKey: 'menu.mrp' }
);
}
opts.push(
- { label: t('menu.apps'), path: '/apps', category: 'Admin' },
- { label: t('menu.autoCodes'), path: '/admin/auto-codes', category: 'Admin' },
- { label: t('menu.customFields'), path: '/admin/custom-fields', category: 'Admin' }
+ { label: t('menu.apps'), path: '/apps', category: t('menu.administration'), translationKey: 'menu.apps' },
+ { label: t('menu.autoCodes'), path: '/admin/auto-codes', category: t('menu.administration'), translationKey: 'menu.autoCodes' },
+ { label: t('menu.customFields'), path: '/admin/custom-fields', category: t('menu.administration'), translationKey: 'menu.customFields' }
);
return opts;
@@ -128,7 +129,7 @@ export default function SearchBar() {
getOptionLabel={(option) => typeof option === 'string' ? option : option.label}
onChange={(_, value) => {
if (typeof value !== 'string' && value) {
- openTab(value.path, value.label);
+ openTab(value.path, value.label, true, value.translationKey);
}
}}
renderInput={(params) => {
@@ -141,7 +142,7 @@ export default function SearchBar() {
diff --git a/src/frontend/src/components/Sidebar.tsx b/src/frontend/src/components/Sidebar.tsx
index 46d1f7b..0bb252c 100644
--- a/src/frontend/src/components/Sidebar.tsx
+++ b/src/frontend/src/components/Sidebar.tsx
@@ -54,6 +54,7 @@ interface MenuItem {
path?: string;
children?: MenuItem[];
appCode?: string;
+ translationKey?: string;
}
interface SidebarProps {
@@ -90,7 +91,7 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
}
if (item.path) {
- openTab(item.path, item.tabLabel || item.label);
+ openTab(item.path, item.tabLabel || item.label, true, item.translationKey);
if (onClose) onClose();
} else if (item.children) {
handleToggle(item.id);
@@ -103,19 +104,21 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
label: 'Zentral Dashboard',
icon: ,
path: '/',
+ translationKey: 'menu.dashboard',
},
{
id: 'warehouse',
label: t('menu.warehouse'),
icon: ,
appCode: 'warehouse',
+ translationKey: 'menu.warehouse',
children: [
- { id: 'wh-dashboard', label: 'Dashboard', tabLabel: t('menu.warehouse'), icon: , path: '/warehouse' },
- { id: 'wh-articles', label: t('menu.articles'), icon: , path: '/warehouse/articles' },
- { id: 'wh-locations', label: t('menu.location'), icon: , path: '/warehouse/locations' },
- { id: 'wh-movements', label: 'Movimenti', icon: , path: '/warehouse/movements' },
- { id: 'wh-stock', label: 'Giacenze', icon: , path: '/warehouse/stock' },
- { id: 'wh-inventory', label: 'Inventario', icon: , path: '/warehouse/inventory' },
+ { id: 'wh-dashboard', label: t('menu.dashboard'), tabLabel: t('menu.warehouse'), icon: , path: '/warehouse', translationKey: 'menu.warehouse' },
+ { id: 'wh-articles', label: t('menu.articles'), icon: , path: '/warehouse/articles', translationKey: 'menu.articles' },
+ { id: 'wh-locations', label: t('menu.location'), icon: , path: '/warehouse/locations', translationKey: 'menu.location' },
+ { id: 'wh-movements', label: t('menu.movements'), icon: , path: '/warehouse/movements', translationKey: 'menu.movements' },
+ { id: 'wh-stock', label: t('menu.stock'), icon: , path: '/warehouse/stock', translationKey: 'menu.stock' },
+ { id: 'wh-inventory', label: t('menu.inventory'), icon: , path: '/warehouse/inventory', translationKey: 'menu.inventory' },
],
},
{
@@ -123,9 +126,10 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
label: t('menu.purchases'),
icon: ,
appCode: 'purchases',
+ translationKey: 'menu.purchases',
children: [
- { id: 'pur-suppliers', label: 'Fornitori', icon: , path: '/purchases/suppliers' },
- { id: 'pur-orders', label: 'Ordini Acquisto', icon: , path: '/purchases/orders' },
+ { id: 'pur-suppliers', label: t('menu.suppliers'), icon: , path: '/purchases/suppliers', translationKey: 'menu.suppliers' },
+ { id: 'pur-orders', label: t('menu.purchaseOrders'), icon: , path: '/purchases/orders', translationKey: 'menu.purchaseOrders' },
],
},
{
@@ -133,8 +137,9 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
label: t('menu.sales'),
icon: ,
appCode: 'sales',
+ translationKey: 'menu.sales',
children: [
- { id: 'sal-orders', label: 'Ordini Vendita', icon: , path: '/sales/orders' },
+ { id: 'sal-orders', label: t('menu.salesOrders'), icon: , path: '/sales/orders', translationKey: 'menu.salesOrders' },
],
},
{
@@ -142,13 +147,14 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
label: t('menu.production'),
icon: ,
appCode: 'production',
+ translationKey: 'menu.production',
children: [
- { id: 'prod-dashboard', label: 'Dashboard', tabLabel: t('menu.production'), icon: , path: '/production' },
- { id: 'prod-orders', label: 'Ordini Produzione', icon: , path: '/production/orders' },
- { id: 'prod-bom', label: 'Distinte Base', icon: , path: '/production/bom' },
- { id: 'prod-workcenters', label: 'Centri di Lavoro', icon: , path: '/production/work-centers' },
- { id: 'prod-cycles', label: 'Cicli', icon: , path: '/production/cycles' },
- { id: 'prod-mrp', label: 'MRP', icon: , path: '/production/mrp' },
+ { id: 'prod-dashboard', label: t('menu.dashboard'), tabLabel: t('menu.production'), icon: , path: '/production', translationKey: 'menu.production' },
+ { id: 'prod-orders', label: t('menu.productionOrders'), icon: , path: '/production/orders', translationKey: 'menu.productionOrders' },
+ { id: 'prod-bom', label: t('menu.bom'), icon: , path: '/production/bom', translationKey: 'menu.bom' },
+ { id: 'prod-workcenters', label: t('menu.workCenters'), icon: , path: '/production/work-centers', translationKey: 'menu.workCenters' },
+ { id: 'prod-cycles', label: t('menu.cycles'), icon: , path: '/production/cycles', translationKey: 'menu.cycles' },
+ { id: 'prod-mrp', label: t('menu.mrp'), icon: , path: '/production/mrp', translationKey: 'menu.mrp' },
],
},
{
@@ -156,10 +162,11 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
label: t('menu.events'),
icon: ,
appCode: 'events',
+ translationKey: 'menu.events',
children: [
- { id: 'ev-list', label: t('menu.events'), icon: , path: '/events/list' },
- { id: 'ev-calendar', label: t('menu.calendar'), icon: , path: '/events/calendar' },
- { id: 'ev-locations', label: t('menu.location'), icon: , path: '/events/locations' },
+ { id: 'ev-list', label: t('menu.events'), icon: , path: '/events/list', translationKey: 'menu.events' },
+ { id: 'ev-calendar', label: t('menu.calendar'), icon: , path: '/events/calendar', translationKey: 'menu.calendar' },
+ { id: 'ev-locations', label: t('menu.location'), icon: , path: '/events/locations', translationKey: 'menu.location' },
],
},
{
@@ -167,23 +174,25 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
label: t('apps.hr.title'),
icon: ,
appCode: 'hr',
+ translationKey: 'apps.hr.title',
children: [
- { id: 'hr-dipendenti', label: t('apps.hr.dipendenti'), icon: , path: '/hr/dipendenti' },
- { id: 'hr-contratti', label: t('apps.hr.contratti'), icon: , path: '/hr/contratti' },
- { id: 'hr-assenze', label: t('apps.hr.assenze'), icon: , path: '/hr/assenze' },
- { id: 'hr-pagamenti', label: t('apps.hr.pagamenti'), icon: , path: '/hr/pagamenti' },
- { id: 'hr-rimborsi', label: t('apps.hr.rimborsi'), icon: , path: '/hr/rimborsi' },
+ { id: 'hr-dipendenti', label: t('apps.hr.dipendenti'), icon: , path: '/hr/dipendenti', translationKey: 'apps.hr.dipendenti' },
+ { id: 'hr-contratti', label: t('apps.hr.contratti'), icon: , path: '/hr/contratti', translationKey: 'apps.hr.contratti' },
+ { id: 'hr-assenze', label: t('apps.hr.assenze'), icon: , path: '/hr/assenze', translationKey: 'apps.hr.assenze' },
+ { id: 'hr-pagamenti', label: t('apps.hr.pagamenti'), icon: , path: '/hr/pagamenti', translationKey: 'apps.hr.pagamenti' },
+ { id: 'hr-rimborsi', label: t('apps.hr.rimborsi'), icon: , path: '/hr/rimborsi', translationKey: 'apps.hr.rimborsi' },
],
},
{
id: 'admin',
- label: 'Amministrazione',
+ label: t('menu.administration'),
icon: ,
+ translationKey: 'menu.administration',
children: [
- { id: 'apps', label: t('menu.apps'), icon: , path: '/apps' },
- { id: 'autocodes', label: t('menu.autoCodes'), icon: , path: '/admin/auto-codes' },
- { id: 'customfields', label: t('menu.customFields'), icon: , path: '/admin/custom-fields' },
- { id: 'reports', label: t('menu.reports'), icon: , path: '/report-designer', appCode: 'report-designer' },
+ { id: 'apps', label: t('menu.apps'), icon: , path: '/apps', translationKey: 'menu.apps' },
+ { id: 'autocodes', label: t('menu.autoCodes'), icon: , path: '/admin/auto-codes', translationKey: 'menu.autoCodes' },
+ { id: 'customfields', label: t('menu.customFields'), icon: , path: '/admin/custom-fields', translationKey: 'menu.customFields' },
+ { id: 'reports', label: t('menu.reports'), icon: , path: '/report-designer', appCode: 'report-designer', translationKey: 'menu.reports' },
],
},
];
diff --git a/src/frontend/src/components/TabsBar.tsx b/src/frontend/src/components/TabsBar.tsx
index c4d758a..ed41146 100644
--- a/src/frontend/src/components/TabsBar.tsx
+++ b/src/frontend/src/components/TabsBar.tsx
@@ -6,6 +6,7 @@ import { useTheme } from '@mui/material/styles';
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core';
import { arrayMove, SortableContext, sortableKeyboardCoordinates, horizontalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
+import { useLanguage } from '../contexts/LanguageContext';
interface SortableTabProps {
tab: TabType;
@@ -16,6 +17,7 @@ interface SortableTabProps {
}
function SortableTab({ tab, activeTabPath, onActivate, onClose, onContextMenu }: SortableTabProps) {
+ const { t } = useLanguage();
const {
attributes,
listeners,
@@ -52,7 +54,7 @@ function SortableTab({ tab, activeTabPath, onActivate, onClose, onContextMenu }:
- {tab.label}
+ {tab.translationKey ? t(tab.translationKey) : tab.label}
{tab.closable && (
-
+
setGroupsMenuAnchor(e.currentTarget)}
@@ -233,21 +236,21 @@ export default function TabsBar() {
handleCloseContextMenu();
}} disabled={!contextMenu?.tab?.closable}>
- Close
+ {t('navigation.close')}
@@ -262,12 +265,12 @@ export default function TabsBar() {
setGroupsMenuAnchor(null);
}}>
- Save Current Session
+ {t('navigation.saveSession')}
{tabGroups.length === 0 && (
)}
{tabGroups.map((group) => (
@@ -292,12 +295,12 @@ export default function TabsBar() {
{/* Save Group Dialog */}
diff --git a/src/frontend/src/contexts/TabContext.tsx b/src/frontend/src/contexts/TabContext.tsx
index ca85db4..26739b2 100644
--- a/src/frontend/src/contexts/TabContext.tsx
+++ b/src/frontend/src/contexts/TabContext.tsx
@@ -4,6 +4,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
export interface Tab {
path: string;
label: string;
+ translationKey?: string;
closable?: boolean;
}
@@ -16,7 +17,7 @@ interface TabContextType {
tabs: Tab[];
activeTabPath: string;
tabGroups: TabGroup[];
- openTab: (path: string, label: string, closable?: boolean) => void;
+ openTab: (path: string, label: string, closable?: boolean, translationKey?: string) => void;
closeTab: (path: string) => void;
setActiveTab: (path: string) => void;
reorderTabs: (newTabs: Tab[]) => void;
@@ -48,14 +49,14 @@ export function TabProvider({ children }: { children: ReactNode }) {
if (Array.isArray(parsedTabs) && parsedTabs.length > 0) {
setTabs(parsedTabs);
} else {
- setTabs([{ path: '/', label: 'Dashboard', closable: false }]);
+ setTabs([{ path: '/', label: 'Dashboard', translationKey: 'menu.dashboard', closable: false }]);
}
} catch (e) {
console.error("Failed to parse tabs", e);
- setTabs([{ path: '/', label: 'Dashboard', closable: false }]);
+ setTabs([{ path: '/', label: 'Dashboard', translationKey: 'menu.dashboard', closable: false }]);
}
} else {
- setTabs([{ path: '/', label: 'Dashboard', closable: false }]);
+ setTabs([{ path: '/', label: 'Dashboard', translationKey: 'menu.dashboard', closable: false }]);
}
if (savedActiveTab) {
@@ -96,18 +97,19 @@ export function TabProvider({ children }: { children: ReactNode }) {
}
}, [location.pathname, tabs]);
- const openTab = (path: string, label: string, closable: boolean = true) => {
+ const openTab = (path: string, label: string, closable: boolean = true, translationKey?: string) => {
setTabs((prev) => {
const existingTabIndex = prev.findIndex((t) => t.path === path);
if (existingTabIndex !== -1) {
- if (prev[existingTabIndex].label !== label) {
+ // Update label and translationKey if they changed
+ if (prev[existingTabIndex].label !== label || prev[existingTabIndex].translationKey !== translationKey) {
const newTabs = [...prev];
- newTabs[existingTabIndex] = { ...newTabs[existingTabIndex], label };
+ newTabs[existingTabIndex] = { ...newTabs[existingTabIndex], label, translationKey };
return newTabs;
}
return prev;
}
- return [...prev, { path, label, closable }];
+ return [...prev, { path, label, closable, translationKey }];
});
navigate(path);
};
|