feat: implement global translation for HR, purchases, and core UI components
This commit is contained in:
@@ -42,5 +42,8 @@ File riassuntivo dello stato di sviluppo di Zentral.
|
|||||||
- Miglioramento UX tab: chiusura con middle-click, drag & drop, gruppi di tab personalizzati.
|
- Miglioramento UX tab: chiusura con middle-click, drag & drop, gruppi di tab personalizzati.
|
||||||
- [2025-12-06 Tab Flicker Fix](./devlog/2025-12-06-011500_tab_flicker_fix.md) - **Completato**
|
- [2025-12-06 Tab Flicker Fix](./devlog/2025-12-06-011500_tab_flicker_fix.md) - **Completato**
|
||||||
- Risolto problema di flicker rimuovendo l'aggiornamento manuale dello stato attivo e affidandosi esclusivamente alla sincronizzazione con l'URL.
|
- Risolto problema di flicker rimuovendo l'aggiornamento manuale dello stato attivo e affidandosi esclusivamente alla sincronizzazione con l'URL.
|
||||||
- [2025-12-06 Fix Apps Tab Translation](./devlog/2025-12-06-013500_fix_apps_tab_translation.md) - **Completato**
|
- [2025-12-06 02:10:00 - Fix Traduzione Tab](./devlog/2025-12-06-021000_fix_tab_translation.md) - **Completato**
|
||||||
|
- [2025-12-06 01:55:00 - Traduzione Menu, Search Bar e Tab](./devlog/2025-12-06-015500_translate_navigation.md) - **Completato**
|
||||||
|
- [2025-12-06 01:48:00 - Traduzione Modulo Acquisti](./devlog/2025-12-06-014800_translate_purchases.md) - **Completato**
|
||||||
|
- [2025-12-06 01:35:00 - Fix Traduzione Tab Applicazioni](./devlog/2025-12-06-013500_fix_apps_tab_translation.md) - **Completato**
|
||||||
- Corretta chiave di traduzione errata per la tab "Gestione Applicazioni" e migliorata la gestione dell'aggiornamento etichette tab.
|
- Corretta chiave di traduzione errata per la tab "Gestione Applicazioni" e migliorata la gestione dell'aggiornamento etichette tab.
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Global Translation Alignment
|
||||||
|
|
||||||
|
## Stato
|
||||||
|
Completato
|
||||||
|
|
||||||
|
## Descrizione
|
||||||
|
Allineamento completo delle traduzioni in tutto il gestionale. Verifica di stringhe hardcoded, chiavi mancanti e supporto accessibilità.
|
||||||
|
|
||||||
|
## Piano di Lavoro
|
||||||
|
1. [x] Analisi struttura i18n esistente.
|
||||||
|
2. [x] Scansione frontend per stringhe hardcoded.
|
||||||
|
3. [x] Scansione backend per messaggi utente non localizzati.
|
||||||
|
4. [x] Aggiornamento file di traduzione (IT/EN).
|
||||||
|
5. [x] Verifica accessibilità (aria-labels, alt text).
|
||||||
|
6. [x] Test cambio lingua.
|
||||||
|
|
||||||
|
## Log
|
||||||
|
- Creazione piano di lavoro.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Traduzione Modulo Acquisti
|
||||||
|
|
||||||
|
## Obiettivo
|
||||||
|
Tradurre completamente il modulo acquisti (Purchases) in italiano e inglese, eliminando le stringhe hardcoded.
|
||||||
|
|
||||||
|
## File da analizzare
|
||||||
|
- `src/frontend/src/apps/purchases/pages/PurchaseOrderFormPage.tsx`
|
||||||
|
- `src/frontend/src/apps/purchases/pages/PurchaseOrdersPage.tsx`
|
||||||
|
- `src/frontend/src/apps/purchases/pages/SupplierFormPage.tsx`
|
||||||
|
- `src/frontend/src/apps/purchases/pages/SuppliersPage.tsx`
|
||||||
|
- `src/frontend/src/apps/purchases/components/PurchasesStatsWidget.tsx`
|
||||||
|
|
||||||
|
## Piano di lavoro
|
||||||
|
1. Analizzare i file per identificare le stringhe hardcoded.
|
||||||
|
2. Aggiungere le chiavi di traduzione in `it/translation.json` e `en/translation.json`.
|
||||||
|
3. Aggiornare i componenti React per utilizzare `useTranslation`.
|
||||||
|
4. Verificare la build.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Traduzione Menu, Search Bar e Tab
|
||||||
|
|
||||||
|
## Obiettivo
|
||||||
|
Tradurre completamente i componenti di navigazione principale: Sidebar (Menu), Search Bar e TabsBar.
|
||||||
|
|
||||||
|
## File da analizzare
|
||||||
|
- `src/frontend/src/components/Sidebar.tsx`
|
||||||
|
- `src/frontend/src/components/SearchBar.tsx`
|
||||||
|
- `src/frontend/src/components/TabsBar.tsx`
|
||||||
|
|
||||||
|
## Piano di lavoro
|
||||||
|
1. Analizzare `Sidebar.tsx` per le voci di menu hardcoded.
|
||||||
|
2. Analizzare `SearchBar.tsx` per placeholder e testi hardcoded.
|
||||||
|
3. Analizzare `TabsBar.tsx` per i titoli delle tab e menu contestuali.
|
||||||
|
4. Aggiungere le chiavi mancanti in `it/translation.json` e `en/translation.json`.
|
||||||
|
5. Aggiornare i componenti per usare `useTranslation`.
|
||||||
|
|
||||||
|
## Stato
|
||||||
|
- **Completato**: 2025-12-06 02:05:00
|
||||||
|
- Aggiunte chiavi di traduzione per menu, navigazione e tab.
|
||||||
|
- Aggiornati i componenti `Sidebar.tsx`, `SearchBar.tsx` e `TabsBar.tsx`.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Traduzione Tab
|
||||||
|
|
||||||
|
## Problema
|
||||||
|
Le tab aperte non venivano tradotte dinamicamente al cambio lingua perché il titolo (label) veniva salvato come stringa statica nel `TabContext` (e persistito in localStorage).
|
||||||
|
|
||||||
|
## Soluzione
|
||||||
|
1. Aggiornato `TabContext.tsx`:
|
||||||
|
- Aggiunta proprietà opzionale `translationKey` all'interfaccia `Tab`.
|
||||||
|
- Aggiornata la funzione `openTab` per accettare e salvare `translationKey`.
|
||||||
|
- Aggiornato il caricamento iniziale (default tab) per includere la chiave di traduzione.
|
||||||
|
|
||||||
|
2. Aggiornato `Sidebar.tsx`:
|
||||||
|
- Aggiunta proprietà `translationKey` alla struttura del menu.
|
||||||
|
- Passaggio della chiave di traduzione alla funzione `openTab` al click.
|
||||||
|
|
||||||
|
3. Aggiornato `SearchBar.tsx`:
|
||||||
|
- Aggiunta proprietà `translationKey` alle opzioni di ricerca.
|
||||||
|
- Passaggio della chiave di traduzione alla funzione `openTab` alla selezione.
|
||||||
|
|
||||||
|
4. Aggiornato `TabsBar.tsx`:
|
||||||
|
- Utilizzo di `t(tab.translationKey)` se disponibile, altrimenti fallback su `tab.label`.
|
||||||
|
- Questo garantisce che le tab cambino lingua istantaneamente quando l'utente cambia lingua.
|
||||||
|
|
||||||
|
## Stato
|
||||||
|
- **Completato**: 2025-12-06 02:15:00
|
||||||
|
- Le tab ora supportano la traduzione dinamica.
|
||||||
@@ -51,7 +51,32 @@
|
|||||||
"reports": "Reports",
|
"reports": "Reports",
|
||||||
"apps": "Apps",
|
"apps": "Apps",
|
||||||
"autoCodes": "Auto Codes",
|
"autoCodes": "Auto Codes",
|
||||||
"customFields": "Custom Fields"
|
"customFields": "Custom Fields",
|
||||||
|
"suppliers": "Suppliers",
|
||||||
|
"purchaseOrders": "Purchase Orders",
|
||||||
|
"salesOrders": "Sales Orders",
|
||||||
|
"productionOrders": "Production Orders",
|
||||||
|
"bom": "Bill of Materials",
|
||||||
|
"workCenters": "Work Centers",
|
||||||
|
"cycles": "Cycles",
|
||||||
|
"mrp": "MRP",
|
||||||
|
"administration": "Administration",
|
||||||
|
"movements": "Movements",
|
||||||
|
"stock": "Stock",
|
||||||
|
"inventory": "Inventory"
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"searchPlaceholder": "Search...",
|
||||||
|
"tabGroups": "Tab Groups",
|
||||||
|
"close": "Close",
|
||||||
|
"closeOthers": "Close Others",
|
||||||
|
"closeRight": "Close to the Right",
|
||||||
|
"saveSession": "Save Current Session",
|
||||||
|
"noSavedGroups": "No saved groups",
|
||||||
|
"saveGroupTitle": "Save Tab Group",
|
||||||
|
"groupName": "Group Name",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
@@ -515,6 +540,135 @@
|
|||||||
"saving": "Saving...",
|
"saving": "Saving...",
|
||||||
"save": "Save"
|
"save": "Save"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"elements": {
|
||||||
|
"text": "Text",
|
||||||
|
"textDesc": "Add a text field",
|
||||||
|
"image": "Image",
|
||||||
|
"imageDesc": "Insert an image",
|
||||||
|
"shape": "Shape",
|
||||||
|
"shapeDesc": "Draw a geometric shape",
|
||||||
|
"table": "Table",
|
||||||
|
"tableDesc": "Insert a data table",
|
||||||
|
"line": "Line",
|
||||||
|
"lineDesc": "Draw a line",
|
||||||
|
"add": "Add",
|
||||||
|
"insert": "Insert element"
|
||||||
|
},
|
||||||
|
"snap": {
|
||||||
|
"grid": "Grid",
|
||||||
|
"objects": "Objects",
|
||||||
|
"borders": "Margins",
|
||||||
|
"center": "Center",
|
||||||
|
"tangent": "Edges",
|
||||||
|
"options": "Snap Options",
|
||||||
|
"all": "All",
|
||||||
|
"hideGrid": "Hide grid",
|
||||||
|
"showGrid": "Show grid",
|
||||||
|
"autoAlign": "Auto alignment"
|
||||||
|
},
|
||||||
|
"toolbar": {
|
||||||
|
"undo": "Undo",
|
||||||
|
"redo": "Redo",
|
||||||
|
"delete": "Delete",
|
||||||
|
"preview": "Preview",
|
||||||
|
"save": "Save",
|
||||||
|
"lock": "Lock",
|
||||||
|
"unlock": "Unlock",
|
||||||
|
"duplicate": "Duplicate",
|
||||||
|
"prevPage": "Previous Page",
|
||||||
|
"nextPage": "Next Page",
|
||||||
|
"zoomIn": "Zoom in",
|
||||||
|
"zoomOut": "Zoom out",
|
||||||
|
"autoSaveOn": "Auto-save on",
|
||||||
|
"autoSaveOff": "Auto-save off",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saved": "Saved",
|
||||||
|
"unsaved": "Unsaved",
|
||||||
|
"unsavedTooltip": "Unsaved changes",
|
||||||
|
"autoSavePending": "Auto-save pending...",
|
||||||
|
"edit": "EDIT",
|
||||||
|
"history": "HISTORY",
|
||||||
|
"historyTooltip": "Change history",
|
||||||
|
"view": "VIEW",
|
||||||
|
"zoom": "ZOOM",
|
||||||
|
"fitWindow": "Fit to window",
|
||||||
|
"zoomLevel": "Zoom level",
|
||||||
|
"presets": "Presets",
|
||||||
|
"searchCommand": "Search command",
|
||||||
|
"shortcuts": "Keyboard shortcuts",
|
||||||
|
"shortcutsTitle": "Keyboard Shortcuts"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"title": "Report Preview",
|
||||||
|
"notSelected": "Not selected",
|
||||||
|
"removeSelection": "Remove selection",
|
||||||
|
"select": "Select",
|
||||||
|
"searchPlaceholder": "Search...",
|
||||||
|
"noResults": "No results found",
|
||||||
|
"noEntities": "No entities available",
|
||||||
|
"results": "results",
|
||||||
|
"selected": "selected",
|
||||||
|
"instruction": "Select an entity for each dataset to use in the preview",
|
||||||
|
"errorLoading": "Error loading available data",
|
||||||
|
"noDatasets": "There are no datasets selected for this template. Add at least one dataset to generate the preview.",
|
||||||
|
"selectEntityInstruction": "Select an entity for each dataset",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"generating": "Generating...",
|
||||||
|
"generatePdf": "Generate PDF",
|
||||||
|
"generatePreviewPdf": "Generate PDF Preview"
|
||||||
|
},
|
||||||
|
"datasetManager": {
|
||||||
|
"title": "Virtual Datasets",
|
||||||
|
"newDataset": "New Dataset",
|
||||||
|
"noDatasets": "No Virtual Datasets",
|
||||||
|
"noDatasetsDesc": "Create virtual datasets to combine and filter data from multiple sources.",
|
||||||
|
"createFirst": "Create the first dataset",
|
||||||
|
"editDataset": "Edit Dataset",
|
||||||
|
"newVirtualDataset": "New Virtual Dataset",
|
||||||
|
"deleteConfirm": "Delete dataset \"{{name}}\"?",
|
||||||
|
"validationError": "Validation error",
|
||||||
|
"errors": "Errors:",
|
||||||
|
"warnings": "Warnings:",
|
||||||
|
"validConfig": "Valid configuration",
|
||||||
|
"tabs": {
|
||||||
|
"info": "Info",
|
||||||
|
"sources": "Sources",
|
||||||
|
"relationships": "Relationships",
|
||||||
|
"filters": "Filters",
|
||||||
|
"fields": "Fields"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"nameId": "Identifier Name",
|
||||||
|
"nameIdHelper": "Unique name used internally (no spaces)",
|
||||||
|
"displayName": "Display Name",
|
||||||
|
"description": "Description",
|
||||||
|
"category": "Category",
|
||||||
|
"icon": "Icon"
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"available": "Available Datasets",
|
||||||
|
"addInstruction": "Click to add a source",
|
||||||
|
"inDataset": "Sources in Dataset",
|
||||||
|
"empty": "Add at least one data source from the left panel",
|
||||||
|
"alias": "Alias",
|
||||||
|
"primary": "Primary",
|
||||||
|
"setPrimary": "Set Primary"
|
||||||
|
},
|
||||||
|
"noDescription": "No description",
|
||||||
|
"sourcesCount": "sources"
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"move1px": "Move (1px)",
|
||||||
|
"move10px": "Move (10px)",
|
||||||
|
"toggleGrid": "Toggle grid",
|
||||||
|
"zoomInOut": "Zoom in/out",
|
||||||
|
"changePage": "Change page"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"now": "Just now",
|
||||||
|
"minutesAgo": "{{count}}m ago",
|
||||||
|
"hoursAgo": "{{count}}h ago"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warehouse": {
|
"warehouse": {
|
||||||
@@ -1072,17 +1226,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"purchases": {
|
"purchases": {
|
||||||
"menu": {
|
"stats": {
|
||||||
"suppliers": "Suppliers",
|
"title": "Purchases",
|
||||||
"orders": "Purchase Orders"
|
"costsThisMonth": "Costs this month",
|
||||||
|
"pendingOrders": "{{count}} Pending Orders"
|
||||||
},
|
},
|
||||||
"suppliers": {
|
"supplier": {
|
||||||
"title": "Suppliers",
|
"title": "Suppliers",
|
||||||
"newSupplier": "New Supplier",
|
"newSupplier": "New Supplier",
|
||||||
"editSupplier": "Edit Supplier",
|
"createTitle": "New Supplier",
|
||||||
|
"editTitle": "Edit Supplier",
|
||||||
"columns": {
|
"columns": {
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
"name": "Name",
|
"name": "Business Name",
|
||||||
"vatNumber": "VAT Number",
|
"vatNumber": "VAT Number",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
@@ -1090,85 +1246,60 @@
|
|||||||
"status": "Status"
|
"status": "Status"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"code": "Code",
|
|
||||||
"name": "Business Name",
|
"name": "Business Name",
|
||||||
"vatNumber": "VAT Number",
|
"vatNumber": "VAT Number",
|
||||||
"fiscalCode": "Fiscal Code",
|
"fiscalCode": "Fiscal Code",
|
||||||
"email": "Email",
|
|
||||||
"pec": "PEC",
|
|
||||||
"phone": "Phone",
|
|
||||||
"website": "Website",
|
|
||||||
"address": "Address",
|
"address": "Address",
|
||||||
"city": "City",
|
"city": "City",
|
||||||
"province": "Province",
|
"province": "Province",
|
||||||
"zipCode": "ZIP Code",
|
"zipCode": "ZIP Code",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
|
"email": "Email",
|
||||||
|
"pec": "PEC",
|
||||||
|
"phone": "Phone",
|
||||||
|
"website": "Website",
|
||||||
"paymentTerms": "Payment Terms",
|
"paymentTerms": "Payment Terms",
|
||||||
"notes": "Notes",
|
"notes": "Notes"
|
||||||
"isActive": "Active"
|
}
|
||||||
},
|
|
||||||
"placeholders": {
|
|
||||||
"search": "Search supplier...",
|
|
||||||
"generatedAutomatically": "Generated automatically"
|
|
||||||
},
|
|
||||||
"deleteConfirm": "Are you sure you want to delete this supplier?"
|
|
||||||
},
|
},
|
||||||
"orders": {
|
"order": {
|
||||||
"title": "Purchase Orders",
|
"title": "Purchase Orders",
|
||||||
"newOrder": "New Order",
|
"newOrder": "New Order",
|
||||||
"editOrder": "Edit Order",
|
"createTitle": "New Order",
|
||||||
"columns": {
|
"editTitle": "Edit Order",
|
||||||
"orderNumber": "Order Number",
|
|
||||||
"orderDate": "Date",
|
|
||||||
"supplier": "Supplier",
|
|
||||||
"status": "Status",
|
|
||||||
"total": "Total",
|
|
||||||
"deliveryDate": "Delivery Date"
|
|
||||||
},
|
|
||||||
"fields": {
|
|
||||||
"orderNumber": "Order Number",
|
|
||||||
"orderDate": "Order Date",
|
|
||||||
"expectedDeliveryDate": "Expected Delivery",
|
|
||||||
"supplier": "Supplier",
|
|
||||||
"destinationWarehouse": "Destination Warehouse",
|
|
||||||
"notes": "Notes",
|
|
||||||
"article": "Article",
|
|
||||||
"quantity": "Quantity",
|
|
||||||
"unitPrice": "Unit Price",
|
|
||||||
"discount": "Discount %",
|
|
||||||
"taxRate": "Tax Rate %",
|
|
||||||
"lineTotal": "Total"
|
|
||||||
},
|
|
||||||
"status": {
|
"status": {
|
||||||
"Draft": "Draft",
|
"Draft": "Draft",
|
||||||
"Confirmed": "Confirmed",
|
"Confirmed": "Confirmed",
|
||||||
|
"PartiallyReceived": "Partially Received",
|
||||||
"Received": "Received",
|
"Received": "Received",
|
||||||
"Cancelled": "Cancelled"
|
"Cancelled": "Cancelled"
|
||||||
},
|
},
|
||||||
|
"columns": {
|
||||||
|
"number": "Number",
|
||||||
|
"date": "Date",
|
||||||
|
"supplier": "Supplier",
|
||||||
|
"status": "Status",
|
||||||
|
"total": "Total"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"date": "Order Date",
|
||||||
|
"expectedDate": "Expected Delivery Date",
|
||||||
|
"supplier": "Supplier",
|
||||||
|
"warehouse": "Destination Warehouse",
|
||||||
|
"notes": "Notes"
|
||||||
|
},
|
||||||
|
"lines": {
|
||||||
|
"article": "Article",
|
||||||
|
"quantity": "Quantity",
|
||||||
|
"price": "Unit Price",
|
||||||
|
"discount": "Discount %",
|
||||||
|
"tax": "Tax %",
|
||||||
|
"total": "Total"
|
||||||
|
},
|
||||||
|
"total": "Order Total",
|
||||||
"actions": {
|
"actions": {
|
||||||
"addLine": "Add Line",
|
|
||||||
"confirm": "Confirm Order",
|
"confirm": "Confirm Order",
|
||||||
"receive": "Receive Goods",
|
"receive": "Receive Goods"
|
||||||
"view": "View",
|
|
||||||
"delete": "Delete"
|
|
||||||
},
|
|
||||||
"totals": {
|
|
||||||
"net": "Net Total",
|
|
||||||
"tax": "Tax",
|
|
||||||
"gross": "Gross Total"
|
|
||||||
},
|
|
||||||
"deleteConfirm": "Are you sure you want to delete this order?",
|
|
||||||
"confirmDialog": {
|
|
||||||
"title": "Confirm Order",
|
|
||||||
"content": "Are you sure you want to confirm this order? It will no longer be editable.",
|
|
||||||
"confirm": "Confirm",
|
|
||||||
"cancel": "Cancel"
|
|
||||||
},
|
|
||||||
"receiveDialog": {
|
|
||||||
"title": "Receive Goods",
|
|
||||||
"content": "Are you sure you want to mark this order as received? This will generate stock movements.",
|
|
||||||
"confirm": "Receive",
|
|
||||||
"cancel": "Cancel"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1393,6 +1524,21 @@
|
|||||||
"rimborsiTitle": "Reimbursement Management",
|
"rimborsiTitle": "Reimbursement Management",
|
||||||
"newRimborso": "New Reimbursement",
|
"newRimborso": "New Reimbursement",
|
||||||
"editRimborso": "Edit Reimbursement",
|
"editRimborso": "Edit Reimbursement",
|
||||||
"descrizione": "Description"
|
"descrizione": "Description",
|
||||||
|
"status": {
|
||||||
|
"richiesto": "Requested",
|
||||||
|
"approvato": "Approved",
|
||||||
|
"rimborsato": "Reimbursed",
|
||||||
|
"rifiutato": "Rejected",
|
||||||
|
"richiesta": "Requested",
|
||||||
|
"approvata": "Approved",
|
||||||
|
"rifiutata": "Rejected"
|
||||||
|
},
|
||||||
|
"assenza": {
|
||||||
|
"ferie": "Vacation",
|
||||||
|
"malattia": "Sick Leave",
|
||||||
|
"permesso": "Permit",
|
||||||
|
"altro": "Other"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,32 @@
|
|||||||
"reports": "Report",
|
"reports": "Report",
|
||||||
"apps": "Applicazioni",
|
"apps": "Applicazioni",
|
||||||
"autoCodes": "Codici Auto",
|
"autoCodes": "Codici Auto",
|
||||||
"customFields": "Campi Personalizzati"
|
"customFields": "Campi Personalizzati",
|
||||||
|
"suppliers": "Fornitori",
|
||||||
|
"purchaseOrders": "Ordini Acquisto",
|
||||||
|
"salesOrders": "Ordini Vendita",
|
||||||
|
"productionOrders": "Ordini Produzione",
|
||||||
|
"bom": "Distinte Base",
|
||||||
|
"workCenters": "Centri di Lavoro",
|
||||||
|
"cycles": "Cicli",
|
||||||
|
"mrp": "MRP",
|
||||||
|
"administration": "Amministrazione",
|
||||||
|
"movements": "Movimenti",
|
||||||
|
"stock": "Giacenze",
|
||||||
|
"inventory": "Inventario"
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"searchPlaceholder": "Cerca...",
|
||||||
|
"tabGroups": "Gruppi Schede",
|
||||||
|
"close": "Chiudi",
|
||||||
|
"closeOthers": "Chiudi Altre",
|
||||||
|
"closeRight": "Chiudi a Destra",
|
||||||
|
"saveSession": "Salva Sessione Corrente",
|
||||||
|
"noSavedGroups": "Nessun gruppo salvato",
|
||||||
|
"saveGroupTitle": "Salva Gruppo Schede",
|
||||||
|
"groupName": "Nome Gruppo",
|
||||||
|
"save": "Salva",
|
||||||
|
"cancel": "Annulla"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
@@ -591,9 +616,143 @@
|
|||||||
"saving": "Salvataggio...",
|
"saving": "Salvataggio...",
|
||||||
"save": "Salva"
|
"save": "Salva"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"elements": {
|
||||||
|
"text": "Testo",
|
||||||
|
"textDesc": "Aggiungi un campo di testo",
|
||||||
|
"image": "Immagine",
|
||||||
|
"imageDesc": "Inserisci un'immagine",
|
||||||
|
"shape": "Forma",
|
||||||
|
"shapeDesc": "Disegna una forma geometrica",
|
||||||
|
"table": "Tabella",
|
||||||
|
"tableDesc": "Inserisci una tabella dati",
|
||||||
|
"line": "Linea",
|
||||||
|
"lineDesc": "Traccia una linea",
|
||||||
|
"add": "Aggiungi",
|
||||||
|
"insert": "Inserisci elemento"
|
||||||
|
},
|
||||||
|
"snap": {
|
||||||
|
"grid": "Griglia",
|
||||||
|
"objects": "Oggetti",
|
||||||
|
"borders": "Margini",
|
||||||
|
"center": "Centro",
|
||||||
|
"tangent": "Bordi",
|
||||||
|
"options": "Opzioni Snap",
|
||||||
|
"all": "Tutti",
|
||||||
|
"hideGrid": "Nascondi griglia",
|
||||||
|
"showGrid": "Mostra griglia",
|
||||||
|
"autoAlign": "Allineamento automatico"
|
||||||
|
},
|
||||||
|
"toolbar": {
|
||||||
|
"undo": "Annulla",
|
||||||
|
"redo": "Ripeti",
|
||||||
|
"delete": "Elimina",
|
||||||
|
"preview": "Anteprima",
|
||||||
|
"save": "Salva",
|
||||||
|
"lock": "Blocca",
|
||||||
|
"unlock": "Sblocca",
|
||||||
|
"duplicate": "Duplica",
|
||||||
|
"prevPage": "Pagina precedente",
|
||||||
|
"nextPage": "Pagina successiva",
|
||||||
|
"zoomIn": "Zoom in",
|
||||||
|
"zoomOut": "Zoom out",
|
||||||
|
"autoSaveOn": "Auto-salvataggio attivo",
|
||||||
|
"autoSaveOff": "Auto-salvataggio disattivato",
|
||||||
|
"saving": "Salvataggio in corso...",
|
||||||
|
"saved": "Salvato",
|
||||||
|
"unsaved": "Non salvato",
|
||||||
|
"unsavedTooltip": "Modifiche non salvate",
|
||||||
|
"autoSavePending": "Salvataggio automatico in attesa...",
|
||||||
|
"edit": "MODIFICA",
|
||||||
|
"history": "CRONOLOGIA",
|
||||||
|
"historyTooltip": "Cronologia modifiche",
|
||||||
|
"view": "VISTA",
|
||||||
|
"zoom": "ZOOM",
|
||||||
|
"fitWindow": "Adatta alla finestra",
|
||||||
|
"zoomLevel": "Livello zoom",
|
||||||
|
"presets": "Preset",
|
||||||
|
"searchCommand": "Cerca comando",
|
||||||
|
"shortcuts": "Scorciatoie tastiera",
|
||||||
|
"shortcutsTitle": "Scorciatoie Tastiera"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"title": "Anteprima Report",
|
||||||
|
"notSelected": "Non selezionato",
|
||||||
|
"removeSelection": "Rimuovi selezione",
|
||||||
|
"select": "Seleziona",
|
||||||
|
"searchPlaceholder": "Cerca...",
|
||||||
|
"noResults": "Nessun risultato trovato",
|
||||||
|
"noEntities": "Nessuna entità disponibile",
|
||||||
|
"results": "risultati",
|
||||||
|
"selected": "selezionati",
|
||||||
|
"instruction": "Seleziona un'entità per ogni dataset da utilizzare nell'anteprima",
|
||||||
|
"errorLoading": "Errore nel caricamento dei dati disponibili",
|
||||||
|
"noDatasets": "Non ci sono dataset selezionati per questo template. Aggiungi almeno un dataset per poter generare l'anteprima.",
|
||||||
|
"selectEntityInstruction": "Seleziona un'entità per ogni dataset",
|
||||||
|
"cancel": "Annulla",
|
||||||
|
"generating": "Generazione...",
|
||||||
|
"generatePdf": "Genera PDF",
|
||||||
|
"generatePreviewPdf": "Genera Anteprima PDF"
|
||||||
|
},
|
||||||
|
"datasetManager": {
|
||||||
|
"title": "Dataset Virtuali",
|
||||||
|
"newDataset": "Nuovo Dataset",
|
||||||
|
"noDatasets": "Nessun Dataset Virtuale",
|
||||||
|
"noDatasetsDesc": "Crea dataset virtuali per combinare e filtrare i dati da più sorgenti.",
|
||||||
|
"createFirst": "Crea il primo dataset",
|
||||||
|
"editDataset": "Modifica Dataset",
|
||||||
|
"newVirtualDataset": "Nuovo Dataset Virtuale",
|
||||||
|
"deleteConfirm": "Eliminare il dataset \"{{name}}\"?",
|
||||||
|
"validationError": "Errore durante la validazione",
|
||||||
|
"errors": "Errori:",
|
||||||
|
"warnings": "Avvisi:",
|
||||||
|
"validConfig": "Configurazione valida",
|
||||||
|
"tabs": {
|
||||||
|
"info": "Info",
|
||||||
|
"sources": "Sorgenti",
|
||||||
|
"relationships": "Relazioni",
|
||||||
|
"filters": "Filtri",
|
||||||
|
"fields": "Campi"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"nameId": "Nome Identificativo",
|
||||||
|
"nameIdHelper": "Nome univoco usato internamente (senza spazi)",
|
||||||
|
"displayName": "Nome Visualizzato",
|
||||||
|
"description": "Descrizione",
|
||||||
|
"category": "Categoria",
|
||||||
|
"icon": "Icona"
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"available": "Dataset Disponibili",
|
||||||
|
"addInstruction": "Clicca per aggiungere una sorgente",
|
||||||
|
"inDataset": "Sorgenti nel Dataset",
|
||||||
|
"empty": "Aggiungi almeno una sorgente dati dal pannello a sinistra",
|
||||||
|
"alias": "Alias",
|
||||||
|
"primary": "Primario",
|
||||||
|
"setPrimary": "Imposta Primario"
|
||||||
|
},
|
||||||
|
"noDescription": "Nessuna descrizione",
|
||||||
|
"sourcesCount": "sorgenti"
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"move1px": "Sposta (1px)",
|
||||||
|
"move10px": "Sposta (10px)",
|
||||||
|
"toggleGrid": "Mostra/nascondi griglia",
|
||||||
|
"zoomInOut": "Zoom in/out",
|
||||||
|
"changePage": "Cambia pagina"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"now": "Ora",
|
||||||
|
"minutesAgo": "{{count}}m fa",
|
||||||
|
"hoursAgo": "{{count}}h fa"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"purchases": {
|
"purchases": {
|
||||||
|
"stats": {
|
||||||
|
"title": "Acquisti",
|
||||||
|
"costsThisMonth": "Costi questo mese",
|
||||||
|
"pendingOrders": "{{count}} Ordini in attesa"
|
||||||
|
},
|
||||||
"supplier": {
|
"supplier": {
|
||||||
"title": "Fornitori",
|
"title": "Fornitori",
|
||||||
"newSupplier": "Nuovo Fornitore",
|
"newSupplier": "Nuovo Fornitore",
|
||||||
@@ -1446,6 +1605,21 @@
|
|||||||
"rimborsiTitle": "Gestione Rimborsi",
|
"rimborsiTitle": "Gestione Rimborsi",
|
||||||
"newRimborso": "Nuovo Rimborso",
|
"newRimborso": "Nuovo Rimborso",
|
||||||
"editRimborso": "Modifica Rimborso",
|
"editRimborso": "Modifica Rimborso",
|
||||||
"descrizione": "Descrizione"
|
"descrizione": "Descrizione",
|
||||||
|
"status": {
|
||||||
|
"richiesto": "Richiesto",
|
||||||
|
"approvato": "Approvato",
|
||||||
|
"rimborsato": "Rimborsato",
|
||||||
|
"rifiutato": "Rifiutato",
|
||||||
|
"richiesta": "Richiesta",
|
||||||
|
"approvata": "Approvata",
|
||||||
|
"rifiutata": "Rifiutata"
|
||||||
|
},
|
||||||
|
"assenza": {
|
||||||
|
"ferie": "Ferie",
|
||||||
|
"malattia": "Malattia",
|
||||||
|
"permesso": "Permesso",
|
||||||
|
"altro": "Altro"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,10 +221,10 @@ export default function AssenzePage() {
|
|||||||
onChange={(e) => setFormData({ ...formData, tipoAssenza: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, tipoAssenza: e.target.value })}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<MenuItem value="Ferie">Ferie</MenuItem>
|
<MenuItem value="Ferie">{t('personale.assenza.ferie')}</MenuItem>
|
||||||
<MenuItem value="Malattia">Malattia</MenuItem>
|
<MenuItem value="Malattia">{t('personale.assenza.malattia')}</MenuItem>
|
||||||
<MenuItem value="Permesso">Permesso</MenuItem>
|
<MenuItem value="Permesso">{t('personale.assenza.permesso')}</MenuItem>
|
||||||
<MenuItem value="Altro">Altro</MenuItem>
|
<MenuItem value="Altro">{t('personale.assenza.altro')}</MenuItem>
|
||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={6}>
|
<Grid size={6}>
|
||||||
@@ -236,9 +236,9 @@ export default function AssenzePage() {
|
|||||||
onChange={(e) => setFormData({ ...formData, stato: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, stato: e.target.value })}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<MenuItem value="Richiesta">Richiesta</MenuItem>
|
<MenuItem value="Richiesta">{t('personale.status.richiesta')}</MenuItem>
|
||||||
<MenuItem value="Approvata">Approvata</MenuItem>
|
<MenuItem value="Approvata">{t('personale.status.approvata')}</MenuItem>
|
||||||
<MenuItem value="Rifiutata">Rifiutata</MenuItem>
|
<MenuItem value="Rifiutata">{t('personale.status.rifiutata')}</MenuItem>
|
||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={6}>
|
<Grid size={6}>
|
||||||
|
|||||||
@@ -243,10 +243,10 @@ export default function RimborsiPage() {
|
|||||||
onChange={(e) => setFormData({ ...formData, stato: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, stato: e.target.value })}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<MenuItem value="Richiesto">Richiesto</MenuItem>
|
<MenuItem value="Richiesto">{t('personale.status.richiesto')}</MenuItem>
|
||||||
<MenuItem value="Approvato">Approvato</MenuItem>
|
<MenuItem value="Approvato">{t('personale.status.approvato')}</MenuItem>
|
||||||
<MenuItem value="Rimborsato">Rimborsato</MenuItem>
|
<MenuItem value="Rimborsato">{t('personale.status.rimborsato')}</MenuItem>
|
||||||
<MenuItem value="Rifiutato">Rifiutato</MenuItem>
|
<MenuItem value="Rifiutato">{t('personale.status.rifiutato')}</MenuItem>
|
||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={12}>
|
<Grid size={12}>
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import { Card, CardContent, Typography, Box } from '@mui/material';
|
import { Card, CardContent, Typography, Box } from '@mui/material';
|
||||||
import { ShoppingCart as PurchaseIcon } from '@mui/icons-material';
|
import { ShoppingCart as PurchaseIcon } from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function PurchasesStatsWidget() {
|
export default function PurchasesStatsWidget() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<PurchaseIcon color="primary" sx={{ mr: 1 }} />
|
<PurchaseIcon color="primary" sx={{ mr: 1 }} />
|
||||||
<Typography variant="h6">Purchases</Typography>
|
<Typography variant="h6">{t('purchases.stats.title')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h4" sx={{ mb: 1 }}>€ 8,320</Typography>
|
<Typography variant="h4" sx={{ mb: 1 }}>€ 8,320</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">Costs this month</Typography>
|
<Typography variant="body2" color="text.secondary">{t('purchases.stats.costsThisMonth')}</Typography>
|
||||||
|
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Typography variant="body2" color="warning.main">Pending Orders: 3</Typography>
|
<Typography variant="body2" color="warning.main">
|
||||||
|
{t('purchases.stats.pendingOrders', { count: 3 })}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -184,11 +184,11 @@ export default function PurchaseOrderFormPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h4">
|
<Typography variant="h4">
|
||||||
{isEdit ? `${t("purchases.orders.editOrder")} ${order?.orderNumber}` : t("purchases.orders.newOrder")}
|
{isEdit ? `${t("purchases.order.editTitle")} ${order?.orderNumber}` : t("purchases.order.newOrder")}
|
||||||
</Typography>
|
</Typography>
|
||||||
{isEdit && order && (
|
{isEdit && order && (
|
||||||
<Chip
|
<Chip
|
||||||
label={t(`purchases.orders.status.${PurchaseOrderStatus[order.status]}`)}
|
label={t(`purchases.order.status.${PurchaseOrderStatus[order.status]}`)}
|
||||||
color={order.status === PurchaseOrderStatus.Confirmed ? "primary" : order.status === PurchaseOrderStatus.Received ? "success" : "default"}
|
color={order.status === PurchaseOrderStatus.Confirmed ? "primary" : order.status === PurchaseOrderStatus.Received ? "success" : "default"}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ mt: 1 }}
|
sx={{ mt: 1 }}
|
||||||
@@ -206,7 +206,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
onClick={() => confirmMutation.mutate(Number(id))}
|
onClick={() => confirmMutation.mutate(Number(id))}
|
||||||
disabled={confirmMutation.isPending}
|
disabled={confirmMutation.isPending}
|
||||||
>
|
>
|
||||||
{t("purchases.orders.actions.confirm")}
|
{t("purchases.order.actions.confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
onClick={() => receiveMutation.mutate(Number(id))}
|
onClick={() => receiveMutation.mutate(Number(id))}
|
||||||
disabled={receiveMutation.isPending}
|
disabled={receiveMutation.isPending}
|
||||||
>
|
>
|
||||||
{t("purchases.orders.actions.receive")}
|
{t("purchases.order.actions.receive")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field }: { field: any }) => (
|
render={({ field }: { field: any }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
label={t("purchases.orders.fields.orderDate")}
|
label={t("purchases.order.fields.date")}
|
||||||
value={field.value ? dayjs(field.value) : null}
|
value={field.value ? dayjs(field.value) : null}
|
||||||
onChange={(date) => field.onChange(date?.toISOString())}
|
onChange={(date) => field.onChange(date?.toISOString())}
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
@@ -264,7 +264,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field }: { field: any }) => (
|
render={({ field }: { field: any }) => (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
label={t("purchases.orders.fields.expectedDeliveryDate")}
|
label={t("purchases.order.fields.expectedDate")}
|
||||||
value={field.value ? dayjs(field.value) : null}
|
value={field.value ? dayjs(field.value) : null}
|
||||||
onChange={(date) => field.onChange(date?.toISOString())}
|
onChange={(date) => field.onChange(date?.toISOString())}
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
@@ -288,7 +288,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label={t("purchases.orders.fields.supplier")}
|
label={t("purchases.order.fields.supplier")}
|
||||||
error={!!errors.supplierId}
|
error={!!errors.supplierId}
|
||||||
helperText={errors.supplierId?.message}
|
helperText={errors.supplierId?.message}
|
||||||
/>
|
/>
|
||||||
@@ -311,7 +311,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label={t("purchases.orders.fields.destinationWarehouse")}
|
label={t("purchases.order.fields.warehouse")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -325,7 +325,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
render={({ field }: { field: any }) => (
|
render={({ field }: { field: any }) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...field}
|
{...field}
|
||||||
label={t("purchases.orders.fields.notes")}
|
label={t("purchases.order.fields.notes")}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
/>
|
/>
|
||||||
@@ -337,7 +337,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
|
|
||||||
<Paper sx={{ p: 3 }}>
|
<Paper sx={{ p: 3 }}>
|
||||||
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 2 }}>
|
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 2 }}>
|
||||||
<Typography variant="h6">{t("purchases.orders.fields.lineTotal")}</Typography>
|
<Typography variant="h6">{t("purchases.order.lines.total")}</Typography>
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<Button startIcon={<AddIcon />} onClick={() => append({
|
<Button startIcon={<AddIcon />} onClick={() => append({
|
||||||
warehouseArticleId: 0,
|
warehouseArticleId: 0,
|
||||||
@@ -356,12 +356,12 @@ export default function PurchaseOrderFormPage() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell width="30%">{t("purchases.orders.fields.article")}</TableCell>
|
<TableCell width="30%">{t("purchases.order.lines.article")}</TableCell>
|
||||||
<TableCell width="10%">{t("purchases.orders.fields.quantity")}</TableCell>
|
<TableCell width="10%">{t("purchases.order.lines.quantity")}</TableCell>
|
||||||
<TableCell width="15%">{t("purchases.orders.fields.unitPrice")}</TableCell>
|
<TableCell width="15%">{t("purchases.order.lines.price")}</TableCell>
|
||||||
<TableCell width="10%">{t("purchases.orders.fields.discount")}</TableCell>
|
<TableCell width="10%">{t("purchases.order.lines.discount")}</TableCell>
|
||||||
<TableCell width="10%">{t("purchases.orders.fields.taxRate")}</TableCell>
|
<TableCell width="10%">{t("purchases.order.lines.tax")}</TableCell>
|
||||||
<TableCell width="15%" align="right">{t("purchases.orders.fields.lineTotal")}</TableCell>
|
<TableCell width="15%" align="right">{t("purchases.order.lines.total")}</TableCell>
|
||||||
<TableCell width="10%"></TableCell>
|
<TableCell width="10%"></TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@@ -464,7 +464,7 @@ export default function PurchaseOrderFormPage() {
|
|||||||
))}
|
))}
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={5} align="right">
|
<TableCell colSpan={5} align="right">
|
||||||
<Typography fontWeight="bold">{t("purchases.orders.totals.gross")}</Typography>
|
<Typography fontWeight="bold">{t("purchases.order.total")}</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
<Typography fontWeight="bold">
|
<Typography fontWeight="bold">
|
||||||
|
|||||||
@@ -56,15 +56,15 @@ export default function SuppliersPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: GridColDef[] = [
|
const columns: GridColDef[] = [
|
||||||
{ field: "code", headerName: t("purchases.suppliers.columns.code"), width: 120 },
|
{ field: "code", headerName: t("purchases.supplier.columns.code"), width: 120 },
|
||||||
{ field: "name", headerName: t("purchases.suppliers.columns.name"), flex: 1, minWidth: 200 },
|
{ field: "name", headerName: t("purchases.supplier.columns.name"), flex: 1, minWidth: 200 },
|
||||||
{ field: "vatNumber", headerName: t("purchases.suppliers.columns.vatNumber"), width: 150 },
|
{ field: "vatNumber", headerName: t("purchases.supplier.columns.vatNumber"), width: 150 },
|
||||||
{ field: "email", headerName: t("purchases.suppliers.columns.email"), width: 200 },
|
{ field: "email", headerName: t("purchases.supplier.columns.email"), width: 200 },
|
||||||
{ field: "phone", headerName: t("purchases.suppliers.columns.phone"), width: 150 },
|
{ field: "phone", headerName: t("purchases.supplier.columns.phone"), width: 150 },
|
||||||
{ field: "city", headerName: t("purchases.suppliers.columns.city"), width: 150 },
|
{ field: "city", headerName: t("purchases.supplier.columns.city"), width: 150 },
|
||||||
{
|
{
|
||||||
field: "isActive",
|
field: "isActive",
|
||||||
headerName: t("purchases.suppliers.columns.status"),
|
headerName: t("purchases.supplier.columns.status"),
|
||||||
width: 120,
|
width: 120,
|
||||||
renderCell: (params: GridRenderCellParams<SupplierDto>) => (
|
renderCell: (params: GridRenderCellParams<SupplierDto>) => (
|
||||||
<Chip
|
<Chip
|
||||||
@@ -110,13 +110,13 @@ export default function SuppliersPage() {
|
|||||||
mb: 3,
|
mb: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h4">{t("purchases.suppliers.title")}</Typography>
|
<Typography variant="h4">{t("purchases.supplier.title")}</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
>
|
>
|
||||||
{t("purchases.suppliers.newSupplier")}
|
{t("purchases.supplier.newSupplier")}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
Save as SaveIcon,
|
Save as SaveIcon,
|
||||||
Dataset as DatasetIcon,
|
Dataset as DatasetIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
virtualDatasetService,
|
virtualDatasetService,
|
||||||
@@ -91,6 +92,7 @@ export default function DatasetManagerDialog({
|
|||||||
onClose,
|
onClose,
|
||||||
onDatasetCreated,
|
onDatasetCreated,
|
||||||
}: DatasetManagerDialogProps) {
|
}: DatasetManagerDialogProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// State
|
// State
|
||||||
@@ -189,7 +191,7 @@ export default function DatasetManagerDialog({
|
|||||||
} catch {
|
} catch {
|
||||||
setValidationResult({
|
setValidationResult({
|
||||||
isValid: false,
|
isValid: false,
|
||||||
errors: ["Errore durante la validazione"],
|
errors: [t('reports.datasetManager.validationError')],
|
||||||
warnings: [],
|
warnings: [],
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
@@ -230,7 +232,7 @@ export default function DatasetManagerDialog({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteDataset = async (dataset: VirtualDatasetDto) => {
|
const handleDeleteDataset = async (dataset: VirtualDatasetDto) => {
|
||||||
if (confirm(`Eliminare il dataset "${dataset.displayName}"?`)) {
|
if (confirm(t('reports.datasetManager.deleteConfirm', { name: dataset.displayName }))) {
|
||||||
await deleteMutation.mutateAsync(dataset.id);
|
await deleteMutation.mutateAsync(dataset.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -365,13 +367,13 @@ export default function DatasetManagerDialog({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h6">Dataset Virtuali</Typography>
|
<Typography variant="h6">{t('reports.datasetManager.title')}</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
onClick={handleNewDataset}
|
onClick={handleNewDataset}
|
||||||
>
|
>
|
||||||
Nuovo Dataset
|
{t('reports.datasetManager.newDataset')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -379,18 +381,17 @@ export default function DatasetManagerDialog({
|
|||||||
<Box sx={{ p: 4, textAlign: "center" }}>
|
<Box sx={{ p: 4, textAlign: "center" }}>
|
||||||
<DatasetIcon sx={{ fontSize: 64, color: "grey.400", mb: 2 }} />
|
<DatasetIcon sx={{ fontSize: 64, color: "grey.400", mb: 2 }} />
|
||||||
<Typography variant="h6" color="text.secondary" gutterBottom>
|
<Typography variant="h6" color="text.secondary" gutterBottom>
|
||||||
Nessun Dataset Virtuale
|
{t('reports.datasetManager.noDatasets')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" mb={2}>
|
<Typography variant="body2" color="text.secondary" mb={2}>
|
||||||
Crea dataset virtuali per combinare e filtrare i dati da più
|
{t('reports.datasetManager.noDatasetsDesc')}
|
||||||
sorgenti.
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
onClick={handleNewDataset}
|
onClick={handleNewDataset}
|
||||||
>
|
>
|
||||||
Crea il primo dataset
|
{t('reports.datasetManager.createFirst')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
@@ -400,17 +401,17 @@ export default function DatasetManagerDialog({
|
|||||||
key={dataset.id}
|
key={dataset.id}
|
||||||
secondaryAction={
|
secondaryAction={
|
||||||
<Box>
|
<Box>
|
||||||
<Tooltip title="Modifica">
|
<Tooltip title={t('reports.toolbar.edit')}>
|
||||||
<IconButton onClick={() => handleEditDataset(dataset)}>
|
<IconButton onClick={() => handleEditDataset(dataset)}>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Duplica">
|
<Tooltip title={t('reports.toolbar.duplicate')}>
|
||||||
<IconButton onClick={() => handleCloneDataset(dataset)}>
|
<IconButton onClick={() => handleCloneDataset(dataset)}>
|
||||||
<CopyIcon />
|
<CopyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Elimina">
|
<Tooltip title={t('reports.toolbar.delete')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => handleDeleteDataset(dataset)}
|
onClick={() => handleDeleteDataset(dataset)}
|
||||||
color="error"
|
color="error"
|
||||||
@@ -428,7 +429,7 @@ export default function DatasetManagerDialog({
|
|||||||
primary={dataset.displayName}
|
primary={dataset.displayName}
|
||||||
secondary={
|
secondary={
|
||||||
<Box component="span">
|
<Box component="span">
|
||||||
{dataset.descrizione || "Nessuna descrizione"}
|
{dataset.descrizione || t('reports.datasetManager.noDescription')}
|
||||||
<Box component="span" sx={{ display: "block", mt: 0.5 }}>
|
<Box component="span" sx={{ display: "block", mt: 0.5 }}>
|
||||||
<Chip
|
<Chip
|
||||||
label={dataset.categoria}
|
label={dataset.categoria}
|
||||||
@@ -436,7 +437,7 @@ export default function DatasetManagerDialog({
|
|||||||
sx={{ height: 20, fontSize: "0.7rem" }}
|
sx={{ height: 20, fontSize: "0.7rem" }}
|
||||||
/>
|
/>
|
||||||
<Chip
|
<Chip
|
||||||
label={`${dataset.configuration?.sources.length || 0} sorgenti`}
|
label={`${dataset.configuration?.sources.length || 0} ${t('reports.datasetManager.sourcesCount')}`}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={{
|
||||||
ml: 0.5,
|
ml: 0.5,
|
||||||
@@ -463,7 +464,7 @@ export default function DatasetManagerDialog({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Box sx={{ p: 2, borderBottom: 1, borderColor: "divider" }}>
|
<Box sx={{ p: 2, borderBottom: 1, borderColor: "divider" }}>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
{selectedDataset ? "Modifica Dataset" : "Nuovo Dataset Virtuale"}
|
{selectedDataset ? t('reports.datasetManager.editDataset') : t('reports.datasetManager.newVirtualDataset')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -472,7 +473,7 @@ export default function DatasetManagerDialog({
|
|||||||
<Box sx={{ px: 2, pt: 2 }}>
|
<Box sx={{ px: 2, pt: 2 }}>
|
||||||
{validationResult.errors.length > 0 && (
|
{validationResult.errors.length > 0 && (
|
||||||
<Alert severity="error" sx={{ mb: 1 }}>
|
<Alert severity="error" sx={{ mb: 1 }}>
|
||||||
<Typography variant="subtitle2">Errori:</Typography>
|
<Typography variant="subtitle2">{t('reports.datasetManager.errors')}</Typography>
|
||||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||||
{validationResult.errors.map((err, i) => (
|
{validationResult.errors.map((err, i) => (
|
||||||
<li key={i}>{err}</li>
|
<li key={i}>{err}</li>
|
||||||
@@ -482,7 +483,7 @@ export default function DatasetManagerDialog({
|
|||||||
)}
|
)}
|
||||||
{validationResult.warnings.length > 0 && (
|
{validationResult.warnings.length > 0 && (
|
||||||
<Alert severity="warning">
|
<Alert severity="warning">
|
||||||
<Typography variant="subtitle2">Avvisi:</Typography>
|
<Typography variant="subtitle2">{t('reports.datasetManager.warnings')}</Typography>
|
||||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||||
{validationResult.warnings.map((warn, i) => (
|
{validationResult.warnings.map((warn, i) => (
|
||||||
<li key={i}>{warn}</li>
|
<li key={i}>{warn}</li>
|
||||||
@@ -492,7 +493,7 @@ export default function DatasetManagerDialog({
|
|||||||
)}
|
)}
|
||||||
{validationResult.isValid &&
|
{validationResult.isValid &&
|
||||||
validationResult.warnings.length === 0 && (
|
validationResult.warnings.length === 0 && (
|
||||||
<Alert severity="success">Configurazione valida</Alert>
|
<Alert severity="success">{t('reports.datasetManager.validConfig')}</Alert>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -503,24 +504,24 @@ export default function DatasetManagerDialog({
|
|||||||
onChange={(_, v) => setActiveTab(v)}
|
onChange={(_, v) => setActiveTab(v)}
|
||||||
sx={{ borderBottom: 1, borderColor: "divider" }}
|
sx={{ borderBottom: 1, borderColor: "divider" }}
|
||||||
>
|
>
|
||||||
<Tab label="Info" icon={<DatasetIcon />} iconPosition="start" />
|
<Tab label={t('reports.datasetManager.tabs.info')} icon={<DatasetIcon />} iconPosition="start" />
|
||||||
<Tab
|
<Tab
|
||||||
label={`Sorgenti (${editingConfig.sources.length})`}
|
label={`${t('reports.datasetManager.tabs.sources')} (${editingConfig.sources.length})`}
|
||||||
icon={<TableIcon />}
|
icon={<TableIcon />}
|
||||||
iconPosition="start"
|
iconPosition="start"
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
label={`Relazioni (${editingConfig.relationships.length})`}
|
label={`${t('reports.datasetManager.tabs.relationships')} (${editingConfig.relationships.length})`}
|
||||||
icon={<LinkIcon />}
|
icon={<LinkIcon />}
|
||||||
iconPosition="start"
|
iconPosition="start"
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
label={`Filtri (${editingConfig.filters.filter((f) => f.enabled).length})`}
|
label={`${t('reports.datasetManager.tabs.filters')} (${editingConfig.filters.filter((f) => f.enabled).length})`}
|
||||||
icon={<FilterIcon />}
|
icon={<FilterIcon />}
|
||||||
iconPosition="start"
|
iconPosition="start"
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
label={`Campi (${editingConfig.outputFields.filter((f) => f.included).length})`}
|
label={`${t('reports.datasetManager.tabs.fields')} (${editingConfig.outputFields.filter((f) => f.included).length})`}
|
||||||
icon={<FieldsIcon />}
|
icon={<FieldsIcon />}
|
||||||
iconPosition="start"
|
iconPosition="start"
|
||||||
/>
|
/>
|
||||||
@@ -536,8 +537,9 @@ export default function DatasetManagerDialog({
|
|||||||
maxWidth: 500,
|
maxWidth: 500,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Nome Identificativo"
|
label={t('reports.datasetManager.fields.nameId')}
|
||||||
value={editingInfo.nome}
|
value={editingInfo.nome}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setEditingInfo((prev) => ({
|
setEditingInfo((prev) => ({
|
||||||
@@ -546,10 +548,10 @@ export default function DatasetManagerDialog({
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
required
|
required
|
||||||
helperText="Nome univoco usato internamente (senza spazi)"
|
helperText={t('reports.datasetManager.fields.nameIdHelper')}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Nome Visualizzato"
|
label={t('reports.datasetManager.fields.displayName')}
|
||||||
value={editingInfo.displayName}
|
value={editingInfo.displayName}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setEditingInfo((prev) => ({
|
setEditingInfo((prev) => ({
|
||||||
@@ -560,7 +562,7 @@ export default function DatasetManagerDialog({
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Descrizione"
|
label={t('reports.datasetManager.fields.description')}
|
||||||
value={editingInfo.descrizione}
|
value={editingInfo.descrizione}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setEditingInfo((prev) => ({
|
setEditingInfo((prev) => ({
|
||||||
@@ -572,10 +574,10 @@ export default function DatasetManagerDialog({
|
|||||||
rows={2}
|
rows={2}
|
||||||
/>
|
/>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<InputLabel>Categoria</InputLabel>
|
<InputLabel>{t('reports.datasetManager.fields.category')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={editingInfo.categoria}
|
value={editingInfo.categoria}
|
||||||
label="Categoria"
|
label={t('reports.datasetManager.fields.category')}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setEditingInfo((prev) => ({
|
setEditingInfo((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -591,10 +593,10 @@ export default function DatasetManagerDialog({
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<InputLabel>Icona</InputLabel>
|
<InputLabel>{t('reports.datasetManager.fields.icon')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={editingInfo.icon}
|
value={editingInfo.icon}
|
||||||
label="Icona"
|
label={t('reports.datasetManager.fields.icon')}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setEditingInfo((prev) => ({ ...prev, icon: e.target.value }))
|
setEditingInfo((prev) => ({ ...prev, icon: e.target.value }))
|
||||||
}
|
}
|
||||||
@@ -640,7 +642,7 @@ export default function DatasetManagerDialog({
|
|||||||
{/* Dataset disponibili */}
|
{/* Dataset disponibili */}
|
||||||
<Paper variant="outlined" sx={{ width: 280, p: 2 }}>
|
<Paper variant="outlined" sx={{ width: 280, p: 2 }}>
|
||||||
<Typography variant="subtitle2" gutterBottom>
|
<Typography variant="subtitle2" gutterBottom>
|
||||||
Dataset Disponibili
|
{t('reports.datasetManager.sources.available')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
@@ -648,7 +650,7 @@ export default function DatasetManagerDialog({
|
|||||||
display="block"
|
display="block"
|
||||||
mb={2}
|
mb={2}
|
||||||
>
|
>
|
||||||
Clicca per aggiungere una sorgente
|
{t('reports.datasetManager.sources.addInstruction')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<List dense>
|
<List dense>
|
||||||
{availableBaseDatasets.map((dataset) => (
|
{availableBaseDatasets.map((dataset) => (
|
||||||
@@ -677,11 +679,11 @@ export default function DatasetManagerDialog({
|
|||||||
{/* Sorgenti selezionate */}
|
{/* Sorgenti selezionate */}
|
||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<Typography variant="subtitle2" gutterBottom>
|
<Typography variant="subtitle2" gutterBottom>
|
||||||
Sorgenti nel Dataset ({editingConfig.sources.length})
|
{t('reports.datasetManager.sources.inDataset')} ({editingConfig.sources.length})
|
||||||
</Typography>
|
</Typography>
|
||||||
{editingConfig.sources.length === 0 ? (
|
{editingConfig.sources.length === 0 ? (
|
||||||
<Alert severity="info">
|
<Alert severity="info">
|
||||||
Aggiungi almeno una sorgente dati dal pannello a sinistra
|
{t('reports.datasetManager.sources.empty')}
|
||||||
</Alert>
|
</Alert>
|
||||||
) : (
|
) : (
|
||||||
<List>
|
<List>
|
||||||
@@ -725,7 +727,7 @@ export default function DatasetManagerDialog({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Alias"
|
label={t('reports.datasetManager.sources.alias')}
|
||||||
size="small"
|
size="small"
|
||||||
value={source.alias}
|
value={source.alias}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -737,7 +739,7 @@ export default function DatasetManagerDialog({
|
|||||||
{source.isPrimary ? (
|
{source.isPrimary ? (
|
||||||
<Chip
|
<Chip
|
||||||
icon={<CheckIcon />}
|
icon={<CheckIcon />}
|
||||||
label="Primario"
|
label={t('reports.datasetManager.sources.primary')}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
@@ -746,7 +748,7 @@ export default function DatasetManagerDialog({
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleSetPrimary(source.id)}
|
onClick={() => handleSetPrimary(source.id)}
|
||||||
>
|
>
|
||||||
Imposta Primario
|
{t('reports.datasetManager.sources.setPrimary')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import {
|
|||||||
History as HistoryIcon,
|
History as HistoryIcon,
|
||||||
AutoMode as AutoSaveIcon,
|
AutoMode as AutoSaveIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import type { ElementType } from "../../../../types/report";
|
import type { ElementType } from "../../../../types/report";
|
||||||
|
|
||||||
// Snap options type
|
// Snap options type
|
||||||
@@ -130,42 +131,42 @@ const ELEMENT_TYPES = [
|
|||||||
{
|
{
|
||||||
type: "text" as ElementType,
|
type: "text" as ElementType,
|
||||||
icon: TextIcon,
|
icon: TextIcon,
|
||||||
label: "Testo",
|
label: "reports.elements.text",
|
||||||
shortcut: "T",
|
shortcut: "T",
|
||||||
color: "#2196f3",
|
color: "#2196f3",
|
||||||
description: "Aggiungi un campo di testo",
|
description: "reports.elements.textDesc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "image" as ElementType,
|
type: "image" as ElementType,
|
||||||
icon: ImageIcon,
|
icon: ImageIcon,
|
||||||
label: "Immagine",
|
label: "reports.elements.image",
|
||||||
shortcut: "I",
|
shortcut: "I",
|
||||||
color: "#9c27b0",
|
color: "#9c27b0",
|
||||||
description: "Inserisci un'immagine",
|
description: "reports.elements.imageDesc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "shape" as ElementType,
|
type: "shape" as ElementType,
|
||||||
icon: ShapeIcon,
|
icon: ShapeIcon,
|
||||||
label: "Forma",
|
label: "reports.elements.shape",
|
||||||
shortcut: "R",
|
shortcut: "R",
|
||||||
color: "#ff9800",
|
color: "#ff9800",
|
||||||
description: "Disegna una forma geometrica",
|
description: "reports.elements.shapeDesc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "table" as ElementType,
|
type: "table" as ElementType,
|
||||||
icon: TableIcon,
|
icon: TableIcon,
|
||||||
label: "Tabella",
|
label: "reports.elements.table",
|
||||||
shortcut: "B",
|
shortcut: "B",
|
||||||
color: "#4caf50",
|
color: "#4caf50",
|
||||||
description: "Inserisci una tabella dati",
|
description: "reports.elements.tableDesc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "line" as ElementType,
|
type: "line" as ElementType,
|
||||||
icon: LineIcon,
|
icon: LineIcon,
|
||||||
label: "Linea",
|
label: "reports.elements.line",
|
||||||
shortcut: "L",
|
shortcut: "L",
|
||||||
color: "#607d8b",
|
color: "#607d8b",
|
||||||
description: "Traccia una linea",
|
description: "reports.elements.lineDesc",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -174,47 +175,37 @@ const SNAP_OPTIONS_CONFIG = [
|
|||||||
{
|
{
|
||||||
key: "grid" as keyof SnapOptions,
|
key: "grid" as keyof SnapOptions,
|
||||||
icon: GridSnapIcon,
|
icon: GridSnapIcon,
|
||||||
label: "Griglia",
|
label: "reports.snap.grid",
|
||||||
description: "Allinea alla griglia",
|
description: "reports.snap.grid",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "objects" as keyof SnapOptions,
|
key: "objects" as keyof SnapOptions,
|
||||||
icon: ObjectSnapIcon,
|
icon: ObjectSnapIcon,
|
||||||
label: "Oggetti",
|
label: "reports.snap.objects",
|
||||||
description: "Allinea agli altri oggetti",
|
description: "reports.snap.objects",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "borders" as keyof SnapOptions,
|
key: "borders" as keyof SnapOptions,
|
||||||
icon: BorderSnapIcon,
|
icon: BorderSnapIcon,
|
||||||
label: "Margini",
|
label: "reports.snap.borders",
|
||||||
description: "Allinea ai margini pagina",
|
description: "reports.snap.borders",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "center" as keyof SnapOptions,
|
key: "center" as keyof SnapOptions,
|
||||||
icon: CenterSnapIcon,
|
icon: CenterSnapIcon,
|
||||||
label: "Centro",
|
label: "reports.snap.center",
|
||||||
description: "Allinea al centro",
|
description: "reports.snap.center",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "tangent" as keyof SnapOptions,
|
key: "tangent" as keyof SnapOptions,
|
||||||
icon: TangentSnapIcon,
|
icon: TangentSnapIcon,
|
||||||
label: "Bordi",
|
label: "reports.snap.tangent",
|
||||||
description: "Allinea ai bordi adiacenti",
|
description: "reports.snap.tangent",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Format time ago
|
// Format time ago
|
||||||
function formatTimeAgo(date: Date | null | undefined): string {
|
|
||||||
if (!date) return "";
|
|
||||||
const now = new Date();
|
|
||||||
const diff = now.getTime() - date.getTime();
|
|
||||||
const minutes = Math.floor(diff / 60000);
|
|
||||||
if (minutes < 1) return "Ora";
|
|
||||||
if (minutes < 60) return `${minutes}m fa`;
|
|
||||||
const hours = Math.floor(minutes / 60);
|
|
||||||
if (hours < 24) return `${hours}h fa`;
|
|
||||||
return date.toLocaleDateString("it-IT", { day: "2-digit", month: "short" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToolbarSection component for consistent styling
|
// ToolbarSection component for consistent styling
|
||||||
function ToolbarSection({
|
function ToolbarSection({
|
||||||
@@ -353,7 +344,20 @@ export default function EditorToolbar({
|
|||||||
autoSaveEnabled = true,
|
autoSaveEnabled = true,
|
||||||
onAutoSaveToggle,
|
onAutoSaveToggle,
|
||||||
}: EditorToolbarProps) {
|
}: EditorToolbarProps) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const formatTimeAgo = (date: Date | null | undefined) => {
|
||||||
|
if (!date) return "";
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now.getTime() - date.getTime();
|
||||||
|
const minutes = Math.floor(diff / 60000);
|
||||||
|
if (minutes < 1) return t('reports.time.now');
|
||||||
|
if (minutes < 60) return t('reports.time.minutesAgo', { count: minutes });
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
if (hours < 24) return t('reports.time.hoursAgo', { count: hours });
|
||||||
|
return date.toLocaleDateString(i18n.language, { day: "2-digit", month: "short" });
|
||||||
|
};
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.between("sm", "md"));
|
const isTablet = useMediaQuery(theme.breakpoints.between("sm", "md"));
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down("lg"));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down("lg"));
|
||||||
@@ -412,8 +416,8 @@ export default function EditorToolbar({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
autoSaveEnabled
|
autoSaveEnabled
|
||||||
? "Auto-salvataggio attivo"
|
? t('reports.toolbar.autoSaveOn')
|
||||||
: "Auto-salvataggio disattivato"
|
: t('reports.toolbar.autoSaveOff')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -439,11 +443,11 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Save status */}
|
{/* Save status */}
|
||||||
{isSaving ? (
|
{isSaving ? (
|
||||||
<Tooltip title="Salvataggio in corso...">
|
<Tooltip title={t('reports.toolbar.saving')}>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
||||||
<CircularProgress size={16} />
|
<CircularProgress size={16} />
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
Salvo...
|
{t('reports.toolbar.saving')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -451,14 +455,14 @@ export default function EditorToolbar({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
autoSaveEnabled
|
autoSaveEnabled
|
||||||
? "Salvataggio automatico in attesa..."
|
? t('reports.toolbar.autoSavePending')
|
||||||
: "Modifiche non salvate"
|
: t('reports.toolbar.unsavedTooltip')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
||||||
<UnsavedIcon fontSize="small" sx={{ color: "warning.main" }} />
|
<UnsavedIcon fontSize="small" sx={{ color: "warning.main" }} />
|
||||||
<Typography variant="caption" color="warning.main">
|
<Typography variant="caption" color="warning.main">
|
||||||
Non salvato
|
{t('reports.toolbar.unsaved')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -466,14 +470,14 @@ export default function EditorToolbar({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
lastSavedAt
|
lastSavedAt
|
||||||
? `Ultimo salvataggio: ${formatTimeAgo(lastSavedAt)}`
|
? `${t('reports.toolbar.saved')}: ${formatTimeAgo(lastSavedAt)}`
|
||||||
: "Salvato"
|
: t('reports.toolbar.saved')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
||||||
<SavedIcon fontSize="small" sx={{ color: "success.main" }} />
|
<SavedIcon fontSize="small" sx={{ color: "success.main" }} />
|
||||||
<Typography variant="caption" color="success.main">
|
<Typography variant="caption" color="success.main">
|
||||||
{formatTimeAgo(lastSavedAt) || "Salvato"}
|
{formatTimeAgo(lastSavedAt) || t('reports.toolbar.saved')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -523,14 +527,14 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Undo/Redo */}
|
{/* Undo/Redo */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Annulla"
|
tooltip={t('reports.toolbar.undo')}
|
||||||
onClick={onUndo}
|
onClick={onUndo}
|
||||||
disabled={!canUndo}
|
disabled={!canUndo}
|
||||||
>
|
>
|
||||||
<UndoIcon fontSize="small" />
|
<UndoIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Ripeti"
|
tooltip={t('reports.toolbar.redo')}
|
||||||
onClick={onRedo}
|
onClick={onRedo}
|
||||||
disabled={!canRedo}
|
disabled={!canRedo}
|
||||||
>
|
>
|
||||||
@@ -541,7 +545,7 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Delete */}
|
{/* Delete */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Elimina"
|
tooltip={t('reports.toolbar.delete')}
|
||||||
onClick={onDeleteElement}
|
onClick={onDeleteElement}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
color="#f44336"
|
color="#f44336"
|
||||||
@@ -571,12 +575,12 @@ export default function EditorToolbar({
|
|||||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.25 }} />
|
<Divider orientation="vertical" flexItem sx={{ mx: 0.25 }} />
|
||||||
|
|
||||||
{/* Save/Preview */}
|
{/* Save/Preview */}
|
||||||
<StyledIconButton tooltip="Anteprima" onClick={onPreview}>
|
<StyledIconButton tooltip={t('reports.toolbar.preview')} onClick={onPreview}>
|
||||||
<PreviewIcon fontSize="small" />
|
<PreviewIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
{!autoSaveEnabled && (
|
{!autoSaveEnabled && (
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Salva"
|
tooltip={t('reports.toolbar.save')}
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
color="#1976d2"
|
color="#1976d2"
|
||||||
@@ -615,7 +619,7 @@ export default function EditorToolbar({
|
|||||||
>
|
>
|
||||||
{/* Zoom */}
|
{/* Zoom */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Zoom out"
|
tooltip={t('reports.toolbar.zoomOut')}
|
||||||
onClick={() => onZoomChange(Math.max(0.25, zoom - 0.25))}
|
onClick={() => onZoomChange(Math.max(0.25, zoom - 0.25))}
|
||||||
>
|
>
|
||||||
<ZoomOutIcon fontSize="small" />
|
<ZoomOutIcon fontSize="small" />
|
||||||
@@ -634,7 +638,7 @@ export default function EditorToolbar({
|
|||||||
{Math.round(zoom * 100)}%
|
{Math.round(zoom * 100)}%
|
||||||
</Button>
|
</Button>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Zoom in"
|
tooltip={t('reports.toolbar.zoomIn')}
|
||||||
onClick={() => onZoomChange(Math.min(3, zoom + 0.25))}
|
onClick={() => onZoomChange(Math.min(3, zoom + 0.25))}
|
||||||
>
|
>
|
||||||
<ZoomInIcon fontSize="small" />
|
<ZoomInIcon fontSize="small" />
|
||||||
@@ -644,7 +648,7 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Grid & Snap */}
|
{/* Grid & Snap */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Griglia"
|
tooltip={t('reports.snap.grid')}
|
||||||
onClick={onToggleGrid}
|
onClick={onToggleGrid}
|
||||||
active={showGrid}
|
active={showGrid}
|
||||||
>
|
>
|
||||||
@@ -655,7 +659,7 @@ export default function EditorToolbar({
|
|||||||
)}
|
)}
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Snap"
|
tooltip={t('reports.snap.options')}
|
||||||
onClick={(e) => setSnapMenuAnchor(e.currentTarget)}
|
onClick={(e) => setSnapMenuAnchor(e.currentTarget)}
|
||||||
active={activeSnapCount > 0}
|
active={activeSnapCount > 0}
|
||||||
badge={activeSnapCount || undefined}
|
badge={activeSnapCount || undefined}
|
||||||
@@ -667,14 +671,14 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Copy/Lock */}
|
{/* Copy/Lock */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Duplica"
|
tooltip={t('reports.toolbar.duplicate')}
|
||||||
onClick={onCopyElement}
|
onClick={onCopyElement}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
>
|
>
|
||||||
<CopyIcon fontSize="small" />
|
<CopyIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip={isLocked ? "Sblocca" : "Blocca"}
|
tooltip={isLocked ? t('reports.toolbar.unlock') : t('reports.toolbar.lock')}
|
||||||
onClick={onToggleLock}
|
onClick={onToggleLock}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
active={isLocked}
|
active={isLocked}
|
||||||
@@ -711,8 +715,8 @@ export default function EditorToolbar({
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={label}
|
primary={t(label)}
|
||||||
secondary={description}
|
secondary={t(description)}
|
||||||
primaryTypographyProps={{ fontWeight: 500 }}
|
primaryTypographyProps={{ fontWeight: 500 }}
|
||||||
secondaryTypographyProps={{ fontSize: "0.7rem" }}
|
secondaryTypographyProps={{ fontSize: "0.7rem" }}
|
||||||
/>
|
/>
|
||||||
@@ -775,7 +779,7 @@ export default function EditorToolbar({
|
|||||||
mb={1}
|
mb={1}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle2" fontWeight={600}>
|
<Typography variant="subtitle2" fontWeight={600}>
|
||||||
Opzioni Snap
|
{t('reports.snap.options')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Switch
|
<Switch
|
||||||
size="small"
|
size="small"
|
||||||
@@ -798,27 +802,23 @@ export default function EditorToolbar({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemIcon sx={{ minWidth: 32 }}>
|
<ListItemIcon sx={{ minWidth: 32 }}>
|
||||||
<Icon
|
<Icon fontSize="small" color={snapOptions[key] ? "primary" : "inherit"} />
|
||||||
fontSize="small"
|
|
||||||
color={snapOptions[key] ? "primary" : "inherit"}
|
|
||||||
/>
|
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText primary={t(label)} />
|
||||||
primary={label}
|
|
||||||
primaryTypographyProps={{ variant: "body2" }}
|
|
||||||
/>
|
|
||||||
<Switch size="small" checked={snapOptions[key]} />
|
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Popover>
|
</Popover >
|
||||||
|
|
||||||
{/* Zoom Popover */}
|
{/* Zoom Popover */}
|
||||||
<Popover
|
< Popover
|
||||||
open={Boolean(zoomMenuAnchor)}
|
open={Boolean(zoomMenuAnchor)}
|
||||||
anchorEl={zoomMenuAnchor}
|
anchorEl={zoomMenuAnchor}
|
||||||
onClose={() => setZoomMenuAnchor(null)}
|
onClose={() => setZoomMenuAnchor(null)
|
||||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
}
|
||||||
|
anchorOrigin={{ vertical: "bottom", horizontal: "center" }
|
||||||
|
}
|
||||||
PaperProps={{ sx: { borderRadius: 2 } }}
|
PaperProps={{ sx: { borderRadius: 2 } }}
|
||||||
>
|
>
|
||||||
<Box sx={{ p: 1.5, width: 180 }}>
|
<Box sx={{ p: 1.5, width: 180 }}>
|
||||||
@@ -850,8 +850,8 @@ export default function EditorToolbar({
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Popover>
|
</Popover >
|
||||||
</Paper>
|
</Paper >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,7 +883,7 @@ export default function EditorToolbar({
|
|||||||
onClick={(e) => setAddMenuAnchor(e.currentTarget)}
|
onClick={(e) => setAddMenuAnchor(e.currentTarget)}
|
||||||
sx={{ borderRadius: 2, textTransform: "none", fontWeight: 600 }}
|
sx={{ borderRadius: 2, textTransform: "none", fontWeight: 600 }}
|
||||||
>
|
>
|
||||||
Aggiungi
|
{t('reports.elements.add')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
|
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
|
||||||
@@ -906,14 +906,14 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Selection Actions */}
|
{/* Selection Actions */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Duplica"
|
tooltip={t('reports.toolbar.duplicate')}
|
||||||
onClick={onCopyElement}
|
onClick={onCopyElement}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
>
|
>
|
||||||
<CopyIcon fontSize="small" />
|
<CopyIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Elimina"
|
tooltip={t('reports.toolbar.delete')}
|
||||||
onClick={onDeleteElement}
|
onClick={onDeleteElement}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
color="#f44336"
|
color="#f44336"
|
||||||
@@ -921,7 +921,7 @@ export default function EditorToolbar({
|
|||||||
<DeleteIcon fontSize="small" />
|
<DeleteIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip={isLocked ? "Sblocca" : "Blocca"}
|
tooltip={isLocked ? t('reports.toolbar.unlock') : t('reports.toolbar.lock')}
|
||||||
onClick={onToggleLock}
|
onClick={onToggleLock}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
active={isLocked}
|
active={isLocked}
|
||||||
@@ -938,14 +938,14 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Undo/Redo */}
|
{/* Undo/Redo */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Annulla (Ctrl+Z)"
|
tooltip={`${t('reports.toolbar.undo')} (Ctrl+Z)`}
|
||||||
onClick={onUndo}
|
onClick={onUndo}
|
||||||
disabled={!canUndo}
|
disabled={!canUndo}
|
||||||
>
|
>
|
||||||
<UndoIcon fontSize="small" />
|
<UndoIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Ripeti (Ctrl+Y)"
|
tooltip={`${t('reports.toolbar.redo')} (Ctrl+Y)`}
|
||||||
onClick={onRedo}
|
onClick={onRedo}
|
||||||
disabled={!canRedo}
|
disabled={!canRedo}
|
||||||
>
|
>
|
||||||
@@ -956,7 +956,7 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* View Controls */}
|
{/* View Controls */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Griglia"
|
tooltip={t('reports.snap.grid')}
|
||||||
onClick={onToggleGrid}
|
onClick={onToggleGrid}
|
||||||
active={showGrid}
|
active={showGrid}
|
||||||
>
|
>
|
||||||
@@ -967,7 +967,7 @@ export default function EditorToolbar({
|
|||||||
)}
|
)}
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Snap"
|
tooltip={t('reports.snap.options')}
|
||||||
onClick={(e) => setSnapMenuAnchor(e.currentTarget)}
|
onClick={(e) => setSnapMenuAnchor(e.currentTarget)}
|
||||||
active={activeSnapCount > 0}
|
active={activeSnapCount > 0}
|
||||||
badge={activeSnapCount || undefined}
|
badge={activeSnapCount || undefined}
|
||||||
@@ -979,7 +979,7 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Zoom */}
|
{/* Zoom */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Zoom out"
|
tooltip={t('reports.toolbar.zoomOut')}
|
||||||
onClick={() => onZoomChange(Math.max(0.25, zoom - 0.25))}
|
onClick={() => onZoomChange(Math.max(0.25, zoom - 0.25))}
|
||||||
>
|
>
|
||||||
<ZoomOutIcon fontSize="small" />
|
<ZoomOutIcon fontSize="small" />
|
||||||
@@ -998,7 +998,7 @@ export default function EditorToolbar({
|
|||||||
{Math.round(zoom * 100)}%
|
{Math.round(zoom * 100)}%
|
||||||
</Button>
|
</Button>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Zoom in"
|
tooltip={t('reports.toolbar.zoomIn')}
|
||||||
onClick={() => onZoomChange(Math.min(3, zoom + 0.25))}
|
onClick={() => onZoomChange(Math.min(3, zoom + 0.25))}
|
||||||
>
|
>
|
||||||
<ZoomInIcon fontSize="small" />
|
<ZoomInIcon fontSize="small" />
|
||||||
@@ -1008,7 +1008,7 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Page Navigation */}
|
{/* Page Navigation */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Pagina precedente"
|
tooltip={t('reports.toolbar.prevPage')}
|
||||||
onClick={onPrevPage}
|
onClick={onPrevPage}
|
||||||
disabled={currentPageIndex <= 0}
|
disabled={currentPageIndex <= 0}
|
||||||
>
|
>
|
||||||
@@ -1034,7 +1034,7 @@ export default function EditorToolbar({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Pagina successiva"
|
tooltip={t('reports.toolbar.nextPage')}
|
||||||
onClick={onNextPage}
|
onClick={onNextPage}
|
||||||
disabled={currentPageIndex >= totalPages - 1}
|
disabled={currentPageIndex >= totalPages - 1}
|
||||||
>
|
>
|
||||||
@@ -1051,7 +1051,7 @@ export default function EditorToolbar({
|
|||||||
onClick={onPreview}
|
onClick={onPreview}
|
||||||
sx={{ borderRadius: 2, textTransform: "none" }}
|
sx={{ borderRadius: 2, textTransform: "none" }}
|
||||||
>
|
>
|
||||||
Anteprima
|
{t('reports.toolbar.preview')}
|
||||||
</Button>
|
</Button>
|
||||||
{!autoSaveEnabled && (
|
{!autoSaveEnabled && (
|
||||||
<Button
|
<Button
|
||||||
@@ -1068,7 +1068,7 @@ export default function EditorToolbar({
|
|||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
sx={{ borderRadius: 2, textTransform: "none", fontWeight: 600 }}
|
sx={{ borderRadius: 2, textTransform: "none", fontWeight: 600 }}
|
||||||
>
|
>
|
||||||
{isSaving ? "Salvo..." : "Salva"}
|
{isSaving ? t('reports.toolbar.saving') : t('reports.toolbar.save')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -1317,7 +1317,7 @@ export default function EditorToolbar({
|
|||||||
color: "text.secondary",
|
color: "text.secondary",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Inserisci elemento
|
{t('reports.elements.insert')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<List dense sx={{ py: 0 }}>
|
<List dense sx={{ py: 0 }}>
|
||||||
{ELEMENT_TYPES.map(
|
{ELEMENT_TYPES.map(
|
||||||
@@ -1388,16 +1388,16 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
|
|
||||||
{/* Selection Actions */}
|
{/* Selection Actions */}
|
||||||
<ToolbarSection label={isSmallScreen ? undefined : "MODIFICA"}>
|
<ToolbarSection label={isSmallScreen ? undefined : t('reports.toolbar.edit')}>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Duplica (Ctrl+D)"
|
tooltip={`${t('reports.toolbar.duplicate')} (Ctrl+D)`}
|
||||||
onClick={onCopyElement}
|
onClick={onCopyElement}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
>
|
>
|
||||||
<CopyIcon fontSize="small" />
|
<CopyIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Elimina (Canc)"
|
tooltip={`${t('reports.toolbar.delete')} (Canc)`}
|
||||||
onClick={onDeleteElement}
|
onClick={onDeleteElement}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
color="#f44336"
|
color="#f44336"
|
||||||
@@ -1405,7 +1405,7 @@ export default function EditorToolbar({
|
|||||||
<DeleteIcon fontSize="small" />
|
<DeleteIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip={isLocked ? "Sblocca (Ctrl+L)" : "Blocca (Ctrl+L)"}
|
tooltip={isLocked ? `${t('reports.toolbar.unlock')} (Ctrl+L)` : `${t('reports.toolbar.lock')} (Ctrl+L)`}
|
||||||
onClick={onToggleLock}
|
onClick={onToggleLock}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
active={isLocked}
|
active={isLocked}
|
||||||
@@ -1426,16 +1426,16 @@ export default function EditorToolbar({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* History */}
|
{/* History */}
|
||||||
<ToolbarSection label={isSmallScreen ? undefined : "CRONOLOGIA"}>
|
<ToolbarSection label={isSmallScreen ? undefined : t('reports.toolbar.history')}>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Annulla (Ctrl+Z)"
|
tooltip={`${t('reports.toolbar.undo')} (Ctrl+Z)`}
|
||||||
onClick={onUndo}
|
onClick={onUndo}
|
||||||
disabled={!canUndo}
|
disabled={!canUndo}
|
||||||
>
|
>
|
||||||
<UndoIcon fontSize="small" />
|
<UndoIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Ripeti (Ctrl+Y)"
|
tooltip={`${t('reports.toolbar.redo')} (Ctrl+Y)`}
|
||||||
onClick={onRedo}
|
onClick={onRedo}
|
||||||
disabled={!canRedo}
|
disabled={!canRedo}
|
||||||
>
|
>
|
||||||
@@ -1443,7 +1443,7 @@ export default function EditorToolbar({
|
|||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
{onOpenHistory && (
|
{onOpenHistory && (
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Cronologia modifiche"
|
tooltip={t('reports.toolbar.historyTooltip')}
|
||||||
onClick={onOpenHistory}
|
onClick={onOpenHistory}
|
||||||
>
|
>
|
||||||
<HistoryIcon fontSize="small" />
|
<HistoryIcon fontSize="small" />
|
||||||
@@ -1458,9 +1458,9 @@ export default function EditorToolbar({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* View Controls */}
|
{/* View Controls */}
|
||||||
<ToolbarSection label={isSmallScreen ? undefined : "VISTA"}>
|
<ToolbarSection label={isSmallScreen ? undefined : t('reports.toolbar.view')}>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip={showGrid ? "Nascondi griglia (G)" : "Mostra griglia (G)"}
|
tooltip={showGrid ? `${t('reports.snap.hideGrid')} (G)` : `${t('reports.snap.showGrid')} (G)`}
|
||||||
onClick={onToggleGrid}
|
onClick={onToggleGrid}
|
||||||
active={showGrid}
|
active={showGrid}
|
||||||
>
|
>
|
||||||
@@ -1498,7 +1498,7 @@ export default function EditorToolbar({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Snap
|
{t('reports.snap.options')}
|
||||||
</Button>
|
</Button>
|
||||||
</ToolbarSection>
|
</ToolbarSection>
|
||||||
|
|
||||||
@@ -1518,7 +1518,7 @@ export default function EditorToolbar({
|
|||||||
mb={1.5}
|
mb={1.5}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle1" fontWeight={600}>
|
<Typography variant="subtitle1" fontWeight={600}>
|
||||||
Allineamento automatico
|
{t('reports.snap.autoAlign')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
@@ -1529,7 +1529,7 @@ export default function EditorToolbar({
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Tutti
|
{t('reports.snap.all')}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
labelPlacement="start"
|
labelPlacement="start"
|
||||||
@@ -1606,9 +1606,9 @@ export default function EditorToolbar({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Zoom Controls */}
|
{/* Zoom Controls */}
|
||||||
<ToolbarSection label={isSmallScreen ? undefined : "ZOOM"}>
|
<ToolbarSection label={isSmallScreen ? undefined : t('reports.toolbar.zoom')}>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Riduci zoom (-)"
|
tooltip={`${t('reports.toolbar.zoomOut')} (-)`}
|
||||||
onClick={() => onZoomChange(Math.max(0.25, zoom - 0.25))}
|
onClick={() => onZoomChange(Math.max(0.25, zoom - 0.25))}
|
||||||
>
|
>
|
||||||
<ZoomOutIcon fontSize="small" />
|
<ZoomOutIcon fontSize="small" />
|
||||||
@@ -1632,14 +1632,14 @@ export default function EditorToolbar({
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Aumenta zoom (+)"
|
tooltip={`${t('reports.toolbar.zoomIn')} (+)`}
|
||||||
onClick={() => onZoomChange(Math.min(3, zoom + 0.25))}
|
onClick={() => onZoomChange(Math.min(3, zoom + 0.25))}
|
||||||
>
|
>
|
||||||
<ZoomInIcon fontSize="small" />
|
<ZoomInIcon fontSize="small" />
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
|
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Adatta alla finestra"
|
tooltip={t('reports.toolbar.fitWindow')}
|
||||||
onClick={() => onZoomChange(0.75)}
|
onClick={() => onZoomChange(0.75)}
|
||||||
>
|
>
|
||||||
<FitIcon fontSize="small" />
|
<FitIcon fontSize="small" />
|
||||||
@@ -1660,7 +1660,7 @@ export default function EditorToolbar({
|
|||||||
gutterBottom
|
gutterBottom
|
||||||
sx={{ display: "block" }}
|
sx={{ display: "block" }}
|
||||||
>
|
>
|
||||||
Livello zoom: {Math.round(zoom * 100)}%
|
{t('reports.toolbar.zoomLevel')}: {Math.round(zoom * 100)}%
|
||||||
</Typography>
|
</Typography>
|
||||||
<Slider
|
<Slider
|
||||||
value={zoom}
|
value={zoom}
|
||||||
@@ -1677,7 +1677,7 @@ export default function EditorToolbar({
|
|||||||
color="text.secondary"
|
color="text.secondary"
|
||||||
sx={{ mb: 1, display: "block" }}
|
sx={{ mb: 1, display: "block" }}
|
||||||
>
|
>
|
||||||
Preset
|
{t('reports.toolbar.presets')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box display="flex" flexWrap="wrap" gap={0.5}>
|
<Box display="flex" flexWrap="wrap" gap={0.5}>
|
||||||
{ZOOM_PRESETS.map(({ value, label }) => (
|
{ZOOM_PRESETS.map(({ value, label }) => (
|
||||||
@@ -1711,7 +1711,7 @@ export default function EditorToolbar({
|
|||||||
|
|
||||||
{/* Command Palette / Search */}
|
{/* Command Palette / Search */}
|
||||||
{onOpenCommandPalette && (
|
{onOpenCommandPalette && (
|
||||||
<Tooltip title="Cerca comando (Ctrl+K)">
|
<Tooltip title={`${t('reports.toolbar.searchCommand')} (Ctrl+K)`}>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
onClick={onOpenCommandPalette}
|
onClick={onOpenCommandPalette}
|
||||||
@@ -1725,14 +1725,14 @@ export default function EditorToolbar({
|
|||||||
"&:hover": { bgcolor: "action.selected" },
|
"&:hover": { bgcolor: "action.selected" },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cerca...
|
{t('reports.toolbar.searchCommand')}...
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Keyboard Shortcuts */}
|
{/* Keyboard Shortcuts */}
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
tooltip="Scorciatoie tastiera"
|
tooltip={t('reports.toolbar.shortcuts')}
|
||||||
onClick={(e) => setShortcutsAnchor(e.currentTarget)}
|
onClick={(e) => setShortcutsAnchor(e.currentTarget)}
|
||||||
>
|
>
|
||||||
<ShortcutsIcon fontSize="small" />
|
<ShortcutsIcon fontSize="small" />
|
||||||
@@ -1748,7 +1748,7 @@ export default function EditorToolbar({
|
|||||||
PaperProps={{ sx: { mt: 1, borderRadius: 2, p: 2, minWidth: 300 } }}
|
PaperProps={{ sx: { mt: 1, borderRadius: 2, p: 2, minWidth: 300 } }}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle1" fontWeight={600} gutterBottom>
|
<Typography variant="subtitle1" fontWeight={600} gutterBottom>
|
||||||
Scorciatoie Tastiera
|
{t('reports.toolbar.shortcutsTitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider sx={{ mb: 1.5 }} />
|
<Divider sx={{ mb: 1.5 }} />
|
||||||
<Box
|
<Box
|
||||||
@@ -1761,17 +1761,17 @@ export default function EditorToolbar({
|
|||||||
>
|
>
|
||||||
<tbody>
|
<tbody>
|
||||||
{[
|
{[
|
||||||
["Ctrl + Z", "Annulla"],
|
["Ctrl + Z", t('reports.toolbar.undo')],
|
||||||
["Ctrl + Y", "Ripeti"],
|
["Ctrl + Y", t('reports.toolbar.redo')],
|
||||||
["Ctrl + S", "Salva"],
|
["Ctrl + S", t('reports.toolbar.save')],
|
||||||
["Ctrl + D", "Duplica"],
|
["Ctrl + D", t('reports.toolbar.duplicate')],
|
||||||
["Ctrl + K", "Cerca comando"],
|
["Ctrl + K", t('reports.toolbar.searchCommand')],
|
||||||
["Canc / Backspace", "Elimina"],
|
["Canc / Backspace", t('reports.toolbar.delete')],
|
||||||
["Frecce", "Sposta (1px)"],
|
["Frecce", t('reports.shortcuts.move1px')],
|
||||||
["Shift + Frecce", "Sposta (10px)"],
|
["Shift + Frecce", t('reports.shortcuts.move10px')],
|
||||||
["G", "Mostra/nascondi griglia"],
|
["G", t('reports.shortcuts.toggleGrid')],
|
||||||
["+ / -", "Zoom in/out"],
|
["+ / -", t('reports.shortcuts.zoomInOut')],
|
||||||
["PgUp / PgDn", "Cambia pagina"],
|
["PgUp / PgDn", t('reports.shortcuts.changePage')],
|
||||||
].map(([key, action]) => (
|
].map(([key, action]) => (
|
||||||
<tr key={key}>
|
<tr key={key}>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
Close as CloseIcon,
|
Close as CloseIcon,
|
||||||
ArrowBack as BackIcon,
|
ArrowBack as BackIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useQueries } from "@tanstack/react-query";
|
import { useQueries } from "@tanstack/react-query";
|
||||||
import { reportGeneratorService } from "../../../../services/reportService";
|
import { reportGeneratorService } from "../../../../services/reportService";
|
||||||
import type {
|
import type {
|
||||||
@@ -66,6 +67,7 @@ export default function PreviewDialog({
|
|||||||
onGeneratePreview,
|
onGeneratePreview,
|
||||||
isGenerating,
|
isGenerating,
|
||||||
}: PreviewDialogProps) {
|
}: PreviewDialogProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
|
||||||
@@ -268,7 +270,7 @@ export default function PreviewDialog({
|
|||||||
<ListItemText
|
<ListItemText
|
||||||
primary={dataset.name}
|
primary={dataset.name}
|
||||||
secondary={
|
secondary={
|
||||||
selectedEntity ? selectedEntity.label : "Non selezionato"
|
selectedEntity ? selectedEntity.label : t('reports.preview.notSelected')
|
||||||
}
|
}
|
||||||
primaryTypographyProps={{
|
primaryTypographyProps={{
|
||||||
variant: "body2",
|
variant: "body2",
|
||||||
@@ -281,7 +283,7 @@ export default function PreviewDialog({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<Tooltip title="Rimuovi selezione">
|
<Tooltip title={t('reports.preview.removeSelection')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -327,7 +329,7 @@ export default function PreviewDialog({
|
|||||||
<IconButton size="small" onClick={() => setMobileShowList(true)}>
|
<IconButton size="small" onClick={() => setMobileShowList(true)}>
|
||||||
<BackIcon />
|
<BackIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="subtitle2">Seleziona</Typography>
|
<Typography variant="subtitle2">{t('reports.preview.select')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box display="flex" alignItems="center" gap={1} mb={1}>
|
<Box display="flex" alignItems="center" gap={1} mb={1}>
|
||||||
@@ -357,7 +359,7 @@ export default function PreviewDialog({
|
|||||||
|
|
||||||
{/* Ricerca */}
|
{/* Ricerca */}
|
||||||
<TextField
|
<TextField
|
||||||
placeholder={`Cerca...`}
|
placeholder={t('reports.preview.searchPlaceholder')}
|
||||||
size="small"
|
size="small"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={searchTerms[activeDataset || ""] || ""}
|
value={searchTerms[activeDataset || ""] || ""}
|
||||||
@@ -392,8 +394,8 @@ export default function PreviewDialog({
|
|||||||
<Box sx={{ p: 3, textAlign: "center" }}>
|
<Box sx={{ p: 3, textAlign: "center" }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
{searchTerms[activeDataset || ""]
|
{searchTerms[activeDataset || ""]
|
||||||
? "Nessun risultato trovato"
|
? t('reports.preview.noResults')
|
||||||
: "Nessuna entità disponibile"}
|
: t('reports.preview.noEntities')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
@@ -482,7 +484,7 @@ export default function PreviewDialog({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{filteredEntities.length} risultati
|
{filteredEntities.length} {t('reports.preview.results')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -511,7 +513,7 @@ export default function PreviewDialog({
|
|||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h6" sx={{ flex: 1, ml: 1 }}>
|
<Typography variant="h6" sx={{ flex: 1, ml: 1 }}>
|
||||||
Anteprima Report
|
{t('reports.preview.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
label={`${selectedCount}/${selectedDatasets.length}`}
|
label={`${selectedCount}/${selectedDatasets.length}`}
|
||||||
@@ -527,15 +529,15 @@ export default function PreviewDialog({
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
>
|
>
|
||||||
<Typography variant="h6">Anteprima Report</Typography>
|
<Typography variant="h6">{t('reports.preview.title')}</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
label={`${selectedCount}/${selectedDatasets.length} selezionati`}
|
label={`${selectedCount}/${selectedDatasets.length} ${t('reports.preview.selected')}`}
|
||||||
color={allSelected ? "success" : "default"}
|
color={allSelected ? "success" : "default"}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Seleziona un'entità per ogni dataset da utilizzare nell'anteprima
|
{t('reports.preview.instruction')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
)}
|
)}
|
||||||
@@ -545,15 +547,14 @@ export default function PreviewDialog({
|
|||||||
<DialogContent sx={{ p: 0, display: "flex", overflow: "hidden" }}>
|
<DialogContent sx={{ p: 0, display: "flex", overflow: "hidden" }}>
|
||||||
{hasError && (
|
{hasError && (
|
||||||
<Alert severity="error" sx={{ m: 2 }}>
|
<Alert severity="error" sx={{ m: 2 }}>
|
||||||
Errore nel caricamento dei dati disponibili
|
{t('reports.preview.errorLoading')}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedDatasets.length === 0 ? (
|
{selectedDatasets.length === 0 ? (
|
||||||
<Box sx={{ p: 3, textAlign: "center", width: "100%" }}>
|
<Box sx={{ p: 3, textAlign: "center", width: "100%" }}>
|
||||||
<Alert severity="info">
|
<Alert severity="info">
|
||||||
Non ci sono dataset selezionati per questo template. Aggiungi
|
{t('reports.preview.noDatasets')}
|
||||||
almeno un dataset per poter generare l'anteprima.
|
|
||||||
</Alert>
|
</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
) : isMobile ? (
|
) : isMobile ? (
|
||||||
@@ -577,7 +578,7 @@ export default function PreviewDialog({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Seleziona un'entità per ogni dataset
|
{t('reports.preview.selectEntityInstruction')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{renderDatasetList()}
|
{renderDatasetList()}
|
||||||
@@ -613,7 +614,7 @@ export default function PreviewDialog({
|
|||||||
|
|
||||||
<DialogActions sx={{ px: isMobile ? 2 : 3, py: 2 }}>
|
<DialogActions sx={{ px: isMobile ? 2 : 3, py: 2 }}>
|
||||||
<Button onClick={onClose} fullWidth={isMobile}>
|
<Button onClick={onClose} fullWidth={isMobile}>
|
||||||
Annulla
|
{t('reports.preview.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -625,10 +626,10 @@ export default function PreviewDialog({
|
|||||||
fullWidth={isMobile}
|
fullWidth={isMobile}
|
||||||
>
|
>
|
||||||
{isGenerating
|
{isGenerating
|
||||||
? "Generazione..."
|
? t('reports.preview.generating')
|
||||||
: isMobile
|
: isMobile
|
||||||
? "Genera PDF"
|
? t('reports.preview.generatePdf')
|
||||||
: "Genera Anteprima PDF"}
|
: t('reports.preview.generatePreviewPdf')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ interface SearchOption {
|
|||||||
label: string;
|
label: string;
|
||||||
path: string;
|
path: string;
|
||||||
category: string;
|
category: string;
|
||||||
|
translationKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SearchBar() {
|
export default function SearchBar() {
|
||||||
@@ -63,55 +64,55 @@ export default function SearchBar() {
|
|||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
const opts: SearchOption[] = [
|
const opts: SearchOption[] = [
|
||||||
// Core
|
// Core
|
||||||
{ label: t('menu.dashboard'), path: '/', category: 'Zentral' },
|
{ label: t('menu.dashboard'), path: '/', category: 'Zentral', translationKey: 'menu.dashboard' },
|
||||||
{ label: t('menu.calendar'), path: '/calendario', category: 'Zentral' },
|
{ label: t('menu.calendar'), path: '/calendario', category: 'Zentral', translationKey: 'menu.calendar' },
|
||||||
{ label: t('menu.events'), path: '/eventi', category: 'Zentral' },
|
{ label: t('menu.events'), path: '/eventi', category: 'Zentral', translationKey: 'menu.events' },
|
||||||
{ label: t('menu.clients'), path: '/clienti', category: 'Zentral' },
|
{ label: t('menu.clients'), path: '/clienti', category: 'Zentral', translationKey: 'menu.clients' },
|
||||||
{ label: t('menu.location'), path: '/location', category: 'Zentral' },
|
{ label: t('menu.location'), path: '/location', category: 'Zentral', translationKey: 'menu.location' },
|
||||||
{ label: t('menu.articles'), path: '/articoli', category: 'Zentral' },
|
{ label: t('menu.articles'), path: '/articoli', category: 'Zentral', translationKey: 'menu.articles' },
|
||||||
{ label: t('menu.resources'), path: '/risorse', category: 'Zentral' },
|
{ label: t('menu.resources'), path: '/risorse', category: 'Zentral', translationKey: 'menu.resources' },
|
||||||
{ label: t('menu.reports'), path: '/report-templates', category: 'Zentral' },
|
{ label: t('menu.reports'), path: '/report-templates', category: 'Zentral', translationKey: 'menu.reports' },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (activeAppCodes.includes('warehouse')) {
|
if (activeAppCodes.includes('warehouse')) {
|
||||||
opts.push(
|
opts.push(
|
||||||
{ label: t('menu.warehouse') + ' Dashboard', path: '/warehouse', 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') },
|
{ 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') },
|
{ label: t('menu.location'), path: '/warehouse/locations', category: t('menu.warehouse'), translationKey: 'menu.location' },
|
||||||
{ label: 'Movimenti', path: '/warehouse/movements', category: t('menu.warehouse') },
|
{ label: t('menu.movements'), path: '/warehouse/movements', category: t('menu.warehouse'), translationKey: 'menu.movements' },
|
||||||
{ label: 'Giacenze', path: '/warehouse/stock', category: t('menu.warehouse') },
|
{ label: t('menu.stock'), path: '/warehouse/stock', category: t('menu.warehouse'), translationKey: 'menu.stock' },
|
||||||
{ label: 'Inventario', path: '/warehouse/inventory', category: t('menu.warehouse') }
|
{ label: t('menu.inventory'), path: '/warehouse/inventory', category: t('menu.warehouse'), translationKey: 'menu.inventory' }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeAppCodes.includes('purchases')) {
|
if (activeAppCodes.includes('purchases')) {
|
||||||
opts.push(
|
opts.push(
|
||||||
{ label: 'Fornitori', path: '/purchases/suppliers', category: t('menu.purchases') },
|
{ label: t('menu.suppliers'), path: '/purchases/suppliers', category: t('menu.purchases'), translationKey: 'menu.suppliers' },
|
||||||
{ label: 'Ordini Acquisto', path: '/purchases/orders', category: t('menu.purchases') }
|
{ label: t('menu.purchaseOrders'), path: '/purchases/orders', category: t('menu.purchases'), translationKey: 'menu.purchaseOrders' }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeAppCodes.includes('sales')) {
|
if (activeAppCodes.includes('sales')) {
|
||||||
opts.push(
|
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')) {
|
if (activeAppCodes.includes('production')) {
|
||||||
opts.push(
|
opts.push(
|
||||||
{ label: t('menu.production') + ' Dashboard', path: '/production', category: t('menu.production') },
|
{ label: t('menu.production') + ' ' + t('menu.dashboard'), path: '/production', category: t('menu.production'), translationKey: 'menu.production' },
|
||||||
{ label: 'Ordini Produzione', path: '/production/orders', category: t('menu.production') },
|
{ label: t('menu.productionOrders'), path: '/production/orders', category: t('menu.production'), translationKey: 'menu.productionOrders' },
|
||||||
{ label: 'Distinte Base', path: '/production/bom', category: t('menu.production') },
|
{ label: t('menu.bom'), path: '/production/bom', category: t('menu.production'), translationKey: 'menu.bom' },
|
||||||
{ label: 'Centri di Lavoro', path: '/production/work-centers', category: t('menu.production') },
|
{ label: t('menu.workCenters'), path: '/production/work-centers', category: t('menu.production'), translationKey: 'menu.workCenters' },
|
||||||
{ label: 'Cicli', path: '/production/cycles', category: t('menu.production') },
|
{ label: t('menu.cycles'), path: '/production/cycles', category: t('menu.production'), translationKey: 'menu.cycles' },
|
||||||
{ label: 'MRP', path: '/production/mrp', category: t('menu.production') }
|
{ label: t('menu.mrp'), path: '/production/mrp', category: t('menu.production'), translationKey: 'menu.mrp' }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.push(
|
opts.push(
|
||||||
{ label: t('menu.apps'), path: '/apps', category: 'Admin' },
|
{ label: t('menu.apps'), path: '/apps', category: t('menu.administration'), translationKey: 'menu.apps' },
|
||||||
{ label: t('menu.autoCodes'), path: '/admin/auto-codes', category: 'Admin' },
|
{ label: t('menu.autoCodes'), path: '/admin/auto-codes', category: t('menu.administration'), translationKey: 'menu.autoCodes' },
|
||||||
{ label: t('menu.customFields'), path: '/admin/custom-fields', category: 'Admin' }
|
{ label: t('menu.customFields'), path: '/admin/custom-fields', category: t('menu.administration'), translationKey: 'menu.customFields' }
|
||||||
);
|
);
|
||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
@@ -128,7 +129,7 @@ export default function SearchBar() {
|
|||||||
getOptionLabel={(option) => typeof option === 'string' ? option : option.label}
|
getOptionLabel={(option) => typeof option === 'string' ? option : option.label}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
if (typeof value !== 'string' && value) {
|
if (typeof value !== 'string' && value) {
|
||||||
openTab(value.path, value.label);
|
openTab(value.path, value.label, true, value.translationKey);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => {
|
renderInput={(params) => {
|
||||||
@@ -141,7 +142,7 @@ export default function SearchBar() {
|
|||||||
<StyledInputBase
|
<StyledInputBase
|
||||||
{...InputProps}
|
{...InputProps}
|
||||||
{...rest}
|
{...rest}
|
||||||
placeholder="Search..."
|
placeholder={t('navigation.searchPlaceholder')}
|
||||||
inputProps={{ ...params.inputProps, 'aria-label': 'search' }}
|
inputProps={{ ...params.inputProps, 'aria-label': 'search' }}
|
||||||
/>
|
/>
|
||||||
</Search>
|
</Search>
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ interface MenuItem {
|
|||||||
path?: string;
|
path?: string;
|
||||||
children?: MenuItem[];
|
children?: MenuItem[];
|
||||||
appCode?: string;
|
appCode?: string;
|
||||||
|
translationKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
@@ -90,7 +91,7 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (item.path) {
|
if (item.path) {
|
||||||
openTab(item.path, item.tabLabel || item.label);
|
openTab(item.path, item.tabLabel || item.label, true, item.translationKey);
|
||||||
if (onClose) onClose();
|
if (onClose) onClose();
|
||||||
} else if (item.children) {
|
} else if (item.children) {
|
||||||
handleToggle(item.id);
|
handleToggle(item.id);
|
||||||
@@ -103,19 +104,21 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
|
|||||||
label: 'Zentral Dashboard',
|
label: 'Zentral Dashboard',
|
||||||
icon: <DashboardIcon />,
|
icon: <DashboardIcon />,
|
||||||
path: '/',
|
path: '/',
|
||||||
|
translationKey: 'menu.dashboard',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'warehouse',
|
id: 'warehouse',
|
||||||
label: t('menu.warehouse'),
|
label: t('menu.warehouse'),
|
||||||
icon: <WarehouseIcon />,
|
icon: <WarehouseIcon />,
|
||||||
appCode: 'warehouse',
|
appCode: 'warehouse',
|
||||||
|
translationKey: 'menu.warehouse',
|
||||||
children: [
|
children: [
|
||||||
{ id: 'wh-dashboard', label: 'Dashboard', tabLabel: t('menu.warehouse'), icon: <DashboardIcon />, path: '/warehouse' },
|
{ id: 'wh-dashboard', label: t('menu.dashboard'), tabLabel: t('menu.warehouse'), icon: <DashboardIcon />, path: '/warehouse', translationKey: 'menu.warehouse' },
|
||||||
{ id: 'wh-articles', label: t('menu.articles'), icon: <CategoryIcon />, path: '/warehouse/articles' },
|
{ id: 'wh-articles', label: t('menu.articles'), icon: <CategoryIcon />, path: '/warehouse/articles', translationKey: 'menu.articles' },
|
||||||
{ id: 'wh-locations', label: t('menu.location'), icon: <PlaceIcon />, path: '/warehouse/locations' },
|
{ id: 'wh-locations', label: t('menu.location'), icon: <PlaceIcon />, path: '/warehouse/locations', translationKey: 'menu.location' },
|
||||||
{ id: 'wh-movements', label: 'Movimenti', icon: <SwapIcon />, path: '/warehouse/movements' },
|
{ id: 'wh-movements', label: t('menu.movements'), icon: <SwapIcon />, path: '/warehouse/movements', translationKey: 'menu.movements' },
|
||||||
{ id: 'wh-stock', label: 'Giacenze', icon: <StorageIcon />, path: '/warehouse/stock' },
|
{ id: 'wh-stock', label: t('menu.stock'), icon: <StorageIcon />, path: '/warehouse/stock', translationKey: 'menu.stock' },
|
||||||
{ id: 'wh-inventory', label: 'Inventario', icon: <AssignmentIcon />, path: '/warehouse/inventory' },
|
{ id: 'wh-inventory', label: t('menu.inventory'), icon: <AssignmentIcon />, path: '/warehouse/inventory', translationKey: 'menu.inventory' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -123,9 +126,10 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
|
|||||||
label: t('menu.purchases'),
|
label: t('menu.purchases'),
|
||||||
icon: <ShoppingCartIcon />,
|
icon: <ShoppingCartIcon />,
|
||||||
appCode: 'purchases',
|
appCode: 'purchases',
|
||||||
|
translationKey: 'menu.purchases',
|
||||||
children: [
|
children: [
|
||||||
{ id: 'pur-suppliers', label: 'Fornitori', icon: <PeopleIcon />, path: '/purchases/suppliers' },
|
{ id: 'pur-suppliers', label: t('menu.suppliers'), icon: <PeopleIcon />, path: '/purchases/suppliers', translationKey: 'menu.suppliers' },
|
||||||
{ id: 'pur-orders', label: 'Ordini Acquisto', icon: <ListAltIcon />, path: '/purchases/orders' },
|
{ id: 'pur-orders', label: t('menu.purchaseOrders'), icon: <ListAltIcon />, path: '/purchases/orders', translationKey: 'menu.purchaseOrders' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -133,8 +137,9 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
|
|||||||
label: t('menu.sales'),
|
label: t('menu.sales'),
|
||||||
icon: <SellIcon />,
|
icon: <SellIcon />,
|
||||||
appCode: 'sales',
|
appCode: 'sales',
|
||||||
|
translationKey: 'menu.sales',
|
||||||
children: [
|
children: [
|
||||||
{ id: 'sal-orders', label: 'Ordini Vendita', icon: <ListAltIcon />, path: '/sales/orders' },
|
{ id: 'sal-orders', label: t('menu.salesOrders'), icon: <ListAltIcon />, path: '/sales/orders', translationKey: 'menu.salesOrders' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -142,13 +147,14 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
|
|||||||
label: t('menu.production'),
|
label: t('menu.production'),
|
||||||
icon: <ProductionIcon />,
|
icon: <ProductionIcon />,
|
||||||
appCode: 'production',
|
appCode: 'production',
|
||||||
|
translationKey: 'menu.production',
|
||||||
children: [
|
children: [
|
||||||
{ id: 'prod-dashboard', label: 'Dashboard', tabLabel: t('menu.production'), icon: <DashboardIcon />, path: '/production' },
|
{ id: 'prod-dashboard', label: t('menu.dashboard'), tabLabel: t('menu.production'), icon: <DashboardIcon />, path: '/production', translationKey: 'menu.production' },
|
||||||
{ id: 'prod-orders', label: 'Ordini Produzione', icon: <ListAltIcon />, path: '/production/orders' },
|
{ id: 'prod-orders', label: t('menu.productionOrders'), icon: <ListAltIcon />, path: '/production/orders', translationKey: 'menu.productionOrders' },
|
||||||
{ id: 'prod-bom', label: 'Distinte Base', icon: <AssignmentIcon />, path: '/production/bom' },
|
{ id: 'prod-bom', label: t('menu.bom'), icon: <AssignmentIcon />, path: '/production/bom', translationKey: 'menu.bom' },
|
||||||
{ id: 'prod-workcenters', label: 'Centri di Lavoro', icon: <BuildIcon />, path: '/production/work-centers' },
|
{ id: 'prod-workcenters', label: t('menu.workCenters'), icon: <BuildIcon />, path: '/production/work-centers', translationKey: 'menu.workCenters' },
|
||||||
{ id: 'prod-cycles', label: 'Cicli', icon: <TimelineIcon />, path: '/production/cycles' },
|
{ id: 'prod-cycles', label: t('menu.cycles'), icon: <TimelineIcon />, path: '/production/cycles', translationKey: 'menu.cycles' },
|
||||||
{ id: 'prod-mrp', label: 'MRP', icon: <ManufacturingIcon />, path: '/production/mrp' },
|
{ id: 'prod-mrp', label: t('menu.mrp'), icon: <ManufacturingIcon />, path: '/production/mrp', translationKey: 'menu.mrp' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -156,10 +162,11 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
|
|||||||
label: t('menu.events'),
|
label: t('menu.events'),
|
||||||
icon: <EventIcon />,
|
icon: <EventIcon />,
|
||||||
appCode: 'events',
|
appCode: 'events',
|
||||||
|
translationKey: 'menu.events',
|
||||||
children: [
|
children: [
|
||||||
{ id: 'ev-list', label: t('menu.events'), icon: <EventIcon />, path: '/events/list' },
|
{ id: 'ev-list', label: t('menu.events'), icon: <EventIcon />, path: '/events/list', translationKey: 'menu.events' },
|
||||||
{ id: 'ev-calendar', label: t('menu.calendar'), icon: <CalendarIcon />, path: '/events/calendar' },
|
{ id: 'ev-calendar', label: t('menu.calendar'), icon: <CalendarIcon />, path: '/events/calendar', translationKey: 'menu.calendar' },
|
||||||
{ id: 'ev-locations', label: t('menu.location'), icon: <PlaceIcon />, path: '/events/locations' },
|
{ id: 'ev-locations', label: t('menu.location'), icon: <PlaceIcon />, path: '/events/locations', translationKey: 'menu.location' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -167,23 +174,25 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
|
|||||||
label: t('apps.hr.title'),
|
label: t('apps.hr.title'),
|
||||||
icon: <PeopleIcon />,
|
icon: <PeopleIcon />,
|
||||||
appCode: 'hr',
|
appCode: 'hr',
|
||||||
|
translationKey: 'apps.hr.title',
|
||||||
children: [
|
children: [
|
||||||
{ id: 'hr-dipendenti', label: t('apps.hr.dipendenti'), icon: <PeopleIcon />, path: '/hr/dipendenti' },
|
{ id: 'hr-dipendenti', label: t('apps.hr.dipendenti'), icon: <PeopleIcon />, path: '/hr/dipendenti', translationKey: 'apps.hr.dipendenti' },
|
||||||
{ id: 'hr-contratti', label: t('apps.hr.contratti'), icon: <AssignmentIcon />, path: '/hr/contratti' },
|
{ id: 'hr-contratti', label: t('apps.hr.contratti'), icon: <AssignmentIcon />, path: '/hr/contratti', translationKey: 'apps.hr.contratti' },
|
||||||
{ id: 'hr-assenze', label: t('apps.hr.assenze'), icon: <EventIcon />, path: '/hr/assenze' },
|
{ id: 'hr-assenze', label: t('apps.hr.assenze'), icon: <EventIcon />, path: '/hr/assenze', translationKey: 'apps.hr.assenze' },
|
||||||
{ id: 'hr-pagamenti', label: t('apps.hr.pagamenti'), icon: <AttachMoneyIcon />, path: '/hr/pagamenti' },
|
{ id: 'hr-pagamenti', label: t('apps.hr.pagamenti'), icon: <AttachMoneyIcon />, path: '/hr/pagamenti', translationKey: 'apps.hr.pagamenti' },
|
||||||
{ id: 'hr-rimborsi', label: t('apps.hr.rimborsi'), icon: <ReceiptIcon />, path: '/hr/rimborsi' },
|
{ id: 'hr-rimborsi', label: t('apps.hr.rimborsi'), icon: <ReceiptIcon />, path: '/hr/rimborsi', translationKey: 'apps.hr.rimborsi' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'admin',
|
id: 'admin',
|
||||||
label: 'Amministrazione',
|
label: t('menu.administration'),
|
||||||
icon: <SettingsIcon />,
|
icon: <SettingsIcon />,
|
||||||
|
translationKey: 'menu.administration',
|
||||||
children: [
|
children: [
|
||||||
{ id: 'apps', label: t('menu.apps'), icon: <ModulesIcon />, path: '/apps' },
|
{ id: 'apps', label: t('menu.apps'), icon: <ModulesIcon />, path: '/apps', translationKey: 'menu.apps' },
|
||||||
{ id: 'autocodes', label: t('menu.autoCodes'), icon: <AutoCodeIcon />, path: '/admin/auto-codes' },
|
{ id: 'autocodes', label: t('menu.autoCodes'), icon: <AutoCodeIcon />, path: '/admin/auto-codes', translationKey: 'menu.autoCodes' },
|
||||||
{ id: 'customfields', label: t('menu.customFields'), icon: <AutoCodeIcon />, path: '/admin/custom-fields' },
|
{ id: 'customfields', label: t('menu.customFields'), icon: <AutoCodeIcon />, path: '/admin/custom-fields', translationKey: 'menu.customFields' },
|
||||||
{ id: 'reports', label: t('menu.reports'), icon: <PrintIcon />, path: '/report-designer', appCode: 'report-designer' },
|
{ id: 'reports', label: t('menu.reports'), icon: <PrintIcon />, path: '/report-designer', appCode: 'report-designer', translationKey: 'menu.reports' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useTheme } from '@mui/material/styles';
|
|||||||
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core';
|
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core';
|
||||||
import { arrayMove, SortableContext, sortableKeyboardCoordinates, horizontalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
|
import { arrayMove, SortableContext, sortableKeyboardCoordinates, horizontalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
|
|
||||||
interface SortableTabProps {
|
interface SortableTabProps {
|
||||||
tab: TabType;
|
tab: TabType;
|
||||||
@@ -16,6 +17,7 @@ interface SortableTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SortableTab({ tab, activeTabPath, onActivate, onClose, onContextMenu }: SortableTabProps) {
|
function SortableTab({ tab, activeTabPath, onActivate, onClose, onContextMenu }: SortableTabProps) {
|
||||||
|
const { t } = useLanguage();
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
listeners,
|
listeners,
|
||||||
@@ -52,7 +54,7 @@ function SortableTab({ tab, activeTabPath, onActivate, onClose, onContextMenu }:
|
|||||||
<Tab
|
<Tab
|
||||||
label={
|
label={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<span>{tab.label}</span>
|
<span>{tab.translationKey ? t(tab.translationKey) : tab.label}</span>
|
||||||
{tab.closable && (
|
{tab.closable && (
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
@@ -100,6 +102,7 @@ function SortableTab({ tab, activeTabPath, onActivate, onClose, onContextMenu }:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function TabsBar() {
|
export default function TabsBar() {
|
||||||
|
const { t } = useLanguage();
|
||||||
const {
|
const {
|
||||||
tabs,
|
tabs,
|
||||||
activeTabPath,
|
activeTabPath,
|
||||||
@@ -207,7 +210,7 @@ export default function TabsBar() {
|
|||||||
|
|
||||||
{/* Tab Groups & Actions */}
|
{/* Tab Groups & Actions */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', px: 1, borderLeft: 1, borderColor: 'divider' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', px: 1, borderLeft: 1, borderColor: 'divider' }}>
|
||||||
<Tooltip title="Tab Groups">
|
<Tooltip title={t('navigation.tabGroups')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={(e) => setGroupsMenuAnchor(e.currentTarget)}
|
onClick={(e) => setGroupsMenuAnchor(e.currentTarget)}
|
||||||
@@ -233,21 +236,21 @@ export default function TabsBar() {
|
|||||||
handleCloseContextMenu();
|
handleCloseContextMenu();
|
||||||
}} disabled={!contextMenu?.tab?.closable}>
|
}} disabled={!contextMenu?.tab?.closable}>
|
||||||
<ListItemIcon><CloseIcon fontSize="small" /></ListItemIcon>
|
<ListItemIcon><CloseIcon fontSize="small" /></ListItemIcon>
|
||||||
<ListItemText>Close</ListItemText>
|
<ListItemText>{t('navigation.close')}</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => {
|
<MenuItem onClick={() => {
|
||||||
if (contextMenu?.tab) closeOtherTabs(contextMenu.tab.path);
|
if (contextMenu?.tab) closeOtherTabs(contextMenu.tab.path);
|
||||||
handleCloseContextMenu();
|
handleCloseContextMenu();
|
||||||
}}>
|
}}>
|
||||||
<ListItemIcon><ClearAllIcon fontSize="small" /></ListItemIcon>
|
<ListItemIcon><ClearAllIcon fontSize="small" /></ListItemIcon>
|
||||||
<ListItemText>Close Others</ListItemText>
|
<ListItemText>{t('navigation.closeOthers')}</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => {
|
<MenuItem onClick={() => {
|
||||||
if (contextMenu?.tab) closeTabsToRight(contextMenu.tab.path);
|
if (contextMenu?.tab) closeTabsToRight(contextMenu.tab.path);
|
||||||
handleCloseContextMenu();
|
handleCloseContextMenu();
|
||||||
}}>
|
}}>
|
||||||
<ListItemIcon><ArrowForwardIcon fontSize="small" /></ListItemIcon>
|
<ListItemIcon><ArrowForwardIcon fontSize="small" /></ListItemIcon>
|
||||||
<ListItemText>Close to the Right</ListItemText>
|
<ListItemText>{t('navigation.closeRight')}</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
@@ -262,12 +265,12 @@ export default function TabsBar() {
|
|||||||
setGroupsMenuAnchor(null);
|
setGroupsMenuAnchor(null);
|
||||||
}}>
|
}}>
|
||||||
<ListItemIcon><SaveIcon fontSize="small" /></ListItemIcon>
|
<ListItemIcon><SaveIcon fontSize="small" /></ListItemIcon>
|
||||||
<ListItemText>Save Current Session</ListItemText>
|
<ListItemText>{t('navigation.saveSession')}</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
{tabGroups.length === 0 && (
|
{tabGroups.length === 0 && (
|
||||||
<MenuItem disabled>
|
<MenuItem disabled>
|
||||||
<ListItemText secondary="No saved groups" />
|
<ListItemText secondary={t('navigation.noSavedGroups')} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{tabGroups.map((group) => (
|
{tabGroups.map((group) => (
|
||||||
@@ -292,12 +295,12 @@ export default function TabsBar() {
|
|||||||
|
|
||||||
{/* Save Group Dialog */}
|
{/* Save Group Dialog */}
|
||||||
<Dialog open={saveGroupDialogOpen} onClose={() => setSaveGroupDialogOpen(false)}>
|
<Dialog open={saveGroupDialogOpen} onClose={() => setSaveGroupDialogOpen(false)}>
|
||||||
<DialogTitle>Save Tab Group</DialogTitle>
|
<DialogTitle>{t('navigation.saveGroupTitle')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label="Group Name"
|
label={t('navigation.groupName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={newGroupName}
|
value={newGroupName}
|
||||||
@@ -305,8 +308,8 @@ export default function TabsBar() {
|
|||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setSaveGroupDialogOpen(false)}>Cancel</Button>
|
<Button onClick={() => setSaveGroupDialogOpen(false)}>{t('navigation.cancel')}</Button>
|
||||||
<Button onClick={handleSaveGroup} variant="contained">Save</Button>
|
<Button onClick={handleSaveGroup} variant="contained">{t('navigation.save')}</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
|||||||
export interface Tab {
|
export interface Tab {
|
||||||
path: string;
|
path: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
translationKey?: string;
|
||||||
closable?: boolean;
|
closable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ interface TabContextType {
|
|||||||
tabs: Tab[];
|
tabs: Tab[];
|
||||||
activeTabPath: string;
|
activeTabPath: string;
|
||||||
tabGroups: TabGroup[];
|
tabGroups: TabGroup[];
|
||||||
openTab: (path: string, label: string, closable?: boolean) => void;
|
openTab: (path: string, label: string, closable?: boolean, translationKey?: string) => void;
|
||||||
closeTab: (path: string) => void;
|
closeTab: (path: string) => void;
|
||||||
setActiveTab: (path: string) => void;
|
setActiveTab: (path: string) => void;
|
||||||
reorderTabs: (newTabs: Tab[]) => void;
|
reorderTabs: (newTabs: Tab[]) => void;
|
||||||
@@ -48,14 +49,14 @@ export function TabProvider({ children }: { children: ReactNode }) {
|
|||||||
if (Array.isArray(parsedTabs) && parsedTabs.length > 0) {
|
if (Array.isArray(parsedTabs) && parsedTabs.length > 0) {
|
||||||
setTabs(parsedTabs);
|
setTabs(parsedTabs);
|
||||||
} else {
|
} else {
|
||||||
setTabs([{ path: '/', label: 'Dashboard', closable: false }]);
|
setTabs([{ path: '/', label: 'Dashboard', translationKey: 'menu.dashboard', closable: false }]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to parse tabs", e);
|
console.error("Failed to parse tabs", e);
|
||||||
setTabs([{ path: '/', label: 'Dashboard', closable: false }]);
|
setTabs([{ path: '/', label: 'Dashboard', translationKey: 'menu.dashboard', closable: false }]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setTabs([{ path: '/', label: 'Dashboard', closable: false }]);
|
setTabs([{ path: '/', label: 'Dashboard', translationKey: 'menu.dashboard', closable: false }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedActiveTab) {
|
if (savedActiveTab) {
|
||||||
@@ -96,18 +97,19 @@ export function TabProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
}, [location.pathname, tabs]);
|
}, [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) => {
|
setTabs((prev) => {
|
||||||
const existingTabIndex = prev.findIndex((t) => t.path === path);
|
const existingTabIndex = prev.findIndex((t) => t.path === path);
|
||||||
if (existingTabIndex !== -1) {
|
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];
|
const newTabs = [...prev];
|
||||||
newTabs[existingTabIndex] = { ...newTabs[existingTabIndex], label };
|
newTabs[existingTabIndex] = { ...newTabs[existingTabIndex], label, translationKey };
|
||||||
return newTabs;
|
return newTabs;
|
||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
return [...prev, { path, label, closable }];
|
return [...prev, { path, label, closable, translationKey }];
|
||||||
});
|
});
|
||||||
navigate(path);
|
navigate(path);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user