Add supplier and imporve inventory frontend
This commit is contained in:
@@ -207,6 +207,7 @@ const InventoryPage: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Sort by priority: expired → out of stock → low stock → normal → overstock
|
||||
// Within each priority level, sort by most critical items first
|
||||
return items.sort((a, b) => {
|
||||
@@ -263,22 +264,8 @@ const InventoryPage: React.FC = () => {
|
||||
|
||||
// Helper function to get category display name
|
||||
const getCategoryDisplayName = (category?: string): string => {
|
||||
const categoryMappings: Record<string, string> = {
|
||||
'flour': 'Harina',
|
||||
'dairy': 'Lácteos',
|
||||
'eggs': 'Huevos',
|
||||
'sugar': 'Azúcar',
|
||||
'yeast': 'Levadura',
|
||||
'fats': 'Grasas',
|
||||
'spices': 'Especias',
|
||||
'croissants': 'Croissants',
|
||||
'pastries': 'Pastelería',
|
||||
'beverages': 'Bebidas',
|
||||
'bread': 'Pan',
|
||||
'other': 'Otros'
|
||||
};
|
||||
|
||||
return categoryMappings[category?.toLowerCase() || ''] || category || 'Sin categoría';
|
||||
if (!category) return 'Sin categoría';
|
||||
return category;
|
||||
};
|
||||
|
||||
// Focused action handlers
|
||||
|
||||
@@ -1,93 +1,54 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Plus, Download, Building2, Phone, Mail, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, DollarSign } from 'lucide-react';
|
||||
import { Plus, Building2, Phone, Mail, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, DollarSign, Loader } from 'lucide-react';
|
||||
import { Button, Input, Card, Badge, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui';
|
||||
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { SupplierStatus, SupplierType, PaymentTerms } from '../../../../api/types/suppliers';
|
||||
import { useSuppliers, useSupplierStatistics } from '../../../../api/hooks/suppliers';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { useAuthUser } from '../../../../stores/auth.store';
|
||||
import { useSupplierEnums } from '../../../../utils/enumHelpers';
|
||||
|
||||
const SuppliersPage: React.FC = () => {
|
||||
const [activeTab] = useState('all');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [modalMode, setModalMode] = useState<'view' | 'edit'>('view');
|
||||
const [selectedSupplier, setSelectedSupplier] = useState<typeof mockSuppliers[0] | null>(null);
|
||||
const [selectedSupplier, setSelectedSupplier] = useState<any>(null);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
const mockSuppliers = [
|
||||
{
|
||||
id: 'SUP-2024-001',
|
||||
supplier_code: 'HAR001',
|
||||
name: 'Harinas del Norte S.L.',
|
||||
supplier_type: SupplierType.INGREDIENTS,
|
||||
status: SupplierStatus.ACTIVE,
|
||||
contact_person: 'María González',
|
||||
email: 'maria@harinasdelnorte.es',
|
||||
phone: '+34 987 654 321',
|
||||
city: 'León',
|
||||
country: 'España',
|
||||
payment_terms: PaymentTerms.NET_30,
|
||||
credit_limit: 5000,
|
||||
currency: 'EUR',
|
||||
standard_lead_time: 5,
|
||||
minimum_order_amount: 200,
|
||||
total_orders: 45,
|
||||
total_spend: 12750.50,
|
||||
last_order_date: '2024-01-25T14:30:00Z',
|
||||
performance_score: 92,
|
||||
notes: 'Proveedor principal de harinas. Excelente calidad y puntualidad.'
|
||||
},
|
||||
{
|
||||
id: 'SUP-2024-002',
|
||||
supplier_code: 'EMB002',
|
||||
name: 'Embalajes Biodegradables SA',
|
||||
supplier_type: SupplierType.PACKAGING,
|
||||
status: SupplierStatus.ACTIVE,
|
||||
contact_person: 'Carlos Ruiz',
|
||||
email: 'carlos@embalajes-bio.com',
|
||||
phone: '+34 600 123 456',
|
||||
city: 'Valencia',
|
||||
country: 'España',
|
||||
payment_terms: PaymentTerms.NET_15,
|
||||
credit_limit: 2500,
|
||||
currency: 'EUR',
|
||||
standard_lead_time: 3,
|
||||
minimum_order_amount: 150,
|
||||
total_orders: 28,
|
||||
total_spend: 4280.75,
|
||||
last_order_date: '2024-01-24T10:15:00Z',
|
||||
performance_score: 88,
|
||||
notes: 'Especialista en packaging sostenible.'
|
||||
},
|
||||
{
|
||||
id: 'SUP-2024-003',
|
||||
supplier_code: 'MAN003',
|
||||
name: 'Maquinaria Industrial López',
|
||||
supplier_type: SupplierType.EQUIPMENT,
|
||||
status: SupplierStatus.PENDING_APPROVAL,
|
||||
contact_person: 'Ana López',
|
||||
email: 'ana@maquinaria-lopez.es',
|
||||
phone: '+34 655 987 654',
|
||||
city: 'Madrid',
|
||||
country: 'España',
|
||||
payment_terms: PaymentTerms.NET_45,
|
||||
credit_limit: 15000,
|
||||
currency: 'EUR',
|
||||
standard_lead_time: 14,
|
||||
minimum_order_amount: 500,
|
||||
total_orders: 0,
|
||||
total_spend: 0,
|
||||
last_order_date: null,
|
||||
performance_score: null,
|
||||
notes: 'Nuevo proveedor de equipamiento industrial. Pendiente de aprobación.'
|
||||
},
|
||||
];
|
||||
// Get tenant ID from tenant store (preferred) or auth user (fallback)
|
||||
const currentTenant = useCurrentTenant();
|
||||
const user = useAuthUser();
|
||||
const tenantId = currentTenant?.id || user?.tenant_id || '';
|
||||
|
||||
// API hooks
|
||||
const {
|
||||
data: suppliersData,
|
||||
isLoading: suppliersLoading,
|
||||
error: suppliersError
|
||||
} = useSuppliers(tenantId, {
|
||||
search_term: searchTerm || undefined,
|
||||
status: activeTab !== 'all' ? activeTab as any : undefined,
|
||||
limit: 100
|
||||
});
|
||||
|
||||
const {
|
||||
data: statisticsData,
|
||||
isLoading: statisticsLoading,
|
||||
error: statisticsError
|
||||
} = useSupplierStatistics(tenantId);
|
||||
|
||||
const suppliers = suppliersData || [];
|
||||
const supplierEnums = useSupplierEnums();
|
||||
|
||||
const getSupplierStatusConfig = (status: SupplierStatus) => {
|
||||
const statusConfig = {
|
||||
[SupplierStatus.ACTIVE]: { text: 'Activo', icon: CheckCircle },
|
||||
[SupplierStatus.INACTIVE]: { text: 'Inactivo', icon: Timer },
|
||||
[SupplierStatus.PENDING_APPROVAL]: { text: 'Pendiente Aprobación', icon: AlertCircle },
|
||||
[SupplierStatus.SUSPENDED]: { text: 'Suspendido', icon: AlertCircle },
|
||||
[SupplierStatus.BLACKLISTED]: { text: 'Lista Negra', icon: AlertCircle },
|
||||
[SupplierStatus.ACTIVE]: { text: supplierEnums.getSupplierStatusLabel(status), icon: CheckCircle },
|
||||
[SupplierStatus.INACTIVE]: { text: supplierEnums.getSupplierStatusLabel(status), icon: Timer },
|
||||
[SupplierStatus.PENDING_APPROVAL]: { text: supplierEnums.getSupplierStatusLabel(status), icon: AlertCircle },
|
||||
[SupplierStatus.SUSPENDED]: { text: supplierEnums.getSupplierStatusLabel(status), icon: AlertCircle },
|
||||
[SupplierStatus.BLACKLISTED]: { text: supplierEnums.getSupplierStatusLabel(status), icon: AlertCircle },
|
||||
};
|
||||
|
||||
const config = statusConfig[status];
|
||||
@@ -104,110 +65,122 @@ const SuppliersPage: React.FC = () => {
|
||||
};
|
||||
|
||||
const getSupplierTypeText = (type: SupplierType): string => {
|
||||
const typeMap = {
|
||||
[SupplierType.INGREDIENTS]: 'Ingredientes',
|
||||
[SupplierType.PACKAGING]: 'Embalajes',
|
||||
[SupplierType.EQUIPMENT]: 'Equipamiento',
|
||||
[SupplierType.SERVICES]: 'Servicios',
|
||||
[SupplierType.UTILITIES]: 'Servicios Públicos',
|
||||
[SupplierType.MULTI]: 'Múltiple',
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
return supplierEnums.getSupplierTypeLabel(type);
|
||||
};
|
||||
|
||||
const getPaymentTermsText = (terms: PaymentTerms): string => {
|
||||
const termsMap = {
|
||||
[PaymentTerms.CASH_ON_DELIVERY]: 'Pago Contraentrega',
|
||||
[PaymentTerms.NET_15]: 'Neto 15 días',
|
||||
[PaymentTerms.NET_30]: 'Neto 30 días',
|
||||
[PaymentTerms.NET_45]: 'Neto 45 días',
|
||||
[PaymentTerms.NET_60]: 'Neto 60 días',
|
||||
[PaymentTerms.PREPAID]: 'Prepago',
|
||||
};
|
||||
return termsMap[terms] || terms;
|
||||
return supplierEnums.getPaymentTermsLabel(terms);
|
||||
};
|
||||
|
||||
const filteredSuppliers = mockSuppliers.filter(supplier => {
|
||||
const matchesSearch = supplier.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
supplier.supplier_code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
supplier.email?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
supplier.contact_person?.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
|
||||
const matchesTab = activeTab === 'all' || supplier.status === activeTab;
|
||||
|
||||
return matchesSearch && matchesTab;
|
||||
});
|
||||
// Filtering is now handled by the API query parameters
|
||||
const filteredSuppliers = suppliers;
|
||||
|
||||
const mockSupplierStats = {
|
||||
total: mockSuppliers.length,
|
||||
active: mockSuppliers.filter(s => s.status === SupplierStatus.ACTIVE).length,
|
||||
pendingApproval: mockSuppliers.filter(s => s.status === SupplierStatus.PENDING_APPROVAL).length,
|
||||
suspended: mockSuppliers.filter(s => s.status === SupplierStatus.SUSPENDED).length,
|
||||
totalSpend: mockSuppliers.reduce((sum, supplier) => sum + supplier.total_spend, 0),
|
||||
averageScore: mockSuppliers
|
||||
.filter(s => s.performance_score !== null)
|
||||
.reduce((sum, supplier, _, arr) => sum + (supplier.performance_score || 0) / arr.length, 0),
|
||||
totalOrders: mockSuppliers.reduce((sum, supplier) => sum + supplier.total_orders, 0),
|
||||
const supplierStats = statisticsData || {
|
||||
total_suppliers: 0,
|
||||
active_suppliers: 0,
|
||||
pending_suppliers: 0,
|
||||
avg_quality_rating: 0,
|
||||
avg_delivery_rating: 0,
|
||||
total_spend: 0
|
||||
};
|
||||
|
||||
const stats = [
|
||||
{
|
||||
title: 'Total Proveedores',
|
||||
value: mockSupplierStats.total,
|
||||
value: supplierStats.total_suppliers,
|
||||
variant: 'default' as const,
|
||||
icon: Building2,
|
||||
},
|
||||
{
|
||||
title: 'Activos',
|
||||
value: mockSupplierStats.active,
|
||||
value: supplierStats.active_suppliers,
|
||||
variant: 'success' as const,
|
||||
icon: CheckCircle,
|
||||
},
|
||||
{
|
||||
title: 'Pendientes',
|
||||
value: mockSupplierStats.pendingApproval,
|
||||
value: supplierStats.pending_suppliers,
|
||||
variant: 'warning' as const,
|
||||
icon: AlertCircle,
|
||||
},
|
||||
{
|
||||
title: 'Gasto Total',
|
||||
value: formatters.currency(mockSupplierStats.totalSpend),
|
||||
value: formatters.currency(supplierStats.total_spend),
|
||||
variant: 'info' as const,
|
||||
icon: DollarSign,
|
||||
},
|
||||
{
|
||||
title: 'Total Pedidos',
|
||||
value: mockSupplierStats.totalOrders,
|
||||
variant: 'default' as const,
|
||||
icon: Building2,
|
||||
},
|
||||
{
|
||||
title: 'Puntuación Media',
|
||||
value: mockSupplierStats.averageScore.toFixed(1),
|
||||
title: 'Calidad Media',
|
||||
value: supplierStats.avg_quality_rating?.toFixed(1) || '0.0',
|
||||
variant: 'success' as const,
|
||||
icon: CheckCircle,
|
||||
},
|
||||
{
|
||||
title: 'Entrega Media',
|
||||
value: supplierStats.avg_delivery_rating?.toFixed(1) || '0.0',
|
||||
variant: 'info' as const,
|
||||
icon: Building2,
|
||||
},
|
||||
];
|
||||
|
||||
// Loading state
|
||||
if (suppliersLoading || statisticsLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<Loader className="h-8 w-8 animate-spin mx-auto mb-4 text-[var(--primary)]" />
|
||||
<p className="text-[var(--text-secondary)]">Cargando proveedores...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (suppliersError || statisticsError) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<AlertCircle className="h-8 w-8 mx-auto mb-4 text-red-500" />
|
||||
<p className="text-red-600 mb-2">Error al cargar los proveedores</p>
|
||||
<p className="text-[var(--text-secondary)] text-sm">
|
||||
{(suppliersError as any)?.message || (statisticsError as any)?.message || 'Error desconocido'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Gestión de Proveedores"
|
||||
description="Administra y supervisa todos los proveedores de la panadería"
|
||||
actions={[
|
||||
{
|
||||
id: "export",
|
||||
label: "Exportar",
|
||||
variant: "outline" as const,
|
||||
icon: Download,
|
||||
onClick: () => console.log('Export suppliers')
|
||||
},
|
||||
{
|
||||
id: "new",
|
||||
label: "Nuevo Proveedor",
|
||||
label: "Nuevo Proveedor",
|
||||
variant: "primary" as const,
|
||||
icon: Plus,
|
||||
onClick: () => setShowForm(true)
|
||||
onClick: () => {
|
||||
setSelectedSupplier({
|
||||
name: '',
|
||||
contact_person: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
city: '',
|
||||
country: '',
|
||||
supplier_code: '',
|
||||
supplier_type: SupplierType.INGREDIENTS,
|
||||
payment_terms: PaymentTerms.NET_30,
|
||||
standard_lead_time: 3,
|
||||
minimum_order_amount: 0,
|
||||
credit_limit: 0,
|
||||
currency: 'EUR'
|
||||
});
|
||||
setIsCreating(true);
|
||||
setModalMode('edit');
|
||||
setShowForm(true);
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -229,10 +202,6 @@ const SuppliersPage: 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>
|
||||
|
||||
@@ -240,10 +209,7 @@ const SuppliersPage: React.FC = () => {
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{filteredSuppliers.map((supplier) => {
|
||||
const statusConfig = getSupplierStatusConfig(supplier.status);
|
||||
const performanceNote = supplier.performance_score
|
||||
? `Puntuación: ${supplier.performance_score}/100`
|
||||
: 'Sin evaluación';
|
||||
|
||||
|
||||
return (
|
||||
<StatusCard
|
||||
key={supplier.id}
|
||||
@@ -251,35 +217,40 @@ const SuppliersPage: React.FC = () => {
|
||||
statusIndicator={statusConfig}
|
||||
title={supplier.name}
|
||||
subtitle={supplier.supplier_code}
|
||||
primaryValue={formatters.currency(supplier.total_spend)}
|
||||
primaryValueLabel={`${supplier.total_orders} pedidos`}
|
||||
primaryValue={supplier.city || 'Sin ubicación'}
|
||||
primaryValueLabel={getSupplierTypeText(supplier.supplier_type)}
|
||||
secondaryInfo={{
|
||||
label: 'Tipo',
|
||||
value: getSupplierTypeText(supplier.supplier_type)
|
||||
label: 'Condiciones',
|
||||
value: getPaymentTermsText(supplier.payment_terms)
|
||||
}}
|
||||
metadata={[
|
||||
supplier.contact_person || 'Sin contacto',
|
||||
supplier.email || 'Sin email',
|
||||
supplier.phone || 'Sin teléfono',
|
||||
performanceNote
|
||||
`Creado: ${new Date(supplier.created_at).toLocaleDateString('es-ES')}`
|
||||
]}
|
||||
actions={[
|
||||
// Primary action - View supplier details
|
||||
{
|
||||
label: 'Ver',
|
||||
label: 'Ver Detalles',
|
||||
icon: Eye,
|
||||
variant: 'outline',
|
||||
variant: 'primary',
|
||||
priority: 'primary',
|
||||
onClick: () => {
|
||||
setSelectedSupplier(supplier);
|
||||
setIsCreating(false);
|
||||
setModalMode('view');
|
||||
setShowForm(true);
|
||||
}
|
||||
},
|
||||
// Secondary action - Edit supplier
|
||||
{
|
||||
label: 'Editar',
|
||||
icon: Edit,
|
||||
variant: 'outline',
|
||||
priority: 'secondary',
|
||||
onClick: () => {
|
||||
setSelectedSupplier(supplier);
|
||||
setIsCreating(false);
|
||||
setModalMode('edit');
|
||||
setShowForm(true);
|
||||
}
|
||||
@@ -308,126 +279,200 @@ const SuppliersPage: React.FC = () => {
|
||||
)}
|
||||
|
||||
{/* Supplier Details Modal */}
|
||||
{showForm && selectedSupplier && (
|
||||
<StatusModal
|
||||
{showForm && selectedSupplier && (() => {
|
||||
const sections = [
|
||||
{
|
||||
title: 'Información de Contacto',
|
||||
icon: Users,
|
||||
fields: [
|
||||
{
|
||||
label: 'Nombre',
|
||||
value: selectedSupplier.name || '',
|
||||
type: 'text',
|
||||
highlight: true,
|
||||
editable: true,
|
||||
required: true,
|
||||
placeholder: 'Nombre del proveedor'
|
||||
},
|
||||
{
|
||||
label: 'Persona de Contacto',
|
||||
value: selectedSupplier.contact_person || '',
|
||||
type: 'text',
|
||||
editable: true,
|
||||
placeholder: 'Nombre del contacto'
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
value: selectedSupplier.email || '',
|
||||
type: 'email',
|
||||
editable: true,
|
||||
placeholder: 'email@ejemplo.com'
|
||||
},
|
||||
{
|
||||
label: 'Teléfono',
|
||||
value: selectedSupplier.phone || '',
|
||||
type: 'tel',
|
||||
editable: true,
|
||||
placeholder: '+34 123 456 789'
|
||||
},
|
||||
{
|
||||
label: 'Ciudad',
|
||||
value: selectedSupplier.city || '',
|
||||
type: 'text',
|
||||
editable: true,
|
||||
placeholder: 'Ciudad'
|
||||
},
|
||||
{
|
||||
label: 'País',
|
||||
value: selectedSupplier.country || '',
|
||||
type: 'text',
|
||||
editable: true,
|
||||
placeholder: 'País'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Información Comercial',
|
||||
icon: Building2,
|
||||
fields: [
|
||||
{
|
||||
label: 'Código de Proveedor',
|
||||
value: selectedSupplier.supplier_code || '',
|
||||
type: 'text',
|
||||
highlight: true,
|
||||
editable: true,
|
||||
placeholder: 'Código único'
|
||||
},
|
||||
{
|
||||
label: 'Tipo de Proveedor',
|
||||
value: selectedSupplier.supplier_type || SupplierType.INGREDIENTS,
|
||||
type: 'select',
|
||||
editable: true,
|
||||
options: supplierEnums.getSupplierTypeOptions()
|
||||
},
|
||||
{
|
||||
label: 'Condiciones de Pago',
|
||||
value: selectedSupplier.payment_terms || PaymentTerms.NET_30,
|
||||
type: 'select',
|
||||
editable: true,
|
||||
options: supplierEnums.getPaymentTermsOptions()
|
||||
},
|
||||
{
|
||||
label: 'Tiempo de Entrega (días)',
|
||||
value: selectedSupplier.standard_lead_time || 3,
|
||||
type: 'number',
|
||||
editable: true,
|
||||
placeholder: '3'
|
||||
},
|
||||
{
|
||||
label: 'Pedido Mínimo',
|
||||
value: selectedSupplier.minimum_order_amount || 0,
|
||||
type: 'currency',
|
||||
editable: true,
|
||||
placeholder: '0.00'
|
||||
},
|
||||
{
|
||||
label: 'Límite de Crédito',
|
||||
value: selectedSupplier.credit_limit || 0,
|
||||
type: 'currency',
|
||||
editable: true,
|
||||
placeholder: '0.00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Rendimiento y Estadísticas',
|
||||
icon: DollarSign,
|
||||
fields: [
|
||||
{
|
||||
label: 'Moneda',
|
||||
value: selectedSupplier.currency || 'EUR',
|
||||
type: 'text',
|
||||
editable: true,
|
||||
placeholder: 'EUR'
|
||||
},
|
||||
{
|
||||
label: 'Fecha de Creación',
|
||||
value: selectedSupplier.created_at,
|
||||
type: 'datetime',
|
||||
highlight: true
|
||||
},
|
||||
{
|
||||
label: 'Última Actualización',
|
||||
value: selectedSupplier.updated_at,
|
||||
type: 'datetime'
|
||||
}
|
||||
]
|
||||
},
|
||||
...(selectedSupplier.notes ? [{
|
||||
title: 'Notas',
|
||||
fields: [
|
||||
{
|
||||
label: 'Observaciones',
|
||||
value: selectedSupplier.notes,
|
||||
type: 'list',
|
||||
span: 2 as const,
|
||||
editable: true,
|
||||
placeholder: 'Notas sobre el proveedor'
|
||||
}
|
||||
]
|
||||
}] : [])
|
||||
];
|
||||
|
||||
return (
|
||||
<StatusModal
|
||||
isOpen={showForm}
|
||||
onClose={() => {
|
||||
setShowForm(false);
|
||||
setSelectedSupplier(null);
|
||||
setModalMode('view');
|
||||
setIsCreating(false);
|
||||
}}
|
||||
mode={modalMode}
|
||||
onModeChange={setModalMode}
|
||||
title={selectedSupplier.name}
|
||||
subtitle={`Proveedor ${selectedSupplier.supplier_code}`}
|
||||
statusIndicator={getSupplierStatusConfig(selectedSupplier.status)}
|
||||
title={isCreating ? 'Nuevo Proveedor' : selectedSupplier.name || 'Proveedor'}
|
||||
subtitle={isCreating ? 'Crear nuevo proveedor' : `Proveedor ${selectedSupplier.supplier_code || ''}`}
|
||||
statusIndicator={isCreating ? undefined : getSupplierStatusConfig(selectedSupplier.status)}
|
||||
size="lg"
|
||||
sections={[
|
||||
{
|
||||
title: 'Información de Contacto',
|
||||
icon: Users,
|
||||
fields: [
|
||||
{
|
||||
label: 'Nombre',
|
||||
value: selectedSupplier.name,
|
||||
highlight: true
|
||||
},
|
||||
{
|
||||
label: 'Persona de Contacto',
|
||||
value: selectedSupplier.contact_person || 'No especificado'
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
value: selectedSupplier.email || 'No especificado'
|
||||
},
|
||||
{
|
||||
label: 'Teléfono',
|
||||
value: selectedSupplier.phone || 'No especificado'
|
||||
},
|
||||
{
|
||||
label: 'Ciudad',
|
||||
value: selectedSupplier.city || 'No especificado'
|
||||
},
|
||||
{
|
||||
label: 'País',
|
||||
value: selectedSupplier.country || 'No especificado'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Información Comercial',
|
||||
icon: Building2,
|
||||
fields: [
|
||||
{
|
||||
label: 'Código de Proveedor',
|
||||
value: selectedSupplier.supplier_code,
|
||||
highlight: true
|
||||
},
|
||||
{
|
||||
label: 'Tipo de Proveedor',
|
||||
value: getSupplierTypeText(selectedSupplier.supplier_type)
|
||||
},
|
||||
{
|
||||
label: 'Condiciones de Pago',
|
||||
value: getPaymentTermsText(selectedSupplier.payment_terms)
|
||||
},
|
||||
{
|
||||
label: 'Tiempo de Entrega',
|
||||
value: `${selectedSupplier.standard_lead_time} días`
|
||||
},
|
||||
{
|
||||
label: 'Pedido Mínimo',
|
||||
value: selectedSupplier.minimum_order_amount,
|
||||
type: 'currency'
|
||||
},
|
||||
{
|
||||
label: 'Límite de Crédito',
|
||||
value: selectedSupplier.credit_limit,
|
||||
type: 'currency'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Rendimiento y Estadísticas',
|
||||
icon: DollarSign,
|
||||
fields: [
|
||||
{
|
||||
label: 'Total de Pedidos',
|
||||
value: selectedSupplier.total_orders.toString()
|
||||
},
|
||||
{
|
||||
label: 'Gasto Total',
|
||||
value: selectedSupplier.total_spend,
|
||||
type: 'currency',
|
||||
highlight: true
|
||||
},
|
||||
{
|
||||
label: 'Puntuación de Rendimiento',
|
||||
value: selectedSupplier.performance_score ? `${selectedSupplier.performance_score}/100` : 'No evaluado'
|
||||
},
|
||||
{
|
||||
label: 'Último Pedido',
|
||||
value: selectedSupplier.last_order_date || 'Nunca',
|
||||
type: selectedSupplier.last_order_date ? 'datetime' : undefined
|
||||
}
|
||||
]
|
||||
},
|
||||
...(selectedSupplier.notes ? [{
|
||||
title: 'Notas',
|
||||
fields: [
|
||||
{
|
||||
label: 'Observaciones',
|
||||
value: selectedSupplier.notes,
|
||||
span: 2 as const
|
||||
}
|
||||
]
|
||||
}] : [])
|
||||
]}
|
||||
onEdit={() => {
|
||||
console.log('Editing supplier:', selectedSupplier.id);
|
||||
sections={sections}
|
||||
showDefaultActions={true}
|
||||
onSave={async () => {
|
||||
// TODO: Implement save functionality
|
||||
console.log('Saving supplier:', selectedSupplier);
|
||||
}}
|
||||
onFieldChange={(sectionIndex, fieldIndex, value) => {
|
||||
// Update the selectedSupplier state when fields change
|
||||
const newSupplier = { ...selectedSupplier };
|
||||
const section = sections[sectionIndex];
|
||||
const field = section.fields[fieldIndex];
|
||||
|
||||
// Map field labels to supplier properties
|
||||
const fieldMapping: { [key: string]: string } = {
|
||||
'Nombre': 'name',
|
||||
'Persona de Contacto': 'contact_person',
|
||||
'Email': 'email',
|
||||
'Teléfono': 'phone',
|
||||
'Ciudad': 'city',
|
||||
'País': 'country',
|
||||
'Código de Proveedor': 'supplier_code',
|
||||
'Tipo de Proveedor': 'supplier_type',
|
||||
'Condiciones de Pago': 'payment_terms',
|
||||
'Tiempo de Entrega (días)': 'standard_lead_time',
|
||||
'Pedido Mínimo': 'minimum_order_amount',
|
||||
'Límite de Crédito': 'credit_limit',
|
||||
'Moneda': 'currency',
|
||||
'Observaciones': 'notes'
|
||||
};
|
||||
|
||||
const propertyName = fieldMapping[field.label];
|
||||
if (propertyName) {
|
||||
newSupplier[propertyName] = value;
|
||||
setSelectedSupplier(newSupplier);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user