|
|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { Plus, Download, ShoppingCart, Truck, DollarSign, Calendar, Clock, CheckCircle, AlertCircle, Package, Eye, Loader, Edit, ArrowRight, X, Save, Building2, Play } from 'lucide-react';
|
|
|
|
|
import { Plus, ShoppingCart, Truck, Euro, Calendar, Clock, CheckCircle, AlertCircle, Package, Eye, Loader, Edit, ArrowRight, X, Save, Building2, Play } from 'lucide-react';
|
|
|
|
|
import { Button, Input, Card, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui';
|
|
|
|
|
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
|
|
|
|
|
import { PageHeader } from '../../../../components/layout';
|
|
|
|
|
@@ -22,6 +22,23 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
const [editFormData, setEditFormData] = useState<any>({});
|
|
|
|
|
const [selectedPlanForRequirements, setSelectedPlanForRequirements] = useState<string | null>(null);
|
|
|
|
|
const [showCriticalRequirements, setShowCriticalRequirements] = useState(false);
|
|
|
|
|
const [showGeneratePlanModal, setShowGeneratePlanModal] = useState(false);
|
|
|
|
|
const [showRequirementDetailsModal, setShowRequirementDetailsModal] = useState(false);
|
|
|
|
|
const [selectedRequirement, setSelectedRequirement] = useState<any>(null);
|
|
|
|
|
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
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Requirement details functionality
|
|
|
|
|
const handleViewRequirementDetails = (requirement: any) => {
|
|
|
|
|
setSelectedRequirement(requirement);
|
|
|
|
|
setShowRequirementDetailsModal(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const { currentTenant } = useTenantStore();
|
|
|
|
|
const tenantId = currentTenant?.id || '';
|
|
|
|
|
@@ -55,13 +72,6 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
return isLowStock || isNearDeadline || hasHighPriority;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Debug logging
|
|
|
|
|
console.log('📊 Plan Requirements Debug:', {
|
|
|
|
|
selectedPlanId: selectedPlanForRequirements,
|
|
|
|
|
allRequirements: allPlanRequirements?.length || 0,
|
|
|
|
|
criticalRequirements: planRequirements?.length || 0,
|
|
|
|
|
sampleRequirement: allPlanRequirements?.[0]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const generatePlanMutation = useGenerateProcurementPlan();
|
|
|
|
|
const updatePlanStatusMutation = useUpdateProcurementPlanStatus();
|
|
|
|
|
@@ -123,7 +133,6 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
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
|
|
|
|
|
console.log('Saving plan edits:', editFormData);
|
|
|
|
|
setEditingPlan(null);
|
|
|
|
|
setEditFormData({});
|
|
|
|
|
// Here you would typically call an update API
|
|
|
|
|
@@ -135,7 +144,6 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleShowCriticalRequirements = (planId: string) => {
|
|
|
|
|
console.log('🔍 Opening critical requirements for plan:', planId);
|
|
|
|
|
setSelectedPlanForRequirements(planId);
|
|
|
|
|
setShowCriticalRequirements(true);
|
|
|
|
|
};
|
|
|
|
|
@@ -191,6 +199,7 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
return matchesSearch;
|
|
|
|
|
}) || [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const stats = {
|
|
|
|
|
totalPlans: dashboardData?.summary?.total_plans || 0,
|
|
|
|
|
activePlans: dashboardData?.summary?.active_plans || 0,
|
|
|
|
|
@@ -229,13 +238,13 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
title: 'Costo Estimado',
|
|
|
|
|
value: formatters.currency(stats.totalEstimatedCost),
|
|
|
|
|
variant: 'info' as const,
|
|
|
|
|
icon: DollarSign,
|
|
|
|
|
icon: Euro,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Costo Aprobado',
|
|
|
|
|
value: formatters.currency(stats.totalApprovedCost),
|
|
|
|
|
variant: 'success' as const,
|
|
|
|
|
icon: DollarSign,
|
|
|
|
|
icon: Euro,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
@@ -245,27 +254,12 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
title="Planificación de Compras"
|
|
|
|
|
description="Administra planes de compras, requerimientos y análisis de procurement"
|
|
|
|
|
actions={[
|
|
|
|
|
{
|
|
|
|
|
id: "export",
|
|
|
|
|
label: "Exportar",
|
|
|
|
|
variant: "outline" as const,
|
|
|
|
|
icon: Download,
|
|
|
|
|
onClick: () => console.log('Export procurement data')
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "generate",
|
|
|
|
|
label: "Generar Plan",
|
|
|
|
|
variant: "primary" as const,
|
|
|
|
|
icon: Plus,
|
|
|
|
|
onClick: () => generatePlanMutation.mutate({
|
|
|
|
|
tenantId,
|
|
|
|
|
request: {
|
|
|
|
|
force_regenerate: false,
|
|
|
|
|
planning_horizon_days: 14,
|
|
|
|
|
include_safety_stock: true,
|
|
|
|
|
safety_stock_percentage: 20
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
onClick: () => setShowGeneratePlanModal(true)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "trigger",
|
|
|
|
|
@@ -275,7 +269,7 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
onClick: () => {
|
|
|
|
|
triggerSchedulerMutation.mutate(tenantId, {
|
|
|
|
|
onSuccess: (data) => {
|
|
|
|
|
console.log('✅ Scheduler ejecutado exitosamente:', data.message);
|
|
|
|
|
// Scheduler executed successfully
|
|
|
|
|
// Show success notification (if you have a notification system)
|
|
|
|
|
// toast.success(data.message);
|
|
|
|
|
},
|
|
|
|
|
@@ -314,10 +308,6 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
className="w-full"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<Button variant="outline" onClick={() => console.log('Export filtered')}>
|
|
|
|
|
<Download className="w-4 h-4 mr-2" />
|
|
|
|
|
Exportar
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
@@ -610,7 +600,7 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
icon: Eye,
|
|
|
|
|
variant: 'primary',
|
|
|
|
|
priority: 'primary',
|
|
|
|
|
onClick: () => console.log('View requirement details', requirement)
|
|
|
|
|
onClick: () => handleViewRequirementDetails(requirement)
|
|
|
|
|
},
|
|
|
|
|
...(requirement.purchase_order_number ? [
|
|
|
|
|
{
|
|
|
|
|
@@ -618,7 +608,9 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
icon: Eye,
|
|
|
|
|
variant: 'outline' as const,
|
|
|
|
|
priority: 'secondary' as const,
|
|
|
|
|
onClick: () => console.log('View PO', requirement.purchase_order_number)
|
|
|
|
|
onClick: () => {
|
|
|
|
|
// TODO: Open purchase order details
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
] : [
|
|
|
|
|
{
|
|
|
|
|
@@ -626,7 +618,9 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
icon: Plus,
|
|
|
|
|
variant: 'outline' as const,
|
|
|
|
|
priority: 'secondary' as const,
|
|
|
|
|
onClick: () => console.log('Create PO for', requirement)
|
|
|
|
|
onClick: () => {
|
|
|
|
|
// TODO: Create purchase order for requirement
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]),
|
|
|
|
|
{
|
|
|
|
|
@@ -634,7 +628,9 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
icon: Building2,
|
|
|
|
|
variant: 'outline' as const,
|
|
|
|
|
priority: 'secondary' as const,
|
|
|
|
|
onClick: () => console.log('Assign supplier')
|
|
|
|
|
onClick: () => {
|
|
|
|
|
// TODO: Open supplier assignment modal
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
@@ -724,7 +720,7 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Información Financiera',
|
|
|
|
|
icon: DollarSign,
|
|
|
|
|
icon: Euro,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
label: 'Costo Estimado Total',
|
|
|
|
|
@@ -780,10 +776,412 @@ const ProcurementPage: React.FC = () => {
|
|
|
|
|
}] : [])
|
|
|
|
|
]}
|
|
|
|
|
onEdit={() => {
|
|
|
|
|
console.log('Editing procurement plan:', selectedPlan.id);
|
|
|
|
|
// TODO: Implement plan editing functionality
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Generate Plan Modal */}
|
|
|
|
|
{showGeneratePlanModal && (
|
|
|
|
|
<div className="fixed top-[var(--header-height)] left-0 right-0 bottom-0 bg-black bg-opacity-50 flex items-center justify-center z-40">
|
|
|
|
|
<div className="bg-[var(--bg-primary)] rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden mx-4">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex items-center justify-between p-6 border-b border-[var(--border-primary)]">
|
|
|
|
|
<div className="flex items-center space-x-3">
|
|
|
|
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100">
|
|
|
|
|
<Plus className="w-5 h-5 text-blue-600" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-lg font-semibold text-[var(--text-primary)]">
|
|
|
|
|
Generar Plan de Compras
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|
|
|
|
Configura los parámetros para generar un nuevo plan
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={() => setShowGeneratePlanModal(false)}
|
|
|
|
|
className="p-2"
|
|
|
|
|
>
|
|
|
|
|
<X className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Content */}
|
|
|
|
|
<div className="p-6 overflow-y-auto max-h-[calc(90vh-200px)]">
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* Plan Date */}
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
|
|
|
|
Fecha del Plan
|
|
|
|
|
</label>
|
|
|
|
|
<Input
|
|
|
|
|
type="date"
|
|
|
|
|
value={generatePlanForm.plan_date}
|
|
|
|
|
onChange={(e) => setGeneratePlanForm(prev => ({ ...prev, plan_date: e.target.value }))}
|
|
|
|
|
className="w-full"
|
|
|
|
|
/>
|
|
|
|
|
<p className="text-xs text-[var(--text-tertiary)] mt-1">
|
|
|
|
|
Fecha para la cual se generará el plan de compras
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Planning Horizon */}
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
|
|
|
|
Horizonte de Planificación (días)
|
|
|
|
|
</label>
|
|
|
|
|
<Input
|
|
|
|
|
type="number"
|
|
|
|
|
min="1"
|
|
|
|
|
max="365"
|
|
|
|
|
value={generatePlanForm.planning_horizon_days}
|
|
|
|
|
onChange={(e) => setGeneratePlanForm(prev => ({ ...prev, planning_horizon_days: parseInt(e.target.value) }))}
|
|
|
|
|
className="w-full"
|
|
|
|
|
/>
|
|
|
|
|
<p className="text-xs text-[var(--text-tertiary)] mt-1">
|
|
|
|
|
Número de días a considerar en la planificación (1-365)
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Safety Stock */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<div className="flex items-center space-x-3">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
id="include_safety_stock"
|
|
|
|
|
checked={generatePlanForm.include_safety_stock}
|
|
|
|
|
onChange={(e) => setGeneratePlanForm(prev => ({ ...prev, include_safety_stock: e.target.checked }))}
|
|
|
|
|
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
<label htmlFor="include_safety_stock" className="text-sm font-medium text-[var(--text-secondary)]">
|
|
|
|
|
Incluir Stock de Seguridad
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{generatePlanForm.include_safety_stock && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
|
|
|
|
Porcentaje de Stock de Seguridad (%)
|
|
|
|
|
</label>
|
|
|
|
|
<Input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
max="100"
|
|
|
|
|
value={generatePlanForm.safety_stock_percentage}
|
|
|
|
|
onChange={(e) => setGeneratePlanForm(prev => ({ ...prev, safety_stock_percentage: parseInt(e.target.value) }))}
|
|
|
|
|
className="w-full"
|
|
|
|
|
/>
|
|
|
|
|
<p className="text-xs text-[var(--text-tertiary)] mt-1">
|
|
|
|
|
Porcentaje adicional para stock de seguridad (0-100%)
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Force Regenerate */}
|
|
|
|
|
<div className="flex items-center space-x-3">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
id="force_regenerate"
|
|
|
|
|
checked={generatePlanForm.force_regenerate}
|
|
|
|
|
onChange={(e) => setGeneratePlanForm(prev => ({ ...prev, force_regenerate: e.target.checked }))}
|
|
|
|
|
className="w-4 h-4 text-red-600 bg-gray-100 border-gray-300 rounded focus:ring-red-500"
|
|
|
|
|
/>
|
|
|
|
|
<label htmlFor="force_regenerate" className="text-sm font-medium text-[var(--text-secondary)]">
|
|
|
|
|
Forzar Regeneración
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-[var(--text-tertiary)] ml-7">
|
|
|
|
|
Si ya existe un plan para esta fecha, regenerarlo (esto eliminará el plan existente)
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Footer */}
|
|
|
|
|
<div className="flex justify-end space-x-3 p-6 border-t border-[var(--border-primary)]">
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={() => setShowGeneratePlanModal(false)}
|
|
|
|
|
disabled={generatePlanMutation.isPending}
|
|
|
|
|
>
|
|
|
|
|
Cancelar
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
generatePlanMutation.mutate({
|
|
|
|
|
tenantId,
|
|
|
|
|
request: {
|
|
|
|
|
plan_date: generatePlanForm.plan_date,
|
|
|
|
|
planning_horizon_days: generatePlanForm.planning_horizon_days,
|
|
|
|
|
include_safety_stock: generatePlanForm.include_safety_stock,
|
|
|
|
|
safety_stock_percentage: generatePlanForm.safety_stock_percentage,
|
|
|
|
|
force_regenerate: generatePlanForm.force_regenerate
|
|
|
|
|
}
|
|
|
|
|
}, {
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
setShowGeneratePlanModal(false);
|
|
|
|
|
// Reset form to defaults
|
|
|
|
|
setGeneratePlanForm({
|
|
|
|
|
plan_date: new Date().toISOString().split('T')[0],
|
|
|
|
|
planning_horizon_days: 14,
|
|
|
|
|
include_safety_stock: true,
|
|
|
|
|
safety_stock_percentage: 20,
|
|
|
|
|
force_regenerate: false
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
disabled={generatePlanMutation.isPending}
|
|
|
|
|
>
|
|
|
|
|
{generatePlanMutation.isPending ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader className="w-4 h-4 mr-2 animate-spin" />
|
|
|
|
|
Generando...
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
|
|
|
Generar Plan
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Requirement Details Modal */}
|
|
|
|
|
{showRequirementDetailsModal && selectedRequirement && (
|
|
|
|
|
<div className="fixed top-[var(--header-height)] left-0 right-0 bottom-0 bg-black bg-opacity-50 flex items-center justify-center z-40">
|
|
|
|
|
<div className="bg-[var(--bg-primary)] rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden mx-4">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex items-center justify-between p-6 border-b border-[var(--border-primary)]">
|
|
|
|
|
<div className="flex items-center space-x-3">
|
|
|
|
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100">
|
|
|
|
|
<Eye className="w-5 h-5 text-blue-600" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-lg font-semibold text-[var(--text-primary)]">
|
|
|
|
|
Detalles del Requerimiento
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|
|
|
|
{selectedRequirement.product_name}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={() => setShowRequirementDetailsModal(false)}
|
|
|
|
|
className="p-2"
|
|
|
|
|
>
|
|
|
|
|
<X className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Content */}
|
|
|
|
|
<div className="p-6 overflow-y-auto max-h-[calc(90vh-200px)]">
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
|
{/* Product Information */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-md font-semibold text-[var(--text-primary)] border-b pb-2">
|
|
|
|
|
Información del Producto
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Nombre</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.product_name}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">SKU</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.product_sku || 'N/A'}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Categoría</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.product_category || 'N/A'}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Tipo</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.product_type}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Quantities */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-md font-semibold text-[var(--text-primary)] border-b pb-2">
|
|
|
|
|
Cantidades
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Cantidad Requerida</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.required_quantity} {selectedRequirement.unit_of_measure}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Stock de Seguridad</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.safety_stock_quantity} {selectedRequirement.unit_of_measure}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Stock Actual</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.current_stock_level} {selectedRequirement.unit_of_measure}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Requerimiento Neto</label>
|
|
|
|
|
<p className="text-[var(--text-primary)] font-semibold">{selectedRequirement.net_requirement} {selectedRequirement.unit_of_measure}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Costs */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-md font-semibold text-[var(--text-primary)] border-b pb-2">
|
|
|
|
|
Costos
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Costo Unitario Estimado</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">€{selectedRequirement.estimated_unit_cost || 'N/A'}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Costo Total Estimado</label>
|
|
|
|
|
<p className="text-[var(--text-primary)] font-semibold">€{selectedRequirement.estimated_total_cost || 'N/A'}</p>
|
|
|
|
|
</div>
|
|
|
|
|
{selectedRequirement.last_purchase_cost && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Último Precio de Compra</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">€{selectedRequirement.last_purchase_cost}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Dates & Timeline */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-md font-semibold text-[var(--text-primary)] border-b pb-2">
|
|
|
|
|
Fechas
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Requerido Para</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.required_by_date}</p>
|
|
|
|
|
</div>
|
|
|
|
|
{selectedRequirement.suggested_order_date && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Fecha Sugerida de Pedido</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.suggested_order_date}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{selectedRequirement.latest_order_date && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Fecha Límite de Pedido</label>
|
|
|
|
|
<p className="text-[var(--text-primary)] text-red-600">{selectedRequirement.latest_order_date}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Status & Priority */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-md font-semibold text-[var(--text-primary)] border-b pb-2">
|
|
|
|
|
Estado y Prioridad
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Estado</label>
|
|
|
|
|
<span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
|
|
|
|
|
selectedRequirement.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
|
|
|
|
|
selectedRequirement.status === 'approved' ? 'bg-green-100 text-green-800' :
|
|
|
|
|
selectedRequirement.status === 'ordered' ? 'bg-blue-100 text-blue-800' :
|
|
|
|
|
'bg-gray-100 text-gray-800'
|
|
|
|
|
}`}>
|
|
|
|
|
{selectedRequirement.status}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Prioridad</label>
|
|
|
|
|
<span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
|
|
|
|
|
selectedRequirement.priority === 'critical' ? 'bg-red-100 text-red-800' :
|
|
|
|
|
selectedRequirement.priority === 'high' ? 'bg-orange-100 text-orange-800' :
|
|
|
|
|
'bg-green-100 text-green-800'
|
|
|
|
|
}`}>
|
|
|
|
|
{selectedRequirement.priority}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Nivel de Riesgo</label>
|
|
|
|
|
<span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
|
|
|
|
|
selectedRequirement.risk_level === 'high' ? 'bg-red-100 text-red-800' :
|
|
|
|
|
selectedRequirement.risk_level === 'medium' ? 'bg-yellow-100 text-yellow-800' :
|
|
|
|
|
'bg-green-100 text-green-800'
|
|
|
|
|
}`}>
|
|
|
|
|
{selectedRequirement.risk_level}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Supplier Information */}
|
|
|
|
|
{selectedRequirement.supplier_name && (
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-md font-semibold text-[var(--text-primary)] border-b pb-2">
|
|
|
|
|
Proveedor
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Nombre</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.supplier_name}</p>
|
|
|
|
|
</div>
|
|
|
|
|
{selectedRequirement.supplier_lead_time_days && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium text-[var(--text-secondary)]">Tiempo de Entrega</label>
|
|
|
|
|
<p className="text-[var(--text-primary)]">{selectedRequirement.supplier_lead_time_days} días</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Special Requirements */}
|
|
|
|
|
{selectedRequirement.special_requirements && (
|
|
|
|
|
<div className="mt-6 space-y-2">
|
|
|
|
|
<h3 className="text-md font-semibold text-[var(--text-primary)] border-b pb-2">
|
|
|
|
|
Requerimientos Especiales
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-[var(--text-primary)] bg-gray-50 p-3 rounded-lg">
|
|
|
|
|
{selectedRequirement.special_requirements}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Footer */}
|
|
|
|
|
<div className="flex justify-end space-x-3 p-6 border-t border-[var(--border-primary)]">
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={() => setShowRequirementDetailsModal(false)}
|
|
|
|
|
>
|
|
|
|
|
Cerrar
|
|
|
|
|
</Button>
|
|
|
|
|
{selectedRequirement.status === 'pending' && (
|
|
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
// TODO: Implement approval functionality
|
|
|
|
|
setShowRequirementDetailsModal(false);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<CheckCircle className="w-4 h-4 mr-2" />
|
|
|
|
|
Aprobar
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|