2025-09-09 21:39:12 +02:00
|
|
|
import React, { useState } from 'react';
|
2025-09-19 21:39:04 +02:00
|
|
|
import { Plus, Building2, Phone, Mail, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, Euro, Loader } from 'lucide-react';
|
2025-09-26 12:12:17 +02:00
|
|
|
import { Button, Badge, StatsGrid, StatusCard, getStatusColor, EditViewModal, SearchAndFilter, type FilterConfig } from '../../../../components/ui';
|
2025-09-09 21:39:12 +02:00
|
|
|
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
|
|
|
|
|
import { PageHeader } from '../../../../components/layout';
|
|
|
|
|
import { SupplierStatus, SupplierType, PaymentTerms } from '../../../../api/types/suppliers';
|
2025-09-18 23:32:53 +02:00
|
|
|
import { useSuppliers, useSupplierStatistics } from '../../../../api/hooks/suppliers';
|
|
|
|
|
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
|
|
|
|
import { useAuthUser } from '../../../../stores/auth.store';
|
2025-09-26 07:46:25 +02:00
|
|
|
import { useTranslation } from 'react-i18next';
|
2025-09-09 21:39:12 +02:00
|
|
|
|
|
|
|
|
const SuppliersPage: React.FC = () => {
|
|
|
|
|
const [activeTab] = useState('all');
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
2025-09-26 12:12:17 +02:00
|
|
|
const [statusFilter, setStatusFilter] = useState('');
|
|
|
|
|
const [typeFilter, setTypeFilter] = useState('');
|
2025-09-09 21:39:12 +02:00
|
|
|
const [showForm, setShowForm] = useState(false);
|
|
|
|
|
const [modalMode, setModalMode] = useState<'view' | 'edit'>('view');
|
2025-09-18 23:32:53 +02:00
|
|
|
const [selectedSupplier, setSelectedSupplier] = useState<any>(null);
|
|
|
|
|
const [isCreating, setIsCreating] = useState(false);
|
2025-09-09 21:39:12 +02:00
|
|
|
|
2025-09-18 23:32:53 +02:00
|
|
|
// 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 || [];
|
2025-09-26 07:46:25 +02:00
|
|
|
const { t } = useTranslation(['suppliers', 'common']);
|
2025-09-09 21:39:12 +02:00
|
|
|
|
|
|
|
|
const getSupplierStatusConfig = (status: SupplierStatus) => {
|
|
|
|
|
const statusConfig = {
|
2025-09-26 07:46:25 +02:00
|
|
|
[SupplierStatus.ACTIVE]: { text: t(`suppliers:status.${status.toLowerCase()}`), icon: CheckCircle },
|
|
|
|
|
[SupplierStatus.INACTIVE]: { text: t(`suppliers:status.${status.toLowerCase()}`), icon: Timer },
|
|
|
|
|
[SupplierStatus.PENDING_APPROVAL]: { text: t(`suppliers:status.${status.toLowerCase()}`), icon: AlertCircle },
|
|
|
|
|
[SupplierStatus.SUSPENDED]: { text: t(`suppliers:status.${status.toLowerCase()}`), icon: AlertCircle },
|
|
|
|
|
[SupplierStatus.BLACKLISTED]: { text: t(`suppliers:status.${status.toLowerCase()}`), icon: AlertCircle },
|
2025-09-09 21:39:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const config = statusConfig[status];
|
|
|
|
|
const Icon = config?.icon;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
color: getStatusColor(status === SupplierStatus.ACTIVE ? 'completed' :
|
|
|
|
|
status === SupplierStatus.PENDING_APPROVAL ? 'pending' : 'cancelled'),
|
|
|
|
|
text: config?.text || status,
|
|
|
|
|
icon: Icon,
|
|
|
|
|
isCritical: status === SupplierStatus.BLACKLISTED,
|
|
|
|
|
isHighlight: status === SupplierStatus.PENDING_APPROVAL
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getSupplierTypeText = (type: SupplierType): string => {
|
2025-09-26 07:46:25 +02:00
|
|
|
return t(`suppliers:types.${type.toLowerCase()}`);
|
2025-09-09 21:39:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getPaymentTermsText = (terms: PaymentTerms): string => {
|
2025-09-26 07:46:25 +02:00
|
|
|
return t(`suppliers:payment_terms.${terms.toLowerCase()}`);
|
2025-09-09 21:39:12 +02:00
|
|
|
};
|
|
|
|
|
|
2025-09-26 12:12:17 +02:00
|
|
|
// Apply additional client-side filtering
|
|
|
|
|
const filteredSuppliers = suppliers.filter(supplier => {
|
|
|
|
|
const matchesStatus = !statusFilter || supplier.status === statusFilter;
|
|
|
|
|
const matchesType = !typeFilter || supplier.supplier_type === typeFilter;
|
|
|
|
|
return matchesStatus && matchesType;
|
|
|
|
|
});
|
2025-09-09 21:39:12 +02:00
|
|
|
|
2025-09-18 23:32:53 +02:00
|
|
|
const supplierStats = statisticsData || {
|
|
|
|
|
total_suppliers: 0,
|
|
|
|
|
active_suppliers: 0,
|
|
|
|
|
pending_suppliers: 0,
|
|
|
|
|
avg_quality_rating: 0,
|
|
|
|
|
avg_delivery_rating: 0,
|
|
|
|
|
total_spend: 0
|
2025-09-09 21:39:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const stats = [
|
|
|
|
|
{
|
|
|
|
|
title: 'Total Proveedores',
|
2025-09-18 23:32:53 +02:00
|
|
|
value: supplierStats.total_suppliers,
|
2025-09-09 21:39:12 +02:00
|
|
|
variant: 'default' as const,
|
|
|
|
|
icon: Building2,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Activos',
|
2025-09-18 23:32:53 +02:00
|
|
|
value: supplierStats.active_suppliers,
|
2025-09-09 21:39:12 +02:00
|
|
|
variant: 'success' as const,
|
|
|
|
|
icon: CheckCircle,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Pendientes',
|
2025-09-18 23:32:53 +02:00
|
|
|
value: supplierStats.pending_suppliers,
|
2025-09-09 21:39:12 +02:00
|
|
|
variant: 'warning' as const,
|
|
|
|
|
icon: AlertCircle,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Gasto Total',
|
2025-09-18 23:32:53 +02:00
|
|
|
value: formatters.currency(supplierStats.total_spend),
|
2025-09-09 21:39:12 +02:00
|
|
|
variant: 'info' as const,
|
2025-09-19 21:39:04 +02:00
|
|
|
icon: Euro,
|
2025-09-09 21:39:12 +02:00
|
|
|
},
|
|
|
|
|
{
|
2025-09-18 23:32:53 +02:00
|
|
|
title: 'Calidad Media',
|
|
|
|
|
value: supplierStats.avg_quality_rating?.toFixed(1) || '0.0',
|
2025-09-09 21:39:12 +02:00
|
|
|
variant: 'success' as const,
|
|
|
|
|
icon: CheckCircle,
|
|
|
|
|
},
|
2025-09-18 23:32:53 +02:00
|
|
|
{
|
|
|
|
|
title: 'Entrega Media',
|
|
|
|
|
value: supplierStats.avg_delivery_rating?.toFixed(1) || '0.0',
|
|
|
|
|
variant: 'info' as const,
|
|
|
|
|
icon: Building2,
|
|
|
|
|
},
|
2025-09-09 21:39:12 +02:00
|
|
|
];
|
|
|
|
|
|
2025-09-18 23:32:53 +02:00
|
|
|
// 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>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-09 21:39:12 +02:00
|
|
|
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: "new",
|
2025-09-18 23:32:53 +02:00
|
|
|
label: "Nuevo Proveedor",
|
2025-09-09 21:39:12 +02:00
|
|
|
variant: "primary" as const,
|
|
|
|
|
icon: Plus,
|
2025-09-18 23:32:53 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2025-09-09 21:39:12 +02:00
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Stats Grid */}
|
|
|
|
|
<StatsGrid
|
|
|
|
|
stats={stats}
|
|
|
|
|
columns={3}
|
|
|
|
|
/>
|
|
|
|
|
|
2025-09-26 12:12:17 +02:00
|
|
|
{/* Search and Filter Controls */}
|
|
|
|
|
<SearchAndFilter
|
|
|
|
|
searchValue={searchTerm}
|
|
|
|
|
onSearchChange={setSearchTerm}
|
|
|
|
|
searchPlaceholder="Buscar proveedores por nombre, código, email o contacto..."
|
|
|
|
|
filters={[
|
|
|
|
|
{
|
|
|
|
|
key: 'status',
|
|
|
|
|
label: 'Estado',
|
|
|
|
|
type: 'dropdown',
|
|
|
|
|
value: statusFilter,
|
|
|
|
|
onChange: (value) => setStatusFilter(value as string),
|
|
|
|
|
placeholder: 'Todos los estados',
|
|
|
|
|
options: Object.values(SupplierStatus).map(status => ({
|
|
|
|
|
value: status,
|
|
|
|
|
label: t(`suppliers:status.${status.toLowerCase()}`)
|
|
|
|
|
}))
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'type',
|
|
|
|
|
label: 'Tipo',
|
|
|
|
|
type: 'dropdown',
|
|
|
|
|
value: typeFilter,
|
|
|
|
|
onChange: (value) => setTypeFilter(value as string),
|
|
|
|
|
placeholder: 'Todos los tipos',
|
|
|
|
|
options: Object.values(SupplierType).map(type => ({
|
|
|
|
|
value: type,
|
|
|
|
|
label: t(`suppliers:types.${type.toLowerCase()}`)
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
] as FilterConfig[]}
|
|
|
|
|
/>
|
2025-09-09 21:39:12 +02:00
|
|
|
|
|
|
|
|
{/* Suppliers Grid */}
|
|
|
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
|
|
|
{filteredSuppliers.map((supplier) => {
|
|
|
|
|
const statusConfig = getSupplierStatusConfig(supplier.status);
|
2025-09-18 23:32:53 +02:00
|
|
|
|
2025-09-09 21:39:12 +02:00
|
|
|
return (
|
|
|
|
|
<StatusCard
|
|
|
|
|
key={supplier.id}
|
|
|
|
|
id={supplier.id}
|
|
|
|
|
statusIndicator={statusConfig}
|
|
|
|
|
title={supplier.name}
|
2025-09-19 11:44:38 +02:00
|
|
|
subtitle={`${getSupplierTypeText(supplier.supplier_type)} • ${supplier.city || 'Sin ubicación'}`}
|
|
|
|
|
primaryValue={supplier.standard_lead_time || 0}
|
|
|
|
|
primaryValueLabel="días"
|
2025-09-09 21:39:12 +02:00
|
|
|
secondaryInfo={{
|
2025-09-19 11:44:38 +02:00
|
|
|
label: 'Pedido Min.',
|
|
|
|
|
value: `€${formatters.compact(supplier.minimum_order_amount || 0)}`
|
2025-09-09 21:39:12 +02:00
|
|
|
}}
|
|
|
|
|
metadata={[
|
|
|
|
|
supplier.contact_person || 'Sin contacto',
|
|
|
|
|
supplier.email || 'Sin email',
|
|
|
|
|
supplier.phone || 'Sin teléfono',
|
2025-09-18 23:32:53 +02:00
|
|
|
`Creado: ${new Date(supplier.created_at).toLocaleDateString('es-ES')}`
|
2025-09-09 21:39:12 +02:00
|
|
|
]}
|
2025-09-26 12:12:17 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
setSelectedSupplier(supplier);
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setModalMode('view');
|
|
|
|
|
setShowForm(true);
|
|
|
|
|
}}
|
2025-09-09 21:39:12 +02:00
|
|
|
actions={[
|
2025-09-18 23:32:53 +02:00
|
|
|
// Primary action - View supplier details
|
2025-09-09 21:39:12 +02:00
|
|
|
{
|
2025-09-18 23:32:53 +02:00
|
|
|
label: 'Ver Detalles',
|
2025-09-09 21:39:12 +02:00
|
|
|
icon: Eye,
|
2025-09-18 23:32:53 +02:00
|
|
|
variant: 'primary',
|
|
|
|
|
priority: 'primary',
|
2025-09-09 21:39:12 +02:00
|
|
|
onClick: () => {
|
|
|
|
|
setSelectedSupplier(supplier);
|
2025-09-18 23:32:53 +02:00
|
|
|
setIsCreating(false);
|
2025-09-09 21:39:12 +02:00
|
|
|
setModalMode('view');
|
|
|
|
|
setShowForm(true);
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-09-18 23:32:53 +02:00
|
|
|
// Secondary action - Edit supplier
|
2025-09-09 21:39:12 +02:00
|
|
|
{
|
|
|
|
|
label: 'Editar',
|
|
|
|
|
icon: Edit,
|
2025-09-18 23:32:53 +02:00
|
|
|
priority: 'secondary',
|
2025-09-09 21:39:12 +02:00
|
|
|
onClick: () => {
|
|
|
|
|
setSelectedSupplier(supplier);
|
2025-09-18 23:32:53 +02:00
|
|
|
setIsCreating(false);
|
2025-09-09 21:39:12 +02:00
|
|
|
setModalMode('edit');
|
|
|
|
|
setShowForm(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Empty State */}
|
|
|
|
|
{filteredSuppliers.length === 0 && (
|
|
|
|
|
<div className="text-center py-12">
|
|
|
|
|
<Building2 className="mx-auto h-12 w-12 text-[var(--text-tertiary)] mb-4" />
|
|
|
|
|
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">
|
|
|
|
|
No se encontraron proveedores
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-[var(--text-secondary)] mb-4">
|
|
|
|
|
Intenta ajustar la búsqueda o crear un nuevo proveedor
|
|
|
|
|
</p>
|
|
|
|
|
<Button onClick={() => setShowForm(true)}>
|
|
|
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
|
|
|
Nuevo Proveedor
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Supplier Details Modal */}
|
2025-09-18 23:32:53 +02:00
|
|
|
{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,
|
2025-09-26 07:46:25 +02:00
|
|
|
options: Object.values(SupplierType).map(value => ({
|
|
|
|
|
value,
|
|
|
|
|
label: t(`suppliers:types.${value.toLowerCase()}`)
|
|
|
|
|
}))
|
2025-09-18 23:32:53 +02:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Condiciones de Pago',
|
|
|
|
|
value: selectedSupplier.payment_terms || PaymentTerms.NET_30,
|
|
|
|
|
type: 'select',
|
|
|
|
|
editable: true,
|
2025-09-26 07:46:25 +02:00
|
|
|
options: Object.values(PaymentTerms).map(value => ({
|
|
|
|
|
value,
|
|
|
|
|
label: t(`suppliers:payment_terms.${value.toLowerCase()}`)
|
|
|
|
|
}))
|
2025-09-18 23:32:53 +02:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
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',
|
2025-09-19 21:39:04 +02:00
|
|
|
icon: Euro,
|
2025-09-18 23:32:53 +02:00
|
|
|
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 (
|
2025-09-26 07:46:25 +02:00
|
|
|
<EditViewModal
|
2025-09-09 21:39:12 +02:00
|
|
|
isOpen={showForm}
|
|
|
|
|
onClose={() => {
|
|
|
|
|
setShowForm(false);
|
|
|
|
|
setSelectedSupplier(null);
|
|
|
|
|
setModalMode('view');
|
2025-09-18 23:32:53 +02:00
|
|
|
setIsCreating(false);
|
2025-09-09 21:39:12 +02:00
|
|
|
}}
|
|
|
|
|
mode={modalMode}
|
|
|
|
|
onModeChange={setModalMode}
|
2025-09-18 23:32:53 +02:00
|
|
|
title={isCreating ? 'Nuevo Proveedor' : selectedSupplier.name || 'Proveedor'}
|
|
|
|
|
subtitle={isCreating ? 'Crear nuevo proveedor' : `Proveedor ${selectedSupplier.supplier_code || ''}`}
|
|
|
|
|
statusIndicator={isCreating ? undefined : getSupplierStatusConfig(selectedSupplier.status)}
|
2025-09-09 21:39:12 +02:00
|
|
|
size="lg"
|
2025-09-18 23:32:53 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2025-09-09 21:39:12 +02:00
|
|
|
}}
|
|
|
|
|
/>
|
2025-09-18 23:32:53 +02:00
|
|
|
);
|
|
|
|
|
})()}
|
2025-09-09 21:39:12 +02:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default SuppliersPage;
|