feat: Implement and update translations for warehouse categories, core application titles, and other UI elements.

This commit is contained in:
2025-12-12 14:25:16 +01:00
parent 08256f0019
commit c4d58f8354
9 changed files with 236 additions and 69 deletions

View File

@@ -53,10 +53,5 @@ File riassuntivo dello stato di sviluppo di Zentral.
- [2025-12-12 Resend Integration](./devlog/2025-12-12-120000_resend_integration.md) - **Completato** - [2025-12-12 Resend Integration](./devlog/2025-12-12-120000_resend_integration.md) - **Completato**
- [2025-12-12 Magazzino: Categorie Gerarchiche](./devlog/2025-12-12-133000_remove_product_groups_add_categories.md) - **Completato** - [2025-12-12 Magazzino: Categorie Gerarchiche](./devlog/2025-12-12-133000_remove_product_groups_add_categories.md) - **Completato**
- Sostituita la logica "Gruppi Merceologici" con l'utilizzo esteso delle "Categorie Articoli" gerarchiche. - Sostituita la logica "Gruppi Merceologici" con l'utilizzo esteso delle "Categorie Articoli" gerarchiche.
- Riorganizzazione UI Auto Codes, allineamento stile a Custom Fields, miglioramento traduzioni e categorizzazione. - [2025-12-12 Update Translations](./devlog/2025-12-12-141010_update_translations.md) - **In Corso**
- [2025-12-12 - Modulo Comunicazioni](./devlog/2025-12-12-110000_communications_module.md) - **In Corso** - Aggiornamento traduzioni per categorie magazzino, comunicazioni e formazione.
- Implementazione invio email e gestione comunicazioni.
- [2025-12-12 - Gestione Modulo Formazione (Generale)](./devlog/2025-12-12-105500_safety_training_schedule.md) - **In Corso**
- Implementazione modulo formazione generale e scadenziario.
- [2025-12-12 - Implementazione Gruppi Merceologici Magazzino](./devlog/2025-12-12-125000_magazzino_gruppi_merceologici.md) - **In Corso**
- Implementazione gestione gruppi merceologici per il magazzino.

View File

@@ -0,0 +1,21 @@
# Update Translations for New Developments
## Status
- [x] Analysis of new features needing translation
- [x] Update Italian Translations (it)
- [x] Update English Translations (en)
- [x] Verification
## Details
Verified recent developments:
1. **Warehouse - Categories**: New management of article categories.
2. **Communications**: Email configuration and logs.
3. **Training**: New module for courses and training sessions.
I will scan these modules for `t()` calls and update the `translation.json` files in `public/locales/it` and `public/locales/en`.
## Work Done
- **Warehouse Categories**: Updated `CategoriesPage.tsx` to use `useTranslation`. Added keys for titles, buttons, fields, and dialogs in both IT and EN locales.
- **Communications**: Updated `SettingsPage.tsx` and `LogsPage.tsx` to use `useTranslation`. Added complete set of keys for settings, fields, actions, messages and log columns in both IT and EN locales.
- **Components**: Updated `Sidebar.tsx`, `SearchBar.tsx` to use full translations. Added `apps.core.title` and ensure `categories` is available in menu.
- **Training**: Training module files were not found in the current workspace, so no translations were applied for this module yet. Suggest to review separately when module is available.

View File

@@ -65,7 +65,8 @@
"emailConfig": "Email Configuration", "emailConfig": "Email Configuration",
"movements": "Movements", "movements": "Movements",
"stock": "Stock", "stock": "Stock",
"inventory": "Inventory" "inventory": "Inventory",
"categories": "Categories"
}, },
"navigation": { "navigation": {
"searchPlaceholder": "Search...", "searchPlaceholder": "Search...",
@@ -285,12 +286,33 @@
"confermato": "Confirmed" "confermato": "Confirmed"
}, },
"apps": { "apps": {
"core": {
"title": "Zentral"
},
"warehouse": { "warehouse": {
"title": "Warehouse Management", "title": "Warehouse Management",
"inventory": "Inventory", "inventory": "Inventory",
"movements": "Movements", "movements": "Movements",
"stock": "Stock", "stock": "Stock",
"categories": "Categories" "categories": {
"title": "Article Categories",
"new": "New Category",
"edit": "Edit Category",
"empty": "No categories found",
"newParams": {
"root": "New Root Category"
},
"fields": {
"name": "Name",
"description": "Description",
"sortOrder": "Sort Order",
"active": "Active"
},
"deleteDialog": {
"title": "Delete Confirmation",
"content": "Are you sure you want to delete this category? This operation cannot be undone. If the category contains subcategories or articles, it may not be possible to delete it."
}
}
}, },
"hr": { "hr": {
"title": "Human Resources", "title": "Human Resources",
@@ -1552,5 +1574,56 @@
"permesso": "Permit", "permesso": "Permit",
"altro": "Other" "altro": "Other"
} }
},
"communications": {
"settings": {
"title": "Email Configuration",
"fields": {
"provider": "Provider",
"host": "SMTP Host",
"port": "Port",
"user": "Username",
"password": "Password",
"ssl": "Enable SSL/TLS",
"apiKey": "Resend API Key",
"fromEmail": "From Email",
"fromName": "From Name"
},
"helpers": {
"apiKey": "Get your API Key at"
},
"sections": {
"defaultSender": "Default Sender"
},
"actions": {
"testConnection": "Test Connection",
"sendTest": "Send Test"
},
"testStats": {
"title": "Test Email",
"recipient": "Recipient",
"subject": "Subject"
},
"messages": {
"loadError": "Error loading configuration",
"saveSuccess": "Configuration saved successfully",
"saveError": "Error saving configuration",
"recipientRequired": "Recipient email is required for test",
"testSuccess": "Test email sent successfully",
"testError": "Error sending test email"
}
},
"logs": {
"title": "Email Logs",
"columns": {
"id": "ID",
"date": "Date",
"status": "Status",
"sender": "Sender",
"recipient": "Recipient",
"subject": "Subject",
"error": "Error"
}
}
} }
} }

View File

@@ -61,7 +61,8 @@
"emailConfig": "Configurazione Email", "emailConfig": "Configurazione Email",
"movements": "Movimenti", "movements": "Movimenti",
"stock": "Giacenze", "stock": "Giacenze",
"inventory": "Inventario" "inventory": "Inventario",
"categories": "Categorie"
}, },
"navigation": { "navigation": {
"searchPlaceholder": "Cerca...", "searchPlaceholder": "Cerca...",
@@ -281,12 +282,33 @@
"confermato": "Confermato" "confermato": "Confermato"
}, },
"apps": { "apps": {
"core": {
"title": "Zentral"
},
"warehouse": { "warehouse": {
"title": "Gestione Magazzino", "title": "Gestione Magazzino",
"inventory": "Inventario", "inventory": "Inventario",
"movements": "Movimenti", "movements": "Movimenti",
"stock": "Giacenze", "stock": "Giacenze",
"categories": "Categorie" "categories": {
"title": "Categorie Articoli",
"new": "Nuova Categoria",
"edit": "Modifica Categoria",
"empty": "Nessuna categoria trovata",
"newParams": {
"root": "Nuova Categoria Root"
},
"fields": {
"name": "Nome",
"description": "Descrizione",
"sortOrder": "Ordinamento",
"active": "Attivo"
},
"deleteDialog": {
"title": "Conferma Eliminazione",
"content": "Sei sicuro di voler eliminare questa categoria? L'operazione non può essere annullata. Se la categoria contiene sottocategorie o articoli, potrebbe non essere possibile eliminarla."
}
}
}, },
"hr": { "hr": {
"title": "Gestione Personale", "title": "Gestione Personale",
@@ -1633,5 +1655,56 @@
"permesso": "Permesso", "permesso": "Permesso",
"altro": "Altro" "altro": "Altro"
} }
},
"communications": {
"settings": {
"title": "Configurazione Email",
"fields": {
"provider": "Provider",
"host": "SMTP Host",
"port": "Porta",
"user": "Username",
"password": "Password",
"ssl": "Abilita SSL/TLS",
"apiKey": "Resend API Key",
"fromEmail": "Email Mittente",
"fromName": "Nome Mittente"
},
"helpers": {
"apiKey": "Ottieni la tua API Key su"
},
"sections": {
"defaultSender": "Mittente Default"
},
"actions": {
"testConnection": "Test Connessione",
"sendTest": "Invia Test"
},
"testStats": {
"title": "Test Email",
"recipient": "Destinatario",
"subject": "Oggetto"
},
"messages": {
"loadError": "Errore nel caricamento configurazione",
"saveSuccess": "Configurazione salvata con successo",
"saveError": "Errore nel salvataggio configurazione",
"recipientRequired": "Email destinatario obbligatoria per il test",
"testSuccess": "Email di test inviata con successo",
"testError": "Errore nell'invio email di test"
}
},
"logs": {
"title": "Log Email",
"columns": {
"id": "ID",
"date": "Data",
"status": "Stato",
"sender": "Mittente",
"recipient": "Destinatario",
"subject": "Oggetto",
"error": "Errore"
}
}
} }
} }

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DataGrid, GridColDef } from '@mui/x-data-grid'; import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { History } from '@mui/icons-material'; import { History } from '@mui/icons-material';
@@ -7,6 +8,7 @@ import { EmailLog } from '../types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export default function LogsPage() { export default function LogsPage() {
const { t } = useTranslation();
const [logs, setLogs] = useState<EmailLog[]>([]); const [logs, setLogs] = useState<EmailLog[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -27,13 +29,13 @@ export default function LogsPage() {
}; };
const columns: GridColDef[] = [ const columns: GridColDef[] = [
{ field: 'id', headerName: 'ID', width: 70 }, { field: 'id', headerName: t('communications.logs.columns.id'), width: 70 },
{ {
field: 'sentDate', headerName: 'Data', width: 180, field: 'sentDate', headerName: t('communications.logs.columns.date'), width: 180,
valueFormatter: (params) => dayjs(params.value).format('DD/MM/YYYY HH:mm') valueFormatter: (params) => dayjs(params.value).format('DD/MM/YYYY HH:mm')
}, },
{ {
field: 'status', headerName: 'Stato', width: 120, field: 'status', headerName: t('communications.logs.columns.status'), width: 120,
renderCell: (params) => ( renderCell: (params) => (
<span style={{ <span style={{
color: params.value === 'Success' ? 'green' : 'red', color: params.value === 'Success' ? 'green' : 'red',
@@ -43,16 +45,16 @@ export default function LogsPage() {
</span> </span>
) )
}, },
{ field: 'sender', headerName: 'Mittente', width: 200 }, { field: 'sender', headerName: t('communications.logs.columns.sender'), width: 200 },
{ field: 'recipient', headerName: 'Destinatario', width: 200 }, { field: 'recipient', headerName: t('communications.logs.columns.recipient'), width: 200 },
{ field: 'subject', headerName: 'Oggetto', flex: 1 }, { field: 'subject', headerName: t('communications.logs.columns.subject'), flex: 1 },
{ field: 'errorMessage', headerName: 'Errore', width: 200 }, { field: 'errorMessage', headerName: t('communications.logs.columns.error'), width: 200 },
]; ];
return ( return (
<Box p={3} sx={{ height: '80vh', display: 'flex', flexDirection: 'column' }}> <Box p={3} sx={{ height: '80vh', display: 'flex', flexDirection: 'column' }}>
<Box display="flex" justifyContent="space-between" mb={2}> <Box display="flex" justifyContent="space-between" mb={2}>
<Typography variant="h4"><History /> Email Logs</Typography> <Typography variant="h4"><History /> {t('communications.logs.title')}</Typography>
</Box> </Box>
<DataGrid <DataGrid
rows={logs} rows={logs}

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import { import {
Box, Paper, Typography, TextField, Button, Grid, Box, Paper, Typography, TextField, Button, Grid,
@@ -10,6 +11,7 @@ import { communicationsService } from '../services/communicationsService';
import { SmtpConfig, TestEmail } from '../types'; import { SmtpConfig, TestEmail } from '../types';
export default function SettingsPage() { export default function SettingsPage() {
const { t } = useTranslation();
const { control, handleSubmit, reset, watch } = useForm<SmtpConfig>(); const { control, handleSubmit, reset, watch } = useForm<SmtpConfig>();
const provider = watch('provider') || 'smtp'; const provider = watch('provider') || 'smtp';
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -28,7 +30,7 @@ export default function SettingsPage() {
reset(config); reset(config);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setNotification({ type: 'error', message: 'Failed to load configuration' }); setNotification({ type: 'error', message: t('communications.settings.messages.loadError') });
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -38,9 +40,9 @@ export default function SettingsPage() {
try { try {
setLoading(true); setLoading(true);
await communicationsService.saveConfig(data); await communicationsService.saveConfig(data);
setNotification({ type: 'success', message: 'Configuration saved successfully' }); setNotification({ type: 'success', message: t('communications.settings.messages.saveSuccess') });
} catch (error) { } catch (error) {
setNotification({ type: 'error', message: 'Failed to save configuration' }); setNotification({ type: 'error', message: t('communications.settings.messages.saveError') });
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -48,16 +50,16 @@ export default function SettingsPage() {
const sendTest = async () => { const sendTest = async () => {
if (!testData.to) { if (!testData.to) {
setNotification({ type: 'error', message: 'Recipient email is required for test' }); setNotification({ type: 'error', message: t('communications.settings.messages.recipientRequired') });
return; return;
} }
try { try {
setLoading(true); setLoading(true);
await communicationsService.sendTestEmail(testData); await communicationsService.sendTestEmail(testData);
setNotification({ type: 'success', message: 'Test email queued successfully' }); setNotification({ type: 'success', message: t('communications.settings.messages.testSuccess') });
setTestMode(false); setTestMode(false);
} catch (error: any) { } catch (error: any) {
setNotification({ type: 'error', message: error.response?.data?.message || 'Failed to send test email' }); setNotification({ type: 'error', message: error.response?.data?.message || t('communications.settings.messages.testError') });
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -66,7 +68,7 @@ export default function SettingsPage() {
return ( return (
<Box p={3}> <Box p={3}>
<Typography variant="h4" gutterBottom display="flex" alignItems="center" gap={2}> <Typography variant="h4" gutterBottom display="flex" alignItems="center" gap={2}>
<Email fontSize="large" color="primary" /> Configurazione Email <Email fontSize="large" color="primary" /> {t('communications.settings.title')}
</Typography> </Typography>
<Paper sx={{ p: 3, mb: 3 }}> <Paper sx={{ p: 3, mb: 3 }}>
@@ -74,13 +76,13 @@ export default function SettingsPage() {
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12} md={4}> <Grid item xs={12} md={4}>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel>Provider</InputLabel> <InputLabel>{t('communications.settings.fields.provider')}</InputLabel>
<Controller <Controller
name="provider" name="provider"
control={control} control={control}
defaultValue="smtp" defaultValue="smtp"
render={({ field }) => ( render={({ field }) => (
<Select {...field} label="Provider"> <Select {...field} label={t('communications.settings.fields.provider')}>
<MenuItem value="smtp">SMTP</MenuItem> <MenuItem value="smtp">SMTP</MenuItem>
<MenuItem value="resend">Resend</MenuItem> <MenuItem value="resend">Resend</MenuItem>
</Select> </Select>
@@ -96,7 +98,7 @@ export default function SettingsPage() {
name="host" name="host"
control={control} control={control}
defaultValue="" defaultValue=""
render={({ field }) => <TextField {...field} label="SMTP Host" fullWidth required />} render={({ field }) => <TextField {...field} label={t('communications.settings.fields.host')} fullWidth required />}
/> />
</Grid> </Grid>
<Grid item xs={12} md={4}> <Grid item xs={12} md={4}>
@@ -104,7 +106,7 @@ export default function SettingsPage() {
name="port" name="port"
control={control} control={control}
defaultValue={587} defaultValue={587}
render={({ field }) => <TextField {...field} label="Port" type="number" fullWidth required />} render={({ field }) => <TextField {...field} label={t('communications.settings.fields.port')} type="number" fullWidth required />}
/> />
</Grid> </Grid>
@@ -113,7 +115,7 @@ export default function SettingsPage() {
name="user" name="user"
control={control} control={control}
defaultValue="" defaultValue=""
render={({ field }) => <TextField {...field} label="Username" fullWidth />} render={({ field }) => <TextField {...field} label={t('communications.settings.fields.user')} fullWidth />}
/> />
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
@@ -121,7 +123,7 @@ export default function SettingsPage() {
name="password" name="password"
control={control} control={control}
defaultValue="" defaultValue=""
render={({ field }) => <TextField {...field} label="Password" type="password" fullWidth />} render={({ field }) => <TextField {...field} label={t('communications.settings.fields.password')} type="password" fullWidth />}
/> />
</Grid> </Grid>
@@ -133,7 +135,7 @@ export default function SettingsPage() {
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<FormControlLabel <FormControlLabel
control={<Switch checked={value} onChange={onChange} />} control={<Switch checked={value} onChange={onChange} />}
label="Enable SSL/TLS" label={t('communications.settings.fields.ssl')}
/> />
)} )}
/> />
@@ -147,17 +149,17 @@ export default function SettingsPage() {
name="resendApiKey" name="resendApiKey"
control={control} control={control}
defaultValue="" defaultValue=""
render={({ field }) => <TextField {...field} label="Resend API Key" type="password" fullWidth required />} render={({ field }) => <TextField {...field} label={t('communications.settings.fields.apiKey')} type="password" fullWidth required />}
/> />
<Typography variant="caption" color="textSecondary" sx={{ mt: 1, display: 'block' }}> <Typography variant="caption" color="textSecondary" sx={{ mt: 1, display: 'block' }}>
Ottieni la tua API Key su <a href="https://resend.com/api-keys" target="_blank" rel="noopener noreferrer">resend.com</a> {t('communications.settings.helpers.apiKey')} <a href="https://resend.com/api-keys" target="_blank" rel="noopener noreferrer">resend.com</a>
</Typography> </Typography>
</Grid> </Grid>
)} )}
<Grid item xs={12}> <Grid item xs={12}>
<Divider sx={{ my: 2 }} /> <Divider sx={{ my: 2 }} />
<Typography variant="h6">Mittente Default</Typography> <Typography variant="h6">{t('communications.settings.sections.defaultSender')}</Typography>
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
@@ -165,7 +167,7 @@ export default function SettingsPage() {
name="fromEmail" name="fromEmail"
control={control} control={control}
defaultValue="" defaultValue=""
render={({ field }) => <TextField {...field} label="From Email" fullWidth required />} render={({ field }) => <TextField {...field} label={t('communications.settings.fields.fromEmail')} fullWidth required />}
/> />
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
@@ -173,7 +175,7 @@ export default function SettingsPage() {
name="fromName" name="fromName"
control={control} control={control}
defaultValue="" defaultValue=""
render={({ field }) => <TextField {...field} label="From Name" fullWidth />} render={({ field }) => <TextField {...field} label={t('communications.settings.fields.fromName')} fullWidth />}
/> />
</Grid> </Grid>
@@ -183,7 +185,7 @@ export default function SettingsPage() {
startIcon={<Send />} startIcon={<Send />}
onClick={() => setTestMode(!testMode)} onClick={() => setTestMode(!testMode)}
> >
Test Connessione {t('communications.settings.actions.testConnection')}
</Button> </Button>
<Button <Button
type="submit" type="submit"
@@ -191,7 +193,7 @@ export default function SettingsPage() {
startIcon={<Save />} startIcon={<Save />}
disabled={loading} disabled={loading}
> >
Salva Configurazione {t('common.save')}
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
@@ -200,11 +202,11 @@ export default function SettingsPage() {
{testMode && ( {testMode && (
<Paper sx={{ p: 3, bgcolor: '#f5f5f5' }}> <Paper sx={{ p: 3, bgcolor: '#f5f5f5' }}>
<Typography variant="h6" gutterBottom>Test Email</Typography> <Typography variant="h6" gutterBottom>{t('communications.settings.testStats.title')}</Typography>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<TextField <TextField
label="Destinatario" label={t('communications.settings.testStats.recipient')}
fullWidth fullWidth
value={testData.to} value={testData.to}
onChange={(e) => setTestData({ ...testData, to: e.target.value })} onChange={(e) => setTestData({ ...testData, to: e.target.value })}
@@ -212,7 +214,7 @@ export default function SettingsPage() {
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<TextField <TextField
label="Oggetto" label={t('communications.settings.testStats.subject')}
fullWidth fullWidth
value={testData.subject} value={testData.subject}
onChange={(e) => setTestData({ ...testData, subject: e.target.value })} onChange={(e) => setTestData({ ...testData, subject: e.target.value })}
@@ -220,7 +222,7 @@ export default function SettingsPage() {
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<Button variant="contained" color="secondary" onClick={sendTest} disabled={loading}> <Button variant="contained" color="secondary" onClick={sendTest} disabled={loading}>
Invia Test {t('communications.settings.actions.sendTest')}
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -1,4 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { import {
Box, Box,
Button, Button,
@@ -102,6 +103,7 @@ const CategoryItem: React.FC<CategoryItemProps> = ({ category, onEdit, onDelete,
}; };
export default function CategoriesPage() { export default function CategoriesPage() {
const { t } = useTranslation();
const { data: categories, isLoading } = useCategoryTree(); const { data: categories, isLoading } = useCategoryTree();
const createMutation = useCreateCategory(); const createMutation = useCreateCategory();
const updateMutation = useUpdateCategory(); const updateMutation = useUpdateCategory();
@@ -196,21 +198,21 @@ export default function CategoriesPage() {
}; };
if (isLoading) { if (isLoading) {
return <Typography>Caricamento...</Typography>; return <Typography>{t('common.loading')}</Typography>;
} }
return ( return (
<Box> <Box>
<Box sx={{ mb: 3, display: "flex", justifyContent: "space-between", alignItems: "center" }}> <Box sx={{ mb: 3, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="h5" fontWeight="bold"> <Typography variant="h5" fontWeight="bold">
Categorie Articoli {t('apps.warehouse.categories.title')}
</Typography> </Typography>
<Button <Button
variant="contained" variant="contained"
startIcon={<AddIcon />} startIcon={<AddIcon />}
onClick={() => handleOpenDialog()} onClick={() => handleOpenDialog()}
> >
Nuova Categoria Root {t('apps.warehouse.categories.newParams.root')}
</Button> </Button>
</Box> </Box>
@@ -227,7 +229,7 @@ export default function CategoriesPage() {
))} ))}
{(!categories || categories.length === 0) && ( {(!categories || categories.length === 0) && (
<ListItem> <ListItem>
<ListItemText primary="Nessuna categoria trovata" /> <ListItemText primary={t('apps.warehouse.categories.empty')} />
</ListItem> </ListItem>
)} )}
</List> </List>
@@ -236,19 +238,19 @@ export default function CategoriesPage() {
{/* Create/Edit Dialog */} {/* Create/Edit Dialog */}
<Dialog open={openDialog} onClose={handleCloseDialog} maxWidth="sm" fullWidth> <Dialog open={openDialog} onClose={handleCloseDialog} maxWidth="sm" fullWidth>
<DialogTitle> <DialogTitle>
{editingCategory ? "Modifica Categoria" : "Nuova Categoria"} {editingCategory ? t('apps.warehouse.categories.edit') : t('apps.warehouse.categories.new')}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
<TextField <TextField
label="Nome" label={t('apps.warehouse.categories.fields.name')}
value={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
fullWidth fullWidth
required required
/> />
<TextField <TextField
label="Descrizione" label={t('apps.warehouse.categories.fields.description')}
value={formData.description} value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })} onChange={(e) => setFormData({ ...formData, description: e.target.value })}
fullWidth fullWidth
@@ -256,7 +258,7 @@ export default function CategoriesPage() {
rows={3} rows={3}
/> />
<TextField <TextField
label="Ordinamento" label={t('apps.warehouse.categories.fields.sortOrder')}
type="number" type="number"
value={formData.sortOrder} value={formData.sortOrder}
onChange={(e) => setFormData({ ...formData, sortOrder: parseInt(e.target.value) || 0 })} onChange={(e) => setFormData({ ...formData, sortOrder: parseInt(e.target.value) || 0 })}
@@ -270,32 +272,31 @@ export default function CategoriesPage() {
onChange={(e) => setIsActive(e.target.checked)} onChange={(e) => setIsActive(e.target.checked)}
/> />
} }
label="Attivo" label={t('apps.warehouse.categories.fields.active')}
/> />
)} )}
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={handleCloseDialog}>Annulla</Button> <Button onClick={handleCloseDialog}>{t('common.cancel')}</Button>
<Button onClick={handleSubmit} variant="contained" disabled={!formData.name}> <Button onClick={handleSubmit} variant="contained" disabled={!formData.name}>
Salva {t('common.save')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
{/* Delete Confirmation Dialog */} {/* Delete Confirmation Dialog */}
<Dialog open={deleteConfirmOpen} onClose={() => setDeleteConfirmOpen(false)}> <Dialog open={deleteConfirmOpen} onClose={() => setDeleteConfirmOpen(false)}>
<DialogTitle>Conferma Eliminazione</DialogTitle> <DialogTitle>{t('apps.warehouse.categories.deleteDialog.title')}</DialogTitle>
<DialogContent> <DialogContent>
<Typography> <Typography>
Sei sicuro di voler eliminare questa categoria? L'operazione non può essere annullata. {t('apps.warehouse.categories.deleteDialog.content')}
Se la categoria contiene sottocategorie o articoli, potrebbe non essere possibile eliminarla.
</Typography> </Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => setDeleteConfirmOpen(false)}>Annulla</Button> <Button onClick={() => setDeleteConfirmOpen(false)}>{t('common.cancel')}</Button>
<Button onClick={handleConfirmDelete} color="error" variant="contained"> <Button onClick={handleConfirmDelete} color="error" variant="contained">
Elimina {t('common.delete')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -64,14 +64,14 @@ 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', translationKey: 'menu.dashboard' }, { label: t('menu.dashboard'), path: '/', category: t('apps.core.title'), translationKey: 'menu.dashboard' },
{ label: t('menu.calendar'), path: '/calendario', category: 'Zentral', translationKey: 'menu.calendar' }, { label: t('menu.calendar'), path: '/calendario', category: t('apps.core.title'), translationKey: 'menu.calendar' },
{ label: t('menu.events'), path: '/eventi', category: 'Zentral', translationKey: 'menu.events' }, { label: t('menu.events'), path: '/eventi', category: t('apps.core.title'), translationKey: 'menu.events' },
{ label: t('menu.clients'), path: '/clienti', category: 'Zentral', translationKey: 'menu.clients' }, { label: t('menu.clients'), path: '/clienti', category: t('apps.core.title'), translationKey: 'menu.clients' },
{ label: t('menu.location'), path: '/location', category: 'Zentral', translationKey: 'menu.location' }, { label: t('menu.location'), path: '/location', category: t('apps.core.title'), translationKey: 'menu.location' },
{ label: t('menu.articles'), path: '/articoli', category: 'Zentral', translationKey: 'menu.articles' }, { label: t('menu.articles'), path: '/articoli', category: t('apps.core.title'), translationKey: 'menu.articles' },
{ label: t('menu.resources'), path: '/risorse', category: 'Zentral', translationKey: 'menu.resources' }, { label: t('menu.resources'), path: '/risorse', category: t('apps.core.title'), translationKey: 'menu.resources' },
{ label: t('menu.reports'), path: '/report-templates', category: 'Zentral', translationKey: 'menu.reports' }, { label: t('menu.reports'), path: '/report-templates', category: t('apps.core.title'), translationKey: 'menu.reports' },
]; ];
if (activeAppCodes.includes('warehouse')) { if (activeAppCodes.includes('warehouse')) {

View File

@@ -103,7 +103,7 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
const menuStructure: MenuItem[] = [ const menuStructure: MenuItem[] = [
{ {
id: 'dashboard', id: 'dashboard',
label: 'Zentral Dashboard', label: t('menu.dashboard'),
icon: <DashboardIcon />, icon: <DashboardIcon />,
path: '/', path: '/',
translationKey: 'menu.dashboard', translationKey: 'menu.dashboard',