import { defineRouteConfig } from "@medusajs/admin-sdk"; import { Buildings } from "@medusajs/icons"; import { Badge, Button, Checkbox, Container, Heading, Input, Switch, Text, toast } from "@medusajs/ui"; import { useEffect, useState } from "react"; import { sdk } from "../../lib/sdk"; // Panel simplificado para alguien no tecnico (la esposa del dueno del // negocio) -- una fila por producto (este catalogo es 1 variante por // producto, confirmado en vivo), switch para activar seguimiento de // stock y un input de cantidad que guarda solo. La vista nativa de // Medusa Admin (Inventory) sigue disponible para el dueno, esto no la // reemplaza, es un atajo. const LOW_STOCK_THRESHOLD = 5; type Row = { productId: string; variantId: string | null; title: string; thumbnail: string | null; sku: string | null; managed: boolean; quantity: number; }; async function fetchRows(): Promise { const data = await sdk.client.fetch<{ rows: Row[] }>("/admin/inventory-panel"); return data.rows; } async function saveRow(row: { productId: string; variantId: string; managed?: boolean; quantity?: number }) { await sdk.client.fetch("/admin/inventory-panel", { method: "post", body: row }); } const InventoryPanel = () => { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [selected, setSelected] = useState>(new Set()); const [savingId, setSavingId] = useState(null); useEffect(() => { fetchRows().then((data) => { setRows(data); setLoading(false); }); }, []); function toggleSelected(productId: string, checked: boolean) { setSelected((prev) => { const next = new Set(prev); if (checked) next.add(productId); else next.delete(productId); return next; }); } async function updateManaged(row: Row, managed: boolean) { if (!row.variantId) return; setSavingId(row.productId); try { await saveRow({ productId: row.productId, variantId: row.variantId, managed }); setRows((prev) => prev.map((r) => (r.productId === row.productId ? { ...r, managed } : r)), ); } catch { toast.error("No se pudo guardar", { description: row.title }); } finally { setSavingId(null); } } async function updateQuantity(row: Row, quantity: number) { if (!row.variantId || Number.isNaN(quantity) || quantity < 0) return; setSavingId(row.productId); try { await saveRow({ productId: row.productId, variantId: row.variantId, quantity }); setRows((prev) => prev.map((r) => (r.productId === row.productId ? { ...r, quantity } : r)), ); toast.success("Stock actualizado", { description: row.title }); } catch { toast.error("No se pudo guardar", { description: row.title }); } finally { setSavingId(null); } } async function bulkSetManaged(managed: boolean) { const targets = rows.filter((r) => selected.has(r.productId) && r.variantId); for (const row of targets) { await updateManaged(row, managed); } setSelected(new Set()); } return (
Inventario Activa el seguimiento de stock y ajusta la cantidad disponible por producto.
{selected.size > 0 && (
)}
{loading ? (
Cargando…
) : (
{rows.map((row) => ( ))}
Producto SKU Trackear stock Cantidad disponible
toggleSelected(row.productId, checked === true)} /> {row.thumbnail ? ( ) : (
)} {row.title}
{row.sku ?? "—"} updateManaged(row, checked)} /> { const value = Number(event.target.value); if (value !== row.quantity) updateQuantity(row, value); }} /> {row.managed && row.quantity <= LOW_STOCK_THRESHOLD && ( Stock bajo )}
)}
); }; export const config = defineRouteConfig({ label: "Inventario", icon: Buildings, }); export default InventoryPanel;