Add new page designs

This commit is contained in:
Urtzi Alfaro
2025-08-30 19:11:15 +02:00
parent 221781731c
commit 62b1ab9cb1
12 changed files with 2129 additions and 1240 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { Plus, Search, Filter, Download, ShoppingCart, Truck, DollarSign, Calendar } from 'lucide-react';
import { Button, Input, Card, Badge, Table } from '../../../../components/ui';
import type { TableColumn } from '../../../../components/ui';
import { Plus, Search, Download, ShoppingCart, Truck, DollarSign, Calendar, Clock, CheckCircle, AlertCircle, Package, Eye, Edit } from 'lucide-react';
import { Button, Input, Card, Badge, StatsGrid } from '../../../../components/ui';
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
import { PageHeader } from '../../../../components/layout';
const ProcurementPage: React.FC = () => {
@@ -103,152 +103,126 @@ const ProcurementPage: React.FC = () => {
const getStatusBadge = (status: string) => {
const statusConfig = {
pending: { color: 'yellow', text: 'Pendiente' },
approved: { color: 'blue', text: 'Aprobado' },
in_transit: { color: 'purple', text: 'En Tránsito' },
delivered: { color: 'green', text: 'Entregado' },
cancelled: { color: 'red', text: 'Cancelado' },
pending: { color: 'warning', text: 'Pendiente', icon: Clock },
approved: { color: 'info', text: 'Aprobado', icon: CheckCircle },
in_transit: { color: 'secondary', text: 'En Tránsito', icon: Truck },
delivered: { color: 'success', text: 'Entregado', icon: CheckCircle },
cancelled: { color: 'error', text: 'Cancelado', icon: AlertCircle },
};
const config = statusConfig[status as keyof typeof statusConfig];
return <Badge variant={config?.color as any}>{config?.text || status}</Badge>;
const Icon = config?.icon;
return (
<Badge
variant={config?.color as any}
icon={Icon && <Icon size={12} />}
text={config?.text || status}
/>
);
};
const getPaymentStatusBadge = (status: string) => {
const statusConfig = {
pending: { color: 'yellow', text: 'Pendiente' },
paid: { color: 'green', text: 'Pagado' },
overdue: { color: 'red', text: 'Vencido' },
pending: { color: 'warning', text: 'Pendiente', icon: Clock },
paid: { color: 'success', text: 'Pagado', icon: CheckCircle },
overdue: { color: 'error', text: 'Vencido', icon: AlertCircle },
};
const config = statusConfig[status as keyof typeof statusConfig];
return <Badge variant={config?.color as any}>{config?.text || status}</Badge>;
const Icon = config?.icon;
return (
<Badge
variant={config?.color as any}
icon={Icon && <Icon size={12} />}
text={config?.text || status}
/>
);
};
const columns: TableColumn[] = [
{
key: 'id',
title: 'Orden',
dataIndex: 'id',
render: (value, record: any) => (
<div>
<div className="text-sm font-medium text-[var(--text-primary)]">{value}</div>
{record.notes && (
<div className="text-xs text-[var(--text-tertiary)]">{record.notes}</div>
)}
</div>
),
},
{
key: 'supplier',
title: 'Proveedor',
dataIndex: 'supplier',
},
{
key: 'status',
title: 'Estado',
dataIndex: 'status',
render: (value) => getStatusBadge(value),
},
{
key: 'orderDate',
title: 'Fecha Pedido',
dataIndex: 'orderDate',
render: (value) => new Date(value).toLocaleDateString('es-ES'),
},
{
key: 'deliveryDate',
title: 'Fecha Entrega',
dataIndex: 'deliveryDate',
render: (value) => new Date(value).toLocaleDateString('es-ES'),
},
{
key: 'totalAmount',
title: 'Monto Total',
dataIndex: 'totalAmount',
render: (value) => `${value.toLocaleString()}`,
},
{
key: 'paymentStatus',
title: 'Pago',
dataIndex: 'paymentStatus',
render: (value) => getPaymentStatusBadge(value),
},
{
key: 'actions',
title: 'Acciones',
align: 'right' as const,
render: () => (
<div className="flex space-x-2">
<Button variant="outline" size="sm">Ver</Button>
<Button variant="outline" size="sm">Editar</Button>
</div>
),
},
];
const filteredOrders = mockPurchaseOrders.filter(order => {
const matchesSearch = order.supplier.toLowerCase().includes(searchTerm.toLowerCase()) ||
order.id.toLowerCase().includes(searchTerm.toLowerCase()) ||
order.notes.toLowerCase().includes(searchTerm.toLowerCase());
return matchesSearch;
});
const stats = {
const mockPurchaseStats = {
totalOrders: mockPurchaseOrders.length,
pendingOrders: mockPurchaseOrders.filter(o => o.status === 'pending').length,
inTransit: mockPurchaseOrders.filter(o => o.status === 'in_transit').length,
delivered: mockPurchaseOrders.filter(o => o.status === 'delivered').length,
totalSpent: mockPurchaseOrders.reduce((sum, order) => sum + order.totalAmount, 0),
activeSuppliers: mockSuppliers.filter(s => s.status === 'active').length,
};
const purchaseOrderStats = [
{
title: 'Total Órdenes',
value: mockPurchaseStats.totalOrders,
variant: 'default' as const,
icon: ShoppingCart,
},
{
title: 'Pendientes',
value: mockPurchaseStats.pendingOrders,
variant: 'warning' as const,
icon: Clock,
},
{
title: 'En Tránsito',
value: mockPurchaseStats.inTransit,
variant: 'info' as const,
icon: Truck,
},
{
title: 'Entregadas',
value: mockPurchaseStats.delivered,
variant: 'success' as const,
icon: CheckCircle,
},
{
title: 'Gasto Total',
value: formatters.currency(mockPurchaseStats.totalSpent),
variant: 'success' as const,
icon: DollarSign,
},
{
title: 'Proveedores',
value: mockPurchaseStats.activeSuppliers,
variant: 'info' as const,
icon: Package,
},
];
return (
<div className="p-6 space-y-6">
<PageHeader
title="Gestión de Compras"
description="Administra órdenes de compra, proveedores y seguimiento de entregas"
action={
<Button>
<Plus className="w-4 h-4 mr-2" />
Nueva Orden de Compra
</Button>
}
actions={[
{
id: "export",
label: "Exportar",
variant: "outline" as const,
icon: Download,
onClick: () => console.log('Export purchase orders')
},
{
id: "new",
label: "Nueva Orden de Compra",
variant: "primary" as const,
icon: Plus,
onClick: () => console.log('New purchase order')
}
]}
/>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Órdenes Totales</p>
<p className="text-3xl font-bold text-[var(--text-primary)]">{stats.totalOrders}</p>
</div>
<ShoppingCart className="h-12 w-12 text-[var(--color-info)]" />
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Órdenes Pendientes</p>
<p className="text-3xl font-bold text-[var(--color-primary)]">{stats.pendingOrders}</p>
</div>
<Calendar className="h-12 w-12 text-[var(--color-primary)]" />
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Gasto Total</p>
<p className="text-3xl font-bold text-[var(--color-success)]">{stats.totalSpent.toLocaleString()}</p>
</div>
<DollarSign className="h-12 w-12 text-[var(--color-success)]" />
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Proveedores Activos</p>
<p className="text-3xl font-bold text-purple-600">{stats.activeSuppliers}</p>
</div>
<Truck className="h-12 w-12 text-purple-600" />
</div>
</Card>
</div>
{/* Stats Grid */}
<StatsGrid
stats={purchaseOrderStats}
columns={6}
/>
{/* Tabs Navigation */}
<div className="border-b border-[var(--border-primary)]">
@@ -286,48 +260,128 @@ const ProcurementPage: React.FC = () => {
</nav>
</div>
{/* Search and Filters */}
<Card className="p-6">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[var(--text-tertiary)] h-4 w-4" />
{activeTab === 'orders' && (
<Card className="p-4">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<Input
placeholder={`Buscar ${activeTab === 'orders' ? 'órdenes' : 'proveedores'}...`}
placeholder="Buscar órdenes por proveedor, ID o notas..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
className="w-full"
/>
</div>
</div>
<div className="flex gap-2">
<Button variant="outline">
<Filter className="w-4 h-4 mr-2" />
Filtros
</Button>
<Button variant="outline">
<Button variant="outline" onClick={() => console.log('Export filtered')}>
<Download className="w-4 h-4 mr-2" />
Exportar
</Button>
</div>
</div>
</Card>
{/* Tab Content */}
{activeTab === 'orders' && (
<Card className="p-6">
<Table
columns={columns}
data={mockPurchaseOrders}
rowKey="id"
hover={true}
variant="default"
size="md"
/>
</Card>
)}
{/* Purchase Orders Grid */}
{activeTab === 'orders' && (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{filteredOrders.map((order) => (
<Card key={order.id} className="p-4">
<div className="space-y-4">
{/* Header */}
<div className="flex items-start justify-between">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 bg-[var(--color-primary)]/10 p-2 rounded-lg">
<ShoppingCart className="w-4 h-4 text-[var(--text-tertiary)]" />
</div>
<div>
<div className="font-mono text-sm font-semibold text-[var(--color-primary)]">
{order.id}
</div>
<span className="font-medium text-[var(--text-primary)]">
{order.supplier}
</span>
</div>
</div>
{getStatusBadge(order.status)}
</div>
{/* Key Info */}
<div className="flex items-center justify-between">
<div className="text-right">
<div className="text-lg font-bold text-[var(--text-primary)]">
{formatters.currency(order.totalAmount)}
</div>
<div className="text-xs text-[var(--text-tertiary)]">
{order.items?.length} artículos
</div>
</div>
<div className="text-right">
<div className="text-sm text-[var(--text-primary)]">
{new Date(order.deliveryDate).toLocaleDateString('es-ES')}
</div>
<div className="text-xs text-[var(--text-tertiary)]">
Entrega prevista
</div>
</div>
</div>
{/* Payment Status */}
<div className="flex items-center justify-between">
<div className="text-sm text-[var(--text-secondary)]">
Estado del pago:
</div>
{getPaymentStatusBadge(order.paymentStatus)}
</div>
{/* Notes */}
{order.notes && (
<div className="bg-[var(--bg-secondary)] p-2 rounded text-xs text-[var(--text-secondary)] italic">
"{order.notes}"
</div>
)}
{/* Actions */}
<div className="flex gap-2 pt-2 border-t border-[var(--border-primary)]">
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => console.log('View order', order.id)}
>
<Eye className="w-4 h-4 mr-1" />
Ver
</Button>
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => console.log('Edit order', order.id)}
>
<Edit className="w-4 h-4 mr-1" />
Editar
</Button>
</div>
</div>
</Card>
))}
</div>
)}
{/* Empty State for Purchase Orders */}
{activeTab === 'orders' && filteredOrders.length === 0 && (
<div className="text-center py-12">
<ShoppingCart 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 órdenes de compra
</h3>
<p className="text-[var(--text-secondary)] mb-4">
Intenta ajustar la búsqueda o crear una nueva orden de compra
</p>
<Button onClick={() => console.log('New purchase order')}>
<Plus className="w-4 h-4 mr-2" />
Nueva Orden de Compra
</Button>
</div>
)}
{activeTab === 'suppliers' && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{mockSuppliers.map((supplier) => (