Improve backend
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
|
||||
import {
|
||||
Edit3,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
CreditCard,
|
||||
Loader2,
|
||||
X,
|
||||
AlertCircle,
|
||||
} from 'lucide-react';
|
||||
import { useTenant } from '../../../../stores/tenant.store';
|
||||
import { salesService } from '../../../../api/services/sales';
|
||||
@@ -24,6 +26,7 @@ import { showToast } from '../../../../utils/toast';
|
||||
|
||||
const EntryMethodStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNext }) => {
|
||||
const data = dataRef?.current || {};
|
||||
const { t } = useTranslation('wizards');
|
||||
const [selectedMethod, setSelectedMethod] = useState<'manual' | 'upload'>(
|
||||
data.entryMethod || 'manual'
|
||||
);
|
||||
@@ -40,10 +43,10 @@ const EntryMethodStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
<div className="space-y-6">
|
||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
¿Cómo deseas registrar las ventas?
|
||||
{t('salesEntry.entryMethod.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
Elige el método que mejor se adapte a tus necesidades
|
||||
{t('salesEntry.entryMethod.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -77,23 +80,23 @@ const EntryMethodStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
Entrada Manual
|
||||
{t('salesEntry.entryMethod.manual.title')}
|
||||
</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-3">
|
||||
Ingresa una o varias ventas de forma individual
|
||||
{t('salesEntry.entryMethod.manual.description')}
|
||||
</p>
|
||||
<div className="space-y-1 text-xs text-[var(--text-tertiary)]">
|
||||
<p className="flex items-center gap-1.5">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-green-600" />
|
||||
Ideal para totales diarios
|
||||
{t('salesEntry.entryMethod.manual.benefits.1')}
|
||||
</p>
|
||||
<p className="flex items-center gap-1.5">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-green-600" />
|
||||
Control detallado por venta
|
||||
{t('salesEntry.entryMethod.manual.benefits.2')}
|
||||
</p>
|
||||
<p className="flex items-center gap-1.5">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-green-600" />
|
||||
Fácil y rápido
|
||||
{t('salesEntry.entryMethod.manual.benefits.3')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,7 +120,7 @@ const EntryMethodStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
{/* Recommended Badge */}
|
||||
<div className="absolute top-3 right-3">
|
||||
<span className="px-2 py-1 text-xs rounded-full bg-gradient-to-r from-amber-100 to-orange-100 text-orange-800 font-semibold">
|
||||
⭐ Recomendado para históricos
|
||||
{t('salesEntry.entryMethod.file.recommended')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -136,23 +139,23 @@ const EntryMethodStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
Cargar Archivo
|
||||
{t('salesEntry.entryMethod.file.title')}
|
||||
</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-3">
|
||||
Importa desde Excel o CSV
|
||||
{t('salesEntry.entryMethod.file.description')}
|
||||
</p>
|
||||
<div className="space-y-1 text-xs text-[var(--text-tertiary)]">
|
||||
<p className="flex items-center gap-1.5">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-green-600" />
|
||||
Ideal para datos históricos
|
||||
{t('salesEntry.entryMethod.file.benefits.1')}
|
||||
</p>
|
||||
<p className="flex items-center gap-1.5">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-green-600" />
|
||||
Carga masiva (cientos de registros)
|
||||
{t('salesEntry.entryMethod.file.benefits.2')}
|
||||
</p>
|
||||
<p className="flex items-center gap-1.5">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-green-600" />
|
||||
Ahorra tiempo significativo
|
||||
{t('salesEntry.entryMethod.file.benefits.3')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,6 +172,7 @@ const EntryMethodStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
|
||||
const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNext }) => {
|
||||
const data = dataRef?.current || {};
|
||||
const { t } = useTranslation('wizards');
|
||||
const { currentTenant } = useTenant();
|
||||
const [products, setProducts] = useState<any[]>([]);
|
||||
const [loadingProducts, setLoadingProducts] = useState(true);
|
||||
@@ -189,7 +193,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
setProducts(finishedProducts);
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching products:', err);
|
||||
setError('Error al cargar los productos');
|
||||
setError(t('salesEntry.messages.errorLoadingProducts'));
|
||||
} finally {
|
||||
setLoadingProducts(false);
|
||||
}
|
||||
@@ -249,10 +253,10 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
<div className="space-y-6">
|
||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
Registrar Venta Manual
|
||||
{t('salesEntry.manualEntry.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
Ingresa los detalles de la venta
|
||||
{t('salesEntry.manualEntry.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -261,7 +265,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
<Calendar className="w-4 h-4 inline mr-1.5" />
|
||||
Fecha de Venta *
|
||||
{t('salesEntry.manualEntry.fields.saleDate')} *
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
@@ -274,18 +278,18 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
<CreditCard className="w-4 h-4 inline mr-1.5" />
|
||||
Método de Pago *
|
||||
{t('salesEntry.manualEntry.fields.paymentMethod')} *
|
||||
</label>
|
||||
<select
|
||||
value={data.paymentMethod || 'cash'}
|
||||
onChange={(e) => onDataChange?.({ ...data, paymentMethod: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]"
|
||||
>
|
||||
<option value="cash">Efectivo</option>
|
||||
<option value="card">Tarjeta</option>
|
||||
<option value="mobile">Pago Móvil</option>
|
||||
<option value="transfer">Transferencia</option>
|
||||
<option value="other">Otro</option>
|
||||
<option value="cash">{t('salesEntry.paymentMethods.cash')}</option>
|
||||
<option value="card">{t('salesEntry.paymentMethods.card')}</option>
|
||||
<option value="mobile">{t('salesEntry.paymentMethods.mobile')}</option>
|
||||
<option value="transfer">{t('salesEntry.paymentMethods.transfer')}</option>
|
||||
<option value="other">{t('salesEntry.paymentMethods.other')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -302,33 +306,33 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="block text-sm font-medium text-[var(--text-secondary)]">
|
||||
<Package className="w-4 h-4 inline mr-1.5" />
|
||||
Productos Vendidos
|
||||
{t('salesEntry.manualEntry.products.title')}
|
||||
</label>
|
||||
<button
|
||||
onClick={handleAddItem}
|
||||
disabled={loadingProducts || products.length === 0}
|
||||
disabled={loadingProducts}
|
||||
className="px-3 py-1.5 text-sm bg-[var(--color-primary)] text-white rounded-md hover:bg-[var(--color-primary)]/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
+ Agregar Producto
|
||||
{t('salesEntry.manualEntry.products.addProduct')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loadingProducts ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-[var(--color-primary)]" />
|
||||
<span className="ml-3 text-[var(--text-secondary)]">Cargando productos...</span>
|
||||
<span className="ml-3 text-[var(--text-secondary)]">{t('salesEntry.manualEntry.products.loading')}</span>
|
||||
</div>
|
||||
) : products.length === 0 ? (
|
||||
<div className="text-center py-8 border-2 border-dashed border-[var(--border-secondary)] rounded-lg text-[var(--text-tertiary)]">
|
||||
<Package className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p>No hay productos terminados disponibles</p>
|
||||
<p className="text-sm">Agrega productos al inventario primero</p>
|
||||
<p>{t('salesEntry.manualEntry.products.noFinishedProducts')}</p>
|
||||
<p className="text-sm">{t('salesEntry.manualEntry.products.addToInventory')}</p>
|
||||
</div>
|
||||
) : (data.salesItems || []).length === 0 ? (
|
||||
<div className="text-center py-8 border-2 border-dashed border-[var(--border-secondary)] rounded-lg text-[var(--text-tertiary)]">
|
||||
<Package className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p>No hay productos agregados</p>
|
||||
<p className="text-sm">Haz clic en "Agregar Producto" para comenzar</p>
|
||||
<p>{t('salesEntry.manualEntry.products.noProductsAdded')}</p>
|
||||
<p className="text-sm">{t('salesEntry.manualEntry.products.clickToBegin')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
@@ -344,7 +348,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
onChange={(e) => handleUpdateItem(index, 'productId', e.target.value)}
|
||||
className="w-full px-2 py-1.5 text-sm border border-[var(--border-secondary)] rounded focus:outline-none focus:ring-1 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]"
|
||||
>
|
||||
<option value="">Seleccionar producto...</option>
|
||||
<option value="">{t('salesEntry.manualEntry.products.selectProduct')}</option>
|
||||
{products.map((product: any) => (
|
||||
<option key={product.id} value={product.id}>
|
||||
{product.name} - €{(product.average_cost || product.last_purchase_price || 0).toFixed(2)}
|
||||
@@ -355,7 +359,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
<div className="col-span-4 sm:col-span-2">
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Cant."
|
||||
placeholder={t('salesEntry.manualEntry.products.quantity')}
|
||||
value={item.quantity}
|
||||
onChange={(e) =>
|
||||
handleUpdateItem(index, 'quantity', parseFloat(e.target.value) || 0)
|
||||
@@ -368,7 +372,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
<div className="col-span-4 sm:col-span-2">
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Precio"
|
||||
placeholder={t('salesEntry.manualEntry.products.price')}
|
||||
value={item.unitPrice}
|
||||
onChange={(e) =>
|
||||
handleUpdateItem(index, 'unitPrice', parseFloat(e.target.value) || 0)
|
||||
@@ -399,7 +403,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
{(data.salesItems || []).length > 0 && (
|
||||
<div className="pt-3 border-t border-[var(--border-primary)] text-right">
|
||||
<span className="text-lg font-bold text-[var(--text-primary)]">
|
||||
Total: €{calculateTotal().toFixed(2)}
|
||||
{t('salesEntry.manualEntry.products.total')} €{calculateTotal().toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -408,12 +412,12 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
{/* Notes */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
Notas (Opcional)
|
||||
{t('salesEntry.manualEntry.fields.notes')}
|
||||
</label>
|
||||
<textarea
|
||||
value={data.notes || ''}
|
||||
onChange={(e) => onDataChange?.({ ...data, notes: e.target.value })}
|
||||
placeholder="Información adicional sobre esta venta..."
|
||||
placeholder={t('salesEntry.manualEntry.fields.notesPlaceholder')}
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm"
|
||||
/>
|
||||
@@ -428,6 +432,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
|
||||
|
||||
const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNext }) => {
|
||||
const data = dataRef?.current || {};
|
||||
const { t } = useTranslation('wizards');
|
||||
const { currentTenant } = useTenant();
|
||||
const [validating, setValidating] = useState(false);
|
||||
const [importing, setImporting] = useState(false);
|
||||
@@ -461,7 +466,7 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
setValidationResult(result);
|
||||
} catch (err: any) {
|
||||
console.error('Error validating file:', err);
|
||||
setError(err.response?.data?.detail || 'Error al validar el archivo');
|
||||
setError(err.response?.data?.detail || t('salesEntry.messages.errorValidatingFile'));
|
||||
} finally {
|
||||
setValidating(false);
|
||||
}
|
||||
@@ -479,7 +484,7 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
onNext?.();
|
||||
} catch (err: any) {
|
||||
console.error('Error importing file:', err);
|
||||
setError(err.response?.data?.detail || 'Error al importar el archivo');
|
||||
setError(err.response?.data?.detail || t('salesEntry.messages.errorImportingFile'));
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
@@ -501,7 +506,7 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
document.body.removeChild(a);
|
||||
} catch (err: any) {
|
||||
console.error('Error downloading template:', err);
|
||||
setError('Error al descargar la plantilla');
|
||||
setError(t('salesEntry.messages.errorValidatingFile'));
|
||||
} finally {
|
||||
setDownloadingTemplate(false);
|
||||
}
|
||||
@@ -511,10 +516,10 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
<div className="space-y-6">
|
||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
Cargar Archivo de Ventas
|
||||
{t('salesEntry.fileUpload.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
Importa tus ventas desde Excel o CSV
|
||||
{t('salesEntry.fileUpload.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -535,12 +540,12 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
{downloadingTemplate ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Descargando...
|
||||
{t('salesEntry.fileUpload.downloading')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="w-4 h-4" />
|
||||
Descargar Plantilla CSV
|
||||
{t('salesEntry.fileUpload.downloadTemplate')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -551,10 +556,10 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
<div className="border-2 border-dashed border-[var(--border-secondary)] rounded-xl p-8 text-center bg-[var(--bg-secondary)]/30">
|
||||
<FileSpreadsheet className="w-16 h-16 mx-auto mb-4 text-[var(--color-primary)]/50" />
|
||||
<h4 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
Arrastra un archivo aquí
|
||||
{t('salesEntry.fileUpload.dragDrop.title')}
|
||||
</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
||||
o haz clic para seleccionar
|
||||
{t('salesEntry.fileUpload.dragDrop.subtitle')}
|
||||
</p>
|
||||
<label className="inline-block">
|
||||
<input
|
||||
@@ -564,11 +569,11 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
className="hidden"
|
||||
/>
|
||||
<span className="px-6 py-2 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary)]/90 transition-colors cursor-pointer inline-block">
|
||||
Seleccionar Archivo
|
||||
{t('salesEntry.fileUpload.dragDrop.button')}
|
||||
</span>
|
||||
</label>
|
||||
<p className="text-xs text-[var(--text-tertiary)] mt-3">
|
||||
Formatos soportados: CSV, Excel (.xlsx, .xls)
|
||||
{t('salesEntry.fileUpload.dragDrop.supportedFormats')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -595,14 +600,14 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
{validationResult && (
|
||||
<div className="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<p className="text-sm text-blue-800 font-medium mb-2">
|
||||
✓ Archivo validado correctamente
|
||||
{t('salesEntry.fileUpload.validated.title')}
|
||||
</p>
|
||||
<div className="text-xs text-blue-700 space-y-1">
|
||||
<p>Registros encontrados: {validationResult.total_rows || 0}</p>
|
||||
<p>Registros válidos: {validationResult.valid_rows || 0}</p>
|
||||
<p>{t('salesEntry.fileUpload.validated.recordsFound')} {validationResult.total_rows || 0}</p>
|
||||
<p>{t('salesEntry.fileUpload.validated.validRecords')} {validationResult.valid_rows || 0}</p>
|
||||
{validationResult.errors?.length > 0 && (
|
||||
<p className="text-red-600">
|
||||
Errores: {validationResult.errors.length}
|
||||
{t('salesEntry.fileUpload.validated.errors')} {validationResult.errors.length}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -620,12 +625,12 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
{validating ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Validando...
|
||||
{t('salesEntry.fileUpload.validating')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
Validar Archivo
|
||||
{t('salesEntry.fileUpload.validateButton')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -638,12 +643,12 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
{importing ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Importando...
|
||||
{t('salesEntry.fileUpload.importing')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Upload className="w-4 h-4" />
|
||||
Importar Datos
|
||||
{t('salesEntry.fileUpload.importButton')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -652,9 +657,9 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
)}
|
||||
|
||||
<div className="text-center text-sm text-[var(--text-tertiary)]">
|
||||
<p>El archivo debe contener las columnas:</p>
|
||||
<p>{t('salesEntry.fileUpload.instructions.title')}</p>
|
||||
<p className="font-mono text-xs mt-1">
|
||||
fecha, producto, cantidad, precio_unitario, método_pago
|
||||
{t('salesEntry.fileUpload.instructions.columns')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -667,6 +672,7 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
|
||||
|
||||
const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
|
||||
const data = dataRef?.current || {};
|
||||
const { t } = useTranslation('wizards');
|
||||
|
||||
const isManual = data.entryMethod === 'manual';
|
||||
const isUpload = data.entryMethod === 'upload';
|
||||
@@ -680,10 +686,10 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||
Revisar y Confirmar
|
||||
{t('salesEntry.review.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
Verifica que toda la información sea correcta
|
||||
{t('salesEntry.review.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -693,11 +699,11 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
|
||||
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg">
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<span className="text-[var(--text-secondary)]">Fecha:</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('salesEntry.review.fields.date')}</span>
|
||||
<p className="font-semibold text-[var(--text-primary)]">{data.saleDate}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[var(--text-secondary)]">Método de Pago:</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('salesEntry.review.fields.paymentMethod')}</span>
|
||||
<p className="font-semibold text-[var(--text-primary)] capitalize">
|
||||
{data.paymentMethod}
|
||||
</p>
|
||||
@@ -708,7 +714,7 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
|
||||
{/* Items */}
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-2">
|
||||
Productos ({(data.salesItems || []).length})
|
||||
{t('salesEntry.review.fields.products')} ({(data.salesItems || []).length})
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{(data.salesItems || []).map((item: any) => (
|
||||
@@ -735,7 +741,7 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
|
||||
{/* Total */}
|
||||
<div className="p-4 bg-gradient-to-r from-[var(--color-primary)]/5 to-[var(--color-primary)]/10 rounded-lg border-2 border-[var(--color-primary)]/20">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-lg font-semibold text-[var(--text-primary)]">Total:</span>
|
||||
<span className="text-lg font-semibold text-[var(--text-primary)]">{t('salesEntry.review.fields.total')}</span>
|
||||
<span className="text-2xl font-bold text-[var(--color-primary)]">
|
||||
€{data.totalAmount?.toFixed(2)}
|
||||
</span>
|
||||
@@ -745,7 +751,7 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
|
||||
{/* Notes */}
|
||||
{data.notes && (
|
||||
<div className="p-3 bg-[var(--bg-secondary)]/30 rounded-lg">
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-1">Notas:</p>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-1">{t('salesEntry.review.fields.notes')}</p>
|
||||
<p className="text-sm text-[var(--text-primary)]">{data.notes}</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -756,11 +762,11 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||
<p className="text-green-800 font-semibold mb-2">
|
||||
✓ Archivo importado correctamente
|
||||
{t('salesEntry.review.imported.title')}
|
||||
</p>
|
||||
<div className="text-sm text-green-700 space-y-1">
|
||||
<p>Registros importados: {data.importResult.successful_imports || 0}</p>
|
||||
<p>Registros fallidos: {data.importResult.failed_imports || 0}</p>
|
||||
<p>{t('salesEntry.review.imported.recordsImported')} {data.importResult.successful_imports || 0}</p>
|
||||
<p>{t('salesEntry.review.imported.recordsFailed')} {data.importResult.failed_imports || 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -784,8 +790,8 @@ export const SalesEntryWizardSteps = (
|
||||
const steps: WizardStep[] = [
|
||||
{
|
||||
id: 'entry-method',
|
||||
title: 'Método de Entrada',
|
||||
description: 'Elige cómo registrar las ventas',
|
||||
title: 'salesEntry.steps.entryMethod',
|
||||
description: 'salesEntry.steps.entryMethodDescription',
|
||||
component: EntryMethodStep,
|
||||
},
|
||||
];
|
||||
@@ -793,8 +799,8 @@ export const SalesEntryWizardSteps = (
|
||||
if (entryMethod === 'manual') {
|
||||
steps.push({
|
||||
id: 'manual-entry',
|
||||
title: 'Ingresar Datos',
|
||||
description: 'Registra los detalles de la venta',
|
||||
title: 'salesEntry.steps.manualEntry',
|
||||
description: 'salesEntry.steps.manualEntryDescription',
|
||||
component: ManualEntryStep,
|
||||
validate: () => {
|
||||
const data = dataRef.current;
|
||||
@@ -804,16 +810,16 @@ export const SalesEntryWizardSteps = (
|
||||
} else if (entryMethod === 'upload') {
|
||||
steps.push({
|
||||
id: 'file-upload',
|
||||
title: 'Cargar Archivo',
|
||||
description: 'Importa ventas desde archivo',
|
||||
title: 'salesEntry.steps.fileUpload',
|
||||
description: 'salesEntry.steps.fileUploadDescription',
|
||||
component: FileUploadStep,
|
||||
});
|
||||
}
|
||||
|
||||
steps.push({
|
||||
id: 'review',
|
||||
title: 'Revisar',
|
||||
description: 'Confirma los datos antes de guardar',
|
||||
title: 'salesEntry.steps.review',
|
||||
description: 'salesEntry.steps.reviewDescription',
|
||||
component: ReviewStep,
|
||||
validate: async () => {
|
||||
const { useTenant } = await import('../../../../stores/tenant.store');
|
||||
@@ -824,6 +830,7 @@ export const SalesEntryWizardSteps = (
|
||||
const { currentTenant } = useTenant.getState();
|
||||
|
||||
if (!currentTenant?.id) {
|
||||
const { showToast } = await import('../../../../utils/toast');
|
||||
showToast.error('No se pudo obtener información del tenant');
|
||||
return false;
|
||||
}
|
||||
@@ -850,10 +857,12 @@ export const SalesEntryWizardSteps = (
|
||||
}
|
||||
}
|
||||
|
||||
const { showToast } = await import('../../../../utils/toast');
|
||||
showToast.success('Registro de ventas guardado exitosamente');
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
console.error('Error saving sales data:', err);
|
||||
const { showToast } = await import('../../../../utils/toast');
|
||||
const errorMessage = err.response?.data?.detail || 'Error al guardar los datos de ventas';
|
||||
showToast.error(errorMessage);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user