feat: Implement and update translations for warehouse categories, core application titles, and other UI elements.
This commit is contained in:
@@ -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 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.
|
||||
- Riorganizzazione UI Auto Codes, allineamento stile a Custom Fields, miglioramento traduzioni e categorizzazione.
|
||||
- [2025-12-12 - Modulo Comunicazioni](./devlog/2025-12-12-110000_communications_module.md) - **In Corso**
|
||||
- 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.
|
||||
- [2025-12-12 Update Translations](./devlog/2025-12-12-141010_update_translations.md) - **In Corso**
|
||||
- Aggiornamento traduzioni per categorie magazzino, comunicazioni e formazione.
|
||||
@@ -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.
|
||||
@@ -65,7 +65,8 @@
|
||||
"emailConfig": "Email Configuration",
|
||||
"movements": "Movements",
|
||||
"stock": "Stock",
|
||||
"inventory": "Inventory"
|
||||
"inventory": "Inventory",
|
||||
"categories": "Categories"
|
||||
},
|
||||
"navigation": {
|
||||
"searchPlaceholder": "Search...",
|
||||
@@ -285,12 +286,33 @@
|
||||
"confermato": "Confirmed"
|
||||
},
|
||||
"apps": {
|
||||
"core": {
|
||||
"title": "Zentral"
|
||||
},
|
||||
"warehouse": {
|
||||
"title": "Warehouse Management",
|
||||
"inventory": "Inventory",
|
||||
"movements": "Movements",
|
||||
"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": {
|
||||
"title": "Human Resources",
|
||||
@@ -1552,5 +1574,56 @@
|
||||
"permesso": "Permit",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,8 @@
|
||||
"emailConfig": "Configurazione Email",
|
||||
"movements": "Movimenti",
|
||||
"stock": "Giacenze",
|
||||
"inventory": "Inventario"
|
||||
"inventory": "Inventario",
|
||||
"categories": "Categorie"
|
||||
},
|
||||
"navigation": {
|
||||
"searchPlaceholder": "Cerca...",
|
||||
@@ -281,12 +282,33 @@
|
||||
"confermato": "Confermato"
|
||||
},
|
||||
"apps": {
|
||||
"core": {
|
||||
"title": "Zentral"
|
||||
},
|
||||
"warehouse": {
|
||||
"title": "Gestione Magazzino",
|
||||
"inventory": "Inventario",
|
||||
"movements": "Movimenti",
|
||||
"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": {
|
||||
"title": "Gestione Personale",
|
||||
@@ -1633,5 +1655,56 @@
|
||||
"permesso": "Permesso",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DataGrid, GridColDef } from '@mui/x-data-grid';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { History } from '@mui/icons-material';
|
||||
@@ -7,6 +8,7 @@ import { EmailLog } from '../types';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default function LogsPage() {
|
||||
const { t } = useTranslation();
|
||||
const [logs, setLogs] = useState<EmailLog[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -27,13 +29,13 @@ export default function LogsPage() {
|
||||
};
|
||||
|
||||
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')
|
||||
},
|
||||
{
|
||||
field: 'status', headerName: 'Stato', width: 120,
|
||||
field: 'status', headerName: t('communications.logs.columns.status'), width: 120,
|
||||
renderCell: (params) => (
|
||||
<span style={{
|
||||
color: params.value === 'Success' ? 'green' : 'red',
|
||||
@@ -43,16 +45,16 @@ export default function LogsPage() {
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{ field: 'sender', headerName: 'Mittente', width: 200 },
|
||||
{ field: 'recipient', headerName: 'Destinatario', width: 200 },
|
||||
{ field: 'subject', headerName: 'Oggetto', flex: 1 },
|
||||
{ field: 'errorMessage', headerName: 'Errore', width: 200 },
|
||||
{ field: 'sender', headerName: t('communications.logs.columns.sender'), width: 200 },
|
||||
{ field: 'recipient', headerName: t('communications.logs.columns.recipient'), width: 200 },
|
||||
{ field: 'subject', headerName: t('communications.logs.columns.subject'), flex: 1 },
|
||||
{ field: 'errorMessage', headerName: t('communications.logs.columns.error'), width: 200 },
|
||||
];
|
||||
|
||||
return (
|
||||
<Box p={3} sx={{ height: '80vh', display: 'flex', flexDirection: 'column' }}>
|
||||
<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>
|
||||
<DataGrid
|
||||
rows={logs}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import {
|
||||
Box, Paper, Typography, TextField, Button, Grid,
|
||||
@@ -10,6 +11,7 @@ import { communicationsService } from '../services/communicationsService';
|
||||
import { SmtpConfig, TestEmail } from '../types';
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { t } = useTranslation();
|
||||
const { control, handleSubmit, reset, watch } = useForm<SmtpConfig>();
|
||||
const provider = watch('provider') || 'smtp';
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -28,7 +30,7 @@ export default function SettingsPage() {
|
||||
reset(config);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setNotification({ type: 'error', message: 'Failed to load configuration' });
|
||||
setNotification({ type: 'error', message: t('communications.settings.messages.loadError') });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -38,9 +40,9 @@ export default function SettingsPage() {
|
||||
try {
|
||||
setLoading(true);
|
||||
await communicationsService.saveConfig(data);
|
||||
setNotification({ type: 'success', message: 'Configuration saved successfully' });
|
||||
setNotification({ type: 'success', message: t('communications.settings.messages.saveSuccess') });
|
||||
} catch (error) {
|
||||
setNotification({ type: 'error', message: 'Failed to save configuration' });
|
||||
setNotification({ type: 'error', message: t('communications.settings.messages.saveError') });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -48,16 +50,16 @@ export default function SettingsPage() {
|
||||
|
||||
const sendTest = async () => {
|
||||
if (!testData.to) {
|
||||
setNotification({ type: 'error', message: 'Recipient email is required for test' });
|
||||
setNotification({ type: 'error', message: t('communications.settings.messages.recipientRequired') });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setLoading(true);
|
||||
await communicationsService.sendTestEmail(testData);
|
||||
setNotification({ type: 'success', message: 'Test email queued successfully' });
|
||||
setNotification({ type: 'success', message: t('communications.settings.messages.testSuccess') });
|
||||
setTestMode(false);
|
||||
} 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 {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -66,7 +68,7 @@ export default function SettingsPage() {
|
||||
return (
|
||||
<Box p={3}>
|
||||
<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>
|
||||
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
@@ -74,13 +76,13 @@ export default function SettingsPage() {
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={4}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Provider</InputLabel>
|
||||
<InputLabel>{t('communications.settings.fields.provider')}</InputLabel>
|
||||
<Controller
|
||||
name="provider"
|
||||
control={control}
|
||||
defaultValue="smtp"
|
||||
render={({ field }) => (
|
||||
<Select {...field} label="Provider">
|
||||
<Select {...field} label={t('communications.settings.fields.provider')}>
|
||||
<MenuItem value="smtp">SMTP</MenuItem>
|
||||
<MenuItem value="resend">Resend</MenuItem>
|
||||
</Select>
|
||||
@@ -96,7 +98,7 @@ export default function SettingsPage() {
|
||||
name="host"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => <TextField {...field} label="SMTP Host" fullWidth required />}
|
||||
render={({ field }) => <TextField {...field} label={t('communications.settings.fields.host')} fullWidth required />}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
@@ -104,7 +106,7 @@ export default function SettingsPage() {
|
||||
name="port"
|
||||
control={control}
|
||||
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>
|
||||
|
||||
@@ -113,7 +115,7 @@ export default function SettingsPage() {
|
||||
name="user"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => <TextField {...field} label="Username" fullWidth />}
|
||||
render={({ field }) => <TextField {...field} label={t('communications.settings.fields.user')} fullWidth />}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
@@ -121,7 +123,7 @@ export default function SettingsPage() {
|
||||
name="password"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => <TextField {...field} label="Password" type="password" fullWidth />}
|
||||
render={({ field }) => <TextField {...field} label={t('communications.settings.fields.password')} type="password" fullWidth />}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -133,7 +135,7 @@ export default function SettingsPage() {
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<FormControlLabel
|
||||
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"
|
||||
control={control}
|
||||
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' }}>
|
||||
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>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography variant="h6">Mittente Default</Typography>
|
||||
<Typography variant="h6">{t('communications.settings.sections.defaultSender')}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
@@ -165,7 +167,7 @@ export default function SettingsPage() {
|
||||
name="fromEmail"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => <TextField {...field} label="From Email" fullWidth required />}
|
||||
render={({ field }) => <TextField {...field} label={t('communications.settings.fields.fromEmail')} fullWidth required />}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
@@ -173,7 +175,7 @@ export default function SettingsPage() {
|
||||
name="fromName"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => <TextField {...field} label="From Name" fullWidth />}
|
||||
render={({ field }) => <TextField {...field} label={t('communications.settings.fields.fromName')} fullWidth />}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -183,7 +185,7 @@ export default function SettingsPage() {
|
||||
startIcon={<Send />}
|
||||
onClick={() => setTestMode(!testMode)}
|
||||
>
|
||||
Test Connessione
|
||||
{t('communications.settings.actions.testConnection')}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -191,7 +193,7 @@ export default function SettingsPage() {
|
||||
startIcon={<Save />}
|
||||
disabled={loading}
|
||||
>
|
||||
Salva Configurazione
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -200,11 +202,11 @@ export default function SettingsPage() {
|
||||
|
||||
{testMode && (
|
||||
<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 item xs={12} md={6}>
|
||||
<TextField
|
||||
label="Destinatario"
|
||||
label={t('communications.settings.testStats.recipient')}
|
||||
fullWidth
|
||||
value={testData.to}
|
||||
onChange={(e) => setTestData({ ...testData, to: e.target.value })}
|
||||
@@ -212,7 +214,7 @@ export default function SettingsPage() {
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
label="Oggetto"
|
||||
label={t('communications.settings.testStats.subject')}
|
||||
fullWidth
|
||||
value={testData.subject}
|
||||
onChange={(e) => setTestData({ ...testData, subject: e.target.value })}
|
||||
@@ -220,7 +222,7 @@ export default function SettingsPage() {
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button variant="contained" color="secondary" onClick={sendTest} disabled={loading}>
|
||||
Invia Test
|
||||
{t('communications.settings.actions.sendTest')}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -102,6 +103,7 @@ const CategoryItem: React.FC<CategoryItemProps> = ({ category, onEdit, onDelete,
|
||||
};
|
||||
|
||||
export default function CategoriesPage() {
|
||||
const { t } = useTranslation();
|
||||
const { data: categories, isLoading } = useCategoryTree();
|
||||
const createMutation = useCreateCategory();
|
||||
const updateMutation = useUpdateCategory();
|
||||
@@ -196,21 +198,21 @@ export default function CategoriesPage() {
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <Typography>Caricamento...</Typography>;
|
||||
return <Typography>{t('common.loading')}</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ mb: 3, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Typography variant="h5" fontWeight="bold">
|
||||
Categorie Articoli
|
||||
{t('apps.warehouse.categories.title')}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => handleOpenDialog()}
|
||||
>
|
||||
Nuova Categoria Root
|
||||
{t('apps.warehouse.categories.newParams.root')}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -227,7 +229,7 @@ export default function CategoriesPage() {
|
||||
))}
|
||||
{(!categories || categories.length === 0) && (
|
||||
<ListItem>
|
||||
<ListItemText primary="Nessuna categoria trovata" />
|
||||
<ListItemText primary={t('apps.warehouse.categories.empty')} />
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
@@ -236,19 +238,19 @@ export default function CategoriesPage() {
|
||||
{/* Create/Edit Dialog */}
|
||||
<Dialog open={openDialog} onClose={handleCloseDialog} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{editingCategory ? "Modifica Categoria" : "Nuova Categoria"}
|
||||
{editingCategory ? t('apps.warehouse.categories.edit') : t('apps.warehouse.categories.new')}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
|
||||
<TextField
|
||||
label="Nome"
|
||||
label={t('apps.warehouse.categories.fields.name')}
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Descrizione"
|
||||
label={t('apps.warehouse.categories.fields.description')}
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
fullWidth
|
||||
@@ -256,7 +258,7 @@ export default function CategoriesPage() {
|
||||
rows={3}
|
||||
/>
|
||||
<TextField
|
||||
label="Ordinamento"
|
||||
label={t('apps.warehouse.categories.fields.sortOrder')}
|
||||
type="number"
|
||||
value={formData.sortOrder}
|
||||
onChange={(e) => setFormData({ ...formData, sortOrder: parseInt(e.target.value) || 0 })}
|
||||
@@ -270,32 +272,31 @@ export default function CategoriesPage() {
|
||||
onChange={(e) => setIsActive(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Attivo"
|
||||
label={t('apps.warehouse.categories.fields.active')}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCloseDialog}>Annulla</Button>
|
||||
<Button onClick={handleCloseDialog}>{t('common.cancel')}</Button>
|
||||
<Button onClick={handleSubmit} variant="contained" disabled={!formData.name}>
|
||||
Salva
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog open={deleteConfirmOpen} onClose={() => setDeleteConfirmOpen(false)}>
|
||||
<DialogTitle>Conferma Eliminazione</DialogTitle>
|
||||
<DialogTitle>{t('apps.warehouse.categories.deleteDialog.title')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
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.
|
||||
{t('apps.warehouse.categories.deleteDialog.content')}
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteConfirmOpen(false)}>Annulla</Button>
|
||||
<Button onClick={() => setDeleteConfirmOpen(false)}>{t('common.cancel')}</Button>
|
||||
<Button onClick={handleConfirmDelete} color="error" variant="contained">
|
||||
Elimina
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
@@ -64,14 +64,14 @@ export default function SearchBar() {
|
||||
const options = useMemo(() => {
|
||||
const opts: SearchOption[] = [
|
||||
// Core
|
||||
{ label: t('menu.dashboard'), path: '/', category: 'Zentral', translationKey: 'menu.dashboard' },
|
||||
{ label: t('menu.calendar'), path: '/calendario', category: 'Zentral', translationKey: 'menu.calendar' },
|
||||
{ label: t('menu.events'), path: '/eventi', category: 'Zentral', translationKey: 'menu.events' },
|
||||
{ label: t('menu.clients'), path: '/clienti', category: 'Zentral', translationKey: 'menu.clients' },
|
||||
{ label: t('menu.location'), path: '/location', category: 'Zentral', translationKey: 'menu.location' },
|
||||
{ label: t('menu.articles'), path: '/articoli', category: 'Zentral', translationKey: 'menu.articles' },
|
||||
{ label: t('menu.resources'), path: '/risorse', category: 'Zentral', translationKey: 'menu.resources' },
|
||||
{ label: t('menu.reports'), path: '/report-templates', category: 'Zentral', translationKey: 'menu.reports' },
|
||||
{ label: t('menu.dashboard'), path: '/', category: t('apps.core.title'), translationKey: 'menu.dashboard' },
|
||||
{ label: t('menu.calendar'), path: '/calendario', category: t('apps.core.title'), translationKey: 'menu.calendar' },
|
||||
{ label: t('menu.events'), path: '/eventi', category: t('apps.core.title'), translationKey: 'menu.events' },
|
||||
{ label: t('menu.clients'), path: '/clienti', category: t('apps.core.title'), translationKey: 'menu.clients' },
|
||||
{ label: t('menu.location'), path: '/location', category: t('apps.core.title'), translationKey: 'menu.location' },
|
||||
{ label: t('menu.articles'), path: '/articoli', category: t('apps.core.title'), translationKey: 'menu.articles' },
|
||||
{ label: t('menu.resources'), path: '/risorse', category: t('apps.core.title'), translationKey: 'menu.resources' },
|
||||
{ label: t('menu.reports'), path: '/report-templates', category: t('apps.core.title'), translationKey: 'menu.reports' },
|
||||
];
|
||||
|
||||
if (activeAppCodes.includes('warehouse')) {
|
||||
|
||||
@@ -103,7 +103,7 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse
|
||||
const menuStructure: MenuItem[] = [
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: 'Zentral Dashboard',
|
||||
label: t('menu.dashboard'),
|
||||
icon: <DashboardIcon />,
|
||||
path: '/',
|
||||
translationKey: 'menu.dashboard',
|
||||
|
||||
Reference in New Issue
Block a user