Support multiple languages
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Input, Card } from '../../ui';
|
||||
import { useAuthActions, useAuthLoading, useAuthError } from '../../../stores/auth.store';
|
||||
import { useToast } from '../../../hooks/ui/useToast';
|
||||
@@ -24,6 +25,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
className,
|
||||
autoFocus = true
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [credentials, setCredentials] = useState<LoginCredentials>({
|
||||
email: '',
|
||||
password: '',
|
||||
@@ -32,7 +34,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
const [errors, setErrors] = useState<Partial<LoginCredentials>>({});
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const emailInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
|
||||
const { login } = useAuthActions();
|
||||
const isLoading = useAuthLoading();
|
||||
const error = useAuthError();
|
||||
@@ -49,13 +51,13 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
const newErrors: Partial<LoginCredentials> = {};
|
||||
|
||||
if (!credentials.email.trim()) {
|
||||
newErrors.email = 'El email es requerido';
|
||||
newErrors.email = t('auth:validation.email_required', 'El email es requerido');
|
||||
} else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(credentials.email)) {
|
||||
newErrors.email = 'Por favor, ingrese un email válido';
|
||||
newErrors.email = t('auth:validation.email_invalid', 'Por favor, ingrese un email válido');
|
||||
}
|
||||
|
||||
if (!credentials.password) {
|
||||
newErrors.password = 'La contraseña es requerida';
|
||||
newErrors.password = t('auth:validation.password_required', 'La contraseña es requerida');
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
@@ -114,10 +116,10 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
<Card className={`p-8 w-full max-w-md ${className || ''}`} role="main">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold text-text-primary mb-2">
|
||||
Iniciar Sesión
|
||||
{t('auth:login.title', 'Iniciar Sesión')}
|
||||
</h1>
|
||||
<p className="text-text-secondary">
|
||||
Accede al panel de control de tu panadería
|
||||
{t('auth:login.subtitle', 'Accede al panel de control de tu panadería')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -125,14 +127,14 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
onSubmit={handleSubmit}
|
||||
className="space-y-6"
|
||||
noValidate
|
||||
aria-label="Formulario de inicio de sesión"
|
||||
aria-label={t('auth:login.title', 'Formulario de inicio de sesión')}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<div>
|
||||
<Input
|
||||
ref={emailInputRef}
|
||||
type="email"
|
||||
label="Correo Electrónico"
|
||||
label={t('auth:login.email', 'Correo Electrónico')}
|
||||
placeholder="tu.email@panaderia.com"
|
||||
value={credentials.email}
|
||||
onChange={handleInputChange('email')}
|
||||
@@ -163,8 +165,8 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
<div>
|
||||
<Input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
label="Contraseña"
|
||||
placeholder="Tu contraseña segura"
|
||||
label={t('auth:login.password', 'Contraseña')}
|
||||
placeholder={t('auth:login.password', 'Tu contraseña segura')}
|
||||
value={credentials.password}
|
||||
onChange={handleInputChange('password')}
|
||||
error={errors.password}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Button, Input, Card, Select, Avatar, Modal } from '../../ui';
|
||||
import { useAuthUser } from '../../../stores/auth.store';
|
||||
import { useToast } from '../../../hooks/ui/useToast';
|
||||
import { useUpdateProfile, useChangePassword, useAuthProfile } from '../../../api/hooks/auth';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface ProfileSettingsProps {
|
||||
onSuccess?: () => void;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Input, Card } from '../../ui';
|
||||
import { PasswordCriteria, validatePassword, getPasswordErrors } from '../../ui/PasswordCriteria';
|
||||
import { useAuthActions, useAuthLoading, useAuthError } from '../../../stores/auth.store';
|
||||
@@ -23,6 +24,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
onLoginClick,
|
||||
className
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState<SimpleUserRegistration>({
|
||||
full_name: '',
|
||||
email: '',
|
||||
@@ -53,19 +55,19 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
const newErrors: Partial<SimpleUserRegistration> = {};
|
||||
|
||||
if (!formData.full_name.trim()) {
|
||||
newErrors.full_name = 'El nombre completo es requerido';
|
||||
newErrors.full_name = t('auth:validation.first_name_required', 'El nombre completo es requerido');
|
||||
} else if (formData.full_name.trim().length < 2) {
|
||||
newErrors.full_name = 'El nombre debe tener al menos 2 caracteres';
|
||||
newErrors.full_name = t('auth:validation.field_required', 'El nombre debe tener al menos 2 caracteres');
|
||||
}
|
||||
|
||||
if (!formData.email.trim()) {
|
||||
newErrors.email = 'El email es requerido';
|
||||
newErrors.email = t('auth:validation.email_required', 'El email es requerido');
|
||||
} else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(formData.email)) {
|
||||
newErrors.email = 'Por favor, ingrese un email válido';
|
||||
newErrors.email = t('auth:validation.email_invalid', 'Por favor, ingrese un email válido');
|
||||
}
|
||||
|
||||
if (!formData.password) {
|
||||
newErrors.password = 'La contraseña es requerida';
|
||||
newErrors.password = t('auth:validation.password_required', 'La contraseña es requerida');
|
||||
} else {
|
||||
const passwordErrors = getPasswordErrors(formData.password);
|
||||
if (passwordErrors.length > 0) {
|
||||
@@ -74,13 +76,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
}
|
||||
|
||||
if (!formData.confirmPassword) {
|
||||
newErrors.confirmPassword = 'Confirma tu contraseña';
|
||||
newErrors.confirmPassword = t('auth:register.confirm_password', 'Confirma tu contraseña');
|
||||
} else if (formData.password !== formData.confirmPassword) {
|
||||
newErrors.confirmPassword = 'Las contraseñas no coinciden';
|
||||
newErrors.confirmPassword = t('auth:validation.passwords_must_match', 'Las contraseñas no coinciden');
|
||||
}
|
||||
|
||||
if (!formData.acceptTerms) {
|
||||
newErrors.acceptTerms = 'Debes aceptar los términos y condiciones';
|
||||
newErrors.acceptTerms = t('auth:validation.terms_required', 'Debes aceptar los términos y condiciones');
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
@@ -104,13 +106,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
|
||||
await register(registrationData);
|
||||
|
||||
showSuccessToast('¡Bienvenido! Tu cuenta ha sido creada correctamente.', {
|
||||
title: 'Cuenta creada exitosamente'
|
||||
showSuccessToast(t('auth:register.registering', '¡Bienvenido! Tu cuenta ha sido creada correctamente.'), {
|
||||
title: t('auth:alerts.success_create', 'Cuenta creada exitosamente')
|
||||
});
|
||||
onSuccess?.();
|
||||
} catch (err) {
|
||||
showErrorToast(error || 'No se pudo crear la cuenta. Verifica que el email no esté en uso.', {
|
||||
title: 'Error al crear la cuenta'
|
||||
showErrorToast(error || t('auth:register.register_button', 'No se pudo crear la cuenta. Verifica que el email no esté en uso.'), {
|
||||
title: t('auth:alerts.error_create', 'Error al crear la cuenta')
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -127,16 +129,16 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
<Card className={`p-8 w-full max-w-md ${className || ''}`} role="main">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-text-primary mb-2">
|
||||
Crear Cuenta
|
||||
{t('auth:register.title', 'Crear Cuenta')}
|
||||
</h1>
|
||||
<p className="text-text-secondary text-lg">
|
||||
Únete y comienza hoy mismo
|
||||
{t('auth:register.subtitle', 'Únete y comienza hoy mismo')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<Input
|
||||
label="Nombre Completo"
|
||||
label={t('auth:register.first_name', 'Nombre Completo')}
|
||||
placeholder="Juan Pérez García"
|
||||
value={formData.full_name}
|
||||
onChange={handleInputChange('full_name')}
|
||||
@@ -153,7 +155,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
||||
|
||||
<Input
|
||||
type="email"
|
||||
label="Correo Electrónico"
|
||||
label={t('auth:register.email', 'Correo Electrónico')}
|
||||
placeholder="tu.email@ejemplo.com"
|
||||
value={formData.email}
|
||||
onChange={handleInputChange('email')}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardHeader, CardBody } from '../../ui/Card';
|
||||
import { Badge } from '../../ui/Badge';
|
||||
import { Button } from '../../ui/Button';
|
||||
@@ -41,6 +42,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
className,
|
||||
maxAlerts = 10
|
||||
}) => {
|
||||
const { t } = useTranslation(['dashboard']);
|
||||
const [expandedAlert, setExpandedAlert] = useState<string | null>(null);
|
||||
|
||||
const { notifications, isConnected, markAsRead, removeNotification } = useNotifications();
|
||||
@@ -108,11 +110,13 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / (1000 * 60));
|
||||
|
||||
if (diffMins < 1) return 'Ahora';
|
||||
if (diffMins < 60) return `${diffMins}m`;
|
||||
if (diffMins < 1) return t('dashboard:alerts.time.now', 'Ahora');
|
||||
if (diffMins < 60) return t('dashboard:alerts.time.minutes_ago', 'hace {{count}} min', { count: diffMins });
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
if (diffHours < 24) return `${diffHours}h`;
|
||||
return date.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' });
|
||||
if (diffHours < 24) return t('dashboard:alerts.time.hours_ago', 'hace {{count}} h', { count: diffHours });
|
||||
return date.toLocaleDateString() === new Date(now.getTime() - 24 * 60 * 60 * 1000).toLocaleDateString()
|
||||
? t('dashboard:alerts.time.yesterday', 'Ayer')
|
||||
: date.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' });
|
||||
};
|
||||
|
||||
const toggleExpanded = (alertId: string) => {
|
||||
@@ -147,7 +151,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-base font-semibold" style={{ color: 'var(--text-primary)' }}>
|
||||
Alertas
|
||||
{t('dashboard:alerts.title', 'Alertas')}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
{isConnected ? (
|
||||
@@ -156,7 +160,10 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
<WifiOff className="w-3 h-3" style={{ color: 'var(--color-error)' }} />
|
||||
)}
|
||||
<span className="text-xs" style={{ color: 'var(--text-secondary)' }}>
|
||||
{isConnected ? 'En vivo' : 'Desconectado'}
|
||||
{isConnected
|
||||
? t('dashboard:alerts.live', 'En vivo')
|
||||
: t('dashboard:alerts.offline', 'Desconectado')
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -182,7 +189,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
<div className="p-6 text-center">
|
||||
<CheckCircle className="w-6 h-6 mx-auto mb-2" style={{ color: 'var(--color-success)' }} />
|
||||
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||
No hay alertas activas
|
||||
{t('dashboard:alerts.no_alerts', 'No hay alertas activas')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -241,7 +248,10 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
{alert.severity.toUpperCase()}
|
||||
</Badge>
|
||||
<Badge variant="secondary" size="sm">
|
||||
{alert.item_type === 'alert' ? '🚨 Alerta' : '💡 Recomendación'}
|
||||
{alert.item_type === 'alert'
|
||||
? `🚨 ${t('dashboard:alerts.types.alert', 'Alerta')}`
|
||||
: `💡 ${t('dashboard:alerts.types.recommendation', 'Recomendación')}`
|
||||
}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
@@ -277,7 +287,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
{alert.actions && alert.actions.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<p className="text-xs font-semibold mb-2 uppercase tracking-wide" style={{ color: 'var(--text-primary)' }}>
|
||||
Acciones Recomendadas
|
||||
{t('dashboard:alerts.recommended_actions', 'Acciones Recomendadas')}
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
{alert.actions.map((action, index) => (
|
||||
@@ -298,7 +308,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
{alert.metadata && Object.keys(alert.metadata).length > 0 && (
|
||||
<div className="mb-4 p-2 rounded-md" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
||||
<p className="text-xs font-semibold mb-1 uppercase tracking-wide" style={{ color: 'var(--text-primary)' }}>
|
||||
Detalles Adicionales
|
||||
{t('dashboard:alerts.additional_details', 'Detalles Adicionales')}
|
||||
</p>
|
||||
<div className="text-xs space-y-1" style={{ color: 'var(--text-secondary)' }}>
|
||||
{Object.entries(alert.metadata).map(([key, value]) => (
|
||||
@@ -323,7 +333,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
className="h-8 px-3 text-xs font-medium"
|
||||
>
|
||||
<Check className="w-3 h-3 mr-1" />
|
||||
Marcar como leído
|
||||
{t('dashboard:alerts.mark_as_read', 'Marcar como leído')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -335,7 +345,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
className="h-8 px-3 text-xs font-medium text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<Trash2 className="w-3 h-3 mr-1" />
|
||||
Eliminar
|
||||
{t('dashboard:alerts.remove', 'Eliminar')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -355,7 +365,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
|
||||
}}
|
||||
>
|
||||
<p className="text-xs" style={{ color: 'var(--text-secondary)' }}>
|
||||
{activeAlerts.length} alertas activas
|
||||
{t('dashboard:alerts.active_count', '{{count}} alertas activas', { count: activeAlerts.length })}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Plus, Package, Calculator, Settings } from 'lucide-react';
|
||||
import { StatusModal } from '../../ui/StatusModal/StatusModal';
|
||||
import { IngredientCreate, UnitOfMeasure, IngredientCategory, ProductCategory } from '../../../api/types/inventory';
|
||||
@@ -20,6 +21,7 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
|
||||
onClose,
|
||||
onCreateIngredient
|
||||
}) => {
|
||||
const { t } = useTranslation(['inventory']);
|
||||
const [formData, setFormData] = useState<IngredientCreate>({
|
||||
name: '',
|
||||
description: '',
|
||||
@@ -70,32 +72,32 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
|
||||
const handleSave = async () => {
|
||||
// Validation
|
||||
if (!formData.name?.trim()) {
|
||||
alert('El nombre es requerido');
|
||||
alert(t('inventory:validation.name_required', 'El nombre es requerido'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.category) {
|
||||
alert('La categoría es requerida');
|
||||
alert(t('inventory:validation.category_required', 'La categoría es requerida'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.unit_of_measure) {
|
||||
alert('La unidad de medida es requerida');
|
||||
alert(t('inventory:validation.unit_required', 'La unidad de medida es requerida'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.low_stock_threshold || formData.low_stock_threshold < 0) {
|
||||
alert('El umbral de stock bajo debe ser un número positivo');
|
||||
alert(t('inventory:validation.min_greater_than_zero', 'El umbral de stock bajo debe ser un número positivo'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.reorder_point || formData.reorder_point < 0) {
|
||||
alert('El punto de reorden debe ser un número positivo');
|
||||
alert(t('inventory:validation.min_greater_than_zero', 'El punto de reorden debe ser un número positivo'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.reorder_point <= formData.low_stock_threshold) {
|
||||
alert('El punto de reorden debe ser mayor que el umbral de stock bajo');
|
||||
alert(t('inventory:validation.max_greater_than_min', 'El punto de reorden debe ser mayor que el umbral de stock bajo'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,7 +155,7 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
|
||||
|
||||
const statusConfig = {
|
||||
color: statusColors.inProgress.primary,
|
||||
text: 'Nuevo Artículo',
|
||||
text: t('inventory:actions.add_item', 'Nuevo Artículo'),
|
||||
icon: Plus,
|
||||
isCritical: false,
|
||||
isHighlight: true
|
||||
@@ -161,11 +163,11 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: 'Información Básica',
|
||||
title: t('inventory:forms.item_details', 'Información Básica'),
|
||||
icon: Package,
|
||||
fields: [
|
||||
{
|
||||
label: 'Nombre',
|
||||
label: t('inventory:fields.name', 'Nombre'),
|
||||
value: formData.name,
|
||||
type: 'text' as const,
|
||||
editable: true,
|
||||
@@ -173,7 +175,7 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
|
||||
placeholder: 'Ej: Harina de trigo 000'
|
||||
},
|
||||
{
|
||||
label: 'Descripción',
|
||||
label: t('inventory:fields.description', 'Descripción'),
|
||||
value: formData.description || '',
|
||||
type: 'text' as const,
|
||||
editable: true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../ui/Button';
|
||||
import { Card, CardHeader, CardBody } from '../../ui/Card';
|
||||
import { useAuth } from '../../../contexts/AuthContext';
|
||||
@@ -29,40 +30,41 @@ interface StepProps {
|
||||
isLastStep: boolean;
|
||||
}
|
||||
|
||||
// Steps must match backend ONBOARDING_STEPS exactly
|
||||
// Note: "user_registered" is auto-completed and not shown in UI
|
||||
const STEPS: StepConfig[] = [
|
||||
{
|
||||
id: 'setup',
|
||||
title: 'Registrar Panadería',
|
||||
description: 'Configura la información básica de tu panadería',
|
||||
component: RegisterTenantStep,
|
||||
},
|
||||
{
|
||||
id: 'smart-inventory-setup',
|
||||
title: 'Configurar Inventario',
|
||||
description: 'Sube datos de ventas y configura tu inventario inicial',
|
||||
component: UploadSalesDataStep,
|
||||
},
|
||||
{
|
||||
id: 'ml-training',
|
||||
title: 'Entrenamiento IA',
|
||||
description: 'Entrena tu modelo de inteligencia artificial personalizado',
|
||||
component: MLTrainingStep,
|
||||
},
|
||||
{
|
||||
id: 'completion',
|
||||
title: 'Configuración Completa',
|
||||
description: '¡Bienvenido a tu sistema de gestión inteligente!',
|
||||
component: CompletionStep,
|
||||
},
|
||||
];
|
||||
|
||||
export const OnboardingWizard: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
|
||||
// Steps must match backend ONBOARDING_STEPS exactly
|
||||
// Note: "user_registered" is auto-completed and not shown in UI
|
||||
const STEPS: StepConfig[] = [
|
||||
{
|
||||
id: 'setup',
|
||||
title: t('onboarding:wizard.steps.setup.title', 'Registrar Panadería'),
|
||||
description: t('onboarding:wizard.steps.setup.description', 'Configura la información básica de tu panadería'),
|
||||
component: RegisterTenantStep,
|
||||
},
|
||||
{
|
||||
id: 'smart-inventory-setup',
|
||||
title: t('onboarding:wizard.steps.smart_inventory_setup.title', 'Configurar Inventario'),
|
||||
description: t('onboarding:wizard.steps.smart_inventory_setup.description', 'Sube datos de ventas y configura tu inventario inicial'),
|
||||
component: UploadSalesDataStep,
|
||||
},
|
||||
{
|
||||
id: 'ml-training',
|
||||
title: t('onboarding:wizard.steps.ml_training.title', 'Entrenamiento IA'),
|
||||
description: t('onboarding:wizard.steps.ml_training.description', 'Entrena tu modelo de inteligencia artificial personalizado'),
|
||||
component: MLTrainingStep,
|
||||
},
|
||||
{
|
||||
id: 'completion',
|
||||
title: t('onboarding:wizard.steps.completion.title', 'Configuración Completa'),
|
||||
description: t('onboarding:wizard.steps.completion.description', '¡Bienvenido a tu sistema de gestión inteligente!'),
|
||||
component: CompletionStep,
|
||||
},
|
||||
];
|
||||
|
||||
// Check if this is a fresh onboarding (new tenant creation)
|
||||
const isNewTenant = searchParams.get('new') === 'true';
|
||||
|
||||
@@ -277,7 +279,7 @@ export const OnboardingWizard: React.FC = () => {
|
||||
}
|
||||
|
||||
// Don't advance automatically on error - user should see the issue
|
||||
alert(`Error al completar paso "${currentStep.title}": ${errorMessage}`);
|
||||
alert(`${t('onboarding:errors.step_failed', 'Error al completar paso')} "${currentStep.title}": ${errorMessage}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -289,7 +291,7 @@ export const OnboardingWizard: React.FC = () => {
|
||||
<CardBody>
|
||||
<div className="flex items-center justify-center space-x-3">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--color-primary)]"></div>
|
||||
<p className="text-[var(--text-secondary)] text-sm sm:text-base">Cargando tu progreso...</p>
|
||||
<p className="text-[var(--text-secondary)] text-sm sm:text-base">{t('common:loading', 'Cargando tu progreso...')}</p>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -311,17 +313,17 @@ export const OnboardingWizard: React.FC = () => {
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-base sm:text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
Error al cargar progreso
|
||||
{t('onboarding:errors.network_error', 'Error al cargar progreso')}
|
||||
</h2>
|
||||
<p className="text-sm sm:text-base text-[var(--text-secondary)] mb-4 px-2">
|
||||
No pudimos cargar tu progreso de configuración. Puedes continuar desde el inicio.
|
||||
{t('onboarding:errors.try_again', 'No pudimos cargar tu progreso de configuración. Puedes continuar desde el inicio.')}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => setIsInitialized(true)}
|
||||
variant="primary"
|
||||
size="lg"
|
||||
>
|
||||
Continuar
|
||||
{t('onboarding:wizard.navigation.next', 'Continuar')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -350,10 +352,10 @@ export const OnboardingWizard: React.FC = () => {
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--text-primary)]">
|
||||
Creando Nueva Organización
|
||||
{t('onboarding:wizard.title', 'Creando Nueva Organización')}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
Configurarás una nueva panadería desde cero. Este proceso es independiente de tus organizaciones existentes.
|
||||
{t('onboarding:wizard.subtitle', 'Configurarás una nueva panadería desde cero. Este proceso es independiente de tus organizaciones existentes.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -366,21 +368,21 @@ export const OnboardingWizard: React.FC = () => {
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-4 space-y-2 sm:space-y-0">
|
||||
<div className="text-center sm:text-left">
|
||||
<h1 className="text-xl sm:text-2xl font-bold text-[var(--text-primary)]">
|
||||
{isNewTenant ? 'Crear Nueva Organización' : 'Bienvenido a Bakery IA'}
|
||||
{isNewTenant ? t('onboarding:wizard.title', 'Crear Nueva Organización') : t('onboarding:wizard.title', 'Bienvenido a Bakery IA')}
|
||||
</h1>
|
||||
<p className="text-[var(--text-secondary)] text-xs sm:text-sm mt-1">
|
||||
{isNewTenant
|
||||
? 'Configura tu nueva panadería desde cero'
|
||||
: 'Configura tu sistema de gestión inteligente paso a paso'
|
||||
? t('onboarding:wizard.subtitle', 'Configura tu nueva panadería desde cero')
|
||||
: t('onboarding:wizard.subtitle', 'Configura tu sistema de gestión inteligente paso a paso')
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center sm:text-right">
|
||||
<div className="text-sm text-[var(--text-secondary)]">
|
||||
Paso {currentStepIndex + 1} de {STEPS.length}
|
||||
{t('onboarding:wizard.progress.step_of', 'Paso {{current}} de {{total}}', { current: currentStepIndex + 1, total: STEPS.length })}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--text-tertiary)]">
|
||||
{Math.round(progressPercentage)}% completado
|
||||
{Math.round(progressPercentage)}% {t('onboarding:wizard.progress.completed', 'completado')}
|
||||
{isNewTenant && <span className="text-[var(--color-primary)] ml-1">(nuevo)</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Table,
|
||||
Button,
|
||||
@@ -95,35 +96,7 @@ const StatusColors = {
|
||||
[OrderStatus.CANCELADO]: 'red'
|
||||
} as const;
|
||||
|
||||
const StatusLabels = {
|
||||
[OrderStatus.PENDIENTE]: 'Pendiente',
|
||||
[OrderStatus.CONFIRMADO]: 'Confirmado',
|
||||
[OrderStatus.EN_PREPARACION]: 'En Preparación',
|
||||
[OrderStatus.LISTO]: 'Listo para Entrega',
|
||||
[OrderStatus.ENTREGADO]: 'Entregado',
|
||||
[OrderStatus.CANCELADO]: 'Cancelado'
|
||||
} as const;
|
||||
|
||||
const ChannelLabels = {
|
||||
[SalesChannel.STORE_FRONT]: 'Tienda',
|
||||
[SalesChannel.ONLINE]: 'Online',
|
||||
[SalesChannel.PHONE_ORDER]: 'Teléfono',
|
||||
[SalesChannel.DELIVERY]: 'Delivery',
|
||||
[SalesChannel.CATERING]: 'Catering',
|
||||
[SalesChannel.WHOLESALE]: 'Mayorista',
|
||||
[SalesChannel.FARMERS_MARKET]: 'Mercado',
|
||||
[SalesChannel.THIRD_PARTY]: 'Terceros'
|
||||
} as const;
|
||||
|
||||
const PaymentLabels = {
|
||||
[PaymentMethod.CASH]: 'Efectivo',
|
||||
[PaymentMethod.CREDIT_CARD]: 'Tarjeta Crédito',
|
||||
[PaymentMethod.DEBIT_CARD]: 'Tarjeta Débito',
|
||||
[PaymentMethod.DIGITAL_WALLET]: 'Wallet Digital',
|
||||
[PaymentMethod.BANK_TRANSFER]: 'Transferencia',
|
||||
[PaymentMethod.CHECK]: 'Cheque',
|
||||
[PaymentMethod.STORE_CREDIT]: 'Crédito Tienda'
|
||||
} as const;
|
||||
// Note: These will be replaced with translation functions inside the component
|
||||
|
||||
export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
tenantId,
|
||||
@@ -132,6 +105,34 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
onOrderUpdate,
|
||||
initialFilters = {}
|
||||
}) => {
|
||||
const { t } = useTranslation(['sales']);
|
||||
|
||||
// Translation helper functions
|
||||
const getStatusLabel = (status: OrderStatus) => {
|
||||
const statusKey = status.toLowerCase();
|
||||
return t(`sales:orders.status.${statusKey}`, StatusLabels[status] || status);
|
||||
};
|
||||
|
||||
const getChannelLabel = (channel: SalesChannel) => {
|
||||
const channelKey = channel.toLowerCase();
|
||||
return t(`sales:orders.channels.${channelKey}`, channel);
|
||||
};
|
||||
|
||||
const getPaymentLabel = (method: PaymentMethod) => {
|
||||
const methodKey = method.toLowerCase();
|
||||
return t(`sales:orders.payment_methods.${methodKey}`, method);
|
||||
};
|
||||
|
||||
// Legacy objects for fallbacks (will be removed after migration)
|
||||
const StatusLabels = {
|
||||
[OrderStatus.PENDIENTE]: 'Pendiente',
|
||||
[OrderStatus.CONFIRMADO]: 'Confirmado',
|
||||
[OrderStatus.EN_PREPARACION]: 'En Preparación',
|
||||
[OrderStatus.LISTO]: 'Listo para Entrega',
|
||||
[OrderStatus.ENTREGADO]: 'Entregado',
|
||||
[OrderStatus.CANCELADO]: 'Cancelado'
|
||||
} as const;
|
||||
|
||||
// State
|
||||
const [orders, setOrders] = useState<Order[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -270,7 +271,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
},
|
||||
{
|
||||
key: 'id',
|
||||
title: 'Nº Pedido',
|
||||
title: t('sales:orders.table.columns.order_number', 'Nº Pedido'),
|
||||
sortable: true,
|
||||
render: (order: Order) => (
|
||||
<button
|
||||
@@ -286,7 +287,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
},
|
||||
{
|
||||
key: 'customer_name',
|
||||
title: 'Cliente',
|
||||
title: t('sales:orders.table.columns.customer', 'Cliente'),
|
||||
sortable: true,
|
||||
render: (order: Order) => (
|
||||
<div>
|
||||
@@ -299,7 +300,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
},
|
||||
{
|
||||
key: 'products',
|
||||
title: 'Productos',
|
||||
title: t('sales:orders.table.columns.products', 'Productos'),
|
||||
render: (order: Order) => (
|
||||
<div>
|
||||
<div className="font-medium">{order.product_name}</div>
|
||||
@@ -311,7 +312,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
},
|
||||
{
|
||||
key: 'total_revenue',
|
||||
title: 'Total',
|
||||
title: t('sales:orders.table.columns.total', 'Total'),
|
||||
sortable: true,
|
||||
render: (order: Order) => (
|
||||
<div className="text-right">
|
||||
@@ -326,17 +327,17 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: 'Estado',
|
||||
title: t('sales:orders.table.columns.status', 'Estado'),
|
||||
sortable: true,
|
||||
render: (order: Order) => (
|
||||
<Badge color={StatusColors[order.status]} variant="soft">
|
||||
{StatusLabels[order.status]}
|
||||
{getStatusLabel(order.status)}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'sales_channel',
|
||||
title: 'Canal',
|
||||
title: t('sales:orders.table.columns.channel', 'Canal'),
|
||||
render: (order: Order) => (
|
||||
<span className="text-sm text-[var(--text-secondary)]">
|
||||
{ChannelLabels[order.sales_channel]}
|
||||
@@ -345,7 +346,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
},
|
||||
{
|
||||
key: 'date',
|
||||
title: 'Fecha',
|
||||
title: t('sales:orders.table.columns.date', 'Fecha'),
|
||||
sortable: true,
|
||||
render: (order: Order) => (
|
||||
<div>
|
||||
@@ -364,7 +365,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
|
||||
if (showActions) {
|
||||
columns.push({
|
||||
key: 'actions',
|
||||
title: 'Acciones',
|
||||
title: t('sales:orders.table.columns.actions', 'Acciones'),
|
||||
render: (order: Order) => (
|
||||
<div className="flex space-x-2">
|
||||
<Tooltip content="Ver detalles">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useLocation, Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getBreadcrumbs, getRouteByPath } from '../../../router/routes.config';
|
||||
import {
|
||||
ChevronRight,
|
||||
@@ -94,7 +95,7 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
|
||||
items: customItems,
|
||||
showHome = true,
|
||||
homePath = '/',
|
||||
homeLabel = 'Inicio',
|
||||
homeLabel,
|
||||
separator,
|
||||
maxItems = 5,
|
||||
truncateMiddle = true,
|
||||
@@ -103,9 +104,13 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
|
||||
hiddenPaths = [],
|
||||
itemRenderer,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
// Set default home label if not provided
|
||||
const resolvedHomeLabel = homeLabel || t('common:breadcrumbs.home', 'Inicio');
|
||||
|
||||
// Get breadcrumbs from router config or use custom items
|
||||
const routeBreadcrumbs = getBreadcrumbs(location.pathname);
|
||||
|
||||
@@ -115,11 +120,11 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
|
||||
}
|
||||
|
||||
const items: BreadcrumbItem[] = [];
|
||||
|
||||
|
||||
// Add home if enabled
|
||||
if (showHome && location.pathname !== homePath) {
|
||||
items.push({
|
||||
label: homeLabel,
|
||||
label: resolvedHomeLabel,
|
||||
path: homePath,
|
||||
icon: Home,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../ui';
|
||||
import {
|
||||
Heart,
|
||||
@@ -150,15 +151,16 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
compact = false,
|
||||
children,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const footerRef = React.useRef<HTMLDivElement>(null);
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
// Company info - full for public pages, minimal for internal
|
||||
const defaultCompanyInfo: CompanyInfo = compact ? {
|
||||
name: 'Panadería IA',
|
||||
name: t('common:app.name', 'Panadería IA'),
|
||||
} : {
|
||||
name: 'Panadería IA',
|
||||
description: 'Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.',
|
||||
name: t('common:app.name', 'Panadería IA'),
|
||||
description: t('common:footer.company_description', 'Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.'),
|
||||
email: 'contacto@panaderia-ia.com',
|
||||
website: 'https://panaderia-ia.com',
|
||||
};
|
||||
@@ -169,33 +171,33 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
const defaultSections: FooterSection[] = compact ? [] : [
|
||||
{
|
||||
id: 'product',
|
||||
title: 'Producto',
|
||||
title: t('common:footer.sections.product', 'Producto'),
|
||||
links: [
|
||||
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard' },
|
||||
{ id: 'inventory', label: 'Inventario', href: '/inventory' },
|
||||
{ id: 'production', label: 'Producción', href: '/production' },
|
||||
{ id: 'sales', label: 'Ventas', href: '/sales' },
|
||||
{ id: 'forecasting', label: 'Predicciones', href: '/forecasting' },
|
||||
{ id: 'dashboard', label: t('common:footer.links.dashboard', 'Dashboard'), href: '/dashboard' },
|
||||
{ id: 'inventory', label: t('common:footer.links.inventory', 'Inventario'), href: '/inventory' },
|
||||
{ id: 'production', label: t('common:footer.links.production', 'Producción'), href: '/production' },
|
||||
{ id: 'sales', label: t('common:footer.links.sales', 'Ventas'), href: '/sales' },
|
||||
{ id: 'forecasting', label: t('common:footer.links.forecasting', 'Predicciones'), href: '/forecasting' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
title: 'Soporte',
|
||||
title: t('common:footer.sections.support', 'Soporte'),
|
||||
links: [
|
||||
{ id: 'help', label: 'Centro de Ayuda', href: '/help', icon: HelpCircle },
|
||||
{ id: 'docs', label: 'Documentación', href: '/help/docs', icon: FileText },
|
||||
{ id: 'contact', label: 'Contacto', href: '/help/support', icon: MessageSquare },
|
||||
{ id: 'feedback', label: 'Feedback', href: '/help/feedback' },
|
||||
{ id: 'help', label: t('common:footer.links.help', 'Centro de Ayuda'), href: '/help', icon: HelpCircle },
|
||||
{ id: 'docs', label: t('common:footer.links.docs', 'Documentación'), href: '/help/docs', icon: FileText },
|
||||
{ id: 'contact', label: t('common:footer.links.contact', 'Contacto'), href: '/help/support', icon: MessageSquare },
|
||||
{ id: 'feedback', label: t('common:footer.links.feedback', 'Feedback'), href: '/help/feedback' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'company',
|
||||
title: 'Empresa',
|
||||
title: t('common:footer.sections.company', 'Empresa'),
|
||||
links: [
|
||||
{ id: 'about', label: 'Acerca de', href: '/about', external: true },
|
||||
{ id: 'blog', label: 'Blog', href: 'https://blog.panaderia-ia.com', external: true },
|
||||
{ id: 'careers', label: 'Carreras', href: 'https://careers.panaderia-ia.com', external: true },
|
||||
{ id: 'press', label: 'Prensa', href: '/press', external: true },
|
||||
{ id: 'about', label: t('common:footer.links.about', 'Acerca de'), href: '/about', external: true },
|
||||
{ id: 'blog', label: t('common:footer.links.blog', 'Blog'), href: 'https://blog.panaderia-ia.com', external: true },
|
||||
{ id: 'careers', label: t('common:footer.links.careers', 'Carreras'), href: 'https://careers.panaderia-ia.com', external: true },
|
||||
{ id: 'press', label: t('common:footer.links.press', 'Prensa'), href: '/press', external: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -206,19 +208,19 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
const defaultSocialLinks: SocialLink[] = compact ? [] : [
|
||||
{
|
||||
id: 'twitter',
|
||||
label: 'Twitter',
|
||||
label: t('common:footer.social_labels.twitter', 'Twitter'),
|
||||
href: 'https://twitter.com/panaderia-ia',
|
||||
icon: Twitter,
|
||||
},
|
||||
{
|
||||
id: 'linkedin',
|
||||
label: 'LinkedIn',
|
||||
label: t('common:footer.social_labels.linkedin', 'LinkedIn'),
|
||||
href: 'https://linkedin.com/company/panaderia-ia',
|
||||
icon: Linkedin,
|
||||
},
|
||||
{
|
||||
id: 'github',
|
||||
label: 'GitHub',
|
||||
label: t('common:footer.social_labels.github', 'GitHub'),
|
||||
href: 'https://github.com/panaderia-ia',
|
||||
icon: Github,
|
||||
},
|
||||
@@ -228,9 +230,9 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
|
||||
// Privacy links
|
||||
const privacyLinks: FooterLink[] = [
|
||||
{ id: 'privacy', label: 'Privacidad', href: '/privacy', icon: Shield },
|
||||
{ id: 'terms', label: 'Términos', href: '/terms', icon: FileText },
|
||||
{ id: 'cookies', label: 'Cookies', href: '/cookies' },
|
||||
{ id: 'privacy', label: t('common:footer.links.privacy', 'Privacidad'), href: '/privacy', icon: Shield },
|
||||
{ id: 'terms', label: t('common:footer.links.terms', 'Términos'), href: '/terms', icon: FileText },
|
||||
{ id: 'cookies', label: t('common:footer.links.cookies', 'Cookies'), href: '/cookies' },
|
||||
];
|
||||
|
||||
// Scroll into view
|
||||
@@ -375,7 +377,7 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
{socialLinksToShow.length > 0 && (
|
||||
<div className="border-t border-[var(--border-primary)] pt-6 mb-6">
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p className="text-sm text-[var(--text-secondary)]">Síguenos en redes sociales</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('common:footer.social_follow', 'Síguenos en redes sociales')}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{socialLinksToShow.map((social) => renderSocialLink(social))}
|
||||
</div>
|
||||
@@ -404,13 +406,13 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
to="/privacy"
|
||||
className="text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors duration-200"
|
||||
>
|
||||
Privacidad
|
||||
{t('common:footer.links.privacy', 'Privacidad')}
|
||||
</Link>
|
||||
<Link
|
||||
to="/terms"
|
||||
className="text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors duration-200"
|
||||
>
|
||||
Términos
|
||||
{t('common:footer.links.terms', 'Términos')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -148,7 +148,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
|
||||
className
|
||||
)}
|
||||
role="banner"
|
||||
aria-label="Navegación principal"
|
||||
aria-label={t('common:header.main_navigation', 'Navegación principal')}
|
||||
>
|
||||
{/* Left section */}
|
||||
<div className="flex items-center gap-2 sm:gap-4 flex-1 min-w-0 h-full">
|
||||
@@ -158,7 +158,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
|
||||
size="sm"
|
||||
onClick={onMenuClick}
|
||||
className="lg:hidden w-10 h-10 p-0 flex items-center justify-center hover:bg-[var(--bg-secondary)] active:scale-95 transition-all duration-150"
|
||||
aria-label="Abrir menú de navegación"
|
||||
aria-label={t('common:header.open_menu', 'Abrir menú de navegación')}
|
||||
>
|
||||
<Menu className="h-5 w-5 text-[var(--text-primary)]" />
|
||||
</Button>
|
||||
@@ -176,7 +176,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
|
||||
'self-center',
|
||||
sidebarCollapsed ? 'lg:block' : 'lg:hidden xl:block'
|
||||
)}>
|
||||
Panadería IA
|
||||
{t('common:app.name', 'Panadería IA')}
|
||||
</h1>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, ThemeToggle } from '../../ui';
|
||||
import { CompactLanguageSelector } from '../../ui/LanguageSelector';
|
||||
|
||||
export interface PublicHeaderProps {
|
||||
className?: string;
|
||||
@@ -17,6 +18,10 @@ export interface PublicHeaderProps {
|
||||
* Show authentication buttons (login/register)
|
||||
*/
|
||||
showAuthButtons?: boolean;
|
||||
/**
|
||||
* Show language selector
|
||||
*/
|
||||
showLanguageSelector?: boolean;
|
||||
/**
|
||||
* Custom navigation items
|
||||
*/
|
||||
@@ -53,6 +58,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
logo,
|
||||
showThemeToggle = true,
|
||||
showAuthButtons = true,
|
||||
showLanguageSelector = true,
|
||||
navigationItems = [],
|
||||
variant = 'default',
|
||||
}, ref) => {
|
||||
@@ -149,6 +155,11 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Language selector */}
|
||||
{showLanguageSelector && (
|
||||
<CompactLanguageSelector className="hidden sm:flex" />
|
||||
)}
|
||||
|
||||
{/* Theme toggle */}
|
||||
{showThemeToggle && (
|
||||
<ThemeToggle
|
||||
@@ -162,8 +173,8 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
{showAuthButtons && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Link to="/login">
|
||||
<Button
|
||||
variant="ghost"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="hidden sm:inline-flex"
|
||||
>
|
||||
@@ -171,7 +182,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/register">
|
||||
<Button
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-[var(--color-primary)] hover:bg-[var(--color-primary-dark)] text-white"
|
||||
>
|
||||
@@ -219,7 +230,17 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
{renderNavLink(item)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
{/* Mobile language selector */}
|
||||
{showLanguageSelector && (
|
||||
<div className="py-2 border-b border-[var(--border-primary)] sm:hidden">
|
||||
<div className="text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
Idioma
|
||||
</div>
|
||||
<CompactLanguageSelector className="w-full" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile auth buttons */}
|
||||
{showAuthButtons && (
|
||||
<div className="flex flex-col gap-2 pt-4 sm:hidden">
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface PublicLayoutProps {
|
||||
headerProps?: {
|
||||
showThemeToggle?: boolean;
|
||||
showAuthButtons?: boolean;
|
||||
showLanguageSelector?: boolean;
|
||||
navigationItems?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
|
||||
@@ -732,7 +732,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
isCollapsed ? 'justify-center p-2 h-10 w-10 mx-auto rounded-lg' : 'p-4 gap-3',
|
||||
isProfileMenuOpen && 'bg-[var(--bg-secondary)]'
|
||||
)}
|
||||
aria-label="Menú de perfil"
|
||||
aria-label={t('common:profile.profile_menu', 'Menú de perfil')}
|
||||
aria-expanded={isProfileMenuOpen}
|
||||
aria-haspopup="true"
|
||||
>
|
||||
@@ -767,25 +767,25 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
<div className="py-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate('/app/settings/profile');
|
||||
navigate('/app/settings/personal-info');
|
||||
setIsProfileMenuOpen(false);
|
||||
if (onClose) onClose();
|
||||
}}
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors"
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Perfil
|
||||
{t('common:profile.my_profile', 'Mi perfil')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate('/app/settings');
|
||||
navigate('/app/settings/organizations');
|
||||
setIsProfileMenuOpen(false);
|
||||
if (onClose) onClose();
|
||||
}}
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Configuración
|
||||
<Factory className="h-4 w-4" />
|
||||
{t('common:profile.my_locations', 'Mis Locales')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -795,7 +795,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors text-[var(--color-error)]"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
Cerrar Sesión
|
||||
{t('common:profile.logout', 'Cerrar Sesión')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -852,7 +852,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
PI
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-[var(--text-primary)]">
|
||||
Panadería IA
|
||||
{t('common:app.name', 'Panadería IA')}
|
||||
</h2>
|
||||
</div>
|
||||
<Button
|
||||
@@ -860,7 +860,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
className="p-2 hover:bg-[var(--bg-secondary)]"
|
||||
aria-label="Cerrar navegación"
|
||||
aria-label={t('common:profile.close_navigation', 'Cerrar navegación')}
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</Button>
|
||||
@@ -931,7 +931,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
'hover:bg-[var(--bg-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
|
||||
isProfileMenuOpen && 'bg-[var(--bg-secondary)]'
|
||||
)}
|
||||
aria-label="Menú de perfil"
|
||||
aria-label={t('common:profile.profile_menu', 'Menú de perfil')}
|
||||
aria-expanded={isProfileMenuOpen}
|
||||
aria-haspopup="true"
|
||||
>
|
||||
@@ -966,7 +966,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors"
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Perfil
|
||||
{t('common:profile.profile', 'Perfil')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -977,7 +977,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Configuración
|
||||
{t('common:profile.settings', 'Configuración')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -987,7 +987,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors text-[var(--color-error)]"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
Cerrar Sesión
|
||||
{t('common:profile.logout', 'Cerrar Sesión')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { forwardRef, useState, useRef, useEffect, useMemo } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface DatePickerProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> {
|
||||
label?: string;
|
||||
@@ -37,7 +38,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
|
||||
label,
|
||||
error,
|
||||
helperText,
|
||||
placeholder = 'Seleccionar fecha',
|
||||
placeholder,
|
||||
size = 'md',
|
||||
variant = 'outline',
|
||||
value,
|
||||
@@ -68,6 +69,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
|
||||
disabled,
|
||||
...props
|
||||
}, ref) => {
|
||||
const { t } = useTranslation(['ui']);
|
||||
const datePickerId = id || `datepicker-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const [internalValue, setInternalValue] = useState<Date | null>(
|
||||
value !== undefined ? value : defaultValue || null
|
||||
@@ -110,7 +112,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
|
||||
},
|
||||
};
|
||||
|
||||
const t = translations[locale];
|
||||
const localT = translations[locale];
|
||||
|
||||
// Format date for display
|
||||
const formatDate = (date: Date | null): string => {
|
||||
@@ -422,7 +424,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
|
||||
id={datePickerId}
|
||||
type="text"
|
||||
className={inputClasses}
|
||||
placeholder={placeholder}
|
||||
placeholder={placeholder || t('ui:datepicker.placeholder', 'Seleccionar fecha')}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
@@ -475,7 +477,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
|
||||
value={currentMonth}
|
||||
onChange={(e) => setCurrentMonth(parseInt(e.target.value))}
|
||||
>
|
||||
{t.months.map((month, index) => (
|
||||
{localT.months.map((month, index) => (
|
||||
<option key={index} value={index}>
|
||||
{month}
|
||||
</option>
|
||||
@@ -512,7 +514,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
|
||||
|
||||
{/* Weekdays */}
|
||||
<div className="grid grid-cols-7 gap-1 mb-2">
|
||||
{(firstDayOfWeek === 0 ? t.weekdays : [...t.weekdays.slice(1), t.weekdays[0]]).map((day) => (
|
||||
{(firstDayOfWeek === 0 ? localT.weekdays : [...localT.weekdays.slice(1), localT.weekdays[0]]).map((day) => (
|
||||
<div key={day} className="text-xs font-medium text-text-tertiary text-center p-2">
|
||||
{day}
|
||||
</div>
|
||||
@@ -582,7 +584,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
|
||||
className="px-3 py-1 text-sm text-color-primary hover:bg-color-primary/10 rounded transition-colors duration-150"
|
||||
onClick={handleTodayClick}
|
||||
>
|
||||
{t.today}
|
||||
{localT.today}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -592,7 +594,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
|
||||
className="px-3 py-1 text-sm text-text-tertiary hover:text-color-error hover:bg-color-error/10 rounded transition-colors duration-150"
|
||||
onClick={handleClear}
|
||||
>
|
||||
{t.clear}
|
||||
{localT.clear}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface PasswordCriteria {
|
||||
label: string;
|
||||
@@ -18,29 +19,31 @@ export const PasswordCriteria: React.FC<PasswordCriteriaProps> = ({
|
||||
className = '',
|
||||
showOnlyFailed = false
|
||||
}) => {
|
||||
const { t } = useTranslation(['ui']);
|
||||
|
||||
const criteria: PasswordCriteria[] = [
|
||||
{
|
||||
label: 'Al menos 8 caracteres',
|
||||
label: t('ui:password_criteria.min_length', 'Al menos 8 caracteres'),
|
||||
isValid: password.length >= 8,
|
||||
checkFn: (pwd) => pwd.length >= 8
|
||||
},
|
||||
{
|
||||
label: 'Máximo 128 caracteres',
|
||||
label: t('ui:password_criteria.max_length', 'Máximo 128 caracteres'),
|
||||
isValid: password.length <= 128,
|
||||
checkFn: (pwd) => pwd.length <= 128
|
||||
},
|
||||
{
|
||||
label: 'Al menos una letra mayúscula',
|
||||
label: t('ui:password_criteria.uppercase', 'Al menos una letra mayúscula'),
|
||||
isValid: /[A-Z]/.test(password),
|
||||
regex: /[A-Z]/
|
||||
},
|
||||
{
|
||||
label: 'Al menos una letra minúscula',
|
||||
label: t('ui:password_criteria.lowercase', 'Al menos una letra minúscula'),
|
||||
isValid: /[a-z]/.test(password),
|
||||
regex: /[a-z]/
|
||||
},
|
||||
{
|
||||
label: 'Al menos un número',
|
||||
label: t('ui:password_criteria.number', 'Al menos un número'),
|
||||
isValid: /\d/.test(password),
|
||||
regex: /\d/
|
||||
}
|
||||
@@ -104,28 +107,28 @@ export const validatePassword = (password: string): boolean => {
|
||||
);
|
||||
};
|
||||
|
||||
export const getPasswordErrors = (password: string): string[] => {
|
||||
export const getPasswordErrors = (password: string, t?: (key: string, fallback: string) => string): string[] => {
|
||||
const errors: string[] = [];
|
||||
|
||||
|
||||
if (password.length < 8) {
|
||||
errors.push('La contraseña debe tener al menos 8 caracteres');
|
||||
errors.push(t?.('ui:password_criteria.errors.min_length', 'La contraseña debe tener al menos 8 caracteres') ?? 'La contraseña debe tener al menos 8 caracteres');
|
||||
}
|
||||
|
||||
|
||||
if (password.length > 128) {
|
||||
errors.push('La contraseña no puede exceder 128 caracteres');
|
||||
errors.push(t?.('ui:password_criteria.errors.max_length', 'La contraseña no puede exceder 128 caracteres') ?? 'La contraseña no puede exceder 128 caracteres');
|
||||
}
|
||||
|
||||
|
||||
if (!/[A-Z]/.test(password)) {
|
||||
errors.push('La contraseña debe contener al menos una letra mayúscula');
|
||||
errors.push(t?.('ui:password_criteria.errors.uppercase', 'La contraseña debe contener al menos una letra mayúscula') ?? 'La contraseña debe contener al menos una letra mayúscula');
|
||||
}
|
||||
|
||||
|
||||
if (!/[a-z]/.test(password)) {
|
||||
errors.push('La contraseña debe contener al menos una letra minúscula');
|
||||
errors.push(t?.('ui:password_criteria.errors.lowercase', 'La contraseña debe contener al menos una letra minúscula') ?? 'La contraseña debe contener al menos una letra minúscula');
|
||||
}
|
||||
|
||||
|
||||
if (!/\d/.test(password)) {
|
||||
errors.push('La contraseña debe contener al menos un número');
|
||||
errors.push(t?.('ui:password_criteria.errors.number', 'La contraseña debe contener al menos un número') ?? 'La contraseña debe contener al menos un número');
|
||||
}
|
||||
|
||||
|
||||
return errors;
|
||||
};
|
||||
@@ -20,6 +20,7 @@ export { StatsCard, StatsGrid } from './Stats';
|
||||
export { StatusCard, getStatusColor } from './StatusCard';
|
||||
export { StatusModal } from './StatusModal';
|
||||
export { TenantSwitcher } from './TenantSwitcher';
|
||||
export { LanguageSelector, CompactLanguageSelector } from './LanguageSelector';
|
||||
|
||||
// Export types
|
||||
export type { ButtonProps } from './Button';
|
||||
|
||||
@@ -1 +1,166 @@
|
||||
{}
|
||||
{
|
||||
"login": {
|
||||
"title": "Sign in to your account",
|
||||
"subtitle": "Access your bakery control panel",
|
||||
"email": "Email address",
|
||||
"password": "Password",
|
||||
"remember_me": "Remember me",
|
||||
"forgot_password": "Forgot your password?",
|
||||
"login_button": "Sign in",
|
||||
"logging_in": "Signing in...",
|
||||
"no_account": "Don't have an account?",
|
||||
"register_link": "Register here",
|
||||
"demo_account": "Use demo account",
|
||||
"welcome_back": "Welcome back!",
|
||||
"invalid_credentials": "Invalid credentials",
|
||||
"account_locked": "Account temporarily locked",
|
||||
"too_many_attempts": "Too many failed attempts"
|
||||
},
|
||||
"register": {
|
||||
"title": "Create your account",
|
||||
"subtitle": "Join the bakery management platform",
|
||||
"personal_info": "Personal information",
|
||||
"company_info": "Company information",
|
||||
"account_setup": "Account setup",
|
||||
"first_name": "First name",
|
||||
"last_name": "Last name",
|
||||
"email": "Email address",
|
||||
"phone": "Phone",
|
||||
"company_name": "Bakery name",
|
||||
"company_type": "Business type",
|
||||
"employee_count": "Number of employees",
|
||||
"password": "Password",
|
||||
"confirm_password": "Confirm password",
|
||||
"password_requirements": "Minimum 8 characters, includes uppercase, lowercase and numbers",
|
||||
"passwords_dont_match": "Passwords don't match",
|
||||
"accept_terms": "I accept the terms and conditions",
|
||||
"accept_privacy": "I accept the privacy policy",
|
||||
"marketing_consent": "I want to receive newsletters and updates (optional)",
|
||||
"register_button": "Create account",
|
||||
"registering": "Creating account...",
|
||||
"have_account": "Already have an account?",
|
||||
"login_link": "Sign in here",
|
||||
"terms_link": "Terms of Service",
|
||||
"privacy_link": "Privacy Policy",
|
||||
"step_of": "Step {{current}} of {{total}}",
|
||||
"continue": "Continue",
|
||||
"back": "Back"
|
||||
},
|
||||
"forgot_password": {
|
||||
"title": "Reset password",
|
||||
"subtitle": "We'll send you a link to reset your password",
|
||||
"email": "Email address",
|
||||
"send_reset_link": "Send reset link",
|
||||
"sending": "Sending...",
|
||||
"reset_sent": "Reset link sent",
|
||||
"reset_instructions": "Check your email for password reset instructions",
|
||||
"back_to_login": "Back to login",
|
||||
"didnt_receive": "Didn't receive the email?",
|
||||
"resend_link": "Resend link"
|
||||
},
|
||||
"reset_password": {
|
||||
"title": "Reset password",
|
||||
"subtitle": "Enter your new password",
|
||||
"new_password": "New password",
|
||||
"confirm_new_password": "Confirm new password",
|
||||
"reset_button": "Reset password",
|
||||
"resetting": "Resetting...",
|
||||
"password_reset_success": "Password reset successfully",
|
||||
"password_reset_error": "Error resetting password",
|
||||
"invalid_token": "Invalid or expired reset link",
|
||||
"expired_token": "Reset link has expired"
|
||||
},
|
||||
"profile": {
|
||||
"title": "My Profile",
|
||||
"personal_information": "Personal Information",
|
||||
"account_settings": "Account Settings",
|
||||
"security": "Security",
|
||||
"preferences": "Preferences",
|
||||
"notifications": "Notifications",
|
||||
"first_name": "First name",
|
||||
"last_name": "Last name",
|
||||
"email": "Email address",
|
||||
"phone": "Phone",
|
||||
"avatar": "Profile picture",
|
||||
"change_avatar": "Change picture",
|
||||
"current_password": "Current password",
|
||||
"new_password": "New password",
|
||||
"confirm_password": "Confirm password",
|
||||
"change_password": "Change password",
|
||||
"two_factor_auth": "Two-factor authentication",
|
||||
"enable_2fa": "Enable 2FA",
|
||||
"disable_2fa": "Disable 2FA",
|
||||
"language": "Language",
|
||||
"timezone": "Timezone",
|
||||
"theme": "Theme",
|
||||
"theme_light": "Light",
|
||||
"theme_dark": "Dark",
|
||||
"theme_auto": "Auto",
|
||||
"email_notifications": "Email notifications",
|
||||
"push_notifications": "Push notifications",
|
||||
"marketing_emails": "Marketing emails",
|
||||
"save_changes": "Save changes",
|
||||
"changes_saved": "Changes saved successfully",
|
||||
"profile_updated": "Profile updated successfully"
|
||||
},
|
||||
"logout": {
|
||||
"title": "Sign out",
|
||||
"confirm": "Are you sure you want to sign out?",
|
||||
"logout_button": "Sign out",
|
||||
"logging_out": "Signing out...",
|
||||
"logged_out": "You have signed out successfully",
|
||||
"goodbye": "See you later!"
|
||||
},
|
||||
"session": {
|
||||
"expired": "Your session has expired",
|
||||
"expires_soon": "Your session will expire soon",
|
||||
"extend_session": "Extend session",
|
||||
"login_required": "You must sign in to access this page",
|
||||
"unauthorized": "You don't have permission to access this page",
|
||||
"forbidden": "Access denied",
|
||||
"session_timeout": "Session expired due to inactivity"
|
||||
},
|
||||
"validation": {
|
||||
"email_required": "Email address is required",
|
||||
"email_invalid": "Please enter a valid email address",
|
||||
"password_required": "Password is required",
|
||||
"password_min_length": "Password must be at least {{min}} characters",
|
||||
"password_weak": "Password is too weak",
|
||||
"passwords_must_match": "Passwords must match",
|
||||
"first_name_required": "First name is required",
|
||||
"last_name_required": "Last name is required",
|
||||
"phone_required": "Phone is required",
|
||||
"company_name_required": "Company name is required",
|
||||
"terms_required": "You must accept the terms and conditions",
|
||||
"field_required": "This field is required",
|
||||
"invalid_format": "Invalid format"
|
||||
},
|
||||
"roles": {
|
||||
"admin": "Administrator",
|
||||
"manager": "Manager",
|
||||
"baker": "Baker",
|
||||
"staff": "Staff",
|
||||
"owner": "Owner",
|
||||
"supervisor": "Supervisor",
|
||||
"cashier": "Cashier",
|
||||
"assistant": "Assistant"
|
||||
},
|
||||
"global_roles": {
|
||||
"user": "User",
|
||||
"admin": "Administrator",
|
||||
"manager": "Manager",
|
||||
"super_admin": "Super Administrator"
|
||||
},
|
||||
"permissions": {
|
||||
"read": "Read",
|
||||
"write": "Write",
|
||||
"delete": "Delete",
|
||||
"admin": "Administrator",
|
||||
"manage_users": "Manage users",
|
||||
"manage_inventory": "Manage inventory",
|
||||
"manage_production": "Manage production",
|
||||
"manage_sales": "Manage sales",
|
||||
"view_reports": "View reports",
|
||||
"manage_settings": "Manage settings"
|
||||
}
|
||||
}
|
||||
@@ -237,5 +237,58 @@
|
||||
"link": "Link",
|
||||
"tooltip": "Additional information",
|
||||
"search": "Search in the application"
|
||||
},
|
||||
"app": {
|
||||
"name": "Bakery AI",
|
||||
"full_name": "Bakery AI - Intelligent System"
|
||||
},
|
||||
"profile": {
|
||||
"my_profile": "My Profile",
|
||||
"my_locations": "My Locations",
|
||||
"settings": "Settings",
|
||||
"profile": "Profile",
|
||||
"logout": "Logout",
|
||||
"profile_menu": "Profile menu",
|
||||
"close_navigation": "Close navigation"
|
||||
},
|
||||
"header": {
|
||||
"main_navigation": "Main navigation",
|
||||
"open_menu": "Open navigation menu"
|
||||
},
|
||||
"footer": {
|
||||
"company_description": "Intelligent management system for bakeries. Optimize your production, inventory and sales with artificial intelligence.",
|
||||
"sections": {
|
||||
"product": "Product",
|
||||
"support": "Support",
|
||||
"company": "Company"
|
||||
},
|
||||
"links": {
|
||||
"dashboard": "Dashboard",
|
||||
"inventory": "Inventory",
|
||||
"production": "Production",
|
||||
"sales": "Sales",
|
||||
"forecasting": "Forecasting",
|
||||
"help": "Help Center",
|
||||
"docs": "Documentation",
|
||||
"contact": "Contact",
|
||||
"feedback": "Feedback",
|
||||
"about": "About",
|
||||
"blog": "Blog",
|
||||
"careers": "Careers",
|
||||
"press": "Press",
|
||||
"privacy": "Privacy",
|
||||
"terms": "Terms",
|
||||
"cookies": "Cookies"
|
||||
},
|
||||
"social_follow": "Follow us on social media",
|
||||
"social_labels": {
|
||||
"twitter": "Twitter",
|
||||
"linkedin": "LinkedIn",
|
||||
"github": "GitHub"
|
||||
}
|
||||
},
|
||||
"breadcrumbs": {
|
||||
"home": "Home",
|
||||
"truncation": "..."
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,43 @@
|
||||
"manage_staff": "Manage Staff"
|
||||
},
|
||||
"alerts": {
|
||||
"low_stock": "Low Stock",
|
||||
"production_delay": "Production Delay",
|
||||
"quality_issue": "Quality Issue",
|
||||
"equipment_maintenance": "Equipment Maintenance",
|
||||
"order_pending": "Order Pending",
|
||||
"delivery_due": "Delivery Due"
|
||||
"title": "Alerts",
|
||||
"live": "Live",
|
||||
"offline": "Offline",
|
||||
"no_alerts": "No active alerts",
|
||||
"view_all": "View all alerts",
|
||||
"time": {
|
||||
"now": "Now",
|
||||
"minutes_ago": "{{count}} min ago",
|
||||
"hours_ago": "{{count}} h ago",
|
||||
"yesterday": "Yesterday"
|
||||
},
|
||||
"types": {
|
||||
"low_stock": "Low Stock",
|
||||
"production_delay": "Production Delay",
|
||||
"quality_issue": "Quality Issue",
|
||||
"equipment_maintenance": "Equipment Maintenance",
|
||||
"order_pending": "Order Pending",
|
||||
"delivery_due": "Delivery Due",
|
||||
"critical": "Critical",
|
||||
"warning": "Warning",
|
||||
"info": "Information",
|
||||
"success": "Success"
|
||||
},
|
||||
"status": {
|
||||
"new": "New",
|
||||
"acknowledged": "Acknowledged",
|
||||
"resolved": "Resolved"
|
||||
},
|
||||
"types": {
|
||||
"alert": "Alert",
|
||||
"recommendation": "Recommendation"
|
||||
},
|
||||
"recommended_actions": "Recommended Actions",
|
||||
"additional_details": "Additional Details",
|
||||
"mark_as_read": "Mark as read",
|
||||
"remove": "Remove",
|
||||
"active_count": "{{count}} active alerts"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "Welcome back",
|
||||
|
||||
34
frontend/src/locales/en/database.json
Normal file
34
frontend/src/locales/en/database.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "My Bakery",
|
||||
"subtitle": "View and manage all your bakery information",
|
||||
"sections": {
|
||||
"recipes": {
|
||||
"title": "Recipes",
|
||||
"description": "Manage your product recipes"
|
||||
},
|
||||
"orders": {
|
||||
"title": "Orders",
|
||||
"description": "Check the status of all orders"
|
||||
},
|
||||
"suppliers": {
|
||||
"title": "Suppliers",
|
||||
"description": "Manage your suppliers"
|
||||
},
|
||||
"inventory": {
|
||||
"title": "Inventory",
|
||||
"description": "Current inventory status"
|
||||
},
|
||||
"bakery_config": {
|
||||
"title": "Bakery Configuration",
|
||||
"description": "General settings for your bakery"
|
||||
},
|
||||
"team_management": {
|
||||
"title": "Team Management",
|
||||
"description": "Manage your work team"
|
||||
},
|
||||
"communication_preferences": {
|
||||
"title": "Communication Preferences",
|
||||
"description": "Configure notifications and communications"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,13 @@
|
||||
{}
|
||||
{
|
||||
"boundary": {
|
||||
"title": "Oops! Something went wrong",
|
||||
"description": "An unexpected error occurred in the application. Our team has been notified.",
|
||||
"technical_details": "Technical Details",
|
||||
"actions": {
|
||||
"retry": "Retry",
|
||||
"reload": "Reload Page",
|
||||
"go_back": "Go Back",
|
||||
"report": "Report this error"
|
||||
}
|
||||
}
|
||||
}
|
||||
25
frontend/src/locales/en/events.json
Normal file
25
frontend/src/locales/en/events.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"title": "Event Log",
|
||||
"description": "Monitor system activity and important events",
|
||||
"categories": {
|
||||
"all": "All",
|
||||
"sales": "Sales",
|
||||
"production": "Production",
|
||||
"inventory": "Inventory",
|
||||
"system": "System",
|
||||
"customer": "Customers"
|
||||
},
|
||||
"types": {
|
||||
"order_completed": "Order Completed",
|
||||
"batch_started": "Batch Started",
|
||||
"stock_updated": "Stock Updated",
|
||||
"customer_registered": "Customer Registered",
|
||||
"system_alert": "System Alert"
|
||||
},
|
||||
"severity": {
|
||||
"info": "Information",
|
||||
"warning": "Warning",
|
||||
"error": "Error",
|
||||
"success": "Success"
|
||||
}
|
||||
}
|
||||
127
frontend/src/locales/en/landing.json
Normal file
127
frontend/src/locales/en/landing.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"navigation": {
|
||||
"features": "Features",
|
||||
"benefits": "Benefits",
|
||||
"pricing": "Pricing",
|
||||
"testimonials": "Testimonials"
|
||||
},
|
||||
"hero": {
|
||||
"badge": "Advanced AI for Bakeries",
|
||||
"title_line1": "Revolutionize your",
|
||||
"title_line2": "Bakery with AI",
|
||||
"subtitle": "Automatically optimize your production, reduce waste by up to 35%, predict demand with 92% accuracy and increase your sales with artificial intelligence.",
|
||||
"cta_primary": "Start Free 14-Day Trial",
|
||||
"cta_secondary": "Watch Live Demo",
|
||||
"features": {
|
||||
"no_credit_card": "No credit card required",
|
||||
"quick_setup": "5-minute setup",
|
||||
"support_24_7": "24/7 support in English"
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"title": "Everything you need to optimize your bakery",
|
||||
"subtitle": "Powerful tools designed specifically for modern bakeries",
|
||||
"ai_forecasting": {
|
||||
"title": "AI Demand Forecasting",
|
||||
"description": "Advanced algorithms predict which products you'll need each day with 92% accuracy"
|
||||
},
|
||||
"production_optimization": {
|
||||
"title": "Production Optimization",
|
||||
"description": "Automatically plan baking schedules and staff management for maximum efficiency"
|
||||
},
|
||||
"waste_reduction": {
|
||||
"title": "Waste Reduction",
|
||||
"description": "Reduce waste by up to 35% with precise predictions and intelligent inventory management"
|
||||
},
|
||||
"real_time_analytics": {
|
||||
"title": "Real-Time Analytics",
|
||||
"description": "Intuitive dashboard with sales, production and profitability metrics updated instantly"
|
||||
},
|
||||
"inventory_management": {
|
||||
"title": "Inventory Management",
|
||||
"description": "Automatic stock control with smart alerts and automated purchase orders"
|
||||
},
|
||||
"customer_insights": {
|
||||
"title": "Customer Insights",
|
||||
"description": "Understand buying patterns and preferences to improve customer experience"
|
||||
}
|
||||
},
|
||||
"benefits": {
|
||||
"title": "Proven results that transform your business",
|
||||
"subtitle": "Over 1,000 bakeries have already transformed their operations with our AI",
|
||||
"waste_reduction": {
|
||||
"value": "35%",
|
||||
"label": "Waste reduction"
|
||||
},
|
||||
"accuracy": {
|
||||
"value": "92%",
|
||||
"label": "Prediction accuracy"
|
||||
},
|
||||
"time_saved": {
|
||||
"value": "4h",
|
||||
"label": "Daily planning time saved"
|
||||
},
|
||||
"sales_increase": {
|
||||
"value": "28%",
|
||||
"label": "Average sales increase"
|
||||
}
|
||||
},
|
||||
"pricing": {
|
||||
"title": "Plans designed for bakeries of all sizes",
|
||||
"subtitle": "Start free and scale as you grow",
|
||||
"starter": {
|
||||
"name": "Starter",
|
||||
"price": "Free",
|
||||
"description": "Perfect for small bakeries getting started",
|
||||
"features": [
|
||||
"Up to 50 products",
|
||||
"Basic demand forecasting",
|
||||
"Basic dashboard",
|
||||
"Email support"
|
||||
]
|
||||
},
|
||||
"professional": {
|
||||
"name": "Professional",
|
||||
"price": "$49",
|
||||
"price_period": "/month",
|
||||
"description": "Ideal for established bakeries",
|
||||
"features": [
|
||||
"Unlimited products",
|
||||
"Advanced AI forecasting",
|
||||
"Complete analytics",
|
||||
"Inventory management",
|
||||
"Production optimization",
|
||||
"Priority 24/7 support"
|
||||
]
|
||||
},
|
||||
"enterprise": {
|
||||
"name": "Enterprise",
|
||||
"price": "Custom",
|
||||
"description": "Complete solution for chains and franchises",
|
||||
"features": [
|
||||
"Multi-location",
|
||||
"Custom API",
|
||||
"Advanced integrations",
|
||||
"Dedicated support",
|
||||
"Custom training",
|
||||
"Guaranteed SLA"
|
||||
]
|
||||
},
|
||||
"cta": "Get Started Now",
|
||||
"contact": "Contact Sales"
|
||||
},
|
||||
"testimonials": {
|
||||
"title": "What our customers say",
|
||||
"subtitle": "Bakeries worldwide trust our platform"
|
||||
},
|
||||
"cta_section": {
|
||||
"title": "Ready to revolutionize your bakery?",
|
||||
"subtitle": "Join over 1,000 bakeries already using AI to optimize their operations",
|
||||
"cta": "Start Free Today"
|
||||
},
|
||||
"footer_cta": {
|
||||
"security": "Enterprise security",
|
||||
"uptime": "99.9% guaranteed uptime",
|
||||
"data_protection": "GDPR data protection"
|
||||
}
|
||||
}
|
||||
141
frontend/src/locales/en/onboarding.json
Normal file
141
frontend/src/locales/en/onboarding.json
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"wizard": {
|
||||
"title": "Initial Setup",
|
||||
"subtitle": "We'll guide you step by step to configure your bakery",
|
||||
"steps": {
|
||||
"setup": {
|
||||
"title": "Register Bakery",
|
||||
"description": "Configure your bakery's basic information"
|
||||
},
|
||||
"smart_inventory_setup": {
|
||||
"title": "Configure Inventory",
|
||||
"description": "Upload sales data and set up your initial inventory"
|
||||
},
|
||||
"ml_training": {
|
||||
"title": "AI Training",
|
||||
"description": "Train your personalized artificial intelligence model"
|
||||
},
|
||||
"completion": {
|
||||
"title": "Setup Complete",
|
||||
"description": "Welcome to your intelligent management system!"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"complete": "Complete",
|
||||
"skip": "Skip",
|
||||
"finish": "Finish"
|
||||
},
|
||||
"progress": {
|
||||
"step_of": "Step {{current}} of {{total}}",
|
||||
"completed": "Completed",
|
||||
"in_progress": "In progress",
|
||||
"pending": "Pending"
|
||||
}
|
||||
},
|
||||
"steps": {
|
||||
"tenant_registration": {
|
||||
"title": "Your Bakery Information",
|
||||
"subtitle": "Tell us about your business",
|
||||
"fields": {
|
||||
"business_name": "Business name",
|
||||
"business_type": "Business type",
|
||||
"address": "Address",
|
||||
"phone": "Phone",
|
||||
"email": "Contact email",
|
||||
"website": "Website (optional)",
|
||||
"description": "Business description"
|
||||
},
|
||||
"placeholders": {
|
||||
"business_name": "E.g: San José Bakery",
|
||||
"address": "Main Street 123, City",
|
||||
"phone": "+1 123 456 789",
|
||||
"email": "contact@bakery.com",
|
||||
"website": "https://mybakery.com",
|
||||
"description": "Describe your bakery..."
|
||||
}
|
||||
},
|
||||
"inventory_setup": {
|
||||
"title": "Configure Inventory",
|
||||
"subtitle": "Upload your historical sales data",
|
||||
"upload": {
|
||||
"title": "Upload Sales Data",
|
||||
"description": "Upload a CSV file with your historical sales data to train the AI",
|
||||
"drag_drop": "Drag and drop your CSV file here",
|
||||
"or": "or",
|
||||
"browse": "select a file",
|
||||
"supported_formats": "Supported formats: CSV",
|
||||
"max_size": "Maximum size: 10MB"
|
||||
},
|
||||
"sample": {
|
||||
"download": "Download CSV template",
|
||||
"example": "View data example"
|
||||
},
|
||||
"processing": {
|
||||
"uploading": "Uploading file...",
|
||||
"processing": "Processing data...",
|
||||
"success": "Data processed successfully",
|
||||
"error": "Error processing data"
|
||||
}
|
||||
},
|
||||
"ml_training": {
|
||||
"title": "AI Training",
|
||||
"subtitle": "Creating your personalized model",
|
||||
"status": {
|
||||
"preparing": "Preparing data...",
|
||||
"training": "Training model...",
|
||||
"validating": "Validating results...",
|
||||
"completed": "Training completed"
|
||||
},
|
||||
"progress": {
|
||||
"data_preparation": "Data preparation",
|
||||
"model_training": "Model training",
|
||||
"validation": "Validation",
|
||||
"deployment": "Deployment"
|
||||
},
|
||||
"estimated_time": "Estimated time: {{minutes}} minutes",
|
||||
"description": "We're creating a personalized AI model for your bakery based on your historical data."
|
||||
},
|
||||
"completion": {
|
||||
"title": "Setup Complete!",
|
||||
"subtitle": "Your bakery is ready to use AI",
|
||||
"success_message": "Congratulations, you have successfully completed the initial setup.",
|
||||
"next_steps": {
|
||||
"title": "Next steps:",
|
||||
"dashboard": "Explore your dashboard",
|
||||
"first_prediction": "View your first prediction",
|
||||
"inventory": "Configure your inventory",
|
||||
"team": "Invite your team"
|
||||
},
|
||||
"cta": {
|
||||
"dashboard": "Go to Dashboard",
|
||||
"tour": "Start Guided Tour"
|
||||
},
|
||||
"features_unlocked": {
|
||||
"title": "Features unlocked:",
|
||||
"ai_forecasting": "AI demand forecasting",
|
||||
"inventory_management": "Inventory management",
|
||||
"production_planning": "Production planning",
|
||||
"analytics": "Analytics and reports"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"step_failed": "Error in this step",
|
||||
"data_invalid": "Invalid data",
|
||||
"upload_failed": "File upload error",
|
||||
"training_failed": "Training error",
|
||||
"network_error": "Connection error",
|
||||
"try_again": "Try again",
|
||||
"contact_support": "Contact support"
|
||||
},
|
||||
"validation": {
|
||||
"required": "This field is required",
|
||||
"invalid_email": "Invalid email",
|
||||
"invalid_phone": "Invalid phone",
|
||||
"invalid_url": "Invalid URL",
|
||||
"file_too_large": "File too large",
|
||||
"invalid_file_type": "Invalid file type"
|
||||
}
|
||||
}
|
||||
88
frontend/src/locales/en/sales.json
Normal file
88
frontend/src/locales/en/sales.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"orders": {
|
||||
"title": "Orders",
|
||||
"table": {
|
||||
"columns": {
|
||||
"order_number": "Order No.",
|
||||
"customer": "Customer",
|
||||
"products": "Products",
|
||||
"total": "Total",
|
||||
"status": "Status",
|
||||
"channel": "Channel",
|
||||
"date": "Date",
|
||||
"actions": "Actions"
|
||||
},
|
||||
"filters": {
|
||||
"all_status": "All status",
|
||||
"all_channels": "All channels",
|
||||
"items_per_page": {
|
||||
"10": "10 per page",
|
||||
"20": "20 per page",
|
||||
"50": "50 per page",
|
||||
"100": "100 per page"
|
||||
}
|
||||
},
|
||||
"bulk_actions": "Bulk actions",
|
||||
"no_orders": "No orders available",
|
||||
"loading": "Loading orders...",
|
||||
"error": "Error loading orders"
|
||||
},
|
||||
"status": {
|
||||
"pendiente": "Pending",
|
||||
"confirmado": "Confirmed",
|
||||
"en_preparacion": "In Preparation",
|
||||
"listo": "Ready for Delivery",
|
||||
"entregado": "Delivered",
|
||||
"cancelado": "Cancelled"
|
||||
},
|
||||
"channels": {
|
||||
"store_front": "Store",
|
||||
"online": "Online",
|
||||
"phone_order": "Phone",
|
||||
"delivery": "Delivery",
|
||||
"catering": "Catering",
|
||||
"wholesale": "Wholesale",
|
||||
"farmers_market": "Market",
|
||||
"third_party": "Third Party"
|
||||
},
|
||||
"payment_methods": {
|
||||
"cash": "Cash",
|
||||
"credit_card": "Credit Card",
|
||||
"debit_card": "Debit Card",
|
||||
"digital_wallet": "Digital Wallet",
|
||||
"bank_transfer": "Bank Transfer",
|
||||
"check": "Check",
|
||||
"store_credit": "Store Credit"
|
||||
},
|
||||
"actions": {
|
||||
"view": "View",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"print": "Print",
|
||||
"duplicate": "Duplicate",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"complete": "Complete"
|
||||
}
|
||||
},
|
||||
"customers": {
|
||||
"title": "Customers",
|
||||
"name": "Name",
|
||||
"phone": "Phone",
|
||||
"email": "Email",
|
||||
"address": "Address",
|
||||
"orders_count": "Orders",
|
||||
"total_spent": "Total spent",
|
||||
"last_order": "Last order"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Sales Analytics",
|
||||
"revenue": "Revenue",
|
||||
"orders": "Orders",
|
||||
"avg_order": "Average order",
|
||||
"growth": "Growth",
|
||||
"trends": "Trends",
|
||||
"top_products": "Top products",
|
||||
"top_customers": "Top customers"
|
||||
}
|
||||
}
|
||||
91
frontend/src/locales/en/settings.json
Normal file
91
frontend/src/locales/en/settings.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"profile": {
|
||||
"title": "User Profile",
|
||||
"description": "Manage your personal information and preferences",
|
||||
"personal_info": "Personal Information",
|
||||
"edit_profile": "Edit Profile",
|
||||
"change_password": "Change Password",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"save_changes": "Save Changes",
|
||||
"cancel": "Cancel",
|
||||
"fields": {
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"email": "Email Address",
|
||||
"phone": "Phone Number",
|
||||
"language": "Language",
|
||||
"timezone": "Timezone",
|
||||
"avatar": "Avatar"
|
||||
},
|
||||
"password": {
|
||||
"current_password": "Current Password",
|
||||
"new_password": "New Password",
|
||||
"confirm_password": "Confirm Password",
|
||||
"change_password": "Change Password",
|
||||
"password_requirements": "Password must be at least 8 characters long"
|
||||
}
|
||||
},
|
||||
"team": {
|
||||
"title": "Team",
|
||||
"description": "Manage your team members and their permissions",
|
||||
"invite_member": "Invite Member",
|
||||
"members": "Members",
|
||||
"pending_invitations": "Pending Invitations",
|
||||
"role": "Role",
|
||||
"status": "Status",
|
||||
"actions": "Actions"
|
||||
},
|
||||
"organization": {
|
||||
"title": "Organizations",
|
||||
"description": "Manage your organizations and settings",
|
||||
"current_organization": "Current Organization",
|
||||
"switch_organization": "Switch Organization",
|
||||
"create_organization": "Create Organization"
|
||||
},
|
||||
"bakery_config": {
|
||||
"title": "Bakery Configuration",
|
||||
"description": "Configure your bakery-specific settings",
|
||||
"general": "General",
|
||||
"products": "Products",
|
||||
"hours": "Operating Hours",
|
||||
"notifications": "Notifications"
|
||||
},
|
||||
"subscription": {
|
||||
"title": "Subscription",
|
||||
"description": "Manage your subscription plan",
|
||||
"current_plan": "Current Plan",
|
||||
"usage": "Usage",
|
||||
"billing": "Billing",
|
||||
"upgrade": "Upgrade Plan",
|
||||
"manage": "Manage Subscription"
|
||||
},
|
||||
"communication": {
|
||||
"title": "Communication Preferences",
|
||||
"description": "Configure how and when you receive notifications",
|
||||
"email_notifications": "Email Notifications",
|
||||
"push_notifications": "Push Notifications",
|
||||
"sms_notifications": "SMS Notifications",
|
||||
"marketing": "Marketing Communications",
|
||||
"alerts": "System Alerts"
|
||||
},
|
||||
"tabs": {
|
||||
"profile": "Profile",
|
||||
"team": "Team",
|
||||
"organization": "Organization",
|
||||
"bakery_config": "Configuration",
|
||||
"subscription": "Subscription",
|
||||
"communication": "Communication"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"loading": "Loading...",
|
||||
"success": "Success",
|
||||
"error": "Error",
|
||||
"required": "Required",
|
||||
"optional": "Optional"
|
||||
}
|
||||
}
|
||||
45
frontend/src/locales/en/traffic.json
Normal file
45
frontend/src/locales/en/traffic.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"title": "Traffic Analysis",
|
||||
"description": "Monitor customer flow and optimize service hours",
|
||||
"metrics": {
|
||||
"total_visitors": "Total Visitors",
|
||||
"peak_hour": "Peak Hour",
|
||||
"avg_duration": "Average Duration",
|
||||
"busy_days": "Busy Days",
|
||||
"conversion_rate": "Conversion Rate"
|
||||
},
|
||||
"periods": {
|
||||
"week": "Week",
|
||||
"month": "Month",
|
||||
"year": "Year"
|
||||
},
|
||||
"days": {
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"sunday": "Sunday",
|
||||
"mon": "Mon",
|
||||
"tue": "Tue",
|
||||
"wed": "Wed",
|
||||
"thu": "Thu",
|
||||
"fri": "Fri",
|
||||
"sat": "Sat",
|
||||
"sun": "Sun"
|
||||
},
|
||||
"sources": {
|
||||
"walking": "Walk-in",
|
||||
"local_search": "Local Search",
|
||||
"recommendations": "Recommendations",
|
||||
"social_media": "Social Media",
|
||||
"advertising": "Advertising"
|
||||
},
|
||||
"segments": {
|
||||
"morning_regulars": "Morning Regulars",
|
||||
"weekend_families": "Weekend Families",
|
||||
"lunch_office": "Lunch Office Workers",
|
||||
"occasional_customers": "Occasional Customers"
|
||||
}
|
||||
}
|
||||
51
frontend/src/locales/en/ui.json
Normal file
51
frontend/src/locales/en/ui.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"datepicker": {
|
||||
"placeholder": "Select date",
|
||||
"today": "Today",
|
||||
"clear": "Clear",
|
||||
"weekdays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||
"months": [
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
]
|
||||
},
|
||||
"password_criteria": {
|
||||
"min_length": "Minimum 8 characters",
|
||||
"max_length": "Maximum 128 characters",
|
||||
"uppercase": "At least one uppercase letter",
|
||||
"lowercase": "At least one lowercase letter",
|
||||
"number": "At least one number",
|
||||
"special": "At least one special character",
|
||||
"errors": {
|
||||
"min_length": "Password must be at least 8 characters long",
|
||||
"max_length": "Password cannot exceed 128 characters",
|
||||
"uppercase": "Password must contain at least one uppercase letter",
|
||||
"lowercase": "Password must contain at least one lowercase letter",
|
||||
"number": "Password must contain at least one number",
|
||||
"special": "Password must contain at least one special character"
|
||||
}
|
||||
},
|
||||
"avatar": {
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"away": "Away",
|
||||
"busy": "Busy"
|
||||
},
|
||||
"notifications": {
|
||||
"alert": "Alert",
|
||||
"recommendation": "Recommendation",
|
||||
"info": "Information",
|
||||
"warning": "Warning"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Loading...",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"close": "Close",
|
||||
"confirm": "Confirm"
|
||||
}
|
||||
}
|
||||
149
frontend/src/locales/en/weather.json
Normal file
149
frontend/src/locales/en/weather.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"title": "Weather Data",
|
||||
"description": "Integrate weather information to optimize production and sales",
|
||||
"current": {
|
||||
"title": "Current Conditions",
|
||||
"temperature": "Temperature",
|
||||
"humidity": "Humidity",
|
||||
"wind": "Wind",
|
||||
"pressure": "Pressure",
|
||||
"uv": "UV",
|
||||
"visibility": "Visibility",
|
||||
"favorable_conditions": "Favorable conditions"
|
||||
},
|
||||
"forecast": {
|
||||
"title": "Extended Forecast",
|
||||
"next_week": "Next Week",
|
||||
"next_month": "Next Month",
|
||||
"rain": "Rain"
|
||||
},
|
||||
"conditions": {
|
||||
"sunny": "Sunny",
|
||||
"partly_cloudy": "Partly cloudy",
|
||||
"cloudy": "Cloudy",
|
||||
"rainy": "Rainy"
|
||||
},
|
||||
"days": {
|
||||
"saturday": "Saturday",
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday"
|
||||
},
|
||||
"impact": {
|
||||
"title": "Weather Impact",
|
||||
"high_demand": "High Demand",
|
||||
"comfort_food": "Comfort Food",
|
||||
"moderate": "Moderate Demand",
|
||||
"normal": "Normal Demand",
|
||||
"recommendations": "Recommendations"
|
||||
},
|
||||
"impacts": {
|
||||
"sunny_day": {
|
||||
"condition": "Sunny Day",
|
||||
"impact": "25% increase in cold drinks",
|
||||
"recommendations": [
|
||||
"Increase ice cream production",
|
||||
"More refreshing drinks",
|
||||
"Salads and fresh products",
|
||||
"Extended terrace hours"
|
||||
]
|
||||
},
|
||||
"rainy_day": {
|
||||
"condition": "Rainy Day",
|
||||
"impact": "40% increase in hot products",
|
||||
"recommendations": [
|
||||
"More soups and broths",
|
||||
"Hot chocolates",
|
||||
"Freshly baked bread",
|
||||
"Pastry products"
|
||||
]
|
||||
},
|
||||
"cold_day": {
|
||||
"condition": "Intense Cold",
|
||||
"impact": "Preference for comfort food",
|
||||
"recommendations": [
|
||||
"Increase baked goods",
|
||||
"Special hot drinks",
|
||||
"Energy products",
|
||||
"Indoor promotions"
|
||||
]
|
||||
}
|
||||
},
|
||||
"seasonal": {
|
||||
"title": "Seasonal Trends",
|
||||
"spring": {
|
||||
"name": "Spring",
|
||||
"period": "Mar - May",
|
||||
"avg_temp": "15-20°C",
|
||||
"trends": [
|
||||
"Increase in fresh products (+30%)",
|
||||
"Higher demand for salads",
|
||||
"Popular natural drinks",
|
||||
"Effective extended hours"
|
||||
]
|
||||
},
|
||||
"summer": {
|
||||
"name": "Summer",
|
||||
"period": "Jun - Aug",
|
||||
"avg_temp": "25-35°C",
|
||||
"trends": [
|
||||
"Peak of ice cream and slushies (+60%)",
|
||||
"Light products preferred",
|
||||
"Critical morning hours",
|
||||
"Higher tourist traffic"
|
||||
]
|
||||
},
|
||||
"autumn": {
|
||||
"name": "Autumn",
|
||||
"period": "Sep - Nov",
|
||||
"avg_temp": "10-18°C",
|
||||
"trends": [
|
||||
"Return to traditional products",
|
||||
"Increase in pastries (+20%)",
|
||||
"Popular hot drinks",
|
||||
"Regular schedules"
|
||||
]
|
||||
},
|
||||
"winter": {
|
||||
"name": "Winter",
|
||||
"period": "Dec - Feb",
|
||||
"avg_temp": "5-12°C",
|
||||
"trends": [
|
||||
"Maximum hot products (+50%)",
|
||||
"Critical freshly baked bread",
|
||||
"Festive chocolates and sweets",
|
||||
"Lower general traffic (-15%)"
|
||||
]
|
||||
},
|
||||
"impact_levels": {
|
||||
"high": "High",
|
||||
"positive": "Positive",
|
||||
"comfort": "Comfort",
|
||||
"stable": "Stable"
|
||||
}
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Weather Alerts",
|
||||
"heat_wave": {
|
||||
"title": "Heat wave expected",
|
||||
"description": "Temperatures above 30°C expected for the next 3 days",
|
||||
"recommendation": "Increase stock of cold drinks and ice cream"
|
||||
},
|
||||
"heavy_rain": {
|
||||
"title": "Heavy rain on Monday",
|
||||
"description": "80% chance of precipitation with strong winds",
|
||||
"recommendation": "Prepare more hot products and shelter items"
|
||||
},
|
||||
"recommendation_label": "Recommendation"
|
||||
},
|
||||
"recommendations": {
|
||||
"increase_ice_cream": "Increase ice cream and cold drinks production",
|
||||
"standard_production": "Standard production",
|
||||
"comfort_foods": "Increase soups, hot chocolates and freshly baked bread",
|
||||
"indoor_focus": "Focus on indoor products",
|
||||
"fresh_products": "Increase fresh products and salads"
|
||||
}
|
||||
}
|
||||
@@ -237,5 +237,58 @@
|
||||
"link": "Enlace",
|
||||
"tooltip": "Información adicional",
|
||||
"search": "Buscar en la aplicación"
|
||||
},
|
||||
"app": {
|
||||
"name": "Panadería IA",
|
||||
"full_name": "Panadería IA - Sistema Inteligente"
|
||||
},
|
||||
"profile": {
|
||||
"my_profile": "Mi perfil",
|
||||
"my_locations": "Mis Locales",
|
||||
"settings": "Configuración",
|
||||
"profile": "Perfil",
|
||||
"logout": "Cerrar Sesión",
|
||||
"profile_menu": "Menú de perfil",
|
||||
"close_navigation": "Cerrar navegación"
|
||||
},
|
||||
"header": {
|
||||
"main_navigation": "Navegación principal",
|
||||
"open_menu": "Abrir menú de navegación"
|
||||
},
|
||||
"footer": {
|
||||
"company_description": "Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.",
|
||||
"sections": {
|
||||
"product": "Producto",
|
||||
"support": "Soporte",
|
||||
"company": "Empresa"
|
||||
},
|
||||
"links": {
|
||||
"dashboard": "Panel de Control",
|
||||
"inventory": "Inventario",
|
||||
"production": "Producción",
|
||||
"sales": "Ventas",
|
||||
"forecasting": "Predicciones",
|
||||
"help": "Centro de Ayuda",
|
||||
"docs": "Documentación",
|
||||
"contact": "Contacto",
|
||||
"feedback": "Feedback",
|
||||
"about": "Acerca de",
|
||||
"blog": "Blog",
|
||||
"careers": "Carreras",
|
||||
"press": "Prensa",
|
||||
"privacy": "Privacidad",
|
||||
"terms": "Términos",
|
||||
"cookies": "Cookies"
|
||||
},
|
||||
"social_follow": "Síguenos en redes sociales",
|
||||
"social_labels": {
|
||||
"twitter": "Twitter",
|
||||
"linkedin": "LinkedIn",
|
||||
"github": "GitHub"
|
||||
}
|
||||
},
|
||||
"breadcrumbs": {
|
||||
"home": "Inicio",
|
||||
"truncation": "..."
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,43 @@
|
||||
"manage_staff": "Gestionar Personal"
|
||||
},
|
||||
"alerts": {
|
||||
"low_stock": "Stock Bajo",
|
||||
"production_delay": "Retraso en Producción",
|
||||
"quality_issue": "Problema de Calidad",
|
||||
"equipment_maintenance": "Mantenimiento de Equipo",
|
||||
"order_pending": "Pedido Pendiente",
|
||||
"delivery_due": "Entrega Vencida"
|
||||
"title": "Alertas",
|
||||
"live": "En vivo",
|
||||
"offline": "Desconectado",
|
||||
"no_alerts": "No hay alertas activas",
|
||||
"view_all": "Ver todas las alertas",
|
||||
"time": {
|
||||
"now": "Ahora",
|
||||
"minutes_ago": "hace {{count}} min",
|
||||
"hours_ago": "hace {{count}} h",
|
||||
"yesterday": "Ayer"
|
||||
},
|
||||
"types": {
|
||||
"low_stock": "Stock Bajo",
|
||||
"production_delay": "Retraso en Producción",
|
||||
"quality_issue": "Problema de Calidad",
|
||||
"equipment_maintenance": "Mantenimiento de Equipo",
|
||||
"order_pending": "Pedido Pendiente",
|
||||
"delivery_due": "Entrega Vencida",
|
||||
"critical": "Crítico",
|
||||
"warning": "Advertencia",
|
||||
"info": "Información",
|
||||
"success": "Éxito"
|
||||
},
|
||||
"status": {
|
||||
"new": "Nuevo",
|
||||
"acknowledged": "Reconocido",
|
||||
"resolved": "Resuelto"
|
||||
},
|
||||
"types": {
|
||||
"alert": "Alerta",
|
||||
"recommendation": "Recomendación"
|
||||
},
|
||||
"recommended_actions": "Acciones Recomendadas",
|
||||
"additional_details": "Detalles Adicionales",
|
||||
"mark_as_read": "Marcar como leído",
|
||||
"remove": "Eliminar",
|
||||
"active_count": "{{count}} alertas activas"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "Bienvenido de vuelta",
|
||||
|
||||
34
frontend/src/locales/es/database.json
Normal file
34
frontend/src/locales/es/database.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Mi Panadería",
|
||||
"subtitle": "Consulta y gestiona toda la información de tu panadería",
|
||||
"sections": {
|
||||
"recipes": {
|
||||
"title": "Recetas",
|
||||
"description": "Gestiona las recetas de tus productos"
|
||||
},
|
||||
"orders": {
|
||||
"title": "Pedidos",
|
||||
"description": "Consulta el estado de todos los pedidos"
|
||||
},
|
||||
"suppliers": {
|
||||
"title": "Proveedores",
|
||||
"description": "Gestiona tus proveedores"
|
||||
},
|
||||
"inventory": {
|
||||
"title": "Inventario",
|
||||
"description": "Estado actual del inventario"
|
||||
},
|
||||
"bakery_config": {
|
||||
"title": "Configuración de Panadería",
|
||||
"description": "Configuración general de tu panadería"
|
||||
},
|
||||
"team_management": {
|
||||
"title": "Gestión de Equipo",
|
||||
"description": "Administra tu equipo de trabajo"
|
||||
},
|
||||
"communication_preferences": {
|
||||
"title": "Preferencias de Comunicación",
|
||||
"description": "Configura notificaciones y comunicaciones"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,5 +199,16 @@
|
||||
"reload_page": "Recargar página",
|
||||
"clear_cache": "Limpiar caché",
|
||||
"check_permissions": "Verificar permisos"
|
||||
},
|
||||
"boundary": {
|
||||
"title": "Oops! Algo salió mal",
|
||||
"description": "Ha ocurrido un error inesperado en la aplicación. Nuestro equipo ha sido notificado.",
|
||||
"technical_details": "Detalles técnicos",
|
||||
"actions": {
|
||||
"retry": "Reintentar",
|
||||
"reload": "Recargar página",
|
||||
"go_back": "Volver atrás",
|
||||
"report": "Reportar este error"
|
||||
}
|
||||
}
|
||||
}
|
||||
25
frontend/src/locales/es/events.json
Normal file
25
frontend/src/locales/es/events.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"title": "Registro de Eventos",
|
||||
"description": "Monitorea la actividad del sistema y eventos importantes",
|
||||
"categories": {
|
||||
"all": "Todos",
|
||||
"sales": "Ventas",
|
||||
"production": "Producción",
|
||||
"inventory": "Inventario",
|
||||
"system": "Sistema",
|
||||
"customer": "Clientes"
|
||||
},
|
||||
"types": {
|
||||
"order_completed": "Pedido Completado",
|
||||
"batch_started": "Lote Iniciado",
|
||||
"stock_updated": "Stock Actualizado",
|
||||
"customer_registered": "Cliente Registrado",
|
||||
"system_alert": "Alerta del Sistema"
|
||||
},
|
||||
"severity": {
|
||||
"info": "Información",
|
||||
"warning": "Advertencia",
|
||||
"error": "Error",
|
||||
"success": "Éxito"
|
||||
}
|
||||
}
|
||||
127
frontend/src/locales/es/landing.json
Normal file
127
frontend/src/locales/es/landing.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"navigation": {
|
||||
"features": "Características",
|
||||
"benefits": "Beneficios",
|
||||
"pricing": "Precios",
|
||||
"testimonials": "Testimonios"
|
||||
},
|
||||
"hero": {
|
||||
"badge": "IA Avanzada para Panaderías",
|
||||
"title_line1": "Revoluciona tu",
|
||||
"title_line2": "Panadería con IA",
|
||||
"subtitle": "Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%, predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial.",
|
||||
"cta_primary": "Comenzar Gratis 14 Días",
|
||||
"cta_secondary": "Ver Demo en Vivo",
|
||||
"features": {
|
||||
"no_credit_card": "Sin tarjeta de crédito",
|
||||
"quick_setup": "Configuración en 5 minutos",
|
||||
"support_24_7": "Soporte 24/7 en español"
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"title": "Todo lo que necesitas para optimizar tu panadería",
|
||||
"subtitle": "Herramientas potentes diseñadas específicamente para panaderías modernas",
|
||||
"ai_forecasting": {
|
||||
"title": "Predicción IA de Demanda",
|
||||
"description": "Algoritmos avanzados predicen qué productos necesitarás cada día con 92% de precisión"
|
||||
},
|
||||
"production_optimization": {
|
||||
"title": "Optimización de Producción",
|
||||
"description": "Planifica automáticamente horarios de horneado y gestión de personal para máxima eficiencia"
|
||||
},
|
||||
"waste_reduction": {
|
||||
"title": "Reducción de Desperdicios",
|
||||
"description": "Reduce hasta 35% el desperdicio con predicciones precisas y gestión inteligente de inventario"
|
||||
},
|
||||
"real_time_analytics": {
|
||||
"title": "Análisis en Tiempo Real",
|
||||
"description": "Dashboard intuitivo con métricas de ventas, producción y rentabilidad actualizadas al instante"
|
||||
},
|
||||
"inventory_management": {
|
||||
"title": "Gestión de Inventario",
|
||||
"description": "Control automático de stock con alertas inteligentes y órdenes de compra automatizadas"
|
||||
},
|
||||
"customer_insights": {
|
||||
"title": "Insights de Clientes",
|
||||
"description": "Entiende patrones de compra y preferencias para mejorar la experiencia del cliente"
|
||||
}
|
||||
},
|
||||
"benefits": {
|
||||
"title": "Resultados comprobados que transforman tu negocio",
|
||||
"subtitle": "Más de 1,000 panaderías ya han transformado sus operaciones con nuestra IA",
|
||||
"waste_reduction": {
|
||||
"value": "35%",
|
||||
"label": "Reducción en desperdicios"
|
||||
},
|
||||
"accuracy": {
|
||||
"value": "92%",
|
||||
"label": "Precisión en predicciones"
|
||||
},
|
||||
"time_saved": {
|
||||
"value": "4h",
|
||||
"label": "Ahorro diario en planificación"
|
||||
},
|
||||
"sales_increase": {
|
||||
"value": "28%",
|
||||
"label": "Incremento promedio en ventas"
|
||||
}
|
||||
},
|
||||
"pricing": {
|
||||
"title": "Planes diseñados para panaderías de todos los tamaños",
|
||||
"subtitle": "Comienza gratis y escala según crezcas",
|
||||
"starter": {
|
||||
"name": "Iniciante",
|
||||
"price": "Gratis",
|
||||
"description": "Perfecto para panaderías pequeñas que empiezan",
|
||||
"features": [
|
||||
"Hasta 50 productos",
|
||||
"Predicción básica de demanda",
|
||||
"Dashboard básico",
|
||||
"Soporte por email"
|
||||
]
|
||||
},
|
||||
"professional": {
|
||||
"name": "Profesional",
|
||||
"price": "€49",
|
||||
"price_period": "/mes",
|
||||
"description": "Ideal para panaderías establecidas",
|
||||
"features": [
|
||||
"Productos ilimitados",
|
||||
"IA avanzada de predicción",
|
||||
"Analytics completos",
|
||||
"Gestión de inventario",
|
||||
"Optimización de producción",
|
||||
"Soporte prioritario 24/7"
|
||||
]
|
||||
},
|
||||
"enterprise": {
|
||||
"name": "Empresa",
|
||||
"price": "Personalizado",
|
||||
"description": "Solución completa para cadenas y franquicias",
|
||||
"features": [
|
||||
"Multi-ubicación",
|
||||
"API personalizada",
|
||||
"Integraciones avanzadas",
|
||||
"Soporte dedicado",
|
||||
"Capacitación personalizada",
|
||||
"SLA garantizado"
|
||||
]
|
||||
},
|
||||
"cta": "Comenzar Ahora",
|
||||
"contact": "Contactar Ventas"
|
||||
},
|
||||
"testimonials": {
|
||||
"title": "Lo que dicen nuestros clientes",
|
||||
"subtitle": "Panaderías de todo el mundo confían en nuestra plataforma"
|
||||
},
|
||||
"cta_section": {
|
||||
"title": "¿Listo para revolucionar tu panadería?",
|
||||
"subtitle": "Únete a más de 1,000 panaderías que ya están usando IA para optimizar sus operaciones",
|
||||
"cta": "Comenzar Gratis Hoy"
|
||||
},
|
||||
"footer_cta": {
|
||||
"security": "Seguridad empresarial",
|
||||
"uptime": "99.9% uptime garantizado",
|
||||
"data_protection": "Protección de datos RGPD"
|
||||
}
|
||||
}
|
||||
141
frontend/src/locales/es/onboarding.json
Normal file
141
frontend/src/locales/es/onboarding.json
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"wizard": {
|
||||
"title": "Configuración Inicial",
|
||||
"subtitle": "Te guiaremos paso a paso para configurar tu panadería",
|
||||
"steps": {
|
||||
"setup": {
|
||||
"title": "Registrar Panadería",
|
||||
"description": "Configura la información básica de tu panadería"
|
||||
},
|
||||
"smart_inventory_setup": {
|
||||
"title": "Configurar Inventario",
|
||||
"description": "Sube datos de ventas y configura tu inventario inicial"
|
||||
},
|
||||
"ml_training": {
|
||||
"title": "Entrenamiento IA",
|
||||
"description": "Entrena tu modelo de inteligencia artificial personalizado"
|
||||
},
|
||||
"completion": {
|
||||
"title": "Configuración Completa",
|
||||
"description": "¡Bienvenido a tu sistema de gestión inteligente!"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"previous": "Anterior",
|
||||
"next": "Siguiente",
|
||||
"complete": "Completar",
|
||||
"skip": "Omitir",
|
||||
"finish": "Finalizar"
|
||||
},
|
||||
"progress": {
|
||||
"step_of": "Paso {{current}} de {{total}}",
|
||||
"completed": "Completado",
|
||||
"in_progress": "En progreso",
|
||||
"pending": "Pendiente"
|
||||
}
|
||||
},
|
||||
"steps": {
|
||||
"tenant_registration": {
|
||||
"title": "Información de tu Panadería",
|
||||
"subtitle": "Cuéntanos sobre tu negocio",
|
||||
"fields": {
|
||||
"business_name": "Nombre del negocio",
|
||||
"business_type": "Tipo de negocio",
|
||||
"address": "Dirección",
|
||||
"phone": "Teléfono",
|
||||
"email": "Email de contacto",
|
||||
"website": "Sitio web (opcional)",
|
||||
"description": "Descripción del negocio"
|
||||
},
|
||||
"placeholders": {
|
||||
"business_name": "Ej: Panadería San José",
|
||||
"address": "Calle Principal 123, Ciudad",
|
||||
"phone": "+34 123 456 789",
|
||||
"email": "contacto@panaderia.com",
|
||||
"website": "https://mipanaderia.com",
|
||||
"description": "Describe tu panadería..."
|
||||
}
|
||||
},
|
||||
"inventory_setup": {
|
||||
"title": "Configurar Inventario",
|
||||
"subtitle": "Sube tus datos de ventas históricos",
|
||||
"upload": {
|
||||
"title": "Subir Datos de Ventas",
|
||||
"description": "Sube un archivo CSV con tus datos históricos de ventas para entrenar la IA",
|
||||
"drag_drop": "Arrastra y suelta tu archivo CSV aquí",
|
||||
"or": "o",
|
||||
"browse": "selecciona un archivo",
|
||||
"supported_formats": "Formatos soportados: CSV",
|
||||
"max_size": "Tamaño máximo: 10MB"
|
||||
},
|
||||
"sample": {
|
||||
"download": "Descargar plantilla CSV",
|
||||
"example": "Ver ejemplo de datos"
|
||||
},
|
||||
"processing": {
|
||||
"uploading": "Subiendo archivo...",
|
||||
"processing": "Procesando datos...",
|
||||
"success": "Datos procesados exitosamente",
|
||||
"error": "Error al procesar los datos"
|
||||
}
|
||||
},
|
||||
"ml_training": {
|
||||
"title": "Entrenamiento de IA",
|
||||
"subtitle": "Creando tu modelo personalizado",
|
||||
"status": {
|
||||
"preparing": "Preparando datos...",
|
||||
"training": "Entrenando modelo...",
|
||||
"validating": "Validando resultados...",
|
||||
"completed": "Entrenamiento completado"
|
||||
},
|
||||
"progress": {
|
||||
"data_preparation": "Preparación de datos",
|
||||
"model_training": "Entrenamiento del modelo",
|
||||
"validation": "Validación",
|
||||
"deployment": "Despliegue"
|
||||
},
|
||||
"estimated_time": "Tiempo estimado: {{minutes}} minutos",
|
||||
"description": "Estamos creando un modelo de IA personalizado para tu panadería basado en tus datos históricos."
|
||||
},
|
||||
"completion": {
|
||||
"title": "¡Configuración Completa!",
|
||||
"subtitle": "Tu panadería está lista para usar IA",
|
||||
"success_message": "Felicitaciones, has completado exitosamente la configuración inicial.",
|
||||
"next_steps": {
|
||||
"title": "Próximos pasos:",
|
||||
"dashboard": "Explora tu dashboard",
|
||||
"first_prediction": "Ve tu primera predicción",
|
||||
"inventory": "Configura tu inventario",
|
||||
"team": "Invita a tu equipo"
|
||||
},
|
||||
"cta": {
|
||||
"dashboard": "Ir al Dashboard",
|
||||
"tour": "Iniciar Tour Guiado"
|
||||
},
|
||||
"features_unlocked": {
|
||||
"title": "Características desbloqueadas:",
|
||||
"ai_forecasting": "Predicción IA de demanda",
|
||||
"inventory_management": "Gestión de inventario",
|
||||
"production_planning": "Planificación de producción",
|
||||
"analytics": "Análisis y reportes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"step_failed": "Error en este paso",
|
||||
"data_invalid": "Datos inválidos",
|
||||
"upload_failed": "Error al subir archivo",
|
||||
"training_failed": "Error en entrenamiento",
|
||||
"network_error": "Error de conexión",
|
||||
"try_again": "Intentar de nuevo",
|
||||
"contact_support": "Contactar soporte"
|
||||
},
|
||||
"validation": {
|
||||
"required": "Este campo es requerido",
|
||||
"invalid_email": "Email inválido",
|
||||
"invalid_phone": "Teléfono inválido",
|
||||
"invalid_url": "URL inválida",
|
||||
"file_too_large": "Archivo demasiado grande",
|
||||
"invalid_file_type": "Tipo de archivo no válido"
|
||||
}
|
||||
}
|
||||
88
frontend/src/locales/es/sales.json
Normal file
88
frontend/src/locales/es/sales.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"orders": {
|
||||
"title": "Pedidos",
|
||||
"table": {
|
||||
"columns": {
|
||||
"order_number": "Nº Pedido",
|
||||
"customer": "Cliente",
|
||||
"products": "Productos",
|
||||
"total": "Total",
|
||||
"status": "Estado",
|
||||
"channel": "Canal",
|
||||
"date": "Fecha",
|
||||
"actions": "Acciones"
|
||||
},
|
||||
"filters": {
|
||||
"all_status": "Todos los estados",
|
||||
"all_channels": "Todos los canales",
|
||||
"items_per_page": {
|
||||
"10": "10 por página",
|
||||
"20": "20 por página",
|
||||
"50": "50 por página",
|
||||
"100": "100 por página"
|
||||
}
|
||||
},
|
||||
"bulk_actions": "Acciones masivas",
|
||||
"no_orders": "No hay pedidos disponibles",
|
||||
"loading": "Cargando pedidos...",
|
||||
"error": "Error al cargar pedidos"
|
||||
},
|
||||
"status": {
|
||||
"pendiente": "Pendiente",
|
||||
"confirmado": "Confirmado",
|
||||
"en_preparacion": "En Preparación",
|
||||
"listo": "Listo para Entrega",
|
||||
"entregado": "Entregado",
|
||||
"cancelado": "Cancelado"
|
||||
},
|
||||
"channels": {
|
||||
"store_front": "Tienda",
|
||||
"online": "Online",
|
||||
"phone_order": "Teléfono",
|
||||
"delivery": "Delivery",
|
||||
"catering": "Catering",
|
||||
"wholesale": "Mayorista",
|
||||
"farmers_market": "Mercado",
|
||||
"third_party": "Terceros"
|
||||
},
|
||||
"payment_methods": {
|
||||
"cash": "Efectivo",
|
||||
"credit_card": "Tarjeta Crédito",
|
||||
"debit_card": "Tarjeta Débito",
|
||||
"digital_wallet": "Wallet Digital",
|
||||
"bank_transfer": "Transferencia",
|
||||
"check": "Cheque",
|
||||
"store_credit": "Crédito Tienda"
|
||||
},
|
||||
"actions": {
|
||||
"view": "Ver",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"print": "Imprimir",
|
||||
"duplicate": "Duplicar",
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"complete": "Completar"
|
||||
}
|
||||
},
|
||||
"customers": {
|
||||
"title": "Clientes",
|
||||
"name": "Nombre",
|
||||
"phone": "Teléfono",
|
||||
"email": "Email",
|
||||
"address": "Dirección",
|
||||
"orders_count": "Pedidos",
|
||||
"total_spent": "Total gastado",
|
||||
"last_order": "Último pedido"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Análisis de Ventas",
|
||||
"revenue": "Ingresos",
|
||||
"orders": "Pedidos",
|
||||
"avg_order": "Pedido promedio",
|
||||
"growth": "Crecimiento",
|
||||
"trends": "Tendencias",
|
||||
"top_products": "Productos más vendidos",
|
||||
"top_customers": "Mejores clientes"
|
||||
}
|
||||
}
|
||||
91
frontend/src/locales/es/settings.json
Normal file
91
frontend/src/locales/es/settings.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"profile": {
|
||||
"title": "Perfil de Usuario",
|
||||
"description": "Gestiona tu información personal y preferencias",
|
||||
"personal_info": "Información Personal",
|
||||
"edit_profile": "Editar Perfil",
|
||||
"change_password": "Cambiar Contraseña",
|
||||
"online": "En línea",
|
||||
"offline": "Desconectado",
|
||||
"save_changes": "Guardar Cambios",
|
||||
"cancel": "Cancelar",
|
||||
"fields": {
|
||||
"first_name": "Nombre",
|
||||
"last_name": "Apellidos",
|
||||
"email": "Correo Electrónico",
|
||||
"phone": "Teléfono",
|
||||
"language": "Idioma",
|
||||
"timezone": "Zona Horaria",
|
||||
"avatar": "Avatar"
|
||||
},
|
||||
"password": {
|
||||
"current_password": "Contraseña Actual",
|
||||
"new_password": "Nueva Contraseña",
|
||||
"confirm_password": "Confirmar Contraseña",
|
||||
"change_password": "Cambiar Contraseña",
|
||||
"password_requirements": "La contraseña debe tener al menos 8 caracteres"
|
||||
}
|
||||
},
|
||||
"team": {
|
||||
"title": "Equipo",
|
||||
"description": "Gestiona los miembros de tu equipo y sus permisos",
|
||||
"invite_member": "Invitar Miembro",
|
||||
"members": "Miembros",
|
||||
"pending_invitations": "Invitaciones Pendientes",
|
||||
"role": "Rol",
|
||||
"status": "Estado",
|
||||
"actions": "Acciones"
|
||||
},
|
||||
"organization": {
|
||||
"title": "Organizaciones",
|
||||
"description": "Gestiona tus organizaciones y configuraciones",
|
||||
"current_organization": "Organización Actual",
|
||||
"switch_organization": "Cambiar Organización",
|
||||
"create_organization": "Crear Organización"
|
||||
},
|
||||
"bakery_config": {
|
||||
"title": "Configuración de Panadería",
|
||||
"description": "Configura los ajustes específicos de tu panadería",
|
||||
"general": "General",
|
||||
"products": "Productos",
|
||||
"hours": "Horarios",
|
||||
"notifications": "Notificaciones"
|
||||
},
|
||||
"subscription": {
|
||||
"title": "Suscripción",
|
||||
"description": "Gestiona tu plan de suscripción",
|
||||
"current_plan": "Plan Actual",
|
||||
"usage": "Uso",
|
||||
"billing": "Facturación",
|
||||
"upgrade": "Actualizar Plan",
|
||||
"manage": "Gestionar Suscripción"
|
||||
},
|
||||
"communication": {
|
||||
"title": "Preferencias de Comunicación",
|
||||
"description": "Configura cómo y cuándo recibes notificaciones",
|
||||
"email_notifications": "Notificaciones por Email",
|
||||
"push_notifications": "Notificaciones Push",
|
||||
"sms_notifications": "Notificaciones SMS",
|
||||
"marketing": "Comunicaciones de Marketing",
|
||||
"alerts": "Alertas del Sistema"
|
||||
},
|
||||
"tabs": {
|
||||
"profile": "Perfil",
|
||||
"team": "Equipo",
|
||||
"organization": "Organización",
|
||||
"bakery_config": "Configuración",
|
||||
"subscription": "Suscripción",
|
||||
"communication": "Comunicación"
|
||||
},
|
||||
"common": {
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"loading": "Cargando...",
|
||||
"success": "Éxito",
|
||||
"error": "Error",
|
||||
"required": "Requerido",
|
||||
"optional": "Opcional"
|
||||
}
|
||||
}
|
||||
45
frontend/src/locales/es/traffic.json
Normal file
45
frontend/src/locales/es/traffic.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"title": "Análisis de Tráfico",
|
||||
"description": "Monitorea el flujo de clientes y optimiza las horas de atención",
|
||||
"metrics": {
|
||||
"total_visitors": "Visitantes Totales",
|
||||
"peak_hour": "Hora Pico",
|
||||
"avg_duration": "Duración Promedio",
|
||||
"busy_days": "Días Ocupados",
|
||||
"conversion_rate": "Tasa de Conversión"
|
||||
},
|
||||
"periods": {
|
||||
"week": "Semana",
|
||||
"month": "Mes",
|
||||
"year": "Año"
|
||||
},
|
||||
"days": {
|
||||
"monday": "Lunes",
|
||||
"tuesday": "Martes",
|
||||
"wednesday": "Miércoles",
|
||||
"thursday": "Jueves",
|
||||
"friday": "Viernes",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo",
|
||||
"mon": "Lun",
|
||||
"tue": "Mar",
|
||||
"wed": "Mié",
|
||||
"thu": "Jue",
|
||||
"fri": "Vie",
|
||||
"sat": "Sáb",
|
||||
"sun": "Dom"
|
||||
},
|
||||
"sources": {
|
||||
"walking": "Pie",
|
||||
"local_search": "Búsqueda Local",
|
||||
"recommendations": "Recomendaciones",
|
||||
"social_media": "Redes Sociales",
|
||||
"advertising": "Publicidad"
|
||||
},
|
||||
"segments": {
|
||||
"morning_regulars": "Regulares Matutinos",
|
||||
"weekend_families": "Familia Fin de Semana",
|
||||
"lunch_office": "Oficinistas Almuerzo",
|
||||
"occasional_customers": "Clientes Ocasionales"
|
||||
}
|
||||
}
|
||||
51
frontend/src/locales/es/ui.json
Normal file
51
frontend/src/locales/es/ui.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"datepicker": {
|
||||
"placeholder": "Seleccionar fecha",
|
||||
"today": "Hoy",
|
||||
"clear": "Limpiar",
|
||||
"weekdays": ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"],
|
||||
"months": [
|
||||
"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
|
||||
"Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
|
||||
]
|
||||
},
|
||||
"password_criteria": {
|
||||
"min_length": "Mínimo 8 caracteres",
|
||||
"max_length": "Máximo 128 caracteres",
|
||||
"uppercase": "Al menos una letra mayúscula",
|
||||
"lowercase": "Al menos una letra minúscula",
|
||||
"number": "Al menos un número",
|
||||
"special": "Al menos un carácter especial",
|
||||
"errors": {
|
||||
"min_length": "La contraseña debe tener al menos 8 caracteres",
|
||||
"max_length": "La contraseña no puede exceder 128 caracteres",
|
||||
"uppercase": "La contraseña debe contener al menos una letra mayúscula",
|
||||
"lowercase": "La contraseña debe contener al menos una letra minúscula",
|
||||
"number": "La contraseña debe contener al menos un número",
|
||||
"special": "La contraseña debe contener al menos un carácter especial"
|
||||
}
|
||||
},
|
||||
"avatar": {
|
||||
"online": "En línea",
|
||||
"offline": "Desconectado",
|
||||
"away": "Ausente",
|
||||
"busy": "Ocupado"
|
||||
},
|
||||
"notifications": {
|
||||
"alert": "Alerta",
|
||||
"recommendation": "Recomendación",
|
||||
"info": "Información",
|
||||
"warning": "Advertencia"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Cargando...",
|
||||
"error": "Error",
|
||||
"success": "Éxito",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar",
|
||||
"delete": "Eliminar",
|
||||
"edit": "Editar",
|
||||
"close": "Cerrar",
|
||||
"confirm": "Confirmar"
|
||||
}
|
||||
}
|
||||
149
frontend/src/locales/es/weather.json
Normal file
149
frontend/src/locales/es/weather.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"title": "Datos Meteorológicos",
|
||||
"description": "Integra información del clima para optimizar la producción y ventas",
|
||||
"current": {
|
||||
"title": "Condiciones Actuales",
|
||||
"temperature": "Temperatura",
|
||||
"humidity": "Humedad",
|
||||
"wind": "Viento",
|
||||
"pressure": "Presión",
|
||||
"uv": "UV",
|
||||
"visibility": "Visibilidad",
|
||||
"favorable_conditions": "Condiciones favorables"
|
||||
},
|
||||
"forecast": {
|
||||
"title": "Pronóstico Extendido",
|
||||
"next_week": "Próxima Semana",
|
||||
"next_month": "Próximo Mes",
|
||||
"rain": "Lluvia"
|
||||
},
|
||||
"conditions": {
|
||||
"sunny": "Soleado",
|
||||
"partly_cloudy": "Parcialmente nublado",
|
||||
"cloudy": "Nublado",
|
||||
"rainy": "Lluvioso"
|
||||
},
|
||||
"days": {
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo",
|
||||
"monday": "Lunes",
|
||||
"tuesday": "Martes",
|
||||
"wednesday": "Miércoles",
|
||||
"thursday": "Jueves",
|
||||
"friday": "Viernes"
|
||||
},
|
||||
"impact": {
|
||||
"title": "Impacto del Clima",
|
||||
"high_demand": "Alta Demanda",
|
||||
"comfort_food": "Comida Reconfortante",
|
||||
"moderate": "Demanda Moderada",
|
||||
"normal": "Demanda Normal",
|
||||
"recommendations": "Recomendaciones"
|
||||
},
|
||||
"impacts": {
|
||||
"sunny_day": {
|
||||
"condition": "Día Soleado",
|
||||
"impact": "Aumento del 25% en bebidas frías",
|
||||
"recommendations": [
|
||||
"Incrementar producción de helados",
|
||||
"Más bebidas refrescantes",
|
||||
"Ensaladas y productos frescos",
|
||||
"Horario extendido de terraza"
|
||||
]
|
||||
},
|
||||
"rainy_day": {
|
||||
"condition": "Día Lluvioso",
|
||||
"impact": "Aumento del 40% en productos calientes",
|
||||
"recommendations": [
|
||||
"Más sopas y caldos",
|
||||
"Chocolates calientes",
|
||||
"Pan recién horneado",
|
||||
"Productos de repostería"
|
||||
]
|
||||
},
|
||||
"cold_day": {
|
||||
"condition": "Frío Intenso",
|
||||
"impact": "Preferencia por comida reconfortante",
|
||||
"recommendations": [
|
||||
"Aumentar productos horneados",
|
||||
"Bebidas calientes especiales",
|
||||
"Productos energéticos",
|
||||
"Promociones de interior"
|
||||
]
|
||||
}
|
||||
},
|
||||
"seasonal": {
|
||||
"title": "Tendencias Estacionales",
|
||||
"spring": {
|
||||
"name": "Primavera",
|
||||
"period": "Mar - May",
|
||||
"avg_temp": "15-20°C",
|
||||
"trends": [
|
||||
"Aumento en productos frescos (+30%)",
|
||||
"Mayor demanda de ensaladas",
|
||||
"Bebidas naturales populares",
|
||||
"Horarios extendidos efectivos"
|
||||
]
|
||||
},
|
||||
"summer": {
|
||||
"name": "Verano",
|
||||
"period": "Jun - Ago",
|
||||
"avg_temp": "25-35°C",
|
||||
"trends": [
|
||||
"Pico de helados y granizados (+60%)",
|
||||
"Productos ligeros preferidos",
|
||||
"Horario matutino crítico",
|
||||
"Mayor tráfico de turistas"
|
||||
]
|
||||
},
|
||||
"autumn": {
|
||||
"name": "Otoño",
|
||||
"period": "Sep - Nov",
|
||||
"avg_temp": "10-18°C",
|
||||
"trends": [
|
||||
"Regreso a productos tradicionales",
|
||||
"Aumento en bollería (+20%)",
|
||||
"Bebidas calientes populares",
|
||||
"Horarios regulares"
|
||||
]
|
||||
},
|
||||
"winter": {
|
||||
"name": "Invierno",
|
||||
"period": "Dec - Feb",
|
||||
"avg_temp": "5-12°C",
|
||||
"trends": [
|
||||
"Máximo de productos calientes (+50%)",
|
||||
"Pan recién horneado crítico",
|
||||
"Chocolates y dulces festivos",
|
||||
"Menor tráfico general (-15%)"
|
||||
]
|
||||
},
|
||||
"impact_levels": {
|
||||
"high": "Alto",
|
||||
"positive": "Positivo",
|
||||
"comfort": "Confort",
|
||||
"stable": "Estable"
|
||||
}
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Alertas Meteorológicas",
|
||||
"heat_wave": {
|
||||
"title": "Ola de calor prevista",
|
||||
"description": "Se esperan temperaturas superiores a 30°C los próximos 3 días",
|
||||
"recommendation": "Incrementar stock de bebidas frías y helados"
|
||||
},
|
||||
"heavy_rain": {
|
||||
"title": "Lluvia intensa el lunes",
|
||||
"description": "80% probabilidad de precipitación con vientos fuertes",
|
||||
"recommendation": "Preparar más productos calientes y de refugio"
|
||||
},
|
||||
"recommendation_label": "Recomendación"
|
||||
},
|
||||
"recommendations": {
|
||||
"increase_ice_cream": "Incrementar producción de helados y bebidas frías",
|
||||
"standard_production": "Producción estándar",
|
||||
"comfort_foods": "Aumentar sopas, chocolates calientes y pan recién horneado",
|
||||
"indoor_focus": "Enfoque en productos de interior",
|
||||
"fresh_products": "Incrementar productos frescos y ensaladas"
|
||||
}
|
||||
}
|
||||
@@ -1 +1,166 @@
|
||||
{}
|
||||
{
|
||||
"login": {
|
||||
"title": "Hasi saioa zure kontuan",
|
||||
"subtitle": "Sartu zure okindegiaren kontrol panelera",
|
||||
"email": "Helbide elektronikoa",
|
||||
"password": "Pasahitza",
|
||||
"remember_me": "Gogoratu nazazu",
|
||||
"forgot_password": "Pasahitza ahaztu duzu?",
|
||||
"login_button": "Hasi saioa",
|
||||
"logging_in": "Saioa hasten...",
|
||||
"no_account": "Ez duzu konturik?",
|
||||
"register_link": "Erregistratu hemen",
|
||||
"demo_account": "Erabili demo kontua",
|
||||
"welcome_back": "Ongi etorri berriz!",
|
||||
"invalid_credentials": "Kredentzial baliogabeak",
|
||||
"account_locked": "Kontua aldi baterako blokeatuta",
|
||||
"too_many_attempts": "Saiakera gehiegi huts egin"
|
||||
},
|
||||
"register": {
|
||||
"title": "Sortu zure kontua",
|
||||
"subtitle": "Sartu okindegiaren kudeaketa plataforman",
|
||||
"personal_info": "Informazio pertsonala",
|
||||
"company_info": "Enpresaren informazioa",
|
||||
"account_setup": "Kontuaren konfigurazioa",
|
||||
"first_name": "Izena",
|
||||
"last_name": "Abizena",
|
||||
"email": "Helbide elektronikoa",
|
||||
"phone": "Telefonoa",
|
||||
"company_name": "Okindegiaren izena",
|
||||
"company_type": "Negozio mota",
|
||||
"employee_count": "Langile kopurua",
|
||||
"password": "Pasahitza",
|
||||
"confirm_password": "Berretsi pasahitza",
|
||||
"password_requirements": "Gutxienez 8 karaktere, letra larriak, txikiak eta zenbakiak",
|
||||
"passwords_dont_match": "Pasahitzak ez datoz bat",
|
||||
"accept_terms": "Baldintza eta baldintzak onartzen ditut",
|
||||
"accept_privacy": "Pribatutasun politika onartzen dut",
|
||||
"marketing_consent": "Newsletter eta berriak jaso nahi ditut (aukerakoa)",
|
||||
"register_button": "Sortu kontua",
|
||||
"registering": "Kontua sortzen...",
|
||||
"have_account": "Dagoeneko baduzu kontua?",
|
||||
"login_link": "Hasi saioa hemen",
|
||||
"terms_link": "Zerbitzu baldintzak",
|
||||
"privacy_link": "Pribatutasun politika",
|
||||
"step_of": "{{current}}. urratsa {{total}}-tik",
|
||||
"continue": "Jarraitu",
|
||||
"back": "Atzera"
|
||||
},
|
||||
"forgot_password": {
|
||||
"title": "Berrezarri pasahitza",
|
||||
"subtitle": "Pasahitza berrezartzeko esteka bidaliko dizugu",
|
||||
"email": "Helbide elektronikoa",
|
||||
"send_reset_link": "Berrezartze esteka bidali",
|
||||
"sending": "Bidaltzen...",
|
||||
"reset_sent": "Berrezartze esteka bidali da",
|
||||
"reset_instructions": "Begiratu zure emaila pasahitza berrezartzeko argibideentzat",
|
||||
"back_to_login": "Itzuli saio hasierara",
|
||||
"didnt_receive": "Ez duzu emaila jaso?",
|
||||
"resend_link": "Birbidali esteka"
|
||||
},
|
||||
"reset_password": {
|
||||
"title": "Berrezarri pasahitza",
|
||||
"subtitle": "Sartu zure pasahitz berria",
|
||||
"new_password": "Pasahitz berria",
|
||||
"confirm_new_password": "Berretsi pasahitz berria",
|
||||
"reset_button": "Berrezarri pasahitza",
|
||||
"resetting": "Berrezartzen...",
|
||||
"password_reset_success": "Pasahitza behar bezala berrezarri da",
|
||||
"password_reset_error": "Errorea pasahitza berrezartzean",
|
||||
"invalid_token": "Berrezartze esteka baliogabea edo iraungi",
|
||||
"expired_token": "Berrezartze esteka iraungi egin da"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Nire profila",
|
||||
"personal_information": "Informazio Pertsonala",
|
||||
"account_settings": "Kontu ezarpenak",
|
||||
"security": "Segurtasuna",
|
||||
"preferences": "Hobespenak",
|
||||
"notifications": "Jakinarazpenak",
|
||||
"first_name": "Izena",
|
||||
"last_name": "Abizena",
|
||||
"email": "Helbide elektronikoa",
|
||||
"phone": "Telefonoa",
|
||||
"avatar": "Profileko argazkia",
|
||||
"change_avatar": "Aldatu argazkia",
|
||||
"current_password": "Oraingo pasahitza",
|
||||
"new_password": "Pasahitz berria",
|
||||
"confirm_password": "Berretsi pasahitza",
|
||||
"change_password": "Aldatu pasahitza",
|
||||
"two_factor_auth": "Bi faktoreko autentifikazioa",
|
||||
"enable_2fa": "Gaitu 2FA",
|
||||
"disable_2fa": "Desgaitu 2FA",
|
||||
"language": "Hizkuntza",
|
||||
"timezone": "Ordu zona",
|
||||
"theme": "Itxura",
|
||||
"theme_light": "Argia",
|
||||
"theme_dark": "Iluna",
|
||||
"theme_auto": "Automatikoa",
|
||||
"email_notifications": "Email jakinarazpenak",
|
||||
"push_notifications": "Push jakinarazpenak",
|
||||
"marketing_emails": "Marketing emailak",
|
||||
"save_changes": "Gorde aldaketak",
|
||||
"changes_saved": "Aldaketak behar bezala gorde dira",
|
||||
"profile_updated": "Profila behar bezala eguneratu da"
|
||||
},
|
||||
"logout": {
|
||||
"title": "Itxi saioa",
|
||||
"confirm": "Ziur zaude saioa itxi nahi duzula?",
|
||||
"logout_button": "Itxi saioa",
|
||||
"logging_out": "Saioa ixten...",
|
||||
"logged_out": "Saioa behar bezala itxi duzu",
|
||||
"goodbye": "Gero arte!"
|
||||
},
|
||||
"session": {
|
||||
"expired": "Zure saioa iraungitu da",
|
||||
"expires_soon": "Zure saioa laster iraungiko da",
|
||||
"extend_session": "Luzatu saioa",
|
||||
"login_required": "Saioa hasi behar duzu orri honetara sartzeko",
|
||||
"unauthorized": "Ez duzu baimenik orri honetara sartzeko",
|
||||
"forbidden": "Sarbidea ukatu",
|
||||
"session_timeout": "Saioa iraungitu da jarduera ezagatik"
|
||||
},
|
||||
"validation": {
|
||||
"email_required": "Helbide elektronikoa beharrezkoa da",
|
||||
"email_invalid": "Mesedez, sartu baliozko helbide elektroniko bat",
|
||||
"password_required": "Pasahitza beharrezkoa da",
|
||||
"password_min_length": "Pasahitzak gutxienez {{min}} karaktere izan behar ditu",
|
||||
"password_weak": "Pasahitza ahulegia da",
|
||||
"passwords_must_match": "Pasahitzak bat etorri behar dira",
|
||||
"first_name_required": "Izena beharrezkoa da",
|
||||
"last_name_required": "Abizena beharrezkoa da",
|
||||
"phone_required": "Telefonoa beharrezkoa da",
|
||||
"company_name_required": "Enpresaren izena beharrezkoa da",
|
||||
"terms_required": "Baldintza eta baldintzak onartu behar dituzu",
|
||||
"field_required": "Eremu hau beharrezkoa da",
|
||||
"invalid_format": "Formatu baliogabea"
|
||||
},
|
||||
"roles": {
|
||||
"admin": "Administratzailea",
|
||||
"manager": "Kudeatzailea",
|
||||
"baker": "Okindegigilea",
|
||||
"staff": "Langilea",
|
||||
"owner": "Jabea",
|
||||
"supervisor": "Gainbegiralea",
|
||||
"cashier": "Kutxazaina",
|
||||
"assistant": "Laguntzailea"
|
||||
},
|
||||
"global_roles": {
|
||||
"user": "Erabiltzailea",
|
||||
"admin": "Administratzailea",
|
||||
"manager": "Kudeatzailea",
|
||||
"super_admin": "Super Administratzailea"
|
||||
},
|
||||
"permissions": {
|
||||
"read": "Irakurri",
|
||||
"write": "Idatzi",
|
||||
"delete": "Ezabatu",
|
||||
"admin": "Administratzailea",
|
||||
"manage_users": "Kudeatu erabiltzaileak",
|
||||
"manage_inventory": "Kudeatu inbentarioa",
|
||||
"manage_production": "Kudeatu ekoizpena",
|
||||
"manage_sales": "Kudeatu salmentak",
|
||||
"view_reports": "Ikusi txostenak",
|
||||
"manage_settings": "Kudeatu ezarpenak"
|
||||
}
|
||||
}
|
||||
@@ -237,5 +237,58 @@
|
||||
"link": "Esteka",
|
||||
"tooltip": "Informazio gehigarria",
|
||||
"search": "Aplikazioan bilatu"
|
||||
},
|
||||
"app": {
|
||||
"name": "Okindegiaren AA",
|
||||
"full_name": "Okindegiaren AA - Sistema Adimentsua"
|
||||
},
|
||||
"profile": {
|
||||
"my_profile": "Nire profila",
|
||||
"my_locations": "Nire Kokalekuak",
|
||||
"settings": "Ezarpenak",
|
||||
"profile": "Profila",
|
||||
"logout": "Saioa Itxi",
|
||||
"profile_menu": "Profil menua",
|
||||
"close_navigation": "Nabigazioa itxi"
|
||||
},
|
||||
"header": {
|
||||
"main_navigation": "Nabigazio nagusia",
|
||||
"open_menu": "Nabigazioa ireki"
|
||||
},
|
||||
"footer": {
|
||||
"company_description": "Okindegirentzako kudeaketa sistema adimentsua. Optimizatu zure ekoizpena, inbentarioa eta salmentak adimen artifizialarekin.",
|
||||
"sections": {
|
||||
"product": "Produktua",
|
||||
"support": "Laguntza",
|
||||
"company": "Enpresa"
|
||||
},
|
||||
"links": {
|
||||
"dashboard": "Aginte-panela",
|
||||
"inventory": "Inbentarioa",
|
||||
"production": "Ekoizpena",
|
||||
"sales": "Salmentak",
|
||||
"forecasting": "Aurreikuspenak",
|
||||
"help": "Laguntza Zentroa",
|
||||
"docs": "Dokumentazioa",
|
||||
"contact": "Kontaktua",
|
||||
"feedback": "Iritzia",
|
||||
"about": "Guri buruz",
|
||||
"blog": "Bloga",
|
||||
"careers": "Lana",
|
||||
"press": "Prentsa",
|
||||
"privacy": "Pribatutasuna",
|
||||
"terms": "Baldintzak",
|
||||
"cookies": "Cookie-ak"
|
||||
},
|
||||
"social_follow": "Jarraitu gaitzazu sare sozialetan",
|
||||
"social_labels": {
|
||||
"twitter": "Twitter",
|
||||
"linkedin": "LinkedIn",
|
||||
"github": "GitHub"
|
||||
}
|
||||
},
|
||||
"breadcrumbs": {
|
||||
"home": "Hasiera",
|
||||
"truncation": "..."
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,43 @@
|
||||
"manage_staff": "Langilea Kudeatu"
|
||||
},
|
||||
"alerts": {
|
||||
"low_stock": "Stock Baxua",
|
||||
"production_delay": "Ekoizpen Atzerapena",
|
||||
"quality_issue": "Kalitate Arazoa",
|
||||
"equipment_maintenance": "Ekipo Mantentze",
|
||||
"order_pending": "Eskaera Zain",
|
||||
"delivery_due": "Entrega Atzeratua"
|
||||
"title": "Alertak",
|
||||
"live": "Zuzenean",
|
||||
"offline": "Deskonektatuta",
|
||||
"no_alerts": "Ez dago alerta aktiborik",
|
||||
"view_all": "Alerta guztiak ikusi",
|
||||
"time": {
|
||||
"now": "Orain",
|
||||
"minutes_ago": "duela {{count}} min",
|
||||
"hours_ago": "duela {{count}} h",
|
||||
"yesterday": "Atzo"
|
||||
},
|
||||
"types": {
|
||||
"low_stock": "Stock Baxua",
|
||||
"production_delay": "Ekoizpen Atzerapena",
|
||||
"quality_issue": "Kalitate Arazoa",
|
||||
"equipment_maintenance": "Ekipo Mantentze",
|
||||
"order_pending": "Eskaera Zain",
|
||||
"delivery_due": "Entrega Atzeratua",
|
||||
"critical": "Kritikoa",
|
||||
"warning": "Abisua",
|
||||
"info": "Informazioa",
|
||||
"success": "Arrakasta"
|
||||
},
|
||||
"status": {
|
||||
"new": "Berria",
|
||||
"acknowledged": "Onartu",
|
||||
"resolved": "Ebatzi"
|
||||
},
|
||||
"types": {
|
||||
"alert": "Alerta",
|
||||
"recommendation": "Gomendioa"
|
||||
},
|
||||
"recommended_actions": "Gomendatutako Ekintzak",
|
||||
"additional_details": "Xehetasun Gehigarriak",
|
||||
"mark_as_read": "Irakurritako gisa markatu",
|
||||
"remove": "Kendu",
|
||||
"active_count": "{{count}} alerta aktibo"
|
||||
},
|
||||
"messages": {
|
||||
"welcome": "Ongi etorri berriro",
|
||||
|
||||
34
frontend/src/locales/eu/database.json
Normal file
34
frontend/src/locales/eu/database.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Nire Okindegia",
|
||||
"subtitle": "Ikusi eta kudeatu zure okindegiaren informazio guztia",
|
||||
"sections": {
|
||||
"recipes": {
|
||||
"title": "Errezetak",
|
||||
"description": "Kudeatu zure produktuen errezetak"
|
||||
},
|
||||
"orders": {
|
||||
"title": "Eskaerak",
|
||||
"description": "Egiaztatu eskaera guztien egoera"
|
||||
},
|
||||
"suppliers": {
|
||||
"title": "Hornitzaileak",
|
||||
"description": "Kudeatu zure hornitzaileak"
|
||||
},
|
||||
"inventory": {
|
||||
"title": "Inbentarioa",
|
||||
"description": "Oraingo inbentarioaren egoera"
|
||||
},
|
||||
"bakery_config": {
|
||||
"title": "Okindegiaren Konfigurazioa",
|
||||
"description": "Zure okindegiaren ezarpen orokorrak"
|
||||
},
|
||||
"team_management": {
|
||||
"title": "Taldearen Kudeaketa",
|
||||
"description": "Kudeatu zure lan taldea"
|
||||
},
|
||||
"communication_preferences": {
|
||||
"title": "Komunikazio Hobespenak",
|
||||
"description": "Konfiguratu jakinarazpenak eta komunikazioak"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,13 @@
|
||||
{}
|
||||
{
|
||||
"boundary": {
|
||||
"title": "Oops! Zerbait gaizki joan da",
|
||||
"description": "Ustekabeko errore bat gertatu da aplikazioan. Gure taldea jakinarazi da.",
|
||||
"technical_details": "Xehetasun Teknikoak",
|
||||
"actions": {
|
||||
"retry": "Saiatu berriz",
|
||||
"reload": "Orria Birkargatu",
|
||||
"go_back": "Itzuli",
|
||||
"report": "Errore hau salatu"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
frontend/src/locales/eu/events.json
Normal file
25
frontend/src/locales/eu/events.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"title": "Gertaeren Erregistroa",
|
||||
"description": "Kontrolatu sistemaren jarduerak eta gertaera garrantzitsuak",
|
||||
"categories": {
|
||||
"all": "Denak",
|
||||
"sales": "Salmentak",
|
||||
"production": "Ekoizpena",
|
||||
"inventory": "Inbentarioa",
|
||||
"system": "Sistema",
|
||||
"customer": "Bezeroak"
|
||||
},
|
||||
"types": {
|
||||
"order_completed": "Eskaera Osatua",
|
||||
"batch_started": "Lote Hasita",
|
||||
"stock_updated": "Stock Eguneratua",
|
||||
"customer_registered": "Bezero Erregistratua",
|
||||
"system_alert": "Sistemaren Alerta"
|
||||
},
|
||||
"severity": {
|
||||
"info": "Informazioa",
|
||||
"warning": "Abisua",
|
||||
"error": "Errorea",
|
||||
"success": "Arrakasta"
|
||||
}
|
||||
}
|
||||
127
frontend/src/locales/eu/landing.json
Normal file
127
frontend/src/locales/eu/landing.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"navigation": {
|
||||
"features": "Ezaugarriak",
|
||||
"benefits": "Onurak",
|
||||
"pricing": "Prezioak",
|
||||
"testimonials": "Testigantzak"
|
||||
},
|
||||
"hero": {
|
||||
"badge": "AA Aurreratua Okindegirentzat",
|
||||
"title_line1": "Irauli zure",
|
||||
"title_line2": "Okindegia AA-rekin",
|
||||
"subtitle": "Optimizatu automatikoki zure ekoizpena, murriztu hondakinak %35era, aurreikusi eskaria %92ko zehaztasunarekin eta handitu zure salmentak adimen artifizialarekin.",
|
||||
"cta_primary": "Hasi Doan 14 Egunez",
|
||||
"cta_secondary": "Ikusi Demo Zuzenan",
|
||||
"features": {
|
||||
"no_credit_card": "Kreditu txartelik ez",
|
||||
"quick_setup": "5 minutuko konfigurazioa",
|
||||
"support_24_7": "24/7 laguntza euskeraz"
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"title": "Zure okindegia optimizatzeko behar duzun guztia",
|
||||
"subtitle": "Okindegi modernoetarako diseinu bereziki egindako tresna indartsuak",
|
||||
"ai_forecasting": {
|
||||
"title": "AA Eskariaren Aurreikuspena",
|
||||
"description": "Algoritmo aurreratuek egunero behar dituzun produktuak aurreikusten dituzte %92ko zehaztasunarekin"
|
||||
},
|
||||
"production_optimization": {
|
||||
"title": "Ekoizpenaren Optimizazioa",
|
||||
"description": "Planifikatu automatikoki labe ordutegiak eta langile kudeaketa eraginkortasun maximorakoak"
|
||||
},
|
||||
"waste_reduction": {
|
||||
"title": "Hondakinen Murrizketa",
|
||||
"description": "Murriztu hondakinak %35era aurreikuspen zehatzak eta inbentario kudeaketa adimendunarekin"
|
||||
},
|
||||
"real_time_analytics": {
|
||||
"title": "Denbora Errealeko Analisiak",
|
||||
"description": "Dashboard intuitiboa salmenta, ekoizpen eta errentagarritasun metriken eguneratuta berehala"
|
||||
},
|
||||
"inventory_management": {
|
||||
"title": "Inbentario Kudeaketa",
|
||||
"description": "Stock kontrola automatikoa alerta adimentsu eta erosketako agindu automatizatuekin"
|
||||
},
|
||||
"customer_insights": {
|
||||
"title": "Bezeroen Ikuspegia",
|
||||
"description": "Ulertu erosketa ereduak eta lehentasunak bezeroaren esperientzia hobetzeko"
|
||||
}
|
||||
},
|
||||
"benefits": {
|
||||
"title": "Zure negozioa transformatzen duten emaitza frogatuak",
|
||||
"subtitle": "1.000 okindegi baino gehiagok transformatu dituzte jadanik euren eragiketak gure AA-rekin",
|
||||
"waste_reduction": {
|
||||
"value": "%35",
|
||||
"label": "Hondakinen murrizketa"
|
||||
},
|
||||
"accuracy": {
|
||||
"value": "%92",
|
||||
"label": "Aurreikuspenaren zehaztasuna"
|
||||
},
|
||||
"time_saved": {
|
||||
"value": "4o",
|
||||
"label": "Eguneko planifikazioan aurreztutako denbora"
|
||||
},
|
||||
"sales_increase": {
|
||||
"value": "%28",
|
||||
"label": "Batez besteko salmenta igoera"
|
||||
}
|
||||
},
|
||||
"pricing": {
|
||||
"title": "Tamaina guztietako okindegirentzat diseinatutako planak",
|
||||
"subtitle": "Hasi doan eta eskala hazi ahala",
|
||||
"starter": {
|
||||
"name": "Hasiberria",
|
||||
"price": "Doan",
|
||||
"description": "Hasitzen ari diren okindegi txikientzat perfektua",
|
||||
"features": [
|
||||
"50 produktu arte",
|
||||
"Oinarrizko eskariaren aurreikuspena",
|
||||
"Dashboard basikoa",
|
||||
"Email laguntza"
|
||||
]
|
||||
},
|
||||
"professional": {
|
||||
"name": "Profesionala",
|
||||
"price": "49€",
|
||||
"price_period": "/hilabetea",
|
||||
"description": "Ezarritako okindegientzat egokia",
|
||||
"features": [
|
||||
"Produktu mugagabeak",
|
||||
"AA aurreratuko aurreikuspena",
|
||||
"Analisiak osoak",
|
||||
"Inbentario kudeaketa",
|
||||
"Ekoizpen optimizazioa",
|
||||
"Lehentasunezko 24/7 laguntza"
|
||||
]
|
||||
},
|
||||
"enterprise": {
|
||||
"name": "Enpresa",
|
||||
"price": "Pertsonalizatua",
|
||||
"description": "Kate eta frantzizientzako soluzio osoa",
|
||||
"features": [
|
||||
"Hainbat kokaleku",
|
||||
"API pertsonalizatua",
|
||||
"Integrazio aurreratuak",
|
||||
"Laguntza dedikatua",
|
||||
"Prestakuntza pertsonalizatua",
|
||||
"SLA bermatua"
|
||||
]
|
||||
},
|
||||
"cta": "Hasi Orain",
|
||||
"contact": "Salmenta Harremanetan Jarri"
|
||||
},
|
||||
"testimonials": {
|
||||
"title": "Zer dioten gure bezeroek",
|
||||
"subtitle": "Munduko okindegiok konfiantza jartzen dute gure plataforman"
|
||||
},
|
||||
"cta_section": {
|
||||
"title": "Prest zaude zure okindegia iraultzeko?",
|
||||
"subtitle": "Sartu dagoeneko AA erabiltzen ari diren 1.000 okindegitan euren eragiketak optimizatzeko",
|
||||
"cta": "Hasi Doan Gaur"
|
||||
},
|
||||
"footer_cta": {
|
||||
"security": "Enpresako segurtasuna",
|
||||
"uptime": "%99.9 bermatutako erabilgarritasuna",
|
||||
"data_protection": "RGPD datuen babesa"
|
||||
}
|
||||
}
|
||||
141
frontend/src/locales/eu/onboarding.json
Normal file
141
frontend/src/locales/eu/onboarding.json
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"wizard": {
|
||||
"title": "Hasierako Konfigurazioa",
|
||||
"subtitle": "Pausoz pauso gidatuko zaitugu zure okindegia konfiguratzeko",
|
||||
"steps": {
|
||||
"setup": {
|
||||
"title": "Okindegia Erregistratu",
|
||||
"description": "Konfiguratu zure okindegiko oinarrizko informazioa"
|
||||
},
|
||||
"smart_inventory_setup": {
|
||||
"title": "Inbentarioa Konfiguratu",
|
||||
"description": "Salmenten datuak igo eta hasierako inbentarioa ezarri"
|
||||
},
|
||||
"ml_training": {
|
||||
"title": "AA Prestakuntza",
|
||||
"description": "Entrenatu zure adimen artifizial modelo pertsonalizatua"
|
||||
},
|
||||
"completion": {
|
||||
"title": "Konfigurazioa Osatuta",
|
||||
"description": "Ongi etorri zure kudeaketa sistema adimentsu honetara!"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"previous": "Aurrekoa",
|
||||
"next": "Hurrengoa",
|
||||
"complete": "Osatu",
|
||||
"skip": "Saltatu",
|
||||
"finish": "Amaitu"
|
||||
},
|
||||
"progress": {
|
||||
"step_of": "{{current}}. pausoa {{total}}tik",
|
||||
"completed": "Osatuta",
|
||||
"in_progress": "Abian",
|
||||
"pending": "Zain"
|
||||
}
|
||||
},
|
||||
"steps": {
|
||||
"tenant_registration": {
|
||||
"title": "Zure Okindegiko Informazioa",
|
||||
"subtitle": "Kontaiguzu zure negozioari buruz",
|
||||
"fields": {
|
||||
"business_name": "Negozioaren izena",
|
||||
"business_type": "Negozio mota",
|
||||
"address": "Helbidea",
|
||||
"phone": "Telefonoa",
|
||||
"email": "Harremaneko emaila",
|
||||
"website": "Webgunea (aukerakoa)",
|
||||
"description": "Negozioaren deskripzioa"
|
||||
},
|
||||
"placeholders": {
|
||||
"business_name": "Adib.: San José Okindegia",
|
||||
"address": "Kale Nagusia 123, Hiria",
|
||||
"phone": "+34 123 456 789",
|
||||
"email": "kontaktua@okindegia.com",
|
||||
"website": "https://nireokindegia.com",
|
||||
"description": "Deskribatu zure okindegia..."
|
||||
}
|
||||
},
|
||||
"inventory_setup": {
|
||||
"title": "Inbentarioa Konfiguratu",
|
||||
"subtitle": "Igo zure salmenta historikoko datuak",
|
||||
"upload": {
|
||||
"title": "Salmenta Datuak Igo",
|
||||
"description": "Igo CSV fitxategi bat zure salmenta historikoko datuekin AA entrenatzeko",
|
||||
"drag_drop": "Arrastatu eta jaregin zure CSV fitxategia hemen",
|
||||
"or": "edo",
|
||||
"browse": "hautatu fitxategi bat",
|
||||
"supported_formats": "Onartutako formatuak: CSV",
|
||||
"max_size": "Gehienezko tamaina: 10MB"
|
||||
},
|
||||
"sample": {
|
||||
"download": "CSV txantiloia jaitsi",
|
||||
"example": "Datuen adibidea ikusi"
|
||||
},
|
||||
"processing": {
|
||||
"uploading": "Fitxategia igotzen...",
|
||||
"processing": "Datuak prozesatzen...",
|
||||
"success": "Datuak ongi prozesatu dira",
|
||||
"error": "Errorea datuak prozesatzean"
|
||||
}
|
||||
},
|
||||
"ml_training": {
|
||||
"title": "AA Prestakuntza",
|
||||
"subtitle": "Zure modelo pertsonalizatua sortzen",
|
||||
"status": {
|
||||
"preparing": "Datuak prestatzen...",
|
||||
"training": "Modeloa entrenatzen...",
|
||||
"validating": "Emaitzak balioesten...",
|
||||
"completed": "Prestakuntza osatuta"
|
||||
},
|
||||
"progress": {
|
||||
"data_preparation": "Datuen prestaketa",
|
||||
"model_training": "Modeloaren prestakuntza",
|
||||
"validation": "Balioespena",
|
||||
"deployment": "Hedapena"
|
||||
},
|
||||
"estimated_time": "Aurreikusitako denbora: {{minutes}} minutu",
|
||||
"description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian."
|
||||
},
|
||||
"completion": {
|
||||
"title": "Konfigurazioa Osatuta!",
|
||||
"subtitle": "Zure okindegia AA erabiltzeko prest dago",
|
||||
"success_message": "Zorionak, hasierako konfigurazioa ongi osatu duzu.",
|
||||
"next_steps": {
|
||||
"title": "Hurrengo pausoak:",
|
||||
"dashboard": "Arakatu zure panela",
|
||||
"first_prediction": "Ikusi zure lehen aurreikuspena",
|
||||
"inventory": "Konfiguratu zure inbentarioa",
|
||||
"team": "Gonbidatu zure taldea"
|
||||
},
|
||||
"cta": {
|
||||
"dashboard": "Panelera Joan",
|
||||
"tour": "Gidatutako Bisita Hasi"
|
||||
},
|
||||
"features_unlocked": {
|
||||
"title": "Desblokeatutako ezaugarriak:",
|
||||
"ai_forecasting": "AA eskaera aurreikuspena",
|
||||
"inventory_management": "Inbentario kudeaketa",
|
||||
"production_planning": "Ekoizpen planifikazioa",
|
||||
"analytics": "Analisiak eta txostenak"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"step_failed": "Errorea pauso honetan",
|
||||
"data_invalid": "Datu baliogabeak",
|
||||
"upload_failed": "Errorea fitxategia igotzean",
|
||||
"training_failed": "Errorea prestakuntzan",
|
||||
"network_error": "Konexio errorea",
|
||||
"try_again": "Saiatu berriz",
|
||||
"contact_support": "Laguntzarekin harremanetan jarri"
|
||||
},
|
||||
"validation": {
|
||||
"required": "Eremu hau derrigorrezkoa da",
|
||||
"invalid_email": "Email baliogabea",
|
||||
"invalid_phone": "Telefono baliogabea",
|
||||
"invalid_url": "URL baliogabea",
|
||||
"file_too_large": "Fitxategia handiegia",
|
||||
"invalid_file_type": "Fitxategi mota baliogabea"
|
||||
}
|
||||
}
|
||||
88
frontend/src/locales/eu/sales.json
Normal file
88
frontend/src/locales/eu/sales.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"orders": {
|
||||
"title": "Eskaerak",
|
||||
"table": {
|
||||
"columns": {
|
||||
"order_number": "Eskaera Zk.",
|
||||
"customer": "Bezeroa",
|
||||
"products": "Produktuak",
|
||||
"total": "Guztira",
|
||||
"status": "Egoera",
|
||||
"channel": "Kanala",
|
||||
"date": "Data",
|
||||
"actions": "Ekintzak"
|
||||
},
|
||||
"filters": {
|
||||
"all_status": "Egoera guztiak",
|
||||
"all_channels": "Kanal guztiak",
|
||||
"items_per_page": {
|
||||
"10": "10 orriko",
|
||||
"20": "20 orriko",
|
||||
"50": "50 orriko",
|
||||
"100": "100 orriko"
|
||||
}
|
||||
},
|
||||
"bulk_actions": "Ekintza masiboak",
|
||||
"no_orders": "Ez dago eskaerarik",
|
||||
"loading": "Eskaerak kargatzen...",
|
||||
"error": "Errorea eskaerak kargatzean"
|
||||
},
|
||||
"status": {
|
||||
"pendiente": "Zain",
|
||||
"confirmado": "Berretsi",
|
||||
"en_preparacion": "Prestakuntzan",
|
||||
"listo": "Entregatzeko prest",
|
||||
"entregado": "Entregatua",
|
||||
"cancelado": "Bertan behera"
|
||||
},
|
||||
"channels": {
|
||||
"store_front": "Denda",
|
||||
"online": "Online",
|
||||
"phone_order": "Telefonoa",
|
||||
"delivery": "Banaketa",
|
||||
"catering": "Katering",
|
||||
"wholesale": "Handizkako",
|
||||
"farmers_market": "Merkatua",
|
||||
"third_party": "Hirugarrenak"
|
||||
},
|
||||
"payment_methods": {
|
||||
"cash": "Dirutan",
|
||||
"credit_card": "Kreditu Txartela",
|
||||
"debit_card": "Zordunketa Txartela",
|
||||
"digital_wallet": "Digital Poltsikoa",
|
||||
"bank_transfer": "Banku Transferentzia",
|
||||
"check": "Txekea",
|
||||
"store_credit": "Denda Kreditua"
|
||||
},
|
||||
"actions": {
|
||||
"view": "Ikusi",
|
||||
"edit": "Editatu",
|
||||
"delete": "Ezabatu",
|
||||
"print": "Inprimatu",
|
||||
"duplicate": "Bikoiztu",
|
||||
"cancel": "Utzi",
|
||||
"confirm": "Berretsi",
|
||||
"complete": "Osatu"
|
||||
}
|
||||
},
|
||||
"customers": {
|
||||
"title": "Bezeroak",
|
||||
"name": "Izena",
|
||||
"phone": "Telefonoa",
|
||||
"email": "Emaila",
|
||||
"address": "Helbidea",
|
||||
"orders_count": "Eskaerak",
|
||||
"total_spent": "Guztira gastatu",
|
||||
"last_order": "Azken eskaera"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Salmenten Analisia",
|
||||
"revenue": "Diru-sarrerak",
|
||||
"orders": "Eskaerak",
|
||||
"avg_order": "Batez besteko eskaera",
|
||||
"growth": "Hazkundea",
|
||||
"trends": "Joerak",
|
||||
"top_products": "Produktu onenak",
|
||||
"top_customers": "Bezero onenak"
|
||||
}
|
||||
}
|
||||
91
frontend/src/locales/eu/settings.json
Normal file
91
frontend/src/locales/eu/settings.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"profile": {
|
||||
"title": "Erabiltzaile Profila",
|
||||
"description": "Kudeatu zure informazio pertsonala eta lehentasunak",
|
||||
"personal_info": "Informazio Pertsonala",
|
||||
"edit_profile": "Profila Editatu",
|
||||
"change_password": "Pasahitza Aldatu",
|
||||
"online": "Konektatuta",
|
||||
"offline": "Deskonektatuta",
|
||||
"save_changes": "Aldaketak Gorde",
|
||||
"cancel": "Utzi",
|
||||
"fields": {
|
||||
"first_name": "Izena",
|
||||
"last_name": "Abizenak",
|
||||
"email": "Helbide Elektronikoa",
|
||||
"phone": "Telefono Zenbakia",
|
||||
"language": "Hizkuntza",
|
||||
"timezone": "Ordu Zona",
|
||||
"avatar": "Avatarra"
|
||||
},
|
||||
"password": {
|
||||
"current_password": "Oraingo Pasahitza",
|
||||
"new_password": "Pasahitz Berria",
|
||||
"confirm_password": "Pasahitza Berretsi",
|
||||
"change_password": "Pasahitza Aldatu",
|
||||
"password_requirements": "Pasahitzak gutxienez 8 karaktere izan behar ditu"
|
||||
}
|
||||
},
|
||||
"team": {
|
||||
"title": "Taldea",
|
||||
"description": "Kudeatu zure taldekideak eta euren baimenak",
|
||||
"invite_member": "Kidea Gonbidatu",
|
||||
"members": "Kideak",
|
||||
"pending_invitations": "Zain dauden Gonbidapenak",
|
||||
"role": "Rola",
|
||||
"status": "Egoera",
|
||||
"actions": "Ekintzak"
|
||||
},
|
||||
"organization": {
|
||||
"title": "Erakundeak",
|
||||
"description": "Kudeatu zure erakundeak eta konfigurazioak",
|
||||
"current_organization": "Oraingo Erakundea",
|
||||
"switch_organization": "Erakundea Aldatu",
|
||||
"create_organization": "Erakundea Sortu"
|
||||
},
|
||||
"bakery_config": {
|
||||
"title": "Okindegi Konfigurazioa",
|
||||
"description": "Konfiguratu zure okindegiko ezarpen bereziak",
|
||||
"general": "Orokorra",
|
||||
"products": "Produktuak",
|
||||
"hours": "Ordu Koadroa",
|
||||
"notifications": "Jakinarazpenak"
|
||||
},
|
||||
"subscription": {
|
||||
"title": "Harpidetza",
|
||||
"description": "Kudeatu zure harpidetza plana",
|
||||
"current_plan": "Oraingo Plana",
|
||||
"usage": "Erabilera",
|
||||
"billing": "Fakturazioa",
|
||||
"upgrade": "Plana Eguneratu",
|
||||
"manage": "Harpidetza Kudeatu"
|
||||
},
|
||||
"communication": {
|
||||
"title": "Komunikazio Lehentasunak",
|
||||
"description": "Konfiguratu nola eta noiz jasotzen dituzun jakinarazpenak",
|
||||
"email_notifications": "Email Jakinarazpenak",
|
||||
"push_notifications": "Push Jakinarazpenak",
|
||||
"sms_notifications": "SMS Jakinarazpenak",
|
||||
"marketing": "Marketing Komunikazioak",
|
||||
"alerts": "Sistemaren Alertak"
|
||||
},
|
||||
"tabs": {
|
||||
"profile": "Profila",
|
||||
"team": "Taldea",
|
||||
"organization": "Erakundea",
|
||||
"bakery_config": "Konfigurazioa",
|
||||
"subscription": "Harpidetza",
|
||||
"communication": "Komunikazioa"
|
||||
},
|
||||
"common": {
|
||||
"save": "Gorde",
|
||||
"cancel": "Utzi",
|
||||
"edit": "Editatu",
|
||||
"delete": "Ezabatu",
|
||||
"loading": "Kargatzen...",
|
||||
"success": "Arrakasta",
|
||||
"error": "Errorea",
|
||||
"required": "Beharrezkoa",
|
||||
"optional": "Aukerakoa"
|
||||
}
|
||||
}
|
||||
45
frontend/src/locales/eu/traffic.json
Normal file
45
frontend/src/locales/eu/traffic.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"title": "Trafiko Analisia",
|
||||
"description": "Kontrolatu bezeroen fluxua eta optimizatu zerbitzuaren ordutegia",
|
||||
"metrics": {
|
||||
"total_visitors": "Bisitari Guztiak",
|
||||
"peak_hour": "Gailur Ordua",
|
||||
"avg_duration": "Batezbesteko Iraupena",
|
||||
"busy_days": "Egun Okupatuak",
|
||||
"conversion_rate": "Bihurtze Tasa"
|
||||
},
|
||||
"periods": {
|
||||
"week": "Astea",
|
||||
"month": "Hilabetea",
|
||||
"year": "Urtea"
|
||||
},
|
||||
"days": {
|
||||
"monday": "Astelehena",
|
||||
"tuesday": "Asteartea",
|
||||
"wednesday": "Asteazkena",
|
||||
"thursday": "Osteguna",
|
||||
"friday": "Ostirala",
|
||||
"saturday": "Larunbata",
|
||||
"sunday": "Igandea",
|
||||
"mon": "Asl",
|
||||
"tue": "Ast",
|
||||
"wed": "Azk",
|
||||
"thu": "Ost",
|
||||
"fri": "Orl",
|
||||
"sat": "Lar",
|
||||
"sun": "Ign"
|
||||
},
|
||||
"sources": {
|
||||
"walking": "Oinez",
|
||||
"local_search": "Tokiko Bilaketa",
|
||||
"recommendations": "Gomendioak",
|
||||
"social_media": "Sare Sozialak",
|
||||
"advertising": "Publizitatea"
|
||||
},
|
||||
"segments": {
|
||||
"morning_regulars": "Goizeko Erregularrak",
|
||||
"weekend_families": "Asteburu Familiak",
|
||||
"lunch_office": "Bazkari Bulegokideak",
|
||||
"occasional_customers": "Bezero Okazionalak"
|
||||
}
|
||||
}
|
||||
51
frontend/src/locales/eu/ui.json
Normal file
51
frontend/src/locales/eu/ui.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"datepicker": {
|
||||
"placeholder": "Data hautatu",
|
||||
"today": "Gaur",
|
||||
"clear": "Garbitu",
|
||||
"weekdays": ["Ign", "Asl", "Ast", "Azk", "Ost", "Orl", "Lar"],
|
||||
"months": [
|
||||
"Urtarrila", "Otsaila", "Martxoa", "Apirila", "Maiatza", "Ekaina",
|
||||
"Uztaila", "Abuztua", "Iraila", "Urria", "Azaroa", "Abendua"
|
||||
]
|
||||
},
|
||||
"password_criteria": {
|
||||
"min_length": "Gutxienez 8 karaktere",
|
||||
"max_length": "Gehienez 128 karaktere",
|
||||
"uppercase": "Gutxienez letra larri bat",
|
||||
"lowercase": "Gutxienez letra xehe bat",
|
||||
"number": "Gutxienez zenbaki bat",
|
||||
"special": "Gutxienez karaktere berezi bat",
|
||||
"errors": {
|
||||
"min_length": "Pasahitzak gutxienez 8 karaktere izan behar ditu",
|
||||
"max_length": "Pasahitzak ezin ditu 128 karaktere baino gehiago izan",
|
||||
"uppercase": "Pasahitzak gutxienez letra larri bat izan behar du",
|
||||
"lowercase": "Pasahitzak gutxienez letra xehe bat izan behar du",
|
||||
"number": "Pasahitzak gutxienez zenbaki bat izan behar du",
|
||||
"special": "Pasahitzak gutxienez karaktere berezi bat izan behar du"
|
||||
}
|
||||
},
|
||||
"avatar": {
|
||||
"online": "Konektatuta",
|
||||
"offline": "Deskonektatuta",
|
||||
"away": "Kanpoan",
|
||||
"busy": "Okupatuta"
|
||||
},
|
||||
"notifications": {
|
||||
"alert": "Alerta",
|
||||
"recommendation": "Gomendioa",
|
||||
"info": "Informazioa",
|
||||
"warning": "Abisua"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Kargatzen...",
|
||||
"error": "Errorea",
|
||||
"success": "Arrakasta",
|
||||
"cancel": "Utzi",
|
||||
"save": "Gorde",
|
||||
"delete": "Ezabatu",
|
||||
"edit": "Editatu",
|
||||
"close": "Itxi",
|
||||
"confirm": "Berretsi"
|
||||
}
|
||||
}
|
||||
149
frontend/src/locales/eu/weather.json
Normal file
149
frontend/src/locales/eu/weather.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"title": "Eguraldiaren Datuak",
|
||||
"description": "Integratu eguraldiaren informazioa ekoizpena eta salmentak optimizatzeko",
|
||||
"current": {
|
||||
"title": "Uneko Baldintzak",
|
||||
"temperature": "Tenperatura",
|
||||
"humidity": "Hezetasuna",
|
||||
"wind": "Haizea",
|
||||
"pressure": "Presioa",
|
||||
"uv": "UV",
|
||||
"visibility": "Ikusgarritasuna",
|
||||
"favorable_conditions": "Baldintza onak"
|
||||
},
|
||||
"forecast": {
|
||||
"title": "Aurreikuspena Zabaldua",
|
||||
"next_week": "Hurrengo Astea",
|
||||
"next_month": "Hurrengo Hilabetea",
|
||||
"rain": "Euria"
|
||||
},
|
||||
"conditions": {
|
||||
"sunny": "Eguzkitsua",
|
||||
"partly_cloudy": "Partzialki lainotsua",
|
||||
"cloudy": "Lainotsua",
|
||||
"rainy": "Euriatsua"
|
||||
},
|
||||
"days": {
|
||||
"saturday": "Larunbata",
|
||||
"sunday": "Igandea",
|
||||
"monday": "Astelehena",
|
||||
"tuesday": "Asteartea",
|
||||
"wednesday": "Asteazkena",
|
||||
"thursday": "Osteguna",
|
||||
"friday": "Ostirala"
|
||||
},
|
||||
"impact": {
|
||||
"title": "Eguraldiaren Eragina",
|
||||
"high_demand": "Eskari Handia",
|
||||
"comfort_food": "Erosotasun Janaria",
|
||||
"moderate": "Eskari Moderatua",
|
||||
"normal": "Eskari Normala",
|
||||
"recommendations": "Gomendioak"
|
||||
},
|
||||
"impacts": {
|
||||
"sunny_day": {
|
||||
"condition": "Eguzki Eguna",
|
||||
"impact": "%25eko igoera edari hotzetan",
|
||||
"recommendations": [
|
||||
"Handitu izozkien ekoizpena",
|
||||
"Edari freskagarri gehiago",
|
||||
"Entsaladak eta produktu freskoak",
|
||||
"Terrazako ordutegia luzatu"
|
||||
]
|
||||
},
|
||||
"rainy_day": {
|
||||
"condition": "Euri Eguna",
|
||||
"impact": "%40ko igoera produktu beroetan",
|
||||
"recommendations": [
|
||||
"Zopa eta saltsa gehiago",
|
||||
"Txokolate beroak",
|
||||
"Ogi freskoa",
|
||||
"Gozogintza produktuak"
|
||||
]
|
||||
},
|
||||
"cold_day": {
|
||||
"condition": "Hotz Sakona",
|
||||
"impact": "Erosotasun janarien lehentasuna",
|
||||
"recommendations": [
|
||||
"Handitu produktu labetuak",
|
||||
"Edari bero bereziak",
|
||||
"Energia produktuak",
|
||||
"Barruko promozioak"
|
||||
]
|
||||
}
|
||||
},
|
||||
"seasonal": {
|
||||
"title": "Sasoi Joerak",
|
||||
"spring": {
|
||||
"name": "Udaberria",
|
||||
"period": "Mar - Mai",
|
||||
"avg_temp": "15-20°C",
|
||||
"trends": [
|
||||
"Produktu freskoen igoera (+%30)",
|
||||
"Entsaladen eskari handiagoa",
|
||||
"Edari naturalak ezagunak",
|
||||
"Ordutegia luzatzea eraginkorra"
|
||||
]
|
||||
},
|
||||
"summer": {
|
||||
"name": "Uda",
|
||||
"period": "Eka - Abu",
|
||||
"avg_temp": "25-35°C",
|
||||
"trends": [
|
||||
"Izozkien eta granizatuen gailurra (+%60)",
|
||||
"Produktu arinak hobetsiak",
|
||||
"Goizeko ordutegia kritikoa",
|
||||
"Turista trafiko handiagoa"
|
||||
]
|
||||
},
|
||||
"autumn": {
|
||||
"name": "Udazkena",
|
||||
"period": "Ira - Aza",
|
||||
"avg_temp": "10-18°C",
|
||||
"trends": [
|
||||
"Produktu tradizionaletara itzulera",
|
||||
"Gozogintzan igoera (+%20)",
|
||||
"Edari bero ezagunak",
|
||||
"Ordutegia erregularra"
|
||||
]
|
||||
},
|
||||
"winter": {
|
||||
"name": "Negua",
|
||||
"period": "Abe - Ots",
|
||||
"avg_temp": "5-12°C",
|
||||
"trends": [
|
||||
"Produktu beroen maximoa (+%50)",
|
||||
"Ogi freskoa kritikoa",
|
||||
"Txokolate eta gozoki festiboak",
|
||||
"Trafiko orokorra txikiagoa (-%15)"
|
||||
]
|
||||
},
|
||||
"impact_levels": {
|
||||
"high": "Altua",
|
||||
"positive": "Positiboa",
|
||||
"comfort": "Erosotasuna",
|
||||
"stable": "Egonkorra"
|
||||
}
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Eguraldi Alertak",
|
||||
"heat_wave": {
|
||||
"title": "Bero olatu aurreikusia",
|
||||
"description": "30°C baino tenperatura altuagoak espero dira hurrengo 3 egunetan",
|
||||
"recommendation": "Handitu edari hotz eta izozkien stocka"
|
||||
},
|
||||
"heavy_rain": {
|
||||
"title": "Euri sakona astelehenean",
|
||||
"description": "%80ko euritze probabilitatea haize indartsuarekin",
|
||||
"recommendation": "Prestatu produktu bero gehiago eta babeslekukoak"
|
||||
},
|
||||
"recommendation_label": "Gomendioa"
|
||||
},
|
||||
"recommendations": {
|
||||
"increase_ice_cream": "Handitu izozkien eta edari hotzen ekoizpena",
|
||||
"standard_production": "Ekoizpen estandarra",
|
||||
"comfort_foods": "Handitu zopak, txokolate beroak eta ogi freskoa",
|
||||
"indoor_focus": "Barruko produktuetan zentratu",
|
||||
"fresh_products": "Handitu produktu freskoak eta entsaladak"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Calendar, Activity, Filter, Download, Eye, BarChart3 } from 'lucide-react';
|
||||
import { Button, Card, Badge } from '../../../../components/ui';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
|
||||
const EventsPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('week');
|
||||
const [selectedCategory, setSelectedCategory] = useState('all');
|
||||
|
||||
@@ -145,8 +147,8 @@ const EventsPage: React.FC = () => {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<PageHeader
|
||||
title="Registro de Eventos"
|
||||
description="Seguimiento de todas las actividades y eventos del sistema"
|
||||
title={t('events:title', 'Registro de Eventos')}
|
||||
description={t('events:description', 'Seguimiento de todas las actividades y eventos del sistema')}
|
||||
action={
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline">
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Users, Clock, TrendingUp, MapPin, Calendar, BarChart3, Download, Filter } from 'lucide-react';
|
||||
import { Button, Card, Badge } from '../../../../components/ui';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
|
||||
const TrafficPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('week');
|
||||
const [selectedMetric, setSelectedMetric] = useState('visitors');
|
||||
|
||||
@@ -99,8 +101,8 @@ const TrafficPage: React.FC = () => {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<PageHeader
|
||||
title="Análisis de Tráfico"
|
||||
description="Monitorea los patrones de visitas y flujo de clientes"
|
||||
title={t('traffic:title', 'Análisis de Tráfico')}
|
||||
description={t('traffic:description', 'Monitorea los patrones de visitas y flujo de clientes')}
|
||||
action={
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline">
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Cloud, Sun, CloudRain, Thermometer, Wind, Droplets, Calendar, TrendingUp } from 'lucide-react';
|
||||
import { Button, Card, Badge } from '../../../../components/ui';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
|
||||
const WeatherPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('week');
|
||||
|
||||
const currentWeather = {
|
||||
@@ -14,13 +16,13 @@ const WeatherPage: React.FC = () => {
|
||||
pressure: 1013,
|
||||
uvIndex: 4,
|
||||
visibility: 10,
|
||||
description: 'Parcialmente nublado'
|
||||
description: t('weather:conditions.partly_cloudy', 'Parcialmente nublado')
|
||||
};
|
||||
|
||||
const forecast = [
|
||||
{
|
||||
date: '2024-01-27',
|
||||
day: 'Sábado',
|
||||
day: t('weather:days.saturday', 'Sábado'),
|
||||
condition: 'sunny',
|
||||
tempMax: 22,
|
||||
tempMin: 12,
|
||||
@@ -28,11 +30,11 @@ const WeatherPage: React.FC = () => {
|
||||
precipitation: 0,
|
||||
wind: 8,
|
||||
impact: 'high-demand',
|
||||
recommendation: 'Incrementar producción de helados y bebidas frías'
|
||||
recommendation: t('weather:recommendations.increase_ice_cream', 'Incrementar producción de helados y bebidas frías')
|
||||
},
|
||||
{
|
||||
date: '2024-01-28',
|
||||
day: 'Domingo',
|
||||
day: t('weather:days.sunday', 'Domingo'),
|
||||
condition: 'partly-cloudy',
|
||||
tempMax: 19,
|
||||
tempMin: 11,
|
||||
@@ -40,11 +42,11 @@ const WeatherPage: React.FC = () => {
|
||||
precipitation: 20,
|
||||
wind: 15,
|
||||
impact: 'normal',
|
||||
recommendation: 'Producción estándar'
|
||||
recommendation: t('weather:recommendations.standard_production', 'Producción estándar')
|
||||
},
|
||||
{
|
||||
date: '2024-01-29',
|
||||
day: 'Lunes',
|
||||
day: t('weather:days.monday', 'Lunes'),
|
||||
condition: 'rainy',
|
||||
tempMax: 15,
|
||||
tempMin: 8,
|
||||
@@ -52,11 +54,11 @@ const WeatherPage: React.FC = () => {
|
||||
precipitation: 80,
|
||||
wind: 22,
|
||||
impact: 'comfort-food',
|
||||
recommendation: 'Aumentar sopas, chocolates calientes y pan recién horneado'
|
||||
recommendation: t('weather:recommendations.comfort_foods', 'Aumentar sopas, chocolates calientes y pan recién horneado')
|
||||
},
|
||||
{
|
||||
date: '2024-01-30',
|
||||
day: 'Martes',
|
||||
day: t('weather:days.tuesday', 'Martes'),
|
||||
condition: 'cloudy',
|
||||
tempMax: 16,
|
||||
tempMin: 9,
|
||||
@@ -64,11 +66,11 @@ const WeatherPage: React.FC = () => {
|
||||
precipitation: 40,
|
||||
wind: 18,
|
||||
impact: 'moderate',
|
||||
recommendation: 'Enfoque en productos de interior'
|
||||
recommendation: t('weather:recommendations.indoor_focus', 'Enfoque en productos de interior')
|
||||
},
|
||||
{
|
||||
date: '2024-01-31',
|
||||
day: 'Miércoles',
|
||||
day: t('weather:days.wednesday', 'Miércoles'),
|
||||
condition: 'sunny',
|
||||
tempMax: 24,
|
||||
tempMin: 14,
|
||||
@@ -76,44 +78,44 @@ const WeatherPage: React.FC = () => {
|
||||
precipitation: 0,
|
||||
wind: 10,
|
||||
impact: 'high-demand',
|
||||
recommendation: 'Incrementar productos frescos y ensaladas'
|
||||
recommendation: t('weather:recommendations.fresh_products', 'Incrementar productos frescos y ensaladas')
|
||||
}
|
||||
];
|
||||
|
||||
const weatherImpacts = [
|
||||
{
|
||||
condition: 'Día Soleado',
|
||||
condition: t('weather:impacts.sunny_day.condition', 'Día Soleado'),
|
||||
icon: Sun,
|
||||
impact: 'Aumento del 25% en bebidas frías',
|
||||
impact: t('weather:impacts.sunny_day.impact', 'Aumento del 25% en bebidas frías'),
|
||||
recommendations: [
|
||||
'Incrementar producción de helados',
|
||||
'Más bebidas refrescantes',
|
||||
'Ensaladas y productos frescos',
|
||||
'Horario extendido de terraza'
|
||||
t('weather:impacts.sunny_day.recommendations.0', 'Incrementar producción de helados'),
|
||||
t('weather:impacts.sunny_day.recommendations.1', 'Más bebidas refrescantes'),
|
||||
t('weather:impacts.sunny_day.recommendations.2', 'Ensaladas y productos frescos'),
|
||||
t('weather:impacts.sunny_day.recommendations.3', 'Horario extendido de terraza')
|
||||
],
|
||||
color: 'yellow'
|
||||
},
|
||||
{
|
||||
condition: 'Día Lluvioso',
|
||||
condition: t('weather:impacts.rainy_day.condition', 'Día Lluvioso'),
|
||||
icon: CloudRain,
|
||||
impact: 'Aumento del 40% en productos calientes',
|
||||
impact: t('weather:impacts.rainy_day.impact', 'Aumento del 40% en productos calientes'),
|
||||
recommendations: [
|
||||
'Más sopas y caldos',
|
||||
'Chocolates calientes',
|
||||
'Pan recién horneado',
|
||||
'Productos de repostería'
|
||||
t('weather:impacts.rainy_day.recommendations.0', 'Más sopas y caldos'),
|
||||
t('weather:impacts.rainy_day.recommendations.1', 'Chocolates calientes'),
|
||||
t('weather:impacts.rainy_day.recommendations.2', 'Pan recién horneado'),
|
||||
t('weather:impacts.rainy_day.recommendations.3', 'Productos de repostería')
|
||||
],
|
||||
color: 'blue'
|
||||
},
|
||||
{
|
||||
condition: 'Frío Intenso',
|
||||
condition: t('weather:impacts.cold_day.condition', 'Frío Intenso'),
|
||||
icon: Thermometer,
|
||||
impact: 'Preferencia por comida reconfortante',
|
||||
impact: t('weather:impacts.cold_day.impact', 'Preferencia por comida reconfortante'),
|
||||
recommendations: [
|
||||
'Aumentar productos horneados',
|
||||
'Bebidas calientes especiales',
|
||||
'Productos energéticos',
|
||||
'Promociones de interior'
|
||||
t('weather:impacts.cold_day.recommendations.0', 'Aumentar productos horneados'),
|
||||
t('weather:impacts.cold_day.recommendations.1', 'Bebidas calientes especiales'),
|
||||
t('weather:impacts.cold_day.recommendations.2', 'Productos energéticos'),
|
||||
t('weather:impacts.cold_day.recommendations.3', 'Promociones de interior')
|
||||
],
|
||||
color: 'purple'
|
||||
}
|
||||
@@ -183,10 +185,10 @@ const WeatherPage: React.FC = () => {
|
||||
|
||||
const getConditionLabel = (condition: string) => {
|
||||
switch (condition) {
|
||||
case 'sunny': return 'Soleado';
|
||||
case 'partly-cloudy': return 'Parcialmente nublado';
|
||||
case 'cloudy': return 'Nublado';
|
||||
case 'rainy': return 'Lluvioso';
|
||||
case 'sunny': return t('weather:conditions.sunny', 'Soleado');
|
||||
case 'partly-cloudy': return t('weather:conditions.partly_cloudy', 'Parcialmente nublado');
|
||||
case 'cloudy': return t('weather:conditions.cloudy', 'Nublado');
|
||||
case 'rainy': return t('weather:conditions.rainy', 'Lluvioso');
|
||||
default: return condition;
|
||||
}
|
||||
};
|
||||
@@ -203,10 +205,10 @@ const WeatherPage: React.FC = () => {
|
||||
|
||||
const getImpactLabel = (impact: string) => {
|
||||
switch (impact) {
|
||||
case 'high-demand': return 'Alta Demanda';
|
||||
case 'comfort-food': return 'Comida Reconfortante';
|
||||
case 'moderate': return 'Demanda Moderada';
|
||||
case 'normal': return 'Demanda Normal';
|
||||
case 'high-demand': return t('weather:impact.high_demand', 'Alta Demanda');
|
||||
case 'comfort-food': return t('weather:impact.comfort_food', 'Comida Reconfortante');
|
||||
case 'moderate': return t('weather:impact.moderate', 'Demanda Moderada');
|
||||
case 'normal': return t('weather:impact.normal', 'Demanda Normal');
|
||||
default: return impact;
|
||||
}
|
||||
};
|
||||
@@ -214,13 +216,13 @@ const WeatherPage: React.FC = () => {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<PageHeader
|
||||
title="Datos Meteorológicos"
|
||||
description="Integra información del clima para optimizar la producción y ventas"
|
||||
title={t('weather:title', 'Datos Meteorológicos')}
|
||||
description={t('weather:description', 'Integra información del clima para optimizar la producción y ventas')}
|
||||
/>
|
||||
|
||||
{/* Current Weather */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Condiciones Actuales</h3>
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">{t('weather:current.title', 'Condiciones Actuales')}</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
{getWeatherIcon(currentWeather.condition)}
|
||||
@@ -233,28 +235,28 @@ const WeatherPage: React.FC = () => {
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Droplets className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">Humedad: {currentWeather.humidity}%</span>
|
||||
<span className="text-sm text-[var(--text-secondary)]">{t('weather:current.humidity', 'Humedad')}: {currentWeather.humidity}%</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Wind className="w-4 h-4 text-[var(--text-tertiary)]" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">Viento: {currentWeather.windSpeed} km/h</span>
|
||||
<span className="text-sm text-[var(--text-secondary)]">{t('weather:current.wind', 'Viento')}: {currentWeather.windSpeed} km/h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-[var(--text-secondary)]">
|
||||
<span className="font-medium">Presión:</span> {currentWeather.pressure} hPa
|
||||
<span className="font-medium">{t('weather:current.pressure', 'Presión')}:</span> {currentWeather.pressure} hPa
|
||||
</div>
|
||||
<div className="text-sm text-[var(--text-secondary)]">
|
||||
<span className="font-medium">UV:</span> {currentWeather.uvIndex}
|
||||
<span className="font-medium">{t('weather:current.uv', 'UV')}:</span> {currentWeather.uvIndex}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-[var(--text-secondary)]">
|
||||
<span className="font-medium">Visibilidad:</span> {currentWeather.visibility} km
|
||||
<span className="font-medium">{t('weather:current.visibility', 'Visibilidad')}:</span> {currentWeather.visibility} km
|
||||
</div>
|
||||
<Badge variant="blue">Condiciones favorables</Badge>
|
||||
<Badge variant="blue">{t('weather:current.favorable_conditions', 'Condiciones favorables')}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -262,14 +264,14 @@ const WeatherPage: React.FC = () => {
|
||||
{/* Weather Forecast */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Pronóstico Extendido</h3>
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">{t('weather:forecast.title', 'Pronóstico Extendido')}</h3>
|
||||
<select
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => setSelectedPeriod(e.target.value)}
|
||||
className="px-3 py-2 border border-[var(--border-secondary)] rounded-md text-sm"
|
||||
>
|
||||
<option value="week">Próxima Semana</option>
|
||||
<option value="month">Próximo Mes</option>
|
||||
<option value="week">{t('weather:forecast.next_week', 'Próxima Semana')}</option>
|
||||
<option value="month">{t('weather:forecast.next_month', 'Próximo Mes')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -294,15 +296,15 @@ const WeatherPage: React.FC = () => {
|
||||
|
||||
<div className="space-y-2 text-xs text-[var(--text-secondary)]">
|
||||
<div className="flex justify-between">
|
||||
<span>Humedad:</span>
|
||||
<span>{t('weather:current.humidity', 'Humedad')}:</span>
|
||||
<span>{day.humidity}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Lluvia:</span>
|
||||
<span>{t('weather:forecast.rain', 'Lluvia')}:</span>
|
||||
<span>{day.precipitation}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Viento:</span>
|
||||
<span>{t('weather:current.wind', 'Viento')}:</span>
|
||||
<span>{day.wind} km/h</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -324,7 +326,7 @@ const WeatherPage: React.FC = () => {
|
||||
{/* Weather Impact Analysis */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Impacto del Clima</h3>
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">{t('weather:impact.title', 'Impacto del Clima')}</h3>
|
||||
<div className="space-y-4">
|
||||
{weatherImpacts.map((impact, index) => (
|
||||
<div key={index} className="border rounded-lg p-4">
|
||||
@@ -339,7 +341,7 @@ const WeatherPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="ml-10">
|
||||
<p className="text-sm font-medium text-[var(--text-secondary)] mb-2">Recomendaciones:</p>
|
||||
<p className="text-sm font-medium text-[var(--text-secondary)] mb-2">{t('weather:impact.recommendations', 'Recomendaciones')}:</p>
|
||||
<ul className="text-sm text-[var(--text-secondary)] space-y-1">
|
||||
{impact.recommendations.map((rec, idx) => (
|
||||
<li key={idx} className="flex items-center">
|
||||
@@ -356,7 +358,7 @@ const WeatherPage: React.FC = () => {
|
||||
|
||||
{/* Seasonal Trends */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Tendencias Estacionales</h3>
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">{t('weather:seasonal.title', 'Tendencias Estacionales')}</h3>
|
||||
<div className="space-y-4">
|
||||
{seasonalTrends.map((season, index) => (
|
||||
<div key={index} className="border rounded-lg p-4">
|
||||
@@ -395,7 +397,7 @@ const WeatherPage: React.FC = () => {
|
||||
|
||||
{/* Weather Alerts */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Alertas Meteorológicas</h3>
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">{t('weather:alerts.title', 'Alertas Meteorológicas')}</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<Sun className="w-5 h-5 text-yellow-600 mr-3" />
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Outlet, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PageHeader } from '../../../components/layout/PageHeader/PageHeader';
|
||||
import { Database, Package, ShoppingCart, Truck, Settings, Users, MessageSquare } from 'lucide-react';
|
||||
|
||||
const DatabasePage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const isParentRoute = location.pathname === '/app/database';
|
||||
|
||||
@@ -14,8 +16,8 @@ const DatabasePage: React.FC = () => {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<PageHeader
|
||||
title="Mi Panadería"
|
||||
description="Consulta y gestiona toda la información de tu panadería"
|
||||
title={t('database:title', 'Mi Panadería')}
|
||||
description={t('database:subtitle', 'Consulta y gestiona toda la información de tu panadería')}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@@ -24,9 +26,9 @@ const DatabasePage: React.FC = () => {
|
||||
<div className="p-2 bg-blue-100 rounded-lg">
|
||||
<Package className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Recetas</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.recipes.title', 'Recetas')}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">Gestiona las recetas de tus productos</p>
|
||||
<p className="text-gray-600">{t('database:sections.recipes.description', 'Gestiona las recetas de tus productos')}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
|
||||
@@ -34,9 +36,9 @@ const DatabasePage: React.FC = () => {
|
||||
<div className="p-2 bg-green-100 rounded-lg">
|
||||
<ShoppingCart className="w-6 h-6 text-green-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Pedidos</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.orders.title', 'Pedidos')}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">Consulta el estado de todos los pedidos</p>
|
||||
<p className="text-gray-600">{t('database:sections.orders.description', 'Consulta el estado de todos los pedidos')}</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -45,9 +47,9 @@ const DatabasePage: React.FC = () => {
|
||||
<div className="p-2 bg-yellow-100 rounded-lg">
|
||||
<Truck className="w-6 h-6 text-yellow-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Proveedores</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.suppliers.title', 'Proveedores')}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">Gestiona tus proveedores</p>
|
||||
<p className="text-gray-600">{t('database:sections.suppliers.description', 'Gestiona tus proveedores')}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
|
||||
@@ -55,9 +57,9 @@ const DatabasePage: React.FC = () => {
|
||||
<div className="p-2 bg-red-100 rounded-lg">
|
||||
<Database className="w-6 h-6 text-red-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Inventario</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.inventory.title', 'Inventario')}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">Estado actual del inventario</p>
|
||||
<p className="text-gray-600">{t('database:sections.inventory.description', 'Estado actual del inventario')}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
|
||||
@@ -65,9 +67,9 @@ const DatabasePage: React.FC = () => {
|
||||
<div className="p-2 bg-indigo-100 rounded-lg">
|
||||
<Settings className="w-6 h-6 text-indigo-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Configuración de Panadería</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.bakery_config.title', 'Configuración de Panadería')}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">Configuración general de tu panadería</p>
|
||||
<p className="text-gray-600">{t('database:sections.bakery_config.description', 'Configuración general de tu panadería')}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
|
||||
@@ -75,9 +77,9 @@ const DatabasePage: React.FC = () => {
|
||||
<div className="p-2 bg-orange-100 rounded-lg">
|
||||
<Users className="w-6 h-6 text-orange-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Gestión de Equipo</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.team_management.title', 'Gestión de Equipo')}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">Administra tu equipo de trabajo</p>
|
||||
<p className="text-gray-600">{t('database:sections.team_management.description', 'Administra tu equipo de trabajo')}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
|
||||
@@ -85,9 +87,9 @@ const DatabasePage: React.FC = () => {
|
||||
<div className="p-2 bg-teal-100 rounded-lg">
|
||||
<MessageSquare className="w-6 h-6 text-teal-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Preferencias de Comunicación</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.communication_preferences.title', 'Preferencias de Comunicación')}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">Configura notificaciones y comunicaciones</p>
|
||||
<p className="text-gray-600">{t('database:sections.communication_preferences.description', 'Configura notificaciones y comunicaciones')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { Button } from '../../../../components/ui/Button';
|
||||
import { Card, CardHeader, CardBody } from '../../../../components/ui/Card';
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
|
||||
const OrganizationsPage: React.FC = () => {
|
||||
const { t } = useTranslation(['settings']);
|
||||
const navigate = useNavigate();
|
||||
const user = useAuthUser();
|
||||
const { currentTenant, availableTenants, switchTenant } = useTenant();
|
||||
@@ -77,8 +79,8 @@ const OrganizationsPage: React.FC = () => {
|
||||
return (
|
||||
<div className="space-y-6 p-4 sm:p-6">
|
||||
<PageHeader
|
||||
title="Mis Organizaciones"
|
||||
description="Gestiona tus panaderías y negocios"
|
||||
title={t('settings:organization.title', 'Mis Organizaciones')}
|
||||
description={t('settings:organization.description', 'Gestiona tus panaderías y negocios')}
|
||||
actions={
|
||||
<Button
|
||||
onClick={handleAddNewOrganization}
|
||||
|
||||
@@ -29,7 +29,7 @@ interface PasswordData {
|
||||
|
||||
const ProfilePage: React.FC = () => {
|
||||
const user = useAuthUser();
|
||||
const { t } = useTranslation('auth');
|
||||
const { t } = useTranslation(['settings', 'auth']);
|
||||
const { addToast } = useToast();
|
||||
|
||||
const { data: profile, isLoading: profileLoading, error: profileError } = useAuthProfile();
|
||||
@@ -405,7 +405,7 @@ const ProfilePage: React.FC = () => {
|
||||
)}
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span className="text-sm text-text-tertiary">En línea</span>
|
||||
<span className="text-sm text-text-tertiary">{t('settings:profile.online', 'En línea')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
@@ -416,7 +416,7 @@ const ProfilePage: React.FC = () => {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<User className="w-4 h-4" />
|
||||
Editar Perfil
|
||||
{t('settings:profile.edit_profile', 'Editar Perfil')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -425,7 +425,7 @@ const ProfilePage: React.FC = () => {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Lock className="w-4 h-4" />
|
||||
Cambiar Contraseña
|
||||
{t('settings:profile.change_password', 'Cambiar Contraseña')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -435,11 +435,11 @@ const ProfilePage: React.FC = () => {
|
||||
{/* Profile Form */}
|
||||
{activeTab === 'profile' && (
|
||||
<Card className="p-6">
|
||||
<h2 className="text-lg font-semibold mb-4">Información Personal</h2>
|
||||
<h2 className="text-lg font-semibold mb-4">{t('settings:profile.personal_info', 'Información Personal')}</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
<Input
|
||||
label="Nombre"
|
||||
label={t('settings:profile.fields.first_name', 'Nombre')}
|
||||
value={profileData.first_name}
|
||||
onChange={handleInputChange('first_name')}
|
||||
error={errors.first_name}
|
||||
@@ -448,7 +448,7 @@ const ProfilePage: React.FC = () => {
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Apellidos"
|
||||
label={t('settings:profile.fields.last_name', 'Apellidos')}
|
||||
value={profileData.last_name}
|
||||
onChange={handleInputChange('last_name')}
|
||||
error={errors.last_name}
|
||||
@@ -457,7 +457,7 @@ const ProfilePage: React.FC = () => {
|
||||
|
||||
<Input
|
||||
type="email"
|
||||
label="Correo Electrónico"
|
||||
label={t('settings:profile.fields.email', 'Correo Electrónico')}
|
||||
value={profileData.email}
|
||||
onChange={handleInputChange('email')}
|
||||
error={errors.email}
|
||||
@@ -467,7 +467,7 @@ const ProfilePage: React.FC = () => {
|
||||
|
||||
<Input
|
||||
type="tel"
|
||||
label="Teléfono"
|
||||
label={t('settings:profile.fields.phone', 'Teléfono')}
|
||||
value={profileData.phone}
|
||||
onChange={handleInputChange('phone')}
|
||||
error={errors.phone}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Users, Plus, Search, Shield, Trash2, Crown, UserCheck } from 'lucide-react';
|
||||
import { Button, Card, Input, StatusCard, getStatusColor, StatsGrid } from '../../../../components/ui';
|
||||
import AddTeamMemberModal from '../../../../components/domain/team/AddTeamMemberModal';
|
||||
@@ -11,6 +12,7 @@ import { useToast } from '../../../../hooks/ui/useToast';
|
||||
import { TENANT_ROLES } from '../../../../types/roles';
|
||||
|
||||
const TeamPage: React.FC = () => {
|
||||
const { t } = useTranslation(['settings']);
|
||||
const { addToast } = useToast();
|
||||
const currentUser = useAuthUser();
|
||||
const currentTenant = useCurrentTenant();
|
||||
@@ -299,8 +301,8 @@ const TeamPage: React.FC = () => {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<PageHeader
|
||||
title="Gestión de Equipo"
|
||||
description="Administra los miembros del equipo, roles y permisos"
|
||||
title={t('settings:team.title', 'Gestión de Equipo')}
|
||||
description={t('settings:team.description', 'Administra los miembros del equipo, roles y permisos')}
|
||||
actions={
|
||||
canManageTeam ? [{
|
||||
id: 'add-member',
|
||||
|
||||
@@ -10,6 +10,7 @@ const OnboardingPage: React.FC = () => {
|
||||
headerProps={{
|
||||
showThemeToggle: true,
|
||||
showAuthButtons: false,
|
||||
showLanguageSelector: true,
|
||||
variant: "minimal"
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../components/ui';
|
||||
import { PublicLayout } from '../../components/layout';
|
||||
import {
|
||||
@@ -23,6 +24,8 @@ import {
|
||||
} from 'lucide-react';
|
||||
|
||||
const LandingPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const scrollToSection = (sectionId: string) => {
|
||||
const element = document.getElementById(sectionId);
|
||||
if (element) {
|
||||
@@ -37,12 +40,13 @@ const LandingPage: React.FC = () => {
|
||||
headerProps={{
|
||||
showThemeToggle: true,
|
||||
showAuthButtons: true,
|
||||
showLanguageSelector: true,
|
||||
variant: "default",
|
||||
navigationItems: [
|
||||
{ id: 'features', label: 'Características', href: '#features' },
|
||||
{ id: 'benefits', label: 'Beneficios', href: '#benefits' },
|
||||
{ id: 'pricing', label: 'Precios', href: '#pricing' },
|
||||
{ id: 'testimonials', label: 'Testimonios', href: '#testimonials' }
|
||||
{ id: 'features', label: t('landing:navigation.features', 'Características'), href: '#features' },
|
||||
{ id: 'benefits', label: t('landing:navigation.benefits', 'Beneficios'), href: '#benefits' },
|
||||
{ id: 'pricing', label: t('landing:navigation.pricing', 'Precios'), href: '#pricing' },
|
||||
{ id: 'testimonials', label: t('landing:navigation.testimonials', 'Testimonios'), href: '#testimonials' }
|
||||
]
|
||||
}}
|
||||
>
|
||||
@@ -54,24 +58,23 @@ const LandingPage: React.FC = () => {
|
||||
<div className="mb-6">
|
||||
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-[var(--color-primary)]/10 text-[var(--color-primary)]">
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
IA Avanzada para Panaderías
|
||||
{t('landing:hero.badge', 'IA Avanzada para Panaderías')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl tracking-tight font-extrabold text-[var(--text-primary)] sm:text-5xl lg:text-7xl">
|
||||
<span className="block">Revoluciona tu</span>
|
||||
<span className="block text-[var(--color-primary)]">Panadería con IA</span>
|
||||
<span className="block">{t('landing:hero.title_line1', 'Revoluciona tu')}</span>
|
||||
<span className="block text-[var(--color-primary)]">{t('landing:hero.title_line2', 'Panadería con IA')}</span>
|
||||
</h1>
|
||||
|
||||
<p className="mt-6 max-w-3xl mx-auto text-lg text-[var(--text-secondary)] sm:text-xl">
|
||||
Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%,
|
||||
predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial.
|
||||
{t('landing:hero.subtitle', 'Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%, predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial.')}
|
||||
</p>
|
||||
|
||||
<div className="mt-10 flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link to="/register">
|
||||
<Button size="lg" className="px-8 py-4 text-lg font-semibold bg-[var(--color-primary)] hover:bg-[var(--color-primary-dark)] text-white shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200">
|
||||
Comenzar Gratis 14 Días
|
||||
{t('landing:hero.cta_primary', 'Comenzar Gratis 14 Días')}
|
||||
<ArrowRight className="ml-2 w-5 h-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
@@ -82,22 +85,22 @@ const LandingPage: React.FC = () => {
|
||||
onClick={() => scrollToSection('demo')}
|
||||
>
|
||||
<Play className="mr-2 w-5 h-5" />
|
||||
Ver Demo en Vivo
|
||||
{t('landing:hero.cta_secondary', 'Ver Demo en Vivo')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 flex items-center justify-center space-x-6 text-sm text-[var(--text-tertiary)]">
|
||||
<div className="flex items-center">
|
||||
<Check className="w-4 h-4 text-green-500 mr-2" />
|
||||
Sin tarjeta de crédito
|
||||
{t('landing:hero.features.no_credit_card', 'Sin tarjeta de crédito')}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Check className="w-4 h-4 text-green-500 mr-2" />
|
||||
Configuración en 5 minutos
|
||||
{t('landing:hero.features.quick_setup', 'Configuración en 5 minutos')}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Check className="w-4 h-4 text-green-500 mr-2" />
|
||||
Soporte 24/7 en español
|
||||
{t('landing:hero.features.support_24_7', 'Soporte 24/7 en español')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,7 @@ const LoginPage: React.FC = () => {
|
||||
headerProps={{
|
||||
showThemeToggle: true,
|
||||
showAuthButtons: false,
|
||||
showLanguageSelector: true,
|
||||
variant: "minimal"
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -21,6 +21,7 @@ const RegisterPage: React.FC = () => {
|
||||
headerProps={{
|
||||
showThemeToggle: true,
|
||||
showAuthButtons: false,
|
||||
showLanguageSelector: true,
|
||||
variant: "minimal"
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -415,7 +415,7 @@ export const routesConfig: RouteConfig[] = [
|
||||
path: '/app/database/team',
|
||||
name: 'Team',
|
||||
component: 'TeamPage',
|
||||
title: 'Gestión de Equipo',
|
||||
title: 'Trabajadores',
|
||||
icon: 'user',
|
||||
requiresAuth: true,
|
||||
requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS,
|
||||
@@ -460,7 +460,7 @@ export const routesConfig: RouteConfig[] = [
|
||||
path: '/app/settings/personal-info',
|
||||
name: 'PersonalInfo',
|
||||
component: 'PersonalInfoPage',
|
||||
title: 'Información Personal',
|
||||
title: 'Información',
|
||||
icon: 'user',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
@@ -470,7 +470,7 @@ export const routesConfig: RouteConfig[] = [
|
||||
path: '/app/settings/communication-preferences',
|
||||
name: 'CommunicationPreferences',
|
||||
component: 'CommunicationPreferencesPage',
|
||||
title: 'Preferencias de Comunicación',
|
||||
title: 'Notificaciones',
|
||||
icon: 'bell',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
@@ -490,7 +490,7 @@ export const routesConfig: RouteConfig[] = [
|
||||
path: '/app/settings/organizations',
|
||||
name: 'Organizations',
|
||||
component: 'OrganizationsPage',
|
||||
title: 'Mis Organizaciones',
|
||||
title: 'Establecimientos',
|
||||
icon: 'database',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
|
||||
Reference in New Issue
Block a user