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 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.
|
|
||||||
@@ -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",
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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')) {
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user