Add traslations 2
This commit is contained in:
@@ -75,25 +75,25 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
const errors: Record<string, string> = {};
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
if (!formData.name?.trim()) {
|
if (!formData.name?.trim()) {
|
||||||
errors.name = 'El nombre es requerido';
|
errors.name = t('onboarding:child_tenants.validation.name_required');
|
||||||
}
|
}
|
||||||
if (!formData.city?.trim()) {
|
if (!formData.city?.trim()) {
|
||||||
errors.city = 'La ciudad es requerida';
|
errors.city = t('onboarding:child_tenants.validation.city_required');
|
||||||
}
|
}
|
||||||
if (!formData.address?.trim()) {
|
if (!formData.address?.trim()) {
|
||||||
errors.address = 'La dirección es requerida';
|
errors.address = t('onboarding:child_tenants.validation.address_required');
|
||||||
}
|
}
|
||||||
if (!formData.postal_code?.trim()) {
|
if (!formData.postal_code?.trim()) {
|
||||||
errors.postal_code = 'El código postal es requerido';
|
errors.postal_code = t('onboarding:child_tenants.validation.postal_code_required');
|
||||||
} else if (!/^\d{5}$/.test(formData.postal_code)) {
|
} else if (!/^\d{5}$/.test(formData.postal_code)) {
|
||||||
errors.postal_code = 'El código postal debe tener exactamente 5 dígitos';
|
errors.postal_code = t('onboarding:child_tenants.validation.postal_code_format');
|
||||||
}
|
}
|
||||||
if (!formData.location_code?.trim()) {
|
if (!formData.location_code?.trim()) {
|
||||||
errors.location_code = 'El código de ubicación es requerido';
|
errors.location_code = t('onboarding:child_tenants.validation.location_code_required');
|
||||||
} else if (formData.location_code.length > 10) {
|
} else if (formData.location_code.length > 10) {
|
||||||
errors.location_code = 'El código no debe exceder 10 caracteres';
|
errors.location_code = t('onboarding:child_tenants.validation.location_code_max');
|
||||||
} else if (!/^[A-Z0-9\-_.]+$/.test(formData.location_code)) {
|
} else if (!/^[A-Z0-9\-_.]+$/.test(formData.location_code)) {
|
||||||
errors.location_code = 'Solo se permiten letras mayúsculas, números y guiones/guiones bajos';
|
errors.location_code = t('onboarding:child_tenants.validation.location_code_format');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phone validation
|
// Phone validation
|
||||||
@@ -104,7 +104,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
/^(\+34|0034|34)?9\d{8}$/ // Landline
|
/^(\+34|0034|34)?9\d{8}$/ // Landline
|
||||||
];
|
];
|
||||||
if (!patterns.some(pattern => pattern.test(phone))) {
|
if (!patterns.some(pattern => pattern.test(phone))) {
|
||||||
errors.phone = 'Introduce un número de teléfono español válido';
|
errors.phone = t('onboarding:child_tenants.validation.phone_format');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
if (formData.email && formData.email.trim()) {
|
if (formData.email && formData.email.trim()) {
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
if (!emailRegex.test(formData.email)) {
|
if (!emailRegex.test(formData.email)) {
|
||||||
errors.email = 'Introduce un correo electrónico válido';
|
errors.email = t('onboarding:child_tenants.validation.email_format');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
|
|
||||||
const handleContinue = () => {
|
const handleContinue = () => {
|
||||||
if (childTenants.length === 0) {
|
if (childTenants.length === 0) {
|
||||||
alert('Debes agregar al menos una sucursal para continuar');
|
alert(t('onboarding:child_tenants.alerts.require_one'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onComplete?.({ childTenants });
|
onComplete?.({ childTenants });
|
||||||
@@ -204,12 +204,11 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
<div className="flex items-center justify-center gap-3">
|
<div className="flex items-center justify-center gap-3">
|
||||||
<Building2 className="w-10 h-10 text-[var(--color-primary)]" />
|
<Building2 className="w-10 h-10 text-[var(--color-primary)]" />
|
||||||
<h1 className="text-2xl md:text-3xl font-bold text-[var(--text-primary)]">
|
<h1 className="text-2xl md:text-3xl font-bold text-[var(--text-primary)]">
|
||||||
Configuración de Sucursales
|
{t('onboarding:child_tenants.title')}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-base md:text-lg text-[var(--text-secondary)] max-w-2xl mx-auto">
|
<p className="text-base md:text-lg text-[var(--text-secondary)] max-w-2xl mx-auto">
|
||||||
Como empresa con tier Enterprise, tienes un obrador central y múltiples sucursales.
|
{t('onboarding:child_tenants.subtitle')}
|
||||||
Agrega la información de cada sucursal que recibirá productos del obrador central.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -221,11 +220,10 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)]">
|
<h4 className="font-semibold text-[var(--text-primary)]">
|
||||||
Modelo de Negocio Enterprise
|
{t('onboarding:child_tenants.info_box.title')}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">
|
<p className="text-sm text-[var(--text-secondary)]">
|
||||||
Tu obrador central se encargará de la producción, y las sucursales recibirán
|
{t('onboarding:child_tenants.info_box.description')}
|
||||||
los productos terminados mediante transferencias internas optimizadas.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -235,7 +233,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-lg font-semibold text-[var(--text-primary)]">
|
<h2 className="text-lg font-semibold text-[var(--text-primary)]">
|
||||||
Sucursales ({childTenants.length})
|
{t('onboarding:child_tenants.list.title')} ({childTenants.length})
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleOpenModal()}
|
onClick={() => handleOpenModal()}
|
||||||
@@ -243,7 +241,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
size="md"
|
size="md"
|
||||||
leftIcon={<Plus className="w-4 h-4" />}
|
leftIcon={<Plus className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
Agregar Sucursal
|
{t('onboarding:child_tenants.list.add_button')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -255,10 +253,10 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">
|
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">
|
||||||
No hay sucursales agregadas
|
{t('onboarding:child_tenants.list.empty_state.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
||||||
Comienza agregando las sucursales que forman parte de tu red empresarial
|
{t('onboarding:child_tenants.list.empty_state.description')}
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleOpenModal()}
|
onClick={() => handleOpenModal()}
|
||||||
@@ -266,7 +264,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
size="lg"
|
size="lg"
|
||||||
leftIcon={<Plus className="w-5 h-5" />}
|
leftIcon={<Plus className="w-5 h-5" />}
|
||||||
>
|
>
|
||||||
Agregar Primera Sucursal
|
{t('onboarding:child_tenants.list.empty_state.button')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -302,14 +300,14 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleOpenModal(tenant)}
|
onClick={() => handleOpenModal(tenant)}
|
||||||
className="p-2 hover:bg-[var(--bg-tertiary)] rounded-lg transition-colors"
|
className="p-2 hover:bg-[var(--bg-tertiary)] rounded-lg transition-colors"
|
||||||
title="Editar"
|
title={t('onboarding:child_tenants.card.edit')}
|
||||||
>
|
>
|
||||||
<Edit2 className="w-4 h-4 text-[var(--text-secondary)]" />
|
<Edit2 className="w-4 h-4 text-[var(--text-secondary)]" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteTenant(tenant.id)}
|
onClick={() => handleDeleteTenant(tenant.id)}
|
||||||
className="p-2 hover:bg-[var(--color-error)]/10 rounded-lg transition-colors"
|
className="p-2 hover:bg-[var(--color-error)]/10 rounded-lg transition-colors"
|
||||||
title="Eliminar"
|
title={t('onboarding:child_tenants.card.delete')}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 text-[var(--color-error)]" />
|
<Trash2 className="w-4 h-4 text-[var(--color-error)]" />
|
||||||
</button>
|
</button>
|
||||||
@@ -345,7 +343,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
size="lg"
|
size="lg"
|
||||||
className="w-full sm:w-auto sm:min-w-[200px]"
|
className="w-full sm:w-auto sm:min-w-[200px]"
|
||||||
>
|
>
|
||||||
Continuar con {childTenants.length} {childTenants.length === 1 ? 'Sucursal' : 'Sucursales'}
|
{t('onboarding:child_tenants.continue_button', { count: childTenants.length })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -357,7 +355,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
<ModalHeader
|
<ModalHeader
|
||||||
title={editingTenant ? 'Editar Sucursal' : 'Agregar Sucursal'}
|
title={t(editingTenant ? 'onboarding:child_tenants.modal.title_edit' : 'onboarding:child_tenants.modal.title_add')}
|
||||||
showCloseButton
|
showCloseButton
|
||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
/>
|
/>
|
||||||
@@ -366,12 +364,12 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
{/* Name */}
|
{/* Name */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Nombre de la Sucursal *
|
{t('onboarding:child_tenants.modal.fields.name')} *
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
value={formData.name || ''}
|
value={formData.name || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
placeholder="ej. Madrid - Salamanca"
|
placeholder={t('onboarding:child_tenants.modal.placeholders.name')}
|
||||||
error={formErrors.name}
|
error={formErrors.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -379,48 +377,48 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
{/* Location Code */}
|
{/* Location Code */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Código de Ubicación * (máx. 10 caracteres)
|
{t('onboarding:child_tenants.modal.fields.location_code')} * {t('onboarding:child_tenants.modal.fields.location_code_max')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
value={formData.location_code || ''}
|
value={formData.location_code || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, location_code: e.target.value.toUpperCase() })}
|
onChange={(e) => setFormData({ ...formData, location_code: e.target.value.toUpperCase() })}
|
||||||
placeholder="ej. MAD, BCN, VAL"
|
placeholder={t('onboarding:child_tenants.modal.placeholders.location_code')}
|
||||||
maxLength={10}
|
maxLength={10}
|
||||||
error={formErrors.location_code}
|
error={formErrors.location_code}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-[var(--text-tertiary)] mt-1">
|
<p className="text-xs text-[var(--text-tertiary)] mt-1">
|
||||||
Un código corto para identificar esta ubicación
|
{t('onboarding:child_tenants.modal.fields.location_code_help')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Business Type */}
|
{/* Business Type */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Tipo de Negocio
|
{t('onboarding:child_tenants.modal.fields.business_type')}
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<Select
|
||||||
value={formData.business_type || 'bakery'}
|
value={formData.business_type || 'bakery'}
|
||||||
onChange={(e) => setFormData({ ...formData, business_type: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, business_type: e.target.value })}
|
||||||
>
|
>
|
||||||
<option value="bakery">Panadería</option>
|
<option value="bakery">{t('onboarding:child_tenants.modal.business_types.bakery')}</option>
|
||||||
<option value="coffee_shop">Cafetería</option>
|
<option value="coffee_shop">{t('onboarding:child_tenants.modal.business_types.coffee_shop')}</option>
|
||||||
<option value="pastry_shop">Pastelería</option>
|
<option value="pastry_shop">{t('onboarding:child_tenants.modal.business_types.pastry_shop')}</option>
|
||||||
<option value="restaurant">Restaurante</option>
|
<option value="restaurant">{t('onboarding:child_tenants.modal.business_types.restaurant')}</option>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Business Model */}
|
{/* Business Model */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Modelo de Negocio
|
{t('onboarding:child_tenants.modal.fields.business_model')}
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<Select
|
||||||
value={formData.business_model || 'retail_bakery'}
|
value={formData.business_model || 'retail_bakery'}
|
||||||
onChange={(e) => setFormData({ ...formData, business_model: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, business_model: e.target.value })}
|
||||||
>
|
>
|
||||||
<option value="retail_bakery">Panadería Minorista</option>
|
<option value="retail_bakery">{t('onboarding:child_tenants.modal.business_models.retail_bakery')}</option>
|
||||||
<option value="central_baker_satellite">Obrador Central + Sucursales</option>
|
<option value="central_baker_satellite">{t('onboarding:child_tenants.modal.business_models.central_baker_satellite')}</option>
|
||||||
<option value="hybrid_bakery">Modelo Híbrido</option>
|
<option value="hybrid_bakery">{t('onboarding:child_tenants.modal.business_models.hybrid_bakery')}</option>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -428,14 +426,14 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Teléfono
|
{t('onboarding:child_tenants.modal.fields.phone')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Phone className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-[var(--text-secondary)]" />
|
<Phone className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-[var(--text-secondary)]" />
|
||||||
<Input
|
<Input
|
||||||
value={formData.phone || ''}
|
value={formData.phone || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
|
||||||
placeholder="ej. +34 123 456 789"
|
placeholder={t('onboarding:child_tenants.modal.placeholders.phone')}
|
||||||
error={formErrors.phone}
|
error={formErrors.phone}
|
||||||
className="pl-10"
|
className="pl-10"
|
||||||
/>
|
/>
|
||||||
@@ -443,14 +441,14 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Email
|
{t('onboarding:child_tenants.modal.fields.email')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-[var(--text-secondary)]" />
|
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-[var(--text-secondary)]" />
|
||||||
<Input
|
<Input
|
||||||
value={formData.email || ''}
|
value={formData.email || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||||
placeholder="ej. contacto@panaderia.com"
|
placeholder={t('onboarding:child_tenants.modal.placeholders.email')}
|
||||||
error={formErrors.email}
|
error={formErrors.email}
|
||||||
className="pl-10"
|
className="pl-10"
|
||||||
/>
|
/>
|
||||||
@@ -461,7 +459,7 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
{/* Timezone */}
|
{/* Timezone */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Zona Horaria
|
{t('onboarding:child_tenants.modal.fields.timezone')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Clock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-[var(--text-secondary)]" />
|
<Clock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-[var(--text-secondary)]" />
|
||||||
@@ -470,10 +468,10 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
onChange={(e) => setFormData({ ...formData, timezone: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, timezone: e.target.value })}
|
||||||
className="pl-10"
|
className="pl-10"
|
||||||
>
|
>
|
||||||
<option value="Europe/Madrid">Europe/Madrid (UTC+1/UTC+2)</option>
|
<option value="Europe/Madrid">{t('onboarding:child_tenants.modal.timezones.europe_madrid')}</option>
|
||||||
<option value="Europe/Paris">Europe/Paris (UTC+1/UTC+2)</option>
|
<option value="Europe/Paris">{t('onboarding:child_tenants.modal.timezones.europe_paris')}</option>
|
||||||
<option value="Europe/London">Europe/London (UTC+0/UTC+1)</option>
|
<option value="Europe/London">{t('onboarding:child_tenants.modal.timezones.europe_london')}</option>
|
||||||
<option value="America/New_York">America/New_York (UTC-5/UTC-4)</option>
|
<option value="America/New_York">{t('onboarding:child_tenants.modal.timezones.america_new_york')}</option>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -481,26 +479,26 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
{/* Zone */}
|
{/* Zone */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Zona / Barrio (opcional)
|
{t('onboarding:child_tenants.modal.fields.zone')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
value={formData.zone || ''}
|
value={formData.zone || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, zone: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, zone: e.target.value })}
|
||||||
placeholder="ej. Salamanca, Chamberí, Centro"
|
placeholder={t('onboarding:child_tenants.modal.placeholders.zone')}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-[var(--text-tertiary)] mt-1">
|
<p className="text-xs text-[var(--text-tertiary)] mt-1">
|
||||||
Zona o barrio específico dentro de la ciudad
|
{t('onboarding:child_tenants.modal.fields.zone_help')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Address with Autocomplete */}
|
{/* Address with Autocomplete */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Dirección *
|
{t('onboarding:child_tenants.modal.fields.address')} *
|
||||||
</label>
|
</label>
|
||||||
<AddressAutocomplete
|
<AddressAutocomplete
|
||||||
value={formData.address || ''}
|
value={formData.address || ''}
|
||||||
placeholder="ej. Calle de Serrano, 48"
|
placeholder={t('onboarding:child_tenants.modal.placeholders.address')}
|
||||||
onAddressSelect={(address) => {
|
onAddressSelect={(address) => {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -532,23 +530,23 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Ciudad *
|
{t('onboarding:child_tenants.modal.fields.city')} *
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
value={formData.city || ''}
|
value={formData.city || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, city: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, city: e.target.value })}
|
||||||
placeholder="ej. Madrid"
|
placeholder={t('onboarding:child_tenants.modal.placeholders.city')}
|
||||||
error={formErrors.city}
|
error={formErrors.city}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||||
Código Postal *
|
{t('onboarding:child_tenants.modal.fields.postal_code')} *
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
value={formData.postal_code || ''}
|
value={formData.postal_code || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, postal_code: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, postal_code: e.target.value })}
|
||||||
placeholder="ej. 28001"
|
placeholder={t('onboarding:child_tenants.modal.placeholders.postal_code')}
|
||||||
error={formErrors.postal_code}
|
error={formErrors.postal_code}
|
||||||
maxLength={5}
|
maxLength={5}
|
||||||
/>
|
/>
|
||||||
@@ -559,10 +557,10 @@ export const ChildTenantsSetupStep: React.FC<ChildTenantSetupStepProps> = ({
|
|||||||
<ModalFooter justify="end">
|
<ModalFooter justify="end">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button variant="outline" onClick={handleCloseModal}>
|
<Button variant="outline" onClick={handleCloseModal}>
|
||||||
Cancelar
|
{t('onboarding:child_tenants.modal.buttons.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" onClick={handleSaveTenant}>
|
<Button variant="primary" onClick={handleSaveTenant}>
|
||||||
{editingTenant ? 'Guardar Cambios' : 'Agregar Sucursal'}
|
{t(editingTenant ? 'onboarding:child_tenants.modal.buttons.save' : 'onboarding:child_tenants.modal.buttons.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
|||||||
@@ -63,18 +63,18 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
setTrainingProgress({
|
setTrainingProgress({
|
||||||
stage: 'training',
|
stage: 'training',
|
||||||
progress: data.data?.progress || 0,
|
progress: data.data?.progress || 0,
|
||||||
message: data.data?.message || 'Entrenando modelo...',
|
message: data.data?.message || t('onboarding:steps.ml_training.messages.training'),
|
||||||
currentStep: data.data?.current_step,
|
currentStep: data.data?.current_step,
|
||||||
estimatedTimeRemaining: data.data?.estimated_time_remaining_seconds || data.data?.estimated_time_remaining,
|
estimatedTimeRemaining: data.data?.estimated_time_remaining_seconds || data.data?.estimated_time_remaining,
|
||||||
estimatedCompletionTime: data.data?.estimated_completion_time
|
estimatedCompletionTime: data.data?.estimated_completion_time
|
||||||
});
|
});
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
const handleCompleted = useCallback((_data: any) => {
|
const handleCompleted = useCallback((_data: any) => {
|
||||||
setTrainingProgress({
|
setTrainingProgress({
|
||||||
stage: 'completed',
|
stage: 'completed',
|
||||||
progress: 100,
|
progress: 100,
|
||||||
message: 'Entrenamiento completado exitosamente'
|
message: t('onboarding:steps.ml_training.messages.completed')
|
||||||
});
|
});
|
||||||
setIsTraining(false);
|
setIsTraining(false);
|
||||||
|
|
||||||
@@ -82,24 +82,24 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
onComplete({
|
onComplete({
|
||||||
jobId: jobId,
|
jobId: jobId,
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Modelo entrenado correctamente'
|
message: t('onboarding:steps.ml_training.messages.completed')
|
||||||
});
|
});
|
||||||
}, TRAINING_COMPLETION_DELAY_MS);
|
}, TRAINING_COMPLETION_DELAY_MS);
|
||||||
}, [onComplete, jobId]);
|
}, [onComplete, jobId, t]);
|
||||||
|
|
||||||
const handleError = useCallback((data: any) => {
|
const handleError = useCallback((data: any) => {
|
||||||
setError(data.data?.error || data.error || 'Error durante el entrenamiento');
|
setError(data.data?.error || data.error || t('onboarding:errors.training_failed'));
|
||||||
setIsTraining(false);
|
setIsTraining(false);
|
||||||
setTrainingProgress(null);
|
setTrainingProgress(null);
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
const handleStarted = useCallback((_data: any) => {
|
const handleStarted = useCallback((_data: any) => {
|
||||||
setTrainingProgress({
|
setTrainingProgress({
|
||||||
stage: 'starting',
|
stage: 'starting',
|
||||||
progress: 5,
|
progress: 5,
|
||||||
message: 'Iniciando entrenamiento del modelo...'
|
message: t('onboarding:steps.ml_training.messages.starting')
|
||||||
});
|
});
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
// WebSocket for real-time training progress - only connect when we have a jobId
|
// WebSocket for real-time training progress - only connect when we have a jobId
|
||||||
const { isConnected, connectionError } = useTrainingWebSocket(
|
const { isConnected, connectionError } = useTrainingWebSocket(
|
||||||
@@ -140,8 +140,8 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
stage: 'completed',
|
stage: 'completed',
|
||||||
progress: 100,
|
progress: 100,
|
||||||
message: isConnected
|
message: isConnected
|
||||||
? 'Entrenamiento completado exitosamente'
|
? t('onboarding:steps.ml_training.messages.completed')
|
||||||
: 'Entrenamiento completado exitosamente (detectado por verificación HTTP)'
|
: t('onboarding:steps.ml_training.messages.completed_http')
|
||||||
});
|
});
|
||||||
setIsTraining(false);
|
setIsTraining(false);
|
||||||
|
|
||||||
@@ -149,13 +149,13 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
onComplete({
|
onComplete({
|
||||||
jobId: jobId,
|
jobId: jobId,
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Modelo entrenado correctamente',
|
message: t('onboarding:steps.ml_training.messages.completed'),
|
||||||
detectedViaPolling: true
|
detectedViaPolling: true
|
||||||
});
|
});
|
||||||
}, TRAINING_COMPLETION_DELAY_MS);
|
}, TRAINING_COMPLETION_DELAY_MS);
|
||||||
} else if (jobStatus.status === 'failed') {
|
} else if (jobStatus.status === 'failed') {
|
||||||
console.log(`❌ Training failure detected (source: ${isConnected ? 'WebSocket' : 'HTTP polling'})`);
|
console.log(`❌ Training failure detected (source: ${isConnected ? 'WebSocket' : 'HTTP polling'})`);
|
||||||
setError('Error detectado durante el entrenamiento (verificación de estado)');
|
setError(t('onboarding:errors.training_failed'));
|
||||||
setIsTraining(false);
|
setIsTraining(false);
|
||||||
setTrainingProgress(null);
|
setTrainingProgress(null);
|
||||||
} else if (jobStatus.status === 'running' && jobStatus.progress !== undefined) {
|
} else if (jobStatus.status === 'running' && jobStatus.progress !== undefined) {
|
||||||
@@ -167,12 +167,12 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
...prev,
|
...prev,
|
||||||
stage: 'training',
|
stage: 'training',
|
||||||
progress: jobStatus.progress,
|
progress: jobStatus.progress,
|
||||||
message: jobStatus.message || 'Entrenando modelo...',
|
message: jobStatus.message || t('onboarding:steps.ml_training.messages.training'),
|
||||||
currentStep: jobStatus.current_step
|
currentStep: jobStatus.current_step
|
||||||
}) as TrainingProgress);
|
}) as TrainingProgress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [jobStatus, jobId, trainingProgress?.stage, onComplete, isConnected]);
|
}, [jobStatus, jobId, trainingProgress?.stage, onComplete, isConnected, t]);
|
||||||
|
|
||||||
// Auto-trigger training when component mounts (run once)
|
// Auto-trigger training when component mounts (run once)
|
||||||
const hasAutoStarted = React.useRef(false);
|
const hasAutoStarted = React.useRef(false);
|
||||||
@@ -186,7 +186,7 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
|
|
||||||
const handleStartTraining = async () => {
|
const handleStartTraining = async () => {
|
||||||
if (!currentTenant?.id) {
|
if (!currentTenant?.id) {
|
||||||
setError('No se encontró información del tenant');
|
setError(t('onboarding:errors.network_error'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
setTrainingProgress({
|
setTrainingProgress({
|
||||||
stage: 'preparing',
|
stage: 'preparing',
|
||||||
progress: 0,
|
progress: 0,
|
||||||
message: 'Preparando datos para entrenamiento...'
|
message: t('onboarding:steps.ml_training.messages.preparing')
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -213,10 +213,10 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
setTrainingProgress({
|
setTrainingProgress({
|
||||||
stage: 'queued',
|
stage: 'queued',
|
||||||
progress: 10,
|
progress: 10,
|
||||||
message: 'Trabajo de entrenamiento en cola...'
|
message: t('onboarding:steps.ml_training.messages.queued')
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Error al iniciar el entrenamiento del modelo');
|
setError(t('onboarding:errors.training_failed'));
|
||||||
setIsTraining(false);
|
setIsTraining(false);
|
||||||
setTrainingProgress(null);
|
setTrainingProgress(null);
|
||||||
}
|
}
|
||||||
@@ -271,8 +271,7 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-[var(--text-secondary)] mb-6">
|
<p className="text-[var(--text-secondary)] mb-6">
|
||||||
Perfecto! Ahora entrenaremos automáticamente tu modelo de inteligencia artificial utilizando los datos de ventas
|
{t('onboarding:steps.ml_training.intro_text')}
|
||||||
e inventario que has proporcionado. Este proceso puede tomar varios minutos.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -285,9 +284,9 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--color-primary)]"></div>
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--color-primary)]"></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-2">Iniciando Entrenamiento Automático</h3>
|
<h3 className="text-lg font-semibold mb-2">{t('onboarding:steps.ml_training.status.preparing')}</h3>
|
||||||
<p className="text-[var(--text-secondary)] text-sm">
|
<p className="text-[var(--text-secondary)] text-sm">
|
||||||
Preparando el entrenamiento de tu modelo con los datos proporcionados...
|
{t('onboarding:steps.ml_training.messages.preparing')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -320,8 +319,8 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-2">
|
<h3 className="text-lg font-semibold mb-2">
|
||||||
{trainingProgress.stage === 'completed'
|
{trainingProgress.stage === 'completed'
|
||||||
? '¡Entrenamiento Completo!'
|
? t('onboarding:steps.ml_training.status.completed')
|
||||||
: 'Entrenando Modelo IA'
|
: t('onboarding:steps.ml_training.title')
|
||||||
}
|
}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[var(--text-secondary)] text-sm mb-4">
|
<p className="text-[var(--text-secondary)] text-sm mb-4">
|
||||||
@@ -356,7 +355,7 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
<span className="font-medium">{trainingProgress.currentStep || t('onboarding:steps.ml_training.progress.data_preparation', 'Procesando...')}</span>
|
<span className="font-medium">{trainingProgress.currentStep || t('onboarding:steps.ml_training.progress.data_preparation', 'Procesando...')}</span>
|
||||||
{jobId && (
|
{jobId && (
|
||||||
<span className={`text-xs px-2 py-0.5 rounded-full ${isConnected ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' : 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'}`}>
|
<span className={`text-xs px-2 py-0.5 rounded-full ${isConnected ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' : 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'}`}>
|
||||||
{isConnected ? '● En vivo' : '● Reconectando...'}
|
{isConnected ? `● ${t('onboarding:steps.ml_training.messages.live')}` : `● ${t('onboarding:steps.ml_training.messages.reconnecting')}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -381,7 +380,7 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>
|
<span>
|
||||||
Finalizará: {formatEstimatedCompletionTime(trainingProgress.estimatedCompletionTime)}
|
{t('onboarding:steps.ml_training.messages.estimated_completion')} {formatEstimatedCompletionTime(trainingProgress.estimatedCompletionTime)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -426,12 +425,12 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
|
|||||||
|
|
||||||
{/* Training Info */}
|
{/* Training Info */}
|
||||||
<div className="bg-[var(--bg-secondary)] rounded-lg p-4">
|
<div className="bg-[var(--bg-secondary)] rounded-lg p-4">
|
||||||
<h4 className="font-medium mb-2">¿Qué sucede durante el entrenamiento?</h4>
|
<h4 className="font-medium mb-2">{t('onboarding:steps.ml_training.training_info.title')}</h4>
|
||||||
<ul className="text-sm text-[var(--text-secondary)] space-y-1">
|
<ul className="text-sm text-[var(--text-secondary)] space-y-1">
|
||||||
<li>• Análisis de patrones de ventas históricos</li>
|
<li>• {t('onboarding:steps.ml_training.training_info.step1')}</li>
|
||||||
<li>• Creación de modelos predictivos de demanda</li>
|
<li>• {t('onboarding:steps.ml_training.training_info.step2')}</li>
|
||||||
<li>• Optimización de algoritmos de inventario</li>
|
<li>• {t('onboarding:steps.ml_training.training_info.step3')}</li>
|
||||||
<li>• Validación y ajuste de precisión</li>
|
<li>• {t('onboarding:steps.ml_training.training_info.step4')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Input } from '../../../ui';
|
import { Button, Input } from '../../../ui';
|
||||||
import { AddressAutocomplete } from '../../../ui/AddressAutocomplete';
|
import { AddressAutocomplete } from '../../../ui/AddressAutocomplete';
|
||||||
import { useRegisterBakery, useTenant, useUpdateTenant } from '../../../../api/hooks/tenant';
|
import { useRegisterBakery, useTenant, useUpdateTenant } from '../../../../api/hooks/tenant';
|
||||||
@@ -33,6 +34,7 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
onComplete,
|
onComplete,
|
||||||
isFirstStep
|
isFirstStep
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const wizardContext = useWizardContext();
|
const wizardContext = useWizardContext();
|
||||||
const tenantId = wizardContext.state.tenantId;
|
const tenantId = wizardContext.state.tenantId;
|
||||||
|
|
||||||
@@ -126,27 +128,27 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
|
|
||||||
// Required fields according to backend BakeryRegistration schema
|
// Required fields according to backend BakeryRegistration schema
|
||||||
if (!formData.name.trim()) {
|
if (!formData.name.trim()) {
|
||||||
newErrors.name = 'El nombre de la panadería es obligatorio';
|
newErrors.name = t('onboarding:steps.tenant_registration.validation.name_required');
|
||||||
} else if (formData.name.length < 2 || formData.name.length > 200) {
|
} else if (formData.name.length < 2 || formData.name.length > 200) {
|
||||||
newErrors.name = 'El nombre debe tener entre 2 y 200 caracteres';
|
newErrors.name = t('onboarding:steps.tenant_registration.validation.name_length');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.address.trim()) {
|
if (!formData.address.trim()) {
|
||||||
newErrors.address = 'La dirección es obligatoria';
|
newErrors.address = t('onboarding:steps.tenant_registration.validation.address_required');
|
||||||
} else if (formData.address.length < 10 || formData.address.length > 500) {
|
} else if (formData.address.length < 10 || formData.address.length > 500) {
|
||||||
newErrors.address = 'La dirección debe tener entre 10 y 500 caracteres';
|
newErrors.address = t('onboarding:steps.tenant_registration.validation.address_length');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.postal_code.trim()) {
|
if (!formData.postal_code.trim()) {
|
||||||
newErrors.postal_code = 'El código postal es obligatorio';
|
newErrors.postal_code = t('onboarding:steps.tenant_registration.validation.postal_code_required');
|
||||||
} else if (!/^\d{5}$/.test(formData.postal_code)) {
|
} else if (!/^\d{5}$/.test(formData.postal_code)) {
|
||||||
newErrors.postal_code = 'El código postal debe tener exactamente 5 dígitos';
|
newErrors.postal_code = t('onboarding:steps.tenant_registration.validation.postal_code_format');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.phone.trim()) {
|
if (!formData.phone.trim()) {
|
||||||
newErrors.phone = 'El número de teléfono es obligatorio';
|
newErrors.phone = t('onboarding:steps.tenant_registration.validation.phone_required');
|
||||||
} else if (formData.phone.length < 9 || formData.phone.length > 20) {
|
} else if (formData.phone.length < 9 || formData.phone.length > 20) {
|
||||||
newErrors.phone = 'El teléfono debe tener entre 9 y 20 caracteres';
|
newErrors.phone = t('onboarding:steps.tenant_registration.validation.phone_length');
|
||||||
} else {
|
} else {
|
||||||
// Basic Spanish phone validation
|
// Basic Spanish phone validation
|
||||||
const phone = formData.phone.replace(/[\s\-\(\)]/g, '');
|
const phone = formData.phone.replace(/[\s\-\(\)]/g, '');
|
||||||
@@ -155,7 +157,7 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
/^(\+34|0034|34)?9\d{8}$/ // Landline
|
/^(\+34|0034|34)?9\d{8}$/ // Landline
|
||||||
];
|
];
|
||||||
if (!patterns.some(pattern => pattern.test(phone))) {
|
if (!patterns.some(pattern => pattern.test(phone))) {
|
||||||
newErrors.phone = 'Introduce un número de teléfono español válido';
|
newErrors.phone = t('onboarding:steps.tenant_registration.validation.phone_format');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +242,7 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error registering bakery:', error);
|
console.error('Error registering bakery:', error);
|
||||||
setErrors({ submit: 'Error al registrar la panadería. Por favor, inténtalo de nuevo.' });
|
setErrors({ submit: t('onboarding:steps.tenant_registration.errors.register') });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -252,12 +254,16 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
<div className="text-2xl flex-shrink-0">{isEnterprise ? '🏭' : '🏪'}</div>
|
<div className="text-2xl flex-shrink-0">{isEnterprise ? '🏭' : '🏪'}</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-[var(--text-primary)] mb-1">
|
<h3 className="font-semibold text-[var(--text-primary)] mb-1">
|
||||||
{isEnterprise ? 'Registra tu Obrador Central' : 'Registra tu Panadería'}
|
{t(isEnterprise
|
||||||
|
? 'onboarding:steps.tenant_registration.header.title_enterprise'
|
||||||
|
: 'onboarding:steps.tenant_registration.header.title'
|
||||||
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">
|
<p className="text-sm text-[var(--text-secondary)]">
|
||||||
{isEnterprise
|
{t(isEnterprise
|
||||||
? 'Ingresa los datos de tu obrador principal. Después podrás agregar las sucursales.'
|
? 'onboarding:steps.tenant_registration.header.description_enterprise'
|
||||||
: 'Completa la información básica de tu panadería para comenzar.'}
|
: 'onboarding:steps.tenant_registration.header.description'
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -266,8 +272,14 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5 md:gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-5 md:gap-6">
|
||||||
<div className="transform transition-all duration-200 hover:scale-[1.01]">
|
<div className="transform transition-all duration-200 hover:scale-[1.01]">
|
||||||
<Input
|
<Input
|
||||||
label={isEnterprise ? "Nombre del Obrador Central" : "Nombre de la Panadería"}
|
label={t(isEnterprise
|
||||||
placeholder={isEnterprise ? "Ingresa el nombre de tu obrador central" : "Ingresa el nombre de tu panadería"}
|
? 'onboarding:steps.tenant_registration.fields.business_name_enterprise'
|
||||||
|
: 'onboarding:steps.tenant_registration.fields.business_name'
|
||||||
|
)}
|
||||||
|
placeholder={t(isEnterprise
|
||||||
|
? 'onboarding:steps.tenant_registration.placeholders.business_name_enterprise'
|
||||||
|
: 'onboarding:steps.tenant_registration.placeholders.business_name'
|
||||||
|
)}
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => handleInputChange('name', e.target.value)}
|
onChange={(e) => handleInputChange('name', e.target.value)}
|
||||||
error={errors.name}
|
error={errors.name}
|
||||||
@@ -277,9 +289,9 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
|
|
||||||
<div className="transform transition-all duration-200 hover:scale-[1.01]">
|
<div className="transform transition-all duration-200 hover:scale-[1.01]">
|
||||||
<Input
|
<Input
|
||||||
label="Teléfono"
|
label={t('onboarding:steps.tenant_registration.fields.phone')}
|
||||||
type="tel"
|
type="tel"
|
||||||
placeholder="+34 123 456 789"
|
placeholder={t('onboarding:steps.tenant_registration.placeholders.phone')}
|
||||||
value={formData.phone}
|
value={formData.phone}
|
||||||
onChange={(e) => handleInputChange('phone', e.target.value)}
|
onChange={(e) => handleInputChange('phone', e.target.value)}
|
||||||
error={errors.phone}
|
error={errors.phone}
|
||||||
@@ -290,11 +302,14 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
<div className="md:col-span-2 transform transition-all duration-200 hover:scale-[1.01] relative z-20">
|
<div className="md:col-span-2 transform transition-all duration-200 hover:scale-[1.01] relative z-20">
|
||||||
<label className="block text-sm font-semibold text-[var(--text-primary)] mb-2 flex items-center gap-2">
|
<label className="block text-sm font-semibold text-[var(--text-primary)] mb-2 flex items-center gap-2">
|
||||||
<span className="text-lg">📍</span>
|
<span className="text-lg">📍</span>
|
||||||
Dirección <span className="text-red-500">*</span>
|
{t('onboarding:steps.tenant_registration.fields.address')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<AddressAutocomplete
|
<AddressAutocomplete
|
||||||
value={formData.address}
|
value={formData.address}
|
||||||
placeholder={isEnterprise ? "Dirección del obrador central..." : "Dirección de tu panadería..."}
|
placeholder={t(isEnterprise
|
||||||
|
? 'onboarding:steps.tenant_registration.placeholders.address_enterprise'
|
||||||
|
: 'onboarding:steps.tenant_registration.placeholders.address'
|
||||||
|
)}
|
||||||
onAddressSelect={(address) => {
|
onAddressSelect={(address) => {
|
||||||
console.log('Selected:', address.display_name);
|
console.log('Selected:', address.display_name);
|
||||||
handleAddressSelect(address);
|
handleAddressSelect(address);
|
||||||
@@ -313,8 +328,8 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
|
|
||||||
<div className="transform transition-all duration-200 hover:scale-[1.01]">
|
<div className="transform transition-all duration-200 hover:scale-[1.01]">
|
||||||
<Input
|
<Input
|
||||||
label="Código Postal"
|
label={t('onboarding:steps.tenant_registration.fields.postal_code')}
|
||||||
placeholder="28001"
|
placeholder={t('onboarding:steps.tenant_registration.placeholders.postal_code')}
|
||||||
value={formData.postal_code}
|
value={formData.postal_code}
|
||||||
onChange={(e) => handleInputChange('postal_code', e.target.value)}
|
onChange={(e) => handleInputChange('postal_code', e.target.value)}
|
||||||
error={errors.postal_code}
|
error={errors.postal_code}
|
||||||
@@ -325,8 +340,8 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
|
|
||||||
<div className="transform transition-all duration-200 hover:scale-[1.01]">
|
<div className="transform transition-all duration-200 hover:scale-[1.01]">
|
||||||
<Input
|
<Input
|
||||||
label="Ciudad (Opcional)"
|
label={t('onboarding:steps.tenant_registration.fields.city')}
|
||||||
placeholder="Madrid"
|
placeholder={t('onboarding:steps.tenant_registration.placeholders.city')}
|
||||||
value={formData.city}
|
value={formData.city}
|
||||||
onChange={(e) => handleInputChange('city', e.target.value)}
|
onChange={(e) => handleInputChange('city', e.target.value)}
|
||||||
error={errors.city}
|
error={errors.city}
|
||||||
@@ -339,7 +354,9 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<span className="text-2xl flex-shrink-0">⚠️</span>
|
<span className="text-2xl flex-shrink-0">⚠️</span>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-semibold text-[var(--color-error)] mb-1">Error al registrar</h4>
|
<h4 className="font-semibold text-[var(--color-error)] mb-1">
|
||||||
|
{t('onboarding:steps.tenant_registration.errors.register_title')}
|
||||||
|
</h4>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">{errors.submit}</p>
|
<p className="text-sm text-[var(--text-secondary)]">{errors.submit}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -350,14 +367,26 @@ export const RegisterTenantStep: React.FC<RegisterTenantStepProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
isLoading={registerBakery.isPending || updateTenant.isPending}
|
isLoading={registerBakery.isPending || updateTenant.isPending}
|
||||||
loadingText={tenantId ? "Actualizando obrador..." : (isEnterprise ? "Registrando obrador..." : "Registrando...")}
|
loadingText={t(tenantId
|
||||||
|
? 'onboarding:steps.tenant_registration.loading.updating'
|
||||||
|
: (isEnterprise
|
||||||
|
? 'onboarding:steps.tenant_registration.loading.creating_enterprise'
|
||||||
|
: 'onboarding:steps.tenant_registration.loading.creating'
|
||||||
|
)
|
||||||
|
)}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full sm:w-auto sm:min-w-[280px] text-base font-semibold transform transition-all duration-300 hover:scale-105 shadow-lg"
|
className="w-full sm:w-auto sm:min-w-[280px] text-base font-semibold transform transition-all duration-300 hover:scale-105 shadow-lg"
|
||||||
>
|
>
|
||||||
{tenantId
|
{t(tenantId
|
||||||
? (isEnterprise ? "Actualizar Obrador Central y Continuar →" : "Actualizar Panadería y Continuar →")
|
? (isEnterprise
|
||||||
: (isEnterprise ? "Crear Obrador Central y Continuar →" : "Crear Panadería y Continuar →")
|
? 'onboarding:steps.tenant_registration.buttons.update_enterprise'
|
||||||
}
|
: 'onboarding:steps.tenant_registration.buttons.update'
|
||||||
|
)
|
||||||
|
: (isEnterprise
|
||||||
|
? 'onboarding:steps.tenant_registration.buttons.create_enterprise'
|
||||||
|
: 'onboarding:steps.tenant_registration.buttons.create'
|
||||||
|
)
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -79,23 +79,63 @@
|
|||||||
"steps": {
|
"steps": {
|
||||||
"tenant_registration": {
|
"tenant_registration": {
|
||||||
"title": "Your Bakery Information",
|
"title": "Your Bakery Information",
|
||||||
|
"title_enterprise": "Your Central Bakery Information",
|
||||||
"subtitle": "Tell us about your business",
|
"subtitle": "Tell us about your business",
|
||||||
|
"header": {
|
||||||
|
"title": "Register Your Bakery",
|
||||||
|
"title_enterprise": "Register Your Central Bakery",
|
||||||
|
"description": "Complete your bakery's basic information to get started.",
|
||||||
|
"description_enterprise": "Enter your main production facility details. You can add branches afterwards."
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"business_name": "Business name",
|
"business_name": "Bakery Name",
|
||||||
|
"business_name_enterprise": "Central Bakery Name",
|
||||||
"business_type": "Business type",
|
"business_type": "Business type",
|
||||||
"address": "Address",
|
"address": "Address",
|
||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
|
"postal_code": "Postal Code",
|
||||||
|
"city": "City (Optional)",
|
||||||
"email": "Contact email",
|
"email": "Contact email",
|
||||||
"website": "Website (optional)",
|
"website": "Website (optional)",
|
||||||
"description": "Business description"
|
"description": "Business description"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"business_name": "E.g: San José Bakery",
|
"business_name": "Enter your bakery name",
|
||||||
"address": "Main Street 123, City",
|
"business_name_enterprise": "Enter your central bakery name",
|
||||||
"phone": "+1 123 456 789",
|
"address": "Your bakery address...",
|
||||||
|
"address_enterprise": "Central bakery address...",
|
||||||
|
"phone": "+34 123 456 789",
|
||||||
|
"postal_code": "28001",
|
||||||
|
"city": "Madrid",
|
||||||
"email": "contact@bakery.com",
|
"email": "contact@bakery.com",
|
||||||
"website": "https://mybakery.com",
|
"website": "https://mybakery.com",
|
||||||
"description": "Describe your bakery..."
|
"description": "Describe your bakery..."
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"name_required": "Bakery name is required",
|
||||||
|
"name_length": "Name must be between 2 and 200 characters",
|
||||||
|
"address_required": "Address is required",
|
||||||
|
"address_length": "Address must be between 10 and 500 characters",
|
||||||
|
"postal_code_required": "Postal code is required",
|
||||||
|
"postal_code_format": "Postal code must be exactly 5 digits",
|
||||||
|
"phone_required": "Phone number is required",
|
||||||
|
"phone_length": "Phone must be between 9 and 20 characters",
|
||||||
|
"phone_format": "Please enter a valid Spanish phone number"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"create": "Create Bakery and Continue →",
|
||||||
|
"create_enterprise": "Create Central Bakery and Continue →",
|
||||||
|
"update": "Update Bakery and Continue →",
|
||||||
|
"update_enterprise": "Update Central Bakery and Continue →"
|
||||||
|
},
|
||||||
|
"loading": {
|
||||||
|
"creating": "Creating...",
|
||||||
|
"creating_enterprise": "Creating bakery...",
|
||||||
|
"updating": "Updating bakery..."
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"register": "Error registering bakery. Please try again.",
|
||||||
|
"register_title": "Registration Error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inventory_setup": {
|
"inventory_setup": {
|
||||||
@@ -169,6 +209,7 @@
|
|||||||
"ml_training": {
|
"ml_training": {
|
||||||
"title": "AI Training",
|
"title": "AI Training",
|
||||||
"subtitle": "Creating your personalized model",
|
"subtitle": "Creating your personalized model",
|
||||||
|
"intro_text": "Perfect! We will now automatically train your artificial intelligence model using the sales and inventory data you've provided. This process may take several minutes.",
|
||||||
"status": {
|
"status": {
|
||||||
"preparing": "Preparing data...",
|
"preparing": "Preparing data...",
|
||||||
"training": "Training model...",
|
"training": "Training model...",
|
||||||
@@ -179,11 +220,30 @@
|
|||||||
"data_preparation": "Data preparation",
|
"data_preparation": "Data preparation",
|
||||||
"model_training": "Model training",
|
"model_training": "Model training",
|
||||||
"validation": "Validation",
|
"validation": "Validation",
|
||||||
"deployment": "Deployment"
|
"deployment": "Deployment",
|
||||||
|
"processing": "Processing..."
|
||||||
},
|
},
|
||||||
"estimated_time": "Estimated time: {{minutes}} minutes",
|
"estimated_time": "Estimated time: {{minutes}} minutes",
|
||||||
"estimated_time_remaining": "Estimated time remaining: {{time}}",
|
"estimated_time_remaining": "Estimated time remaining: {{time}}",
|
||||||
"description": "We're creating a personalized AI model for your bakery based on your historical data.",
|
"description": "We're creating a personalized AI model for your bakery based on your historical data.",
|
||||||
|
"training_info": {
|
||||||
|
"title": "What happens during training?",
|
||||||
|
"step1": "Analysis of historical sales patterns",
|
||||||
|
"step2": "Creation of demand prediction models",
|
||||||
|
"step3": "Optimization of inventory algorithms",
|
||||||
|
"step4": "Validation and accuracy adjustment"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"training": "Training model...",
|
||||||
|
"completed": "Training completed successfully",
|
||||||
|
"completed_http": "Training completed successfully (detected via HTTP check)",
|
||||||
|
"starting": "Starting model training...",
|
||||||
|
"queued": "Training job queued...",
|
||||||
|
"preparing": "Preparing training data...",
|
||||||
|
"live": "Live",
|
||||||
|
"reconnecting": "Reconnecting...",
|
||||||
|
"estimated_completion": "Will finish:"
|
||||||
|
},
|
||||||
"skip_to_dashboard": {
|
"skip_to_dashboard": {
|
||||||
"title": "Taking too long?",
|
"title": "Taking too long?",
|
||||||
"description": "Training continues in the background. You can go to the dashboard now and explore your system while the model finishes training.",
|
"description": "Training continues in the background. You can go to the dashboard now and explore your system while the model finishes training.",
|
||||||
@@ -298,5 +358,94 @@
|
|||||||
"continue_anyway": "Continue anyway",
|
"continue_anyway": "Continue anyway",
|
||||||
"no_products_title": "Initial Stock",
|
"no_products_title": "Initial Stock",
|
||||||
"no_products_message": "You can configure stock levels later in the inventory section."
|
"no_products_message": "You can configure stock levels later in the inventory section."
|
||||||
|
},
|
||||||
|
"child_tenants": {
|
||||||
|
"title": "Branch Configuration",
|
||||||
|
"subtitle": "As an Enterprise tier company, you have a central bakery and multiple branches. Add information for each branch that will receive products from the central bakery.",
|
||||||
|
"info_box": {
|
||||||
|
"title": "Enterprise Business Model",
|
||||||
|
"description": "Your central bakery handles production, and branches receive finished products through optimized internal transfers."
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "Branches",
|
||||||
|
"add_button": "Add Branch",
|
||||||
|
"empty_state": {
|
||||||
|
"title": "No branches added",
|
||||||
|
"description": "Start by adding the branches that are part of your enterprise network",
|
||||||
|
"button": "Add First Branch"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"title_add": "Add Branch",
|
||||||
|
"title_edit": "Edit Branch",
|
||||||
|
"fields": {
|
||||||
|
"name": "Branch Name",
|
||||||
|
"location_code": "Location Code",
|
||||||
|
"location_code_help": "A short code to identify this location",
|
||||||
|
"location_code_max": "(max 10 characters)",
|
||||||
|
"business_type": "Business Type",
|
||||||
|
"business_model": "Business Model",
|
||||||
|
"phone": "Phone",
|
||||||
|
"email": "Email",
|
||||||
|
"timezone": "Timezone",
|
||||||
|
"zone": "Zone / District (optional)",
|
||||||
|
"zone_help": "Specific zone or district within the city",
|
||||||
|
"address": "Address",
|
||||||
|
"city": "City",
|
||||||
|
"postal_code": "Postal Code"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"name": "e.g. Madrid - Salamanca",
|
||||||
|
"location_code": "e.g. MAD, BCN, VAL",
|
||||||
|
"phone": "e.g. +34 123 456 789",
|
||||||
|
"email": "e.g. contact@bakery.com",
|
||||||
|
"zone": "e.g. Salamanca, Downtown, Center",
|
||||||
|
"address": "e.g. Serrano Street, 48",
|
||||||
|
"city": "e.g. Madrid",
|
||||||
|
"postal_code": "e.g. 28001"
|
||||||
|
},
|
||||||
|
"business_types": {
|
||||||
|
"bakery": "Bakery",
|
||||||
|
"coffee_shop": "Coffee Shop",
|
||||||
|
"pastry_shop": "Pastry Shop",
|
||||||
|
"restaurant": "Restaurant"
|
||||||
|
},
|
||||||
|
"business_models": {
|
||||||
|
"retail_bakery": "Retail Bakery",
|
||||||
|
"central_baker_satellite": "Central Baker + Branches",
|
||||||
|
"hybrid_bakery": "Hybrid Model"
|
||||||
|
},
|
||||||
|
"timezones": {
|
||||||
|
"europe_madrid": "Europe/Madrid (UTC+1/UTC+2)",
|
||||||
|
"europe_paris": "Europe/Paris (UTC+1/UTC+2)",
|
||||||
|
"europe_london": "Europe/London (UTC+0/UTC+1)",
|
||||||
|
"america_new_york": "America/New_York (UTC-5/UTC-4)"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save Changes",
|
||||||
|
"add": "Add Branch"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete"
|
||||||
|
},
|
||||||
|
"continue_button": "Continue with {count} {count, plural, one {Branch} other {Branches}}",
|
||||||
|
"validation": {
|
||||||
|
"name_required": "Name is required",
|
||||||
|
"city_required": "City is required",
|
||||||
|
"address_required": "Address is required",
|
||||||
|
"postal_code_required": "Postal code is required",
|
||||||
|
"postal_code_format": "Postal code must be exactly 5 digits",
|
||||||
|
"location_code_required": "Location code is required",
|
||||||
|
"location_code_max": "Code must not exceed 10 characters",
|
||||||
|
"location_code_format": "Only uppercase letters, numbers, and hyphens/underscores are allowed",
|
||||||
|
"phone_format": "Please enter a valid Spanish phone number",
|
||||||
|
"email_format": "Please enter a valid email address"
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"require_one": "You must add at least one branch to continue"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,23 +100,63 @@
|
|||||||
"steps": {
|
"steps": {
|
||||||
"tenant_registration": {
|
"tenant_registration": {
|
||||||
"title": "Información de tu Panadería",
|
"title": "Información de tu Panadería",
|
||||||
|
"title_enterprise": "Información de tu Obrador Central",
|
||||||
"subtitle": "Cuéntanos sobre tu negocio",
|
"subtitle": "Cuéntanos sobre tu negocio",
|
||||||
|
"header": {
|
||||||
|
"title": "Registra tu Panadería",
|
||||||
|
"title_enterprise": "Registra tu Obrador Central",
|
||||||
|
"description": "Completa la información básica de tu panadería para comenzar.",
|
||||||
|
"description_enterprise": "Ingresa los datos de tu obrador principal. Después podrás agregar las sucursales."
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"business_name": "Nombre del negocio",
|
"business_name": "Nombre de la Panadería",
|
||||||
|
"business_name_enterprise": "Nombre del Obrador Central",
|
||||||
"business_type": "Tipo de negocio",
|
"business_type": "Tipo de negocio",
|
||||||
"address": "Dirección",
|
"address": "Dirección",
|
||||||
"phone": "Teléfono",
|
"phone": "Teléfono",
|
||||||
|
"postal_code": "Código Postal",
|
||||||
|
"city": "Ciudad (Opcional)",
|
||||||
"email": "Email de contacto",
|
"email": "Email de contacto",
|
||||||
"website": "Sitio web (opcional)",
|
"website": "Sitio web (opcional)",
|
||||||
"description": "Descripción del negocio"
|
"description": "Descripción del negocio"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"business_name": "Ej: Panadería San José",
|
"business_name": "Ingresa el nombre de tu panadería",
|
||||||
"address": "Calle Principal 123, Ciudad",
|
"business_name_enterprise": "Ingresa el nombre de tu obrador central",
|
||||||
|
"address": "Dirección de tu panadería...",
|
||||||
|
"address_enterprise": "Dirección del obrador central...",
|
||||||
"phone": "+34 123 456 789",
|
"phone": "+34 123 456 789",
|
||||||
|
"postal_code": "28001",
|
||||||
|
"city": "Madrid",
|
||||||
"email": "contacto@panaderia.com",
|
"email": "contacto@panaderia.com",
|
||||||
"website": "https://mipanaderia.com",
|
"website": "https://mipanaderia.com",
|
||||||
"description": "Describe tu panadería..."
|
"description": "Describe tu panadería..."
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"name_required": "El nombre de la panadería es obligatorio",
|
||||||
|
"name_length": "El nombre debe tener entre 2 y 200 caracteres",
|
||||||
|
"address_required": "La dirección es obligatoria",
|
||||||
|
"address_length": "La dirección debe tener entre 10 y 500 caracteres",
|
||||||
|
"postal_code_required": "El código postal es obligatorio",
|
||||||
|
"postal_code_format": "El código postal debe tener exactamente 5 dígitos",
|
||||||
|
"phone_required": "El número de teléfono es obligatorio",
|
||||||
|
"phone_length": "El teléfono debe tener entre 9 y 20 caracteres",
|
||||||
|
"phone_format": "Introduce un número de teléfono español válido"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"create": "Crear Panadería y Continuar →",
|
||||||
|
"create_enterprise": "Crear Obrador Central y Continuar →",
|
||||||
|
"update": "Actualizar Panadería y Continuar →",
|
||||||
|
"update_enterprise": "Actualizar Obrador Central y Continuar →"
|
||||||
|
},
|
||||||
|
"loading": {
|
||||||
|
"creating": "Registrando...",
|
||||||
|
"creating_enterprise": "Registrando obrador...",
|
||||||
|
"updating": "Actualizando obrador..."
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"register": "Error al registrar la panadería. Por favor, inténtalo de nuevo.",
|
||||||
|
"register_title": "Error al registrar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inventory_setup": {
|
"inventory_setup": {
|
||||||
@@ -190,6 +230,7 @@
|
|||||||
"ml_training": {
|
"ml_training": {
|
||||||
"title": "Entrenamiento de IA",
|
"title": "Entrenamiento de IA",
|
||||||
"subtitle": "Creando tu modelo personalizado",
|
"subtitle": "Creando tu modelo personalizado",
|
||||||
|
"intro_text": "Perfecto! Ahora entrenaremos automáticamente tu modelo de inteligencia artificial utilizando los datos de ventas e inventario que has proporcionado. Este proceso puede tomar varios minutos.",
|
||||||
"status": {
|
"status": {
|
||||||
"preparing": "Preparando datos...",
|
"preparing": "Preparando datos...",
|
||||||
"training": "Entrenando modelo...",
|
"training": "Entrenando modelo...",
|
||||||
@@ -200,11 +241,30 @@
|
|||||||
"data_preparation": "Preparación de datos",
|
"data_preparation": "Preparación de datos",
|
||||||
"model_training": "Entrenamiento del modelo",
|
"model_training": "Entrenamiento del modelo",
|
||||||
"validation": "Validación",
|
"validation": "Validación",
|
||||||
"deployment": "Despliegue"
|
"deployment": "Despliegue",
|
||||||
|
"processing": "Procesando..."
|
||||||
},
|
},
|
||||||
"estimated_time": "Tiempo estimado: {{minutes}} minutos",
|
"estimated_time": "Tiempo estimado: {{minutes}} minutos",
|
||||||
"estimated_time_remaining": "Tiempo restante estimado: {{time}}",
|
"estimated_time_remaining": "Tiempo restante estimado: {{time}}",
|
||||||
"description": "Estamos creando un modelo de IA personalizado para tu panadería basado en tus datos históricos.",
|
"description": "Estamos creando un modelo de IA personalizado para tu panadería basado en tus datos históricos.",
|
||||||
|
"training_info": {
|
||||||
|
"title": "¿Qué sucede durante el entrenamiento?",
|
||||||
|
"step1": "Análisis de patrones de ventas históricos",
|
||||||
|
"step2": "Creación de modelos predictivos de demanda",
|
||||||
|
"step3": "Optimización de algoritmos de inventario",
|
||||||
|
"step4": "Validación y ajuste de precisión"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"training": "Entrenando modelo...",
|
||||||
|
"completed": "Entrenamiento completado exitosamente",
|
||||||
|
"completed_http": "Entrenamiento completado exitosamente (detectado por verificación HTTP)",
|
||||||
|
"starting": "Iniciando entrenamiento del modelo...",
|
||||||
|
"queued": "Trabajo de entrenamiento en cola...",
|
||||||
|
"preparing": "Preparando datos para entrenamiento...",
|
||||||
|
"live": "En vivo",
|
||||||
|
"reconnecting": "Reconectando...",
|
||||||
|
"estimated_completion": "Finalizará:"
|
||||||
|
},
|
||||||
"skip_to_dashboard": {
|
"skip_to_dashboard": {
|
||||||
"title": "¿Toma demasiado tiempo?",
|
"title": "¿Toma demasiado tiempo?",
|
||||||
"description": "El entrenamiento continúa en segundo plano. Puedes ir al dashboard ahora y explorar tu sistema mientras el modelo termina de entrenarse.",
|
"description": "El entrenamiento continúa en segundo plano. Puedes ir al dashboard ahora y explorar tu sistema mientras el modelo termina de entrenarse.",
|
||||||
@@ -420,5 +480,94 @@
|
|||||||
"continue_anyway": "Continuar de todos modos",
|
"continue_anyway": "Continuar de todos modos",
|
||||||
"no_products_title": "Stock Inicial",
|
"no_products_title": "Stock Inicial",
|
||||||
"no_products_message": "Podrás configurar los niveles de stock más tarde en la sección de inventario."
|
"no_products_message": "Podrás configurar los niveles de stock más tarde en la sección de inventario."
|
||||||
|
},
|
||||||
|
"child_tenants": {
|
||||||
|
"title": "Configuración de Sucursales",
|
||||||
|
"subtitle": "Como empresa con tier Enterprise, tienes un obrador central y múltiples sucursales. Agrega la información de cada sucursal que recibirá productos del obrador central.",
|
||||||
|
"info_box": {
|
||||||
|
"title": "Modelo de Negocio Enterprise",
|
||||||
|
"description": "Tu obrador central se encargará de la producción, y las sucursales recibirán los productos terminados mediante transferencias internas optimizadas."
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "Sucursales",
|
||||||
|
"add_button": "Agregar Sucursal",
|
||||||
|
"empty_state": {
|
||||||
|
"title": "No hay sucursales agregadas",
|
||||||
|
"description": "Comienza agregando las sucursales que forman parte de tu red empresarial",
|
||||||
|
"button": "Agregar Primera Sucursal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"title_add": "Agregar Sucursal",
|
||||||
|
"title_edit": "Editar Sucursal",
|
||||||
|
"fields": {
|
||||||
|
"name": "Nombre de la Sucursal",
|
||||||
|
"location_code": "Código de Ubicación",
|
||||||
|
"location_code_help": "Un código corto para identificar esta ubicación",
|
||||||
|
"location_code_max": "(máx. 10 caracteres)",
|
||||||
|
"business_type": "Tipo de Negocio",
|
||||||
|
"business_model": "Modelo de Negocio",
|
||||||
|
"phone": "Teléfono",
|
||||||
|
"email": "Email",
|
||||||
|
"timezone": "Zona Horaria",
|
||||||
|
"zone": "Zona / Barrio (opcional)",
|
||||||
|
"zone_help": "Zona o barrio específico dentro de la ciudad",
|
||||||
|
"address": "Dirección",
|
||||||
|
"city": "Ciudad",
|
||||||
|
"postal_code": "Código Postal"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"name": "ej. Madrid - Salamanca",
|
||||||
|
"location_code": "ej. MAD, BCN, VAL",
|
||||||
|
"phone": "ej. +34 123 456 789",
|
||||||
|
"email": "ej. contacto@panaderia.com",
|
||||||
|
"zone": "ej. Salamanca, Chamberí, Centro",
|
||||||
|
"address": "ej. Calle de Serrano, 48",
|
||||||
|
"city": "ej. Madrid",
|
||||||
|
"postal_code": "ej. 28001"
|
||||||
|
},
|
||||||
|
"business_types": {
|
||||||
|
"bakery": "Panadería",
|
||||||
|
"coffee_shop": "Cafetería",
|
||||||
|
"pastry_shop": "Pastelería",
|
||||||
|
"restaurant": "Restaurante"
|
||||||
|
},
|
||||||
|
"business_models": {
|
||||||
|
"retail_bakery": "Panadería Minorista",
|
||||||
|
"central_baker_satellite": "Obrador Central + Sucursales",
|
||||||
|
"hybrid_bakery": "Modelo Híbrido"
|
||||||
|
},
|
||||||
|
"timezones": {
|
||||||
|
"europe_madrid": "Europe/Madrid (UTC+1/UTC+2)",
|
||||||
|
"europe_paris": "Europe/Paris (UTC+1/UTC+2)",
|
||||||
|
"europe_london": "Europe/London (UTC+0/UTC+1)",
|
||||||
|
"america_new_york": "America/New_York (UTC-5/UTC-4)"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"save": "Guardar Cambios",
|
||||||
|
"add": "Agregar Sucursal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"edit": "Editar",
|
||||||
|
"delete": "Eliminar"
|
||||||
|
},
|
||||||
|
"continue_button": "Continuar con {count} {count, plural, one {Sucursal} other {Sucursales}}",
|
||||||
|
"validation": {
|
||||||
|
"name_required": "El nombre es requerido",
|
||||||
|
"city_required": "La ciudad es requerida",
|
||||||
|
"address_required": "La dirección es requerida",
|
||||||
|
"postal_code_required": "El código postal es requerido",
|
||||||
|
"postal_code_format": "El código postal debe tener exactamente 5 dígitos",
|
||||||
|
"location_code_required": "El código de ubicación es requerido",
|
||||||
|
"location_code_max": "El código no debe exceder 10 caracteres",
|
||||||
|
"location_code_format": "Solo se permiten letras mayúsculas, números y guiones/guiones bajos",
|
||||||
|
"phone_format": "Introduce un número de teléfono español válido",
|
||||||
|
"email_format": "Introduce un correo electrónico válido"
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"require_one": "Debes agregar al menos una sucursal para continuar"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,23 +99,63 @@
|
|||||||
"steps": {
|
"steps": {
|
||||||
"tenant_registration": {
|
"tenant_registration": {
|
||||||
"title": "Zure Okindegiko Informazioa",
|
"title": "Zure Okindegiko Informazioa",
|
||||||
|
"title_enterprise": "Zure Okinleku Zentraleko Informazioa",
|
||||||
"subtitle": "Kontaiguzu zure negozioari buruz",
|
"subtitle": "Kontaiguzu zure negozioari buruz",
|
||||||
|
"header": {
|
||||||
|
"title": "Erregistratu Zure Okindegia",
|
||||||
|
"title_enterprise": "Erregistratu Zure Okinleku Zentrala",
|
||||||
|
"description": "Osatu zure okindegiko oinarrizko informazioa hasteko.",
|
||||||
|
"description_enterprise": "Sartu zure okinleku nagusiaren datuak. Gero adarrak gehitu ahal izango dituzu."
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"business_name": "Negozioaren izena",
|
"business_name": "Okindegiko Izena",
|
||||||
|
"business_name_enterprise": "Okinleku Zentralaren Izena",
|
||||||
"business_type": "Negozio mota",
|
"business_type": "Negozio mota",
|
||||||
"address": "Helbidea",
|
"address": "Helbidea",
|
||||||
"phone": "Telefonoa",
|
"phone": "Telefonoa",
|
||||||
|
"postal_code": "Posta Kodea",
|
||||||
|
"city": "Hiria (Aukerakoa)",
|
||||||
"email": "Harremaneko emaila",
|
"email": "Harremaneko emaila",
|
||||||
"website": "Webgunea (aukerakoa)",
|
"website": "Webgunea (aukerakoa)",
|
||||||
"description": "Negozioaren deskripzioa"
|
"description": "Negozioaren deskripzioa"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"business_name": "Adib.: San José Okindegia",
|
"business_name": "Sartu zure okindegiko izena",
|
||||||
"address": "Kale Nagusia 123, Hiria",
|
"business_name_enterprise": "Sartu zure okinleku zentralaren izena",
|
||||||
|
"address": "Zure okindegiko helbidea...",
|
||||||
|
"address_enterprise": "Okinleku zentralaren helbidea...",
|
||||||
"phone": "+34 123 456 789",
|
"phone": "+34 123 456 789",
|
||||||
|
"postal_code": "28001",
|
||||||
|
"city": "Madrid",
|
||||||
"email": "kontaktua@okindegia.com",
|
"email": "kontaktua@okindegia.com",
|
||||||
"website": "https://nireokindegia.com",
|
"website": "https://nireokindegia.com",
|
||||||
"description": "Deskribatu zure okindegia..."
|
"description": "Deskribatu zure okindegia..."
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"name_required": "Okindegiko izena beharrezkoa da",
|
||||||
|
"name_length": "Izenak 2 eta 200 karaktere artean izan behar ditu",
|
||||||
|
"address_required": "Helbidea beharrezkoa da",
|
||||||
|
"address_length": "Helbideak 10 eta 500 karaktere artean izan behar ditu",
|
||||||
|
"postal_code_required": "Posta kodea beharrezkoa da",
|
||||||
|
"postal_code_format": "Posta kodeak 5 digitu izan behar ditu",
|
||||||
|
"phone_required": "Telefono zenbakia beharrezkoa da",
|
||||||
|
"phone_length": "Telefonoak 9 eta 20 karaktere artean izan behar ditu",
|
||||||
|
"phone_format": "Mesedez, sartu Espainiako telefono zenbaki baliozkoa"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"create": "Sortu Okindegia eta Jarraitu →",
|
||||||
|
"create_enterprise": "Sortu Okinleku Zentrala eta Jarraitu →",
|
||||||
|
"update": "Eguneratu Okindegia eta Jarraitu →",
|
||||||
|
"update_enterprise": "Eguneratu Okinleku Zentrala eta Jarraitu →"
|
||||||
|
},
|
||||||
|
"loading": {
|
||||||
|
"creating": "Erregistratzen...",
|
||||||
|
"creating_enterprise": "Okinlekua erregistratzen...",
|
||||||
|
"updating": "Okinlekua eguneratzen..."
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"register": "Errorea okindegia erregistratzean. Saiatu berriro mesedez.",
|
||||||
|
"register_title": "Erregistro Errorea"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inventory_setup": {
|
"inventory_setup": {
|
||||||
@@ -189,6 +229,7 @@
|
|||||||
"ml_training": {
|
"ml_training": {
|
||||||
"title": "AA Prestakuntza",
|
"title": "AA Prestakuntza",
|
||||||
"subtitle": "Zure modelo pertsonalizatua sortzen",
|
"subtitle": "Zure modelo pertsonalizatua sortzen",
|
||||||
|
"intro_text": "Bikain! Orain automatikoki entrenatuko dugu zure adimen artifizialaren modeloa eman dituzun salmenta eta inbentario datuen bitartez. Prozesu honek minutu batzuk iraun ditzake.",
|
||||||
"status": {
|
"status": {
|
||||||
"preparing": "Datuak prestatzen...",
|
"preparing": "Datuak prestatzen...",
|
||||||
"training": "Modeloa entrenatzen...",
|
"training": "Modeloa entrenatzen...",
|
||||||
@@ -199,11 +240,30 @@
|
|||||||
"data_preparation": "Datuen prestaketa",
|
"data_preparation": "Datuen prestaketa",
|
||||||
"model_training": "Modeloaren prestakuntza",
|
"model_training": "Modeloaren prestakuntza",
|
||||||
"validation": "Balioespena",
|
"validation": "Balioespena",
|
||||||
"deployment": "Hedapena"
|
"deployment": "Hedapena",
|
||||||
|
"processing": "Prozesatzen..."
|
||||||
},
|
},
|
||||||
"estimated_time": "Aurreikusitako denbora: {{minutes}} minutu",
|
"estimated_time": "Aurreikusitako denbora: {{minutes}} minutu",
|
||||||
"estimated_time_remaining": "Geratzen den denbora aurreikusia: {{time}}",
|
"estimated_time_remaining": "Geratzen den denbora aurreikusia: {{time}}",
|
||||||
"description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian.",
|
"description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian.",
|
||||||
|
"training_info": {
|
||||||
|
"title": "Zer gertatzen da prestakuntzaren bitartean?",
|
||||||
|
"step1": "Salmenta-eredu historikoen azterketa",
|
||||||
|
"step2": "Eskaera-aurreikuspen ereduen sorrera",
|
||||||
|
"step3": "Inbentario algoritmoen optimizazioa",
|
||||||
|
"step4": "Balioespena eta zehaztasun-doikuntza"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"training": "Modeloa entrenatzen...",
|
||||||
|
"completed": "Prestakuntza ongi osatu da",
|
||||||
|
"completed_http": "Prestakuntza ongi osatu da (HTTP egiaztapen bidez detektatu da)",
|
||||||
|
"starting": "Modeloaren prestakuntza hasten...",
|
||||||
|
"queued": "Prestakuntza lana ilaran...",
|
||||||
|
"preparing": "Prestakuntza datuak prestatzen...",
|
||||||
|
"live": "Zuzenean",
|
||||||
|
"reconnecting": "Birkonektatzen...",
|
||||||
|
"estimated_completion": "Amaituko da:"
|
||||||
|
},
|
||||||
"skip_to_dashboard": {
|
"skip_to_dashboard": {
|
||||||
"title": "Denbora luzea hartzen al du?",
|
"title": "Denbora luzea hartzen al du?",
|
||||||
"description": "Prestakuntza atzeko planoan jarraitzen du. Panelera joan zaitezke orain eta sistema arakatu modeloa entrenatzen amaitzen duen bitartean.",
|
"description": "Prestakuntza atzeko planoan jarraitzen du. Panelera joan zaitezke orain eta sistema arakatu modeloa entrenatzen amaitzen duen bitartean.",
|
||||||
@@ -403,6 +463,95 @@
|
|||||||
"no_products_title": "Hasierako Stocka",
|
"no_products_title": "Hasierako Stocka",
|
||||||
"no_products_message": "Stock-mailak geroago konfigura ditzakezu inbentario atalean."
|
"no_products_message": "Stock-mailak geroago konfigura ditzakezu inbentario atalean."
|
||||||
},
|
},
|
||||||
|
"child_tenants": {
|
||||||
|
"title": "Sukurtsalen Konfigurazioa",
|
||||||
|
"subtitle": "Enterprise mailako enpresa gisa, okinleku zentral bat eta hainbat sukurtsal dituzu. Gehitu okinleku zentraletik produktuak jasoko dituen sukurtsal bakoitzaren informazioa.",
|
||||||
|
"info_box": {
|
||||||
|
"title": "Enterprise Negozio Eredua",
|
||||||
|
"description": "Zure okinleku zentralak ekoizpena kudeatzen du, eta sukurtsalek produktu amaituak jasoko dituzte barne transferentzia optimizatuen bidez."
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "Sukurtsalak",
|
||||||
|
"add_button": "Sukurtsala Gehitu",
|
||||||
|
"empty_state": {
|
||||||
|
"title": "Ez dago sukurtsalik gehituta",
|
||||||
|
"description": "Hasi zure enpresa-sareko parte diren sukurtsalak gehitzen",
|
||||||
|
"button": "Lehenengo Sukurtsala Gehitu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"title_add": "Sukurtsala Gehitu",
|
||||||
|
"title_edit": "Sukurtsala Editatu",
|
||||||
|
"fields": {
|
||||||
|
"name": "Sukurtsalaren Izena",
|
||||||
|
"location_code": "Kokapen Kodea",
|
||||||
|
"location_code_help": "Kokapen hau identifikatzeko kode laburra",
|
||||||
|
"location_code_max": "(gehienez 10 karaktere)",
|
||||||
|
"business_type": "Negozio Mota",
|
||||||
|
"business_model": "Negozio Eredua",
|
||||||
|
"phone": "Telefonoa",
|
||||||
|
"email": "Emaila",
|
||||||
|
"timezone": "Ordu-zona",
|
||||||
|
"zone": "Eremua / Auzoa (aukerakoa)",
|
||||||
|
"zone_help": "Hiriaren barruko eremu edo auzo zehatza",
|
||||||
|
"address": "Helbidea",
|
||||||
|
"city": "Hiria",
|
||||||
|
"postal_code": "Posta Kodea"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"name": "adib. Madrid - Salamanca",
|
||||||
|
"location_code": "adib. MAD, BCN, VAL",
|
||||||
|
"phone": "adib. +34 123 456 789",
|
||||||
|
"email": "adib. kontaktua@okindegia.com",
|
||||||
|
"zone": "adib. Salamanca, Erdialdea, Zentroa",
|
||||||
|
"address": "adib. Serrano Kalea, 48",
|
||||||
|
"city": "adib. Madrid",
|
||||||
|
"postal_code": "adib. 28001"
|
||||||
|
},
|
||||||
|
"business_types": {
|
||||||
|
"bakery": "Okindegia",
|
||||||
|
"coffee_shop": "Kafetegia",
|
||||||
|
"pastry_shop": "Pasteldegia",
|
||||||
|
"restaurant": "Jatetxea"
|
||||||
|
},
|
||||||
|
"business_models": {
|
||||||
|
"retail_bakery": "Txikizkako Okindegia",
|
||||||
|
"central_baker_satellite": "Okinleku Zentrala + Sukurtsalak",
|
||||||
|
"hybrid_bakery": "Eredu Mistoa"
|
||||||
|
},
|
||||||
|
"timezones": {
|
||||||
|
"europe_madrid": "Europe/Madrid (UTC+1/UTC+2)",
|
||||||
|
"europe_paris": "Europe/Paris (UTC+1/UTC+2)",
|
||||||
|
"europe_london": "Europe/London (UTC+0/UTC+1)",
|
||||||
|
"america_new_york": "America/New_York (UTC-5/UTC-4)"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "Ezeztatu",
|
||||||
|
"save": "Aldaketak Gorde",
|
||||||
|
"add": "Sukurtsala Gehitu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"edit": "Editatu",
|
||||||
|
"delete": "Ezabatu"
|
||||||
|
},
|
||||||
|
"continue_button": "Jarraitu {count} {count, plural, one {Sukurtsalarekin} other {Sukurtsalekin}}",
|
||||||
|
"validation": {
|
||||||
|
"name_required": "Izena beharrezkoa da",
|
||||||
|
"city_required": "Hiria beharrezkoa da",
|
||||||
|
"address_required": "Helbidea beharrezkoa da",
|
||||||
|
"postal_code_required": "Posta kodea beharrezkoa da",
|
||||||
|
"postal_code_format": "Posta kodeak 5 digitu izan behar ditu",
|
||||||
|
"location_code_required": "Kokapen kodea beharrezkoa da",
|
||||||
|
"location_code_max": "Kodeak ezin du 10 karaktere baino gehiago izan",
|
||||||
|
"location_code_format": "Letra larri, zenbaki eta marratxoak/azpiko marratxoak bakarrik onartzen dira",
|
||||||
|
"phone_format": "Mesedez, sartu Espainiako telefono zenbaki baliozkoa",
|
||||||
|
"email_format": "Mesedez, sartu helbide elektroniko baliozkoa"
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"require_one": "Gutxienez sukurtsal bat gehitu behar duzu jarraitzeko"
|
||||||
|
}
|
||||||
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"step_failed": "Errorea pauso honetan",
|
"step_failed": "Errorea pauso honetan",
|
||||||
"data_invalid": "Datu baliogabeak",
|
"data_invalid": "Datu baliogabeak",
|
||||||
|
|||||||
Reference in New Issue
Block a user