import React, { useState } from 'react'; import { Plus, ShoppingCart, Truck, Euro, Calendar, Clock, CheckCircle, AlertCircle, Package, Eye, Loader, Edit, ArrowRight, X, Save, Building2, Play, Zap, User } from 'lucide-react'; import { Button, Card, StatsGrid, StatusCard, getStatusColor, EditViewModal, SearchAndFilter, Input, type FilterConfig } from '../../../../components/ui'; import { formatters } from '../../../../components/ui/Stats/StatsPresets'; import { PageHeader } from '../../../../components/layout'; import { CreatePurchaseOrderModal } from '../../../../components/domain/procurement/CreatePurchaseOrderModal'; import { useProcurementDashboard, useProcurementPlans, usePlanRequirements, useGenerateProcurementPlan, useUpdateProcurementPlanStatus, useTriggerDailyScheduler, useRecalculateProcurementPlan, useApproveProcurementPlan, useRejectProcurementPlan, useCreatePurchaseOrdersFromPlan, useLinkRequirementToPurchaseOrder, useUpdateRequirementDeliveryStatus } from '../../../../api'; import { useTenantStore } from '../../../../stores/tenant.store'; const ProcurementPage: React.FC = () => { const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [showForm, setShowForm] = useState(false); const [modalMode, setModalMode] = useState<'view' | 'edit'>('view'); const [selectedPlan, setSelectedPlan] = useState(null); const [editingPlan, setEditingPlan] = useState(null); const [editFormData, setEditFormData] = useState({}); const [selectedPlanForRequirements, setSelectedPlanForRequirements] = useState(null); const [showCriticalRequirements, setShowCriticalRequirements] = useState(false); const [showGeneratePlanModal, setShowGeneratePlanModal] = useState(false); const [showRequirementDetailsModal, setShowRequirementDetailsModal] = useState(false); const [selectedRequirement, setSelectedRequirement] = useState(null); const [showCreatePurchaseOrderModal, setShowCreatePurchaseOrderModal] = useState(false); const [selectedRequirementsForPO, setSelectedRequirementsForPO] = useState([]); const [isAIMode, setIsAIMode] = useState(true); const [generatePlanForm, setGeneratePlanForm] = useState({ plan_date: new Date().toISOString().split('T')[0], planning_horizon_days: 14, include_safety_stock: true, safety_stock_percentage: 20, force_regenerate: false }); // New feature state const [showApprovalModal, setShowApprovalModal] = useState(false); const [approvalAction, setApprovalAction] = useState<'approve' | 'reject'>('approve'); const [approvalNotes, setApprovalNotes] = useState(''); const [planForApproval, setPlanForApproval] = useState(null); const [showDeliveryUpdateModal, setShowDeliveryUpdateModal] = useState(false); const [requirementForDelivery, setRequirementForDelivery] = useState(null); const [deliveryUpdateForm, setDeliveryUpdateForm] = useState({ delivery_status: 'pending', received_quantity: 0, actual_delivery_date: '', quality_rating: 5 }); // Requirement details functionality const handleViewRequirementDetails = (requirement: any) => { setSelectedRequirement(requirement); setShowRequirementDetailsModal(true); }; const { currentTenant } = useTenantStore(); const tenantId = currentTenant?.id || ''; // Real API data hooks const { data: dashboardData, isLoading: isDashboardLoading } = useProcurementDashboard(tenantId); const { data: procurementPlans, isLoading: isPlansLoading } = useProcurementPlans({ tenant_id: tenantId, limit: 50, offset: 0 }); // Get plan requirements for selected plan const { data: allPlanRequirements, isLoading: isPlanRequirementsLoading } = usePlanRequirements({ tenant_id: tenantId, plan_id: selectedPlanForRequirements || '' // Remove status filter to get all requirements }, { enabled: !!selectedPlanForRequirements && !!tenantId }); // Filter critical requirements client-side const planRequirements = allPlanRequirements?.filter(req => { // Check various conditions that might make a requirement critical const isLowStock = req.current_stock_level && req.required_quantity && (req.current_stock_level / req.required_quantity) < 0.5; const isNearDeadline = req.required_by_date && (new Date(req.required_by_date).getTime() - Date.now()) / (1000 * 60 * 60 * 24) < 7; const hasHighPriority = req.priority === 'high'; return isLowStock || isNearDeadline || hasHighPriority; }); const generatePlanMutation = useGenerateProcurementPlan(); const updatePlanStatusMutation = useUpdateProcurementPlanStatus(); const triggerSchedulerMutation = useTriggerDailyScheduler(); // New feature mutations const recalculatePlanMutation = useRecalculateProcurementPlan(); const approvePlanMutation = useApproveProcurementPlan(); const rejectPlanMutation = useRejectProcurementPlan(); const createPOsMutation = useCreatePurchaseOrdersFromPlan(); const updateDeliveryMutation = useUpdateRequirementDeliveryStatus(); // Helper functions for stage transitions and edit functionality const getNextStage = (currentStatus: string): string | null => { const stageFlow: { [key: string]: string } = { 'draft': 'pending_approval', 'pending_approval': 'approved', 'approved': 'in_execution', 'in_execution': 'completed' }; return stageFlow[currentStatus] || null; }; const getStageActionConfig = (status: string) => { const configs: { [key: string]: { label: string; icon: any; variant: 'primary' | 'outline'; color?: string } } = { 'draft': { label: 'Enviar a Aprobación', icon: ArrowRight, variant: 'primary' }, 'pending_approval': { label: 'Aprobar', icon: CheckCircle, variant: 'primary' }, 'approved': { label: 'Iniciar Ejecución', icon: Play, variant: 'primary' }, 'in_execution': { label: 'Completar', icon: CheckCircle, variant: 'primary' } }; return configs[status]; }; const canEdit = (status: string): boolean => { return status === 'draft'; }; const handleStageTransition = (planId: string, currentStatus: string) => { const nextStage = getNextStage(currentStatus); if (nextStage) { updatePlanStatusMutation.mutate({ tenant_id: tenantId, plan_id: planId, status: nextStage as any }); } }; const handleCancelPlan = (planId: string) => { updatePlanStatusMutation.mutate({ tenant_id: tenantId, plan_id: planId, status: 'cancelled' }); }; const handleEditPlan = (plan: any) => { setEditingPlan(plan); setEditFormData({ special_requirements: plan.special_requirements || '', planning_horizon_days: plan.planning_horizon_days || 14, priority: plan.priority || 'medium' }); }; const handleSaveEdit = () => { // For now, we'll just update the special requirements since that's the main editable field // In a real implementation, you might have a separate API endpoint for updating plan details setEditingPlan(null); setEditFormData({}); // Here you would typically call an update API }; const handleCancelEdit = () => { setEditingPlan(null); setEditFormData({}); }; const handleShowCriticalRequirements = (planId: string) => { setSelectedPlanForRequirements(planId); setShowCriticalRequirements(true); }; const handleCloseCriticalRequirements = () => { setShowCriticalRequirements(false); setSelectedPlanForRequirements(null); }; // NEW FEATURE HANDLERS const handleRecalculatePlan = (plan: any) => { if (window.confirm('¿Recalcular el plan con el inventario actual? Esto puede cambiar las cantidades requeridas.')) { recalculatePlanMutation.mutate({ tenantId, planId: plan.id }); } }; const handleOpenApprovalModal = (plan: any, action: 'approve' | 'reject') => { setPlanForApproval(plan); setApprovalAction(action); setApprovalNotes(''); setShowApprovalModal(true); }; const handleConfirmApproval = () => { if (!planForApproval) return; if (approvalAction === 'approve') { approvePlanMutation.mutate({ tenantId, planId: planForApproval.id, approval_notes: approvalNotes || undefined }, { onSuccess: () => { setShowApprovalModal(false); setPlanForApproval(null); setApprovalNotes(''); } }); } else { rejectPlanMutation.mutate({ tenantId, planId: planForApproval.id, rejection_notes: approvalNotes || undefined }, { onSuccess: () => { setShowApprovalModal(false); setPlanForApproval(null); setApprovalNotes(''); } }); } }; const handleCreatePurchaseOrders = (plan: any) => { if (plan.status !== 'approved') { alert('El plan debe estar aprobado antes de crear órdenes de compra'); return; } if (window.confirm(`¿Crear órdenes de compra automáticamente para ${plan.total_requirements} requerimientos?`)) { createPOsMutation.mutate({ tenantId, planId: plan.id, autoApprove: false }); } }; const handleOpenDeliveryUpdate = (requirement: any) => { setRequirementForDelivery(requirement); setDeliveryUpdateForm({ delivery_status: requirement.delivery_status || 'pending', received_quantity: requirement.received_quantity || 0, actual_delivery_date: requirement.actual_delivery_date || '', quality_rating: requirement.quality_rating || 5 }); setShowDeliveryUpdateModal(true); }; const handleConfirmDeliveryUpdate = () => { if (!requirementForDelivery) return; updateDeliveryMutation.mutate({ tenantId, requirementId: requirementForDelivery.id, request: { delivery_status: deliveryUpdateForm.delivery_status, received_quantity: deliveryUpdateForm.received_quantity || undefined, actual_delivery_date: deliveryUpdateForm.actual_delivery_date || undefined, quality_rating: deliveryUpdateForm.quality_rating || undefined } }, { onSuccess: () => { setShowDeliveryUpdateModal(false); setRequirementForDelivery(null); } }); }; if (!tenantId) { return (

No hay tenant seleccionado

Selecciona un tenant para ver los datos de procurement

); } const getPlanStatusConfig = (status: string) => { const statusConfig = { draft: { text: 'Borrador', icon: Clock }, pending_approval: { text: 'Pendiente Aprobación', icon: Clock }, approved: { text: 'Aprobado', icon: CheckCircle }, in_execution: { text: 'En Ejecución', icon: Truck }, completed: { text: 'Completado', icon: CheckCircle }, cancelled: { text: 'Cancelado', icon: AlertCircle }, }; const config = statusConfig[status as keyof typeof statusConfig]; const Icon = config?.icon; return { color: getStatusColor(status === 'in_execution' ? 'inTransit' : status === 'pending_approval' ? 'pending' : status), text: config?.text || status, icon: Icon, isCritical: status === 'cancelled', isHighlight: status === 'pending_approval' }; }; const filteredPlans = procurementPlans?.plans?.filter(plan => { const matchesSearch = plan.plan_number.toLowerCase().includes(searchTerm.toLowerCase()) || plan.status.toLowerCase().includes(searchTerm.toLowerCase()) || (plan.special_requirements && plan.special_requirements.toLowerCase().includes(searchTerm.toLowerCase())); const matchesStatus = !statusFilter || plan.status === statusFilter; return matchesSearch && matchesStatus; }) || []; const stats = { totalPlans: dashboardData?.summary?.total_plans || 0, activePlans: dashboardData?.summary?.active_plans || 0, pendingRequirements: dashboardData?.summary?.pending_requirements || 0, criticalRequirements: dashboardData?.summary?.critical_requirements || 0, totalEstimatedCost: dashboardData?.summary?.total_estimated_cost || 0, totalApprovedCost: dashboardData?.summary?.total_approved_cost || 0, }; const procurementStats = [ { title: 'Planes Totales', value: stats.totalPlans, variant: 'default' as const, icon: Package, }, { title: 'Planes Activos', value: stats.activePlans, variant: 'success' as const, icon: CheckCircle, }, { title: 'Requerimientos Pendientes', value: stats.pendingRequirements, variant: 'warning' as const, icon: Clock, }, { title: 'Críticos', value: stats.criticalRequirements, variant: 'warning' as const, icon: AlertCircle, }, { title: 'Costo Estimado', value: formatters.currency(stats.totalEstimatedCost), variant: 'info' as const, icon: Euro, }, { title: 'Costo Aprobado', value: formatters.currency(stats.totalApprovedCost), variant: 'success' as const, icon: Euro, }, ]; return (
{/* AI/Manual Mode Segmented Control */}
{/* Action Buttons */} {!isAIMode && ( )} {/* Testing button - keep for development */}
{/* Stats Grid */} {isDashboardLoading ? (
) : ( )} setStatusFilter(value as string), placeholder: 'Todos los estados', options: [ { value: 'draft', label: 'Borrador' }, { value: 'pending_approval', label: 'Pendiente Aprobación' }, { value: 'approved', label: 'Aprobado' }, { value: 'in_execution', label: 'En Ejecución' }, { value: 'completed', label: 'Completado' }, { value: 'cancelled', label: 'Cancelado' } ] } ] as FilterConfig[]} /> {/* Procurement Plans Grid - Mobile-Optimized */}
{isPlansLoading ? (
) : ( filteredPlans.map((plan) => { const statusConfig = getPlanStatusConfig(plan.status); const nextStageConfig = getStageActionConfig(plan.status); const isEditing = editingPlan?.id === plan.id; const isEditable = canEdit(plan.status); // Build actions array with proper priority hierarchy for better UX const actions = []; // Edit mode actions (highest priority when editing) if (isEditing) { actions.push( { label: 'Guardar', icon: Save, variant: 'primary' as const, priority: 'primary' as const, onClick: handleSaveEdit }, { label: 'Cancelar', icon: X, variant: 'outline' as const, priority: 'primary' as const, destructive: true, onClick: handleCancelEdit } ); } else { // NEW FEATURES: Recalculate and Approval actions for draft/pending if (plan.status === 'draft') { const planAgeHours = (new Date().getTime() - new Date(plan.created_at).getTime()) / (1000 * 60 * 60); actions.push({ label: planAgeHours > 24 ? '⚠️ Recalcular' : 'Recalcular', icon: ArrowRight, variant: 'outline' as const, priority: 'primary' as const, onClick: () => handleRecalculatePlan(plan) }); actions.push({ label: 'Aprobar', icon: CheckCircle, variant: 'primary' as const, priority: 'primary' as const, onClick: () => handleOpenApprovalModal(plan, 'approve') }); actions.push({ label: 'Rechazar', icon: X, variant: 'outline' as const, priority: 'secondary' as const, destructive: true, onClick: () => handleOpenApprovalModal(plan, 'reject') }); } else if (plan.status === 'pending_approval') { actions.push({ label: 'Aprobar', icon: CheckCircle, variant: 'primary' as const, priority: 'primary' as const, onClick: () => handleOpenApprovalModal(plan, 'approve') }); actions.push({ label: 'Rechazar', icon: X, variant: 'outline' as const, priority: 'secondary' as const, destructive: true, onClick: () => handleOpenApprovalModal(plan, 'reject') }); } // NEW FEATURE: Auto-create POs for approved plans if (plan.status === 'approved') { actions.push({ label: 'Crear Órdenes de Compra', icon: ShoppingCart, variant: 'primary' as const, priority: 'primary' as const, onClick: () => handleCreatePurchaseOrders(plan) }); actions.push({ label: 'Iniciar Ejecución', icon: Play, variant: 'outline' as const, priority: 'secondary' as const, onClick: () => handleStageTransition(plan.id, plan.status) }); } // Original stage transition for other statuses if (nextStageConfig && !['draft', 'pending_approval', 'approved'].includes(plan.status)) { actions.push({ label: nextStageConfig.label, icon: nextStageConfig.icon, variant: nextStageConfig.variant, priority: 'primary' as const, onClick: () => handleStageTransition(plan.id, plan.status) }); } // Secondary actions: Edit and View if (isEditable) { actions.push({ label: 'Editar', icon: Edit, variant: 'outline' as const, priority: 'secondary' as const, onClick: () => handleEditPlan(plan) }); } actions.push({ label: 'Ver', icon: Eye, variant: 'outline' as const, priority: 'secondary' as const, onClick: () => { setSelectedPlan(plan); setModalMode('view'); setShowForm(true); } }); // Show Critical Requirements button actions.push({ label: 'Req. Críticos', icon: AlertCircle, variant: 'outline' as const, priority: 'secondary' as const, onClick: () => handleShowCriticalRequirements(plan.id) }); // Tertiary action: Cancel (least prominent, destructive) if (!['completed', 'cancelled'].includes(plan.status)) { actions.push({ label: 'Cancelar Plan', icon: X, variant: 'outline' as const, priority: 'tertiary' as const, destructive: true, onClick: () => handleCancelPlan(plan.id) }); } } return (
14 ? '#10b981' : plan.planning_horizon_days > 7 ? '#f59e0b' : '#ef4444' } : undefined} metadata={[ `Período: ${new Date(plan.plan_period_start).toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' })} - ${new Date(plan.plan_period_end).toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' })}`, `Creado: ${new Date(plan.created_at).toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' })}`, ...(plan.special_requirements ? [`Especiales: ${plan.special_requirements.length > 30 ? plan.special_requirements.substring(0, 30) + '...' : plan.special_requirements}`] : []) ]} actions={actions} /> {/* Inline Edit Form for Draft Plans */} {isEditing && (

Editando Plan {plan.plan_number}