'use client'; import { useState, useEffect, useCallback } from 'react'; import { productsApi, inventoryApi } from '@/lib/api-client'; import type { Product, ProductVariant, StockAvailability } from '@/types'; interface VariantRow { productId: string; productName: string; variant: ProductVariant; stock: StockAvailability | null; loading: boolean; editing: boolean; editValue: string; saving: boolean; msg: string; } type StockFilter = 'all' | 'in_stock' | 'low_stock' | 'out_of_stock'; const STOCK_LABELS: Record = { all: 'Todos', in_stock: 'En stock', low_stock: 'Stock bajo', out_of_stock: 'Sin stock', }; function StockBadge({ qty }: { qty: number }) { if (qty === 0) return Sin stock; if (qty < 5) return Stock bajo ({qty}); return En stock ({qty}); } export default function InventoryPage() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [filter, setFilter] = useState('all'); const [search, setSearch] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); // Debounce search useEffect(() => { const t = setTimeout(() => setDebouncedSearch(search), 400); return () => clearTimeout(t); }, [search]); // Load products + variants + stock const load = useCallback(async () => { setLoading(true); setError(''); try { const { items: products } = await productsApi.list({ limit: 100, q: debouncedSearch || undefined, }); const variantRows: VariantRow[] = []; for (const product of products ?? []) { const { items: variants } = await productsApi.getVariants(product.id); for (const variant of variants ?? []) { variantRows.push({ productId: product.id, productName: product.name, variant, stock: null, loading: true, editing: false, editValue: '', saving: false, msg: '', }); } } setRows(variantRows); // Load stock for each variant for (const vr of variantRows) { inventoryApi.getAvailability(vr.variant.id) .then((stock) => { setRows((prev) => prev.map((r) => r.variant.id === vr.variant.id ? { ...r, stock, loading: false, editValue: String(stock.availableQuantity) } : r, ), ); }) .catch(() => { setRows((prev) => prev.map((r) => r.variant.id === vr.variant.id ? { ...r, loading: false, editValue: '0' } : r, ), ); }); } } catch (e) { setError(e instanceof Error ? e.message : 'Error al cargar inventario'); } finally { setLoading(false); } }, [debouncedSearch]); useEffect(() => { load(); }, [load]); // Filter rows const filtered = rows.filter((r) => { if (filter === 'in_stock') return (r.stock?.availableQuantity ?? 0) >= 5; if (filter === 'low_stock') return (r.stock?.availableQuantity ?? 0) > 0 && (r.stock?.availableQuantity ?? 0) < 5; if (filter === 'out_of_stock') return (r.stock?.availableQuantity ?? 0) === 0; return true; }); const inStockCount = rows.filter((r) => (r.stock?.availableQuantity ?? 0) >= 5).length; const lowStockCount = rows.filter((r) => { const q = r.stock?.availableQuantity ?? 0; return q > 0 && q < 5; }).length; const outOfStockCount = rows.filter((r) => (r.stock?.availableQuantity ?? 0) === 0).length; return (
{/* Header */}

Inventario

{rows.length} variantes

{/* Stats */}
{[ { label: 'En stock', count: inStockCount, cls: 'bg-green-50 border-green-100 text-green-700' }, { label: 'Stock bajo', count: lowStockCount, cls: 'bg-amber-50 border-amber-100 text-amber-700' }, { label: 'Sin stock', count: outOfStockCount, cls: 'bg-red-50 border-red-100 text-red-700' }, ].map(({ label, count, cls }) => (

{count}

{label}

))}
{/* Search + filters */}
setSearch(e.target.value)} className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-[#2D6A4F] focus:border-transparent outline-none" />
{(Object.keys(STOCK_LABELS) as StockFilter[]).map((f) => ( ))}
{/* Table */}
{loading ? (
Cargando inventario...
) : error ? (

{error}

) : filtered.length === 0 ? (

📦

No hay variantes para este filtro

) : (
{filtered.map((row) => ( ))}
Producto SKU EAN Stock Estado Acción

{row.productName}

{row.variant.sku} {row.variant.ean ?? '—'} {row.loading ? ( ) : row.editing ? (
setRows((prev) => prev.map((r) => r.variant.id === row.variant.id ? { ...r, editValue: e.target.value } : r, ), ) } className="w-20 px-2 py-1 border border-gray-300 rounded-lg text-sm focus:ring-1 focus:ring-[#2D6A4F] outline-none" />
) : (
{row.stock?.availableQuantity ?? '—'}
)}
{row.msg && ( {row.msg} )}
)}
); }