600 lines
22 KiB
TypeScript
600 lines
22 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|||
|
|
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
|
|||
|
|
import {
|
|||
|
|
Edit3,
|
|||
|
|
Upload,
|
|||
|
|
CheckCircle2,
|
|||
|
|
AlertCircle,
|
|||
|
|
Download,
|
|||
|
|
FileSpreadsheet,
|
|||
|
|
Calendar,
|
|||
|
|
DollarSign,
|
|||
|
|
Package,
|
|||
|
|
CreditCard,
|
|||
|
|
} from 'lucide-react';
|
|||
|
|
|
|||
|
|
// ========================================
|
|||
|
|
// STEP 1: Entry Method Selection
|
|||
|
|
// ========================================
|
|||
|
|
|
|||
|
|
interface EntryMethodStepProps extends WizardStepProps {
|
|||
|
|
data: Record<string, any>;
|
|||
|
|
onDataChange: (data: Record<string, any>) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const EntryMethodStep: React.FC<EntryMethodStepProps> = ({ data, onDataChange, onNext }) => {
|
|||
|
|
const [selectedMethod, setSelectedMethod] = useState<'manual' | 'upload'>(
|
|||
|
|
data.entryMethod || 'manual'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const handleSelect = (method: 'manual' | 'upload') => {
|
|||
|
|
setSelectedMethod(method);
|
|||
|
|
onDataChange({ ...data, entryMethod: method });
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleContinue = () => {
|
|||
|
|
onDataChange({ ...data, entryMethod: selectedMethod });
|
|||
|
|
onNext();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<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?
|
|||
|
|
</h3>
|
|||
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|||
|
|
Elige el método que mejor se adapte a tus necesidades
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|||
|
|
{/* Manual Entry Option */}
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleSelect('manual')}
|
|||
|
|
className={`
|
|||
|
|
p-6 rounded-xl border-2 transition-all duration-200 text-left
|
|||
|
|
hover:shadow-lg hover:-translate-y-1
|
|||
|
|
focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:ring-offset-2
|
|||
|
|
${
|
|||
|
|
selectedMethod === 'manual'
|
|||
|
|
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/5 shadow-md'
|
|||
|
|
: 'border-[var(--border-secondary)] bg-[var(--bg-primary)]'
|
|||
|
|
}
|
|||
|
|
`}
|
|||
|
|
>
|
|||
|
|
<div className="flex items-start gap-4">
|
|||
|
|
<div
|
|||
|
|
className={`
|
|||
|
|
p-3 rounded-lg transition-colors
|
|||
|
|
${
|
|||
|
|
selectedMethod === 'manual'
|
|||
|
|
? 'bg-[var(--color-primary)] text-white'
|
|||
|
|
: 'bg-[var(--bg-secondary)] text-[var(--color-primary)]'
|
|||
|
|
}
|
|||
|
|
`}
|
|||
|
|
>
|
|||
|
|
<Edit3 className="w-6 h-6" />
|
|||
|
|
</div>
|
|||
|
|
<div className="flex-1">
|
|||
|
|
<h4 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
|||
|
|
Entrada Manual
|
|||
|
|
</h4>
|
|||
|
|
<p className="text-sm text-[var(--text-secondary)] mb-3">
|
|||
|
|
Ingresa una o varias ventas de forma individual
|
|||
|
|
</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
|
|||
|
|
</p>
|
|||
|
|
<p className="flex items-center gap-1.5">
|
|||
|
|
<CheckCircle2 className="w-3.5 h-3.5 text-green-600" />
|
|||
|
|
Control detallado por venta
|
|||
|
|
</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
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
{/* File Upload Option */}
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleSelect('upload')}
|
|||
|
|
className={`
|
|||
|
|
relative p-6 rounded-xl border-2 transition-all duration-200 text-left
|
|||
|
|
hover:shadow-lg hover:-translate-y-1
|
|||
|
|
focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:ring-offset-2
|
|||
|
|
${
|
|||
|
|
selectedMethod === 'upload'
|
|||
|
|
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/5 shadow-md'
|
|||
|
|
: 'border-[var(--border-secondary)] bg-[var(--bg-primary)]'
|
|||
|
|
}
|
|||
|
|
`}
|
|||
|
|
>
|
|||
|
|
{/* 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
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-start gap-4">
|
|||
|
|
<div
|
|||
|
|
className={`
|
|||
|
|
p-3 rounded-lg transition-colors
|
|||
|
|
${
|
|||
|
|
selectedMethod === 'upload'
|
|||
|
|
? 'bg-[var(--color-primary)] text-white'
|
|||
|
|
: 'bg-[var(--bg-secondary)] text-[var(--color-primary)]'
|
|||
|
|
}
|
|||
|
|
`}
|
|||
|
|
>
|
|||
|
|
<Upload className="w-6 h-6" />
|
|||
|
|
</div>
|
|||
|
|
<div className="flex-1">
|
|||
|
|
<h4 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
|||
|
|
Cargar Archivo
|
|||
|
|
</h4>
|
|||
|
|
<p className="text-sm text-[var(--text-secondary)] mb-3">
|
|||
|
|
Importa desde Excel o CSV
|
|||
|
|
</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
|
|||
|
|
</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)
|
|||
|
|
</p>
|
|||
|
|
<p className="flex items-center gap-1.5">
|
|||
|
|
<CheckCircle2 className="w-3.5 h-3.5 text-green-600" />
|
|||
|
|
Ahorra tiempo significativo
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Continue Button */}
|
|||
|
|
<div className="flex justify-end pt-4 border-t border-[var(--border-primary)]">
|
|||
|
|
<button
|
|||
|
|
onClick={handleContinue}
|
|||
|
|
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary)]/90 transition-colors font-medium inline-flex items-center gap-2"
|
|||
|
|
>
|
|||
|
|
Continuar
|
|||
|
|
<CheckCircle2 className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ========================================
|
|||
|
|
// STEP 2a: Manual Entry Form
|
|||
|
|
// ========================================
|
|||
|
|
|
|||
|
|
const ManualEntryStep: React.FC<EntryMethodStepProps> = ({ data, onDataChange, onNext }) => {
|
|||
|
|
const [salesItems, setSalesItems] = useState(data.salesItems || []);
|
|||
|
|
const [saleDate, setSaleDate] = useState(
|
|||
|
|
data.saleDate || new Date().toISOString().split('T')[0]
|
|||
|
|
);
|
|||
|
|
const [paymentMethod, setPaymentMethod] = useState(data.paymentMethod || 'cash');
|
|||
|
|
const [notes, setNotes] = useState(data.notes || '');
|
|||
|
|
|
|||
|
|
const handleAddItem = () => {
|
|||
|
|
setSalesItems([
|
|||
|
|
...salesItems,
|
|||
|
|
{ id: Date.now(), product: '', quantity: 1, unitPrice: 0, subtotal: 0 },
|
|||
|
|
]);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleUpdateItem = (index: number, field: string, value: any) => {
|
|||
|
|
const updated = salesItems.map((item: any, i: number) => {
|
|||
|
|
if (i === index) {
|
|||
|
|
const newItem = { ...item, [field]: value };
|
|||
|
|
// Auto-calculate subtotal
|
|||
|
|
if (field === 'quantity' || field === 'unitPrice') {
|
|||
|
|
newItem.subtotal = (newItem.quantity || 0) * (newItem.unitPrice || 0);
|
|||
|
|
}
|
|||
|
|
return newItem;
|
|||
|
|
}
|
|||
|
|
return item;
|
|||
|
|
});
|
|||
|
|
setSalesItems(updated);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleRemoveItem = (index: number) => {
|
|||
|
|
setSalesItems(salesItems.filter((_: any, i: number) => i !== index));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const calculateTotal = () => {
|
|||
|
|
return salesItems.reduce((sum: number, item: any) => sum + (item.subtotal || 0), 0);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSave = () => {
|
|||
|
|
onDataChange({
|
|||
|
|
...data,
|
|||
|
|
salesItems,
|
|||
|
|
saleDate,
|
|||
|
|
paymentMethod,
|
|||
|
|
notes,
|
|||
|
|
totalAmount: calculateTotal(),
|
|||
|
|
});
|
|||
|
|
onNext();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<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
|
|||
|
|
</h3>
|
|||
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|||
|
|
Ingresa los detalles de la venta
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Date and Payment Method */}
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|||
|
|
<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 *
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="date"
|
|||
|
|
value={saleDate}
|
|||
|
|
onChange={(e) => setSaleDate(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)]"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<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 *
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
value={paymentMethod}
|
|||
|
|
onChange={(e) => setPaymentMethod(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>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Sales Items */}
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
<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
|
|||
|
|
</label>
|
|||
|
|
<button
|
|||
|
|
onClick={handleAddItem}
|
|||
|
|
className="px-3 py-1.5 text-sm bg-[var(--color-primary)] text-white rounded-md hover:bg-[var(--color-primary)]/90 transition-colors"
|
|||
|
|
>
|
|||
|
|
+ Agregar Producto
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{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>
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
{salesItems.map((item: any, index: number) => (
|
|||
|
|
<div
|
|||
|
|
key={item.id}
|
|||
|
|
className="p-3 border border-[var(--border-secondary)] rounded-lg bg-[var(--bg-secondary)]/30"
|
|||
|
|
>
|
|||
|
|
<div className="grid grid-cols-12 gap-2 items-center">
|
|||
|
|
<div className="col-span-12 sm:col-span-5">
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
placeholder="Nombre del producto"
|
|||
|
|
value={item.product}
|
|||
|
|
onChange={(e) => handleUpdateItem(index, 'product', 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)]"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="col-span-4 sm:col-span-2">
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
placeholder="Cant."
|
|||
|
|
value={item.quantity}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
handleUpdateItem(index, 'quantity', parseFloat(e.target.value) || 0)
|
|||
|
|
}
|
|||
|
|
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)]"
|
|||
|
|
min="0"
|
|||
|
|
step="1"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="col-span-4 sm:col-span-2">
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
placeholder="Precio"
|
|||
|
|
value={item.unitPrice}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
handleUpdateItem(index, 'unitPrice', parseFloat(e.target.value) || 0)
|
|||
|
|
}
|
|||
|
|
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)]"
|
|||
|
|
min="0"
|
|||
|
|
step="0.01"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="col-span-3 sm:col-span-2 text-sm font-semibold text-[var(--text-primary)]">
|
|||
|
|
€{item.subtotal.toFixed(2)}
|
|||
|
|
</div>
|
|||
|
|
<div className="col-span-1 sm:col-span-1 flex justify-end">
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleRemoveItem(index)}
|
|||
|
|
className="p-1 text-red-500 hover:text-red-700 transition-colors"
|
|||
|
|
>
|
|||
|
|
✕
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* Total */}
|
|||
|
|
{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)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Notes */}
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
|||
|
|
Notas (Opcional)
|
|||
|
|
</label>
|
|||
|
|
<textarea
|
|||
|
|
value={notes}
|
|||
|
|
onChange={(e) => setNotes(e.target.value)}
|
|||
|
|
placeholder="Información adicional sobre esta venta..."
|
|||
|
|
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"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Save Button */}
|
|||
|
|
<div className="flex justify-end pt-4 border-t border-[var(--border-primary)]">
|
|||
|
|
<button
|
|||
|
|
onClick={handleSave}
|
|||
|
|
disabled={salesItems.length === 0}
|
|||
|
|
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary)]/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium"
|
|||
|
|
>
|
|||
|
|
Guardar y Continuar
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ========================================
|
|||
|
|
// STEP 2b: File Upload (Placeholder for now)
|
|||
|
|
// ========================================
|
|||
|
|
|
|||
|
|
const FileUploadStep: React.FC<EntryMethodStepProps> = ({ data, onDataChange, onNext }) => {
|
|||
|
|
return (
|
|||
|
|
<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
|
|||
|
|
</h3>
|
|||
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|||
|
|
Importa tus ventas desde Excel o CSV
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="text-center py-12 border-2 border-dashed border-[var(--border-secondary)] rounded-xl 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">
|
|||
|
|
Función de Carga de Archivos
|
|||
|
|
</h4>
|
|||
|
|
<p className="text-sm text-[var(--text-secondary)] mb-6 max-w-md mx-auto">
|
|||
|
|
Esta funcionalidad avanzada incluirá:
|
|||
|
|
<br />
|
|||
|
|
• Carga de CSV/Excel
|
|||
|
|
<br />
|
|||
|
|
• Mapeo de columnas
|
|||
|
|
<br />
|
|||
|
|
• Validación de datos
|
|||
|
|
<br />
|
|||
|
|
• Importación masiva
|
|||
|
|
</p>
|
|||
|
|
<div className="flex gap-3 justify-center">
|
|||
|
|
<button className="px-4 py-2 border border-[var(--border-secondary)] text-[var(--text-secondary)] rounded-lg hover:bg-[var(--bg-secondary)] transition-colors inline-flex items-center gap-2">
|
|||
|
|
<Download className="w-4 h-4" />
|
|||
|
|
Descargar Plantilla CSV
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={onNext}
|
|||
|
|
className="px-6 py-2 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary)]/90 transition-colors"
|
|||
|
|
>
|
|||
|
|
Continuar (Demo)
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ========================================
|
|||
|
|
// STEP 3: Review & Confirm
|
|||
|
|
// ========================================
|
|||
|
|
|
|||
|
|
const ReviewStep: React.FC<EntryMethodStepProps> = ({ data, onComplete }) => {
|
|||
|
|
const handleConfirm = () => {
|
|||
|
|
// Here you would typically make an API call to save the data
|
|||
|
|
console.log('Saving sales data:', data);
|
|||
|
|
onComplete();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const isManual = data.entryMethod === 'manual';
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="space-y-6">
|
|||
|
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
|||
|
|
<div className="flex items-center justify-center mb-3">
|
|||
|
|
<div className="p-3 bg-green-100 rounded-full">
|
|||
|
|
<CheckCircle2 className="w-8 h-8 text-green-600" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
|||
|
|
Revisar y Confirmar
|
|||
|
|
</h3>
|
|||
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|||
|
|
Verifica que toda la información sea correcta
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{isManual && data.salesItems && (
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
{/* Summary */}
|
|||
|
|
<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>
|
|||
|
|
<p className="font-semibold text-[var(--text-primary)]">{data.saleDate}</p>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-[var(--text-secondary)]">Método de Pago:</span>
|
|||
|
|
<p className="font-semibold text-[var(--text-primary)] capitalize">
|
|||
|
|
{data.paymentMethod}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Items */}
|
|||
|
|
<div>
|
|||
|
|
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-2">
|
|||
|
|
Productos ({data.salesItems.length})
|
|||
|
|
</h4>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
{data.salesItems.map((item: any) => (
|
|||
|
|
<div
|
|||
|
|
key={item.id}
|
|||
|
|
className="p-3 border border-[var(--border-secondary)] rounded-lg bg-[var(--bg-primary)] flex justify-between items-center"
|
|||
|
|
>
|
|||
|
|
<div className="flex-1">
|
|||
|
|
<p className="font-medium text-[var(--text-primary)]">{item.product}</p>
|
|||
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|||
|
|
{item.quantity} × €{item.unitPrice.toFixed(2)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-right">
|
|||
|
|
<p className="font-semibold text-[var(--text-primary)]">
|
|||
|
|
€{item.subtotal.toFixed(2)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 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-2xl font-bold text-[var(--color-primary)]">
|
|||
|
|
€{data.totalAmount?.toFixed(2)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 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-primary)]">{data.notes}</p>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* Confirm Button */}
|
|||
|
|
<div className="flex justify-end gap-3 pt-4 border-t border-[var(--border-primary)]">
|
|||
|
|
<button
|
|||
|
|
onClick={handleConfirm}
|
|||
|
|
className="px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-semibold inline-flex items-center gap-2"
|
|||
|
|
>
|
|||
|
|
<CheckCircle2 className="w-5 h-5" />
|
|||
|
|
Confirmar y Guardar
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ========================================
|
|||
|
|
// Export Wizard Steps
|
|||
|
|
// ========================================
|
|||
|
|
|
|||
|
|
export const SalesEntryWizardSteps = (
|
|||
|
|
data: Record<string, any>,
|
|||
|
|
setData: (data: Record<string, any>) => void
|
|||
|
|
): WizardStep[] => {
|
|||
|
|
const entryMethod = data.entryMethod;
|
|||
|
|
|
|||
|
|
// Dynamic steps based on entry method
|
|||
|
|
const steps: WizardStep[] = [
|
|||
|
|
{
|
|||
|
|
id: 'entry-method',
|
|||
|
|
title: 'Método de Entrada',
|
|||
|
|
description: 'Elige cómo registrar las ventas',
|
|||
|
|
component: (props) => (
|
|||
|
|
<EntryMethodStep {...props} data={data} onDataChange={setData} />
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
if (entryMethod === 'manual') {
|
|||
|
|
steps.push({
|
|||
|
|
id: 'manual-entry',
|
|||
|
|
title: 'Ingresar Datos',
|
|||
|
|
description: 'Registra los detalles de la venta',
|
|||
|
|
component: (props) => <ManualEntryStep {...props} data={data} onDataChange={setData} />,
|
|||
|
|
});
|
|||
|
|
} else if (entryMethod === 'upload') {
|
|||
|
|
steps.push({
|
|||
|
|
id: 'file-upload',
|
|||
|
|
title: 'Cargar Archivo',
|
|||
|
|
description: 'Importa ventas desde archivo',
|
|||
|
|
component: (props) => <FileUploadStep {...props} data={data} onDataChange={setData} />,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
steps.push({
|
|||
|
|
id: 'review',
|
|||
|
|
title: 'Revisar',
|
|||
|
|
description: 'Confirma los datos antes de guardar',
|
|||
|
|
component: (props) => <ReviewStep {...props} data={data} onDataChange={setData} />,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return steps;
|
|||
|
|
};
|