Clean frontend

This commit is contained in:
Urtzi Alfaro
2025-08-28 18:35:29 +02:00
parent 68bb5a6449
commit 2bbbf33d7b
27 changed files with 0 additions and 14880 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,879 +0,0 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import {
Table,
Button,
Badge,
Input,
Select,
Card,
Modal,
Tooltip
} from '../../ui';
import {
SalesRecord,
SalesChannel,
PaymentMethod,
SalesSortField,
SortOrder
} from '../../../types/sales.types';
import { salesService } from '../../../services/api/sales.service';
import { useSales } from '../../../hooks/api/useSales';
// Extended interface for orders
interface Order extends SalesRecord {
customer_name: string;
customer_phone?: string;
delivery_address?: string;
order_notes?: string;
items_count: number;
status: OrderStatus;
}
enum OrderStatus {
PENDIENTE = 'pendiente',
CONFIRMADO = 'confirmado',
EN_PREPARACION = 'en_preparacion',
LISTO = 'listo',
ENTREGADO = 'entregado',
CANCELADO = 'cancelado'
}
interface OrdersTableProps {
tenantId?: string;
showActions?: boolean;
onOrderSelect?: (order: Order) => void;
onOrderUpdate?: (orderId: string, updates: Partial<Order>) => void;
initialFilters?: OrderFilters;
}
interface OrderFilters {
status?: OrderStatus;
customer_name?: string;
date_from?: string;
date_to?: string;
min_total?: number;
max_total?: number;
sales_channel?: SalesChannel;
payment_method?: PaymentMethod;
}
interface BulkActions {
selectedOrders: string[];
action: 'change_status' | 'print_receipts' | 'export' | 'delete';
newStatus?: OrderStatus;
}
const StatusColors = {
[OrderStatus.PENDIENTE]: 'yellow',
[OrderStatus.CONFIRMADO]: 'blue',
[OrderStatus.EN_PREPARACION]: 'orange',
[OrderStatus.LISTO]: 'green',
[OrderStatus.ENTREGADO]: 'emerald',
[OrderStatus.CANCELADO]: 'red'
} as const;
const StatusLabels = {
[OrderStatus.PENDIENTE]: 'Pendiente',
[OrderStatus.CONFIRMADO]: 'Confirmado',
[OrderStatus.EN_PREPARACION]: 'En Preparación',
[OrderStatus.LISTO]: 'Listo para Entrega',
[OrderStatus.ENTREGADO]: 'Entregado',
[OrderStatus.CANCELADO]: 'Cancelado'
} as const;
const ChannelLabels = {
[SalesChannel.STORE_FRONT]: 'Tienda',
[SalesChannel.ONLINE]: 'Online',
[SalesChannel.PHONE_ORDER]: 'Teléfono',
[SalesChannel.DELIVERY]: 'Delivery',
[SalesChannel.CATERING]: 'Catering',
[SalesChannel.WHOLESALE]: 'Mayorista',
[SalesChannel.FARMERS_MARKET]: 'Mercado',
[SalesChannel.THIRD_PARTY]: 'Terceros'
} as const;
const PaymentLabels = {
[PaymentMethod.CASH]: 'Efectivo',
[PaymentMethod.CREDIT_CARD]: 'Tarjeta Crédito',
[PaymentMethod.DEBIT_CARD]: 'Tarjeta Débito',
[PaymentMethod.DIGITAL_WALLET]: 'Wallet Digital',
[PaymentMethod.BANK_TRANSFER]: 'Transferencia',
[PaymentMethod.CHECK]: 'Cheque',
[PaymentMethod.STORE_CREDIT]: 'Crédito Tienda'
} as const;
export const OrdersTable: React.FC<OrdersTableProps> = ({
tenantId,
showActions = true,
onOrderSelect,
onOrderUpdate,
initialFilters = {}
}) => {
// State
const [orders, setOrders] = useState<Order[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Filters and sorting
const [filters, setFilters] = useState<OrderFilters>(initialFilters);
const [sortBy, setSortBy] = useState<SalesSortField>(SalesSortField.DATE);
const [sortOrder, setSortOrder] = useState<SortOrder>(SortOrder.DESC);
const [searchTerm, setSearchTerm] = useState('');
// Selection and bulk actions
const [selectedOrders, setSelectedOrders] = useState<string[]>([]);
const [showBulkActions, setShowBulkActions] = useState(false);
const [bulkActionModal, setBulkActionModal] = useState(false);
const [bulkAction, setBulkAction] = useState<BulkActions['action'] | null>(null);
const [newBulkStatus, setNewBulkStatus] = useState<OrderStatus | null>(null);
// Pagination
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [totalPages, setTotalPages] = useState(1);
const [totalOrders, setTotalOrders] = useState(0);
// Order details modal
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
const [showOrderDetails, setShowOrderDetails] = useState(false);
// Sales hook
const { fetchSalesData } = useSales();
// Load orders
const loadOrders = useCallback(async () => {
setLoading(true);
setError(null);
try {
const queryParams = {
page: currentPage,
size: pageSize,
sort_by: sortBy,
sort_order: sortOrder,
...filters,
search: searchTerm || undefined,
};
// Simulate API call with mock data transformation
const response = await salesService.getSales(queryParams);
if (response.success && response.data) {
// Transform sales records to orders (in real app this would come from orders API)
const transformedOrders: Order[] = response.data.items.map((sale: any, index: number) => ({
...sale,
customer_name: `Cliente ${index + 1}`,
customer_phone: `+34 ${Math.floor(Math.random() * 900000000 + 100000000)}`,
delivery_address: sale.sales_channel === SalesChannel.DELIVERY
? `Calle Ejemplo ${Math.floor(Math.random() * 100)}, Madrid`
: undefined,
order_notes: Math.random() > 0.7 ? 'Sin gluten' : undefined,
items_count: Math.floor(Math.random() * 5) + 1,
status: Object.values(OrderStatus)[Math.floor(Math.random() * Object.values(OrderStatus).length)],
}));
setOrders(transformedOrders);
setTotalOrders(response.data.total);
setTotalPages(response.data.pages);
} else {
setError(response.error || 'Error al cargar pedidos');
}
} catch (err) {
setError('Error de conexión al servidor');
console.error('Error loading orders:', err);
} finally {
setLoading(false);
}
}, [currentPage, pageSize, sortBy, sortOrder, filters, searchTerm]);
// Effects
useEffect(() => {
loadOrders();
}, [loadOrders]);
useEffect(() => {
setCurrentPage(1);
}, [filters, searchTerm]);
// Filtered and sorted orders
const filteredOrders = useMemo(() => {
let filtered = [...orders];
// Apply search filter
if (searchTerm) {
const searchLower = searchTerm.toLowerCase();
filtered = filtered.filter(order =>
order.customer_name.toLowerCase().includes(searchLower) ||
order.id.toLowerCase().includes(searchLower) ||
order.product_name.toLowerCase().includes(searchLower)
);
}
return filtered;
}, [orders, searchTerm]);
// Table columns
const columns = [
{
key: 'select',
title: (
<input
type="checkbox"
checked={selectedOrders.length === filteredOrders.length && filteredOrders.length > 0}
onChange={(e) => {
if (e.target.checked) {
setSelectedOrders(filteredOrders.map(order => order.id));
} else {
setSelectedOrders([]);
}
}}
className="rounded border-gray-300"
/>
),
render: (order: Order) => (
<input
type="checkbox"
checked={selectedOrders.includes(order.id)}
onChange={(e) => {
if (e.target.checked) {
setSelectedOrders(prev => [...prev, order.id]);
} else {
setSelectedOrders(prev => prev.filter(id => id !== order.id));
}
}}
className="rounded border-gray-300"
/>
),
},
{
key: 'id',
title: 'Nº Pedido',
sortable: true,
render: (order: Order) => (
<button
onClick={() => {
setSelectedOrder(order);
setShowOrderDetails(true);
}}
className="text-blue-600 hover:text-blue-800 font-medium"
>
#{order.id.slice(-8)}
</button>
),
},
{
key: 'customer_name',
title: 'Cliente',
sortable: true,
render: (order: Order) => (
<div>
<div className="font-medium">{order.customer_name}</div>
{order.customer_phone && (
<div className="text-sm text-gray-500">{order.customer_phone}</div>
)}
</div>
),
},
{
key: 'products',
title: 'Productos',
render: (order: Order) => (
<div>
<div className="font-medium">{order.product_name}</div>
<div className="text-sm text-gray-500">
{order.items_count} {order.items_count === 1 ? 'artículo' : 'artículos'}
</div>
</div>
),
},
{
key: 'total_revenue',
title: 'Total',
sortable: true,
render: (order: Order) => (
<div className="text-right">
<div className="font-semibold">€{order.total_revenue.toFixed(2)}</div>
{order.discount_applied > 0 && (
<div className="text-sm text-green-600">
-€{order.discount_applied.toFixed(2)}
</div>
)}
</div>
),
},
{
key: 'status',
title: 'Estado',
sortable: true,
render: (order: Order) => (
<Badge color={StatusColors[order.status]} variant="soft">
{StatusLabels[order.status]}
</Badge>
),
},
{
key: 'sales_channel',
title: 'Canal',
render: (order: Order) => (
<span className="text-sm text-gray-600">
{ChannelLabels[order.sales_channel]}
</span>
),
},
{
key: 'date',
title: 'Fecha',
sortable: true,
render: (order: Order) => (
<div>
<div>{new Date(order.date).toLocaleDateString('es-ES')}</div>
<div className="text-sm text-gray-500">
{new Date(order.created_at).toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
})}
</div>
</div>
),
},
];
if (showActions) {
columns.push({
key: 'actions',
title: 'Acciones',
render: (order: Order) => (
<div className="flex space-x-2">
<Tooltip content="Ver detalles">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedOrder(order);
setShowOrderDetails(true);
}}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</Button>
</Tooltip>
<Tooltip content="Cambiar estado">
<Select
size="sm"
value={order.status}
onChange={(value) => handleStatusChange(order.id, value as OrderStatus)}
options={Object.values(OrderStatus).map(status => ({
value: status,
label: StatusLabels[status]
}))}
/>
</Tooltip>
{order.status !== OrderStatus.CANCELADO && (
<Tooltip content="Imprimir recibo">
<Button
size="sm"
variant="outline"
onClick={() => handlePrintReceipt(order.id)}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
</Button>
</Tooltip>
)}
</div>
),
});
}
// Handlers
const handleStatusChange = async (orderId: string, newStatus: OrderStatus) => {
try {
// Update locally first
setOrders(prev => prev.map(order =>
order.id === orderId ? { ...order, status: newStatus } : order
));
// Call API
onOrderUpdate?.(orderId, { status: newStatus });
// In real app, make API call here
} catch (error) {
console.error('Error updating order status:', error);
// Revert on error
loadOrders();
}
};
const handlePrintReceipt = (orderId: string) => {
// In real app, generate and print receipt
console.log('Printing receipt for order:', orderId);
window.print();
};
const handleBulkAction = async () => {
if (!bulkAction || selectedOrders.length === 0) return;
try {
switch (bulkAction) {
case 'change_status':
if (newBulkStatus) {
setOrders(prev => prev.map(order =>
selectedOrders.includes(order.id)
? { ...order, status: newBulkStatus }
: order
));
}
break;
case 'print_receipts':
selectedOrders.forEach(handlePrintReceipt);
break;
case 'export':
handleExportOrders(selectedOrders);
break;
case 'delete':
setOrders(prev => prev.filter(order => !selectedOrders.includes(order.id)));
break;
}
setSelectedOrders([]);
setBulkActionModal(false);
setBulkAction(null);
setNewBulkStatus(null);
} catch (error) {
console.error('Error performing bulk action:', error);
}
};
const handleExportOrders = async (orderIds?: string[]) => {
try {
const exportData = orders
.filter(order => !orderIds || orderIds.includes(order.id))
.map(order => ({
'Nº Pedido': order.id,
'Cliente': order.customer_name,
'Teléfono': order.customer_phone,
'Productos': order.product_name,
'Cantidad': order.quantity_sold,
'Total': order.total_revenue,
'Estado': StatusLabels[order.status],
'Canal': ChannelLabels[order.sales_channel],
'Fecha': new Date(order.date).toLocaleDateString('es-ES'),
}));
const csv = [
Object.keys(exportData[0]).join(','),
...exportData.map(row => Object.values(row).join(','))
].join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `pedidos_${new Date().toISOString().split('T')[0]}.csv`;
a.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error('Error exporting orders:', error);
}
};
return (
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-gray-900">Gestión de Pedidos</h2>
<p className="text-gray-600">
{totalOrders} pedidos encontrados
</p>
</div>
<div className="flex items-center space-x-3">
<Button onClick={() => handleExportOrders()} variant="outline" size="sm">
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Exportar
</Button>
<Button onClick={() => loadOrders()} size="sm">
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Actualizar
</Button>
</div>
</div>
{/* Filters */}
<Card className="p-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<Input
label="Buscar pedidos"
placeholder="Cliente, nº pedido o producto..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Select
label="Estado"
value={filters.status || ''}
onChange={(value) => setFilters(prev => ({
...prev,
status: value ? value as OrderStatus : undefined
}))}
options={[
{ value: '', label: 'Todos los estados' },
...Object.values(OrderStatus).map(status => ({
value: status,
label: StatusLabels[status]
}))
]}
/>
<Select
label="Canal de venta"
value={filters.sales_channel || ''}
onChange={(value) => setFilters(prev => ({
...prev,
sales_channel: value ? value as SalesChannel : undefined
}))}
options={[
{ value: '', label: 'Todos los canales' },
...Object.values(SalesChannel).map(channel => ({
value: channel,
label: ChannelLabels[channel]
}))
]}
/>
<div className="flex space-x-2">
<Input
label="Total mínimo"
type="number"
step="0.01"
value={filters.min_total || ''}
onChange={(e) => setFilters(prev => ({
...prev,
min_total: e.target.value ? parseFloat(e.target.value) : undefined
}))}
placeholder="€0.00"
/>
<Input
label="Total máximo"
type="number"
step="0.01"
value={filters.max_total || ''}
onChange={(e) => setFilters(prev => ({
...prev,
max_total: e.target.value ? parseFloat(e.target.value) : undefined
}))}
placeholder="€999.99"
/>
</div>
</div>
<div className="flex justify-between items-center mt-4 pt-4 border-t border-gray-200">
<div className="text-sm text-gray-600">
Mostrando {(currentPage - 1) * pageSize + 1} - {Math.min(currentPage * pageSize, totalOrders)} de {totalOrders}
</div>
<Select
value={pageSize.toString()}
onChange={(value) => setPageSize(parseInt(value))}
options={[
{ value: '10', label: '10 por página' },
{ value: '20', label: '20 por página' },
{ value: '50', label: '50 por página' },
{ value: '100', label: '100 por página' }
]}
/>
</div>
</Card>
{/* Bulk Actions */}
{selectedOrders.length > 0 && (
<Card className="p-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">
{selectedOrders.length} pedidos seleccionados
</span>
<div className="flex space-x-2">
<Button
size="sm"
variant="outline"
onClick={() => {
setBulkAction('change_status');
setBulkActionModal(true);
}}
>
Cambiar Estado
</Button>
<Button
size="sm"
variant="outline"
onClick={() => {
setBulkAction('print_receipts');
handleBulkAction();
}}
>
Imprimir Recibos
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleExportOrders(selectedOrders)}
>
Exportar
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setSelectedOrders([])}
>
Limpiar Selección
</Button>
</div>
</div>
</Card>
)}
{/* Table */}
<Card>
<div className="overflow-x-auto">
<Table
columns={columns}
data={filteredOrders}
loading={loading}
emptyMessage="No se encontraron pedidos"
/>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="flex items-center justify-between px-6 py-4 border-t border-gray-200">
<Button
variant="outline"
size="sm"
disabled={currentPage === 1}
onClick={() => setCurrentPage(prev => prev - 1)}
>
Anterior
</Button>
<div className="flex space-x-2">
{Array.from({ length: totalPages }, (_, i) => i + 1)
.filter(page =>
page === 1 ||
page === totalPages ||
Math.abs(page - currentPage) <= 2
)
.map((page, index, array) => (
<React.Fragment key={page}>
{index > 0 && array[index - 1] !== page - 1 && (
<span className="px-3 py-1 text-gray-400">...</span>
)}
<Button
size="sm"
variant={currentPage === page ? 'primary' : 'outline'}
onClick={() => setCurrentPage(page)}
>
{page}
</Button>
</React.Fragment>
))}
</div>
<Button
variant="outline"
size="sm"
disabled={currentPage === totalPages}
onClick={() => setCurrentPage(prev => prev + 1)}
>
Siguiente
</Button>
</div>
)}
</Card>
{/* Order Details Modal */}
<Modal
isOpen={showOrderDetails}
onClose={() => setShowOrderDetails(false)}
title={`Pedido #${selectedOrder?.id.slice(-8)}`}
size="lg"
>
{selectedOrder && (
<div className="space-y-6">
<div className="grid grid-cols-2 gap-6">
<div>
<h4 className="font-medium text-gray-900 mb-3">Información del Cliente</h4>
<div className="space-y-2">
<div><span className="text-gray-600">Nombre:</span> {selectedOrder.customer_name}</div>
{selectedOrder.customer_phone && (
<div><span className="text-gray-600">Teléfono:</span> {selectedOrder.customer_phone}</div>
)}
{selectedOrder.delivery_address && (
<div><span className="text-gray-600">Dirección:</span> {selectedOrder.delivery_address}</div>
)}
</div>
</div>
<div>
<h4 className="font-medium text-gray-900 mb-3">Detalles del Pedido</h4>
<div className="space-y-2">
<div><span className="text-gray-600">Estado:</span>
<Badge color={StatusColors[selectedOrder.status]} variant="soft" className="ml-2">
{StatusLabels[selectedOrder.status]}
</Badge>
</div>
<div><span className="text-gray-600">Canal:</span> {ChannelLabels[selectedOrder.sales_channel]}</div>
<div><span className="text-gray-600">Fecha:</span> {new Date(selectedOrder.date).toLocaleDateString('es-ES')}</div>
{selectedOrder.payment_method && (
<div><span className="text-gray-600">Pago:</span> {PaymentLabels[selectedOrder.payment_method]}</div>
)}
</div>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900 mb-3">Productos</h4>
<div className="border rounded-lg p-4">
<div className="flex justify-between items-center">
<div>
<div className="font-medium">{selectedOrder.product_name}</div>
{selectedOrder.category && (
<div className="text-sm text-gray-600">{selectedOrder.category}</div>
)}
</div>
<div className="text-right">
<div className="font-medium">€{selectedOrder.unit_price.toFixed(2)} × {selectedOrder.quantity_sold}</div>
<div className="text-sm text-gray-600">€{selectedOrder.total_revenue.toFixed(2)}</div>
</div>
</div>
</div>
</div>
{selectedOrder.order_notes && (
<div>
<h4 className="font-medium text-gray-900 mb-3">Notas del Pedido</h4>
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3 text-sm">
{selectedOrder.order_notes}
</div>
</div>
)}
<div className="border-t pt-4">
<div className="flex justify-between items-center text-lg font-semibold">
<span>Total del Pedido:</span>
<span>€{selectedOrder.total_revenue.toFixed(2)}</span>
</div>
{selectedOrder.discount_applied > 0 && (
<div className="flex justify-between items-center text-sm text-green-600">
<span>Descuento aplicado:</span>
<span>-€{selectedOrder.discount_applied.toFixed(2)}</span>
</div>
)}
{selectedOrder.tax_amount > 0 && (
<div className="flex justify-between items-center text-sm text-gray-600">
<span>IVA incluido:</span>
<span>€{selectedOrder.tax_amount.toFixed(2)}</span>
</div>
)}
</div>
<div className="flex justify-end space-x-3">
<Button
variant="outline"
onClick={() => handlePrintReceipt(selectedOrder.id)}
>
Imprimir Recibo
</Button>
<Button
onClick={() => {
onOrderSelect?.(selectedOrder);
setShowOrderDetails(false);
}}
>
Editar Pedido
</Button>
</div>
</div>
)}
</Modal>
{/* Bulk Action Modal */}
<Modal
isOpen={bulkActionModal}
onClose={() => setBulkActionModal(false)}
title="Acción masiva"
>
<div className="space-y-4">
<p>
Vas a realizar una acción sobre {selectedOrders.length} pedidos seleccionados.
</p>
{bulkAction === 'change_status' && (
<Select
label="Nuevo estado"
value={newBulkStatus || ''}
onChange={(value) => setNewBulkStatus(value as OrderStatus)}
options={Object.values(OrderStatus).map(status => ({
value: status,
label: StatusLabels[status]
}))}
required
/>
)}
<div className="flex justify-end space-x-3">
<Button variant="outline" onClick={() => setBulkActionModal(false)}>
Cancelar
</Button>
<Button
onClick={handleBulkAction}
disabled={bulkAction === 'change_status' && !newBulkStatus}
>
Confirmar
</Button>
</div>
</div>
</Modal>
{/* Error Display */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex">
<svg className="w-5 h-5 text-red-400 mr-2 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
<div>
<h3 className="text-sm font-medium text-red-800">Error</h3>
<p className="text-sm text-red-700 mt-1">{error}</p>
<Button
size="sm"
variant="outline"
onClick={() => setError(null)}
className="mt-2"
>
Cerrar
</Button>
</div>
</div>
</div>
)}
</div>
);
};
export default OrdersTable;