272 lines
10 KiB
TypeScript
272 lines
10 KiB
TypeScript
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
|
import { Sparkles } from 'lucide-react';
|
|
import { WizardModal, WizardStep } from '../../ui/WizardModal/WizardModal';
|
|
import { ItemTypeSelector, ItemType } from './ItemTypeSelector';
|
|
import { AnyWizardData } from './types';
|
|
import { useTenant } from '../../../stores/tenant.store';
|
|
import { useCreatePurchaseOrder } from '../../../api/hooks/purchase-orders';
|
|
import { useCreateProductionBatch } from '../../../api/hooks/production';
|
|
import { toast } from 'react-hot-toast';
|
|
import type { ProductionBatchCreate } from '../../../api/types/production';
|
|
import { ProductionPriorityEnum } from '../../../api/types/production';
|
|
|
|
// Import specific wizards
|
|
import { InventoryWizardSteps, ProductTypeStep, BasicInfoStep, StockConfigStep } from './wizards/InventoryWizard';
|
|
import { SupplierWizardSteps } from './wizards/SupplierWizard';
|
|
import { RecipeWizardSteps } from './wizards/RecipeWizard';
|
|
import { EquipmentWizardSteps } from './wizards/EquipmentWizard';
|
|
import { QualityTemplateWizardSteps } from './wizards/QualityTemplateWizard';
|
|
import { CustomerOrderWizardSteps } from './wizards/CustomerOrderWizard';
|
|
import { CustomerWizardSteps } from './wizards/CustomerWizard';
|
|
import { TeamMemberWizardSteps } from './wizards/TeamMemberWizard';
|
|
import { SalesEntryWizardSteps } from './wizards/SalesEntryWizard';
|
|
import { PurchaseOrderWizardSteps } from './wizards/PurchaseOrderWizard';
|
|
import { ProductionBatchWizardSteps } from './wizards/ProductionBatchWizard';
|
|
|
|
interface UnifiedAddWizardProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onComplete?: (itemType: ItemType, data?: any) => void;
|
|
// Optional: Start with a specific item type (when opened from individual page buttons)
|
|
initialItemType?: ItemType;
|
|
}
|
|
|
|
export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
onComplete,
|
|
initialItemType,
|
|
}) => {
|
|
const [selectedItemType, setSelectedItemType] = useState<ItemType | null>(
|
|
initialItemType || null
|
|
);
|
|
const [wizardData, setWizardData] = useState<AnyWizardData>({});
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
// Get current tenant
|
|
const { currentTenant } = useTenant();
|
|
|
|
// API hooks
|
|
const createPurchaseOrderMutation = useCreatePurchaseOrder();
|
|
const createProductionBatchMutation = useCreateProductionBatch();
|
|
|
|
// Use a ref to store the current data - this allows step components
|
|
// to always access the latest data without causing the steps array to be recreated
|
|
const dataRef = useRef<AnyWizardData>({});
|
|
|
|
// Update ref whenever data changes
|
|
useEffect(() => {
|
|
dataRef.current = wizardData;
|
|
}, [wizardData]);
|
|
|
|
// Reset state when modal closes
|
|
const handleClose = useCallback(() => {
|
|
setSelectedItemType(initialItemType || null);
|
|
setWizardData({});
|
|
dataRef.current = {};
|
|
setIsSubmitting(false);
|
|
onClose();
|
|
}, [onClose, initialItemType]);
|
|
|
|
// Handle item type selection from step 0
|
|
const handleItemTypeSelect = useCallback((itemType: ItemType) => {
|
|
setSelectedItemType(itemType);
|
|
}, []);
|
|
|
|
// CRITICAL FIX: Update both ref AND state, but wizardSteps won't recreate
|
|
// The step component needs to re-render to show typed text (controlled inputs)
|
|
// But wizardSteps useMemo ensures steps array doesn't recreate, so no component recreation
|
|
const handleDataChange = useCallback((newData: AnyWizardData) => {
|
|
// Update ref first for immediate access
|
|
dataRef.current = newData;
|
|
// Update state to trigger re-render (controlled inputs need this)
|
|
setWizardData(newData);
|
|
}, []);
|
|
|
|
// Handle wizard completion with API submission
|
|
const handleWizardComplete = useCallback(
|
|
async () => {
|
|
if (!selectedItemType || !currentTenant?.id) {
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
|
|
try {
|
|
const finalData = dataRef.current as any; // Cast to any for flexible data access
|
|
|
|
// Handle Purchase Order submission
|
|
if (selectedItemType === 'purchase-order') {
|
|
const subtotal = (finalData.items || []).reduce(
|
|
(sum: number, item: any) => sum + (item.subtotal || 0),
|
|
0
|
|
);
|
|
|
|
await createPurchaseOrderMutation.mutateAsync({
|
|
tenantId: currentTenant.id,
|
|
data: {
|
|
supplier_id: finalData.supplier_id,
|
|
required_delivery_date: finalData.required_delivery_date,
|
|
priority: finalData.priority || 'normal',
|
|
subtotal: String(subtotal),
|
|
tax_amount: String(finalData.tax_amount || 0),
|
|
shipping_cost: String(finalData.shipping_cost || 0),
|
|
discount_amount: String(finalData.discount_amount || 0),
|
|
notes: finalData.notes || undefined,
|
|
items: (finalData.items || []).map((item: any) => ({
|
|
inventory_product_id: item.inventory_product_id,
|
|
ordered_quantity: item.ordered_quantity,
|
|
unit_price: String(item.unit_price),
|
|
unit_of_measure: item.unit_of_measure,
|
|
})),
|
|
},
|
|
});
|
|
toast.success('Orden de compra creada exitosamente');
|
|
}
|
|
|
|
// Handle Production Batch submission
|
|
if (selectedItemType === 'production-batch') {
|
|
// Convert staff_assigned from string to array
|
|
const staffArray = finalData.staff_assigned_string
|
|
? finalData.staff_assigned_string.split(',').map((s: string) => s.trim()).filter((s: string) => s.length > 0)
|
|
: [];
|
|
|
|
const batchData: ProductionBatchCreate = {
|
|
product_id: finalData.product_id,
|
|
product_name: finalData.product_name,
|
|
recipe_id: finalData.recipe_id || undefined,
|
|
planned_start_time: finalData.planned_start_time,
|
|
planned_end_time: finalData.planned_end_time,
|
|
planned_quantity: Number(finalData.planned_quantity),
|
|
planned_duration_minutes: Number(finalData.planned_duration_minutes),
|
|
priority: (finalData.priority || ProductionPriorityEnum.MEDIUM) as ProductionPriorityEnum,
|
|
is_rush_order: finalData.is_rush_order || false,
|
|
is_special_recipe: finalData.is_special_recipe || false,
|
|
production_notes: finalData.production_notes || undefined,
|
|
batch_number: finalData.batch_number || undefined,
|
|
order_id: finalData.order_id || undefined,
|
|
forecast_id: finalData.forecast_id || undefined,
|
|
equipment_used: [],
|
|
staff_assigned: staffArray,
|
|
station_id: finalData.station_id || undefined,
|
|
};
|
|
|
|
await createProductionBatchMutation.mutateAsync({
|
|
tenantId: currentTenant.id,
|
|
batchData,
|
|
});
|
|
toast.success('Lote de producción creado exitosamente');
|
|
}
|
|
|
|
// Call the parent's onComplete callback
|
|
onComplete?.(selectedItemType, finalData);
|
|
|
|
// Close the modal
|
|
handleClose();
|
|
} catch (error: any) {
|
|
console.error('Error submitting wizard data:', error);
|
|
toast.error(error.message || 'Error al crear el elemento');
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
},
|
|
[
|
|
selectedItemType,
|
|
currentTenant,
|
|
createPurchaseOrderMutation,
|
|
createProductionBatchMutation,
|
|
onComplete,
|
|
handleClose,
|
|
]
|
|
);
|
|
|
|
// Get wizard steps based on selected item type
|
|
// ARCHITECTURAL SOLUTION: We pass dataRef and setWizardData to wizard step functions.
|
|
// The wizard steps use these in their component wrappers, which creates a closure
|
|
// that always accesses the CURRENT data from dataRef.current, without needing
|
|
// to recreate the steps array on every data change.
|
|
const wizardSteps = useMemo((): WizardStep[] => {
|
|
if (!selectedItemType) {
|
|
// Step 0: Item Type Selection
|
|
return [
|
|
{
|
|
id: 'item-type-selection',
|
|
title: 'Seleccionar tipo',
|
|
description: 'Elige qué deseas agregar',
|
|
component: () => (
|
|
<ItemTypeSelector onSelect={handleItemTypeSelect} />
|
|
),
|
|
},
|
|
];
|
|
}
|
|
|
|
// Pass dataRef and setWizardData - the wizard step functions will use
|
|
// dataRef.current to always access fresh data without recreating steps
|
|
switch (selectedItemType) {
|
|
case 'inventory':
|
|
return InventoryWizardSteps(dataRef, setWizardData);
|
|
case 'supplier':
|
|
return SupplierWizardSteps(dataRef, setWizardData);
|
|
case 'recipe':
|
|
return RecipeWizardSteps(dataRef, setWizardData);
|
|
case 'equipment':
|
|
return EquipmentWizardSteps(dataRef, setWizardData);
|
|
case 'quality-template':
|
|
return QualityTemplateWizardSteps(dataRef, setWizardData);
|
|
case 'customer-order':
|
|
return CustomerOrderWizardSteps(dataRef, setWizardData);
|
|
case 'customer':
|
|
return CustomerWizardSteps(dataRef, setWizardData);
|
|
case 'team-member':
|
|
return TeamMemberWizardSteps(dataRef, setWizardData);
|
|
case 'sales-entry':
|
|
return SalesEntryWizardSteps(dataRef, setWizardData);
|
|
case 'purchase-order':
|
|
return PurchaseOrderWizardSteps(dataRef, setWizardData);
|
|
case 'production-batch':
|
|
return ProductionBatchWizardSteps(dataRef, setWizardData);
|
|
default:
|
|
return [];
|
|
}
|
|
}, [selectedItemType, handleItemTypeSelect, wizardData.entryMethod]); // Add entryMethod for dynamic sales-entry steps
|
|
|
|
// Get wizard title based on selected item type
|
|
const getWizardTitle = (): string => {
|
|
if (!selectedItemType) {
|
|
return 'Agregar Contenido';
|
|
}
|
|
|
|
const titleMap: Record<ItemType, string> = {
|
|
'inventory': 'Agregar Inventario',
|
|
'supplier': 'Agregar Proveedor',
|
|
'recipe': 'Agregar Receta',
|
|
'equipment': 'Agregar Equipo',
|
|
'quality-template': 'Agregar Plantilla de Calidad',
|
|
'customer-order': 'Agregar Pedido',
|
|
'customer': 'Agregar Cliente',
|
|
'team-member': 'Agregar Miembro del Equipo',
|
|
'sales-entry': 'Registrar Ventas',
|
|
'purchase-order': 'Crear Orden de Compra',
|
|
'production-batch': 'Crear Lote de Producción',
|
|
};
|
|
|
|
return titleMap[selectedItemType] || 'Agregar Contenido';
|
|
};
|
|
|
|
return (
|
|
<WizardModal
|
|
isOpen={isOpen}
|
|
onClose={handleClose}
|
|
onComplete={handleWizardComplete}
|
|
title={getWizardTitle()}
|
|
steps={wizardSteps}
|
|
icon={<Sparkles className="w-6 h-6" />}
|
|
size="xl"
|
|
dataRef={dataRef}
|
|
onDataChange={handleDataChange}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default UnifiedAddWizard;
|