98 lines
4.2 KiB
TypeScript
98 lines
4.2 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import { iconArchive, iconBox, iconCart, iconDelivery, iconTag, iconUsersGroup } from "../_core/icons";
|
|
import { useOperatingDay } from "../_core/hooks/use-operating-day";
|
|
import { productsResource, shopCartsResource, usersResource, warehousesResource } from "../_core/services/entities";
|
|
import { QuickActionCard } from "./_components/quick-action-card";
|
|
import { StatCard } from "./_components/stat-card";
|
|
|
|
const ROLE_LABELS: Record<string, string> = { admin: "administrador", cashier: "cajero" };
|
|
|
|
function money(value: number) {
|
|
return value.toLocaleString("es-AR", { style: "currency", currency: "USD" });
|
|
}
|
|
|
|
export default function AdminDashboardPage() {
|
|
const { data: carts, isLoading: loadingCarts } = shopCartsResource.useList();
|
|
const { data: products, isLoading: loadingProducts } = productsResource.useList();
|
|
const { data: warehouses } = warehousesResource.useList();
|
|
const { data: users, isLoading: loadingUsers } = usersResource.useList();
|
|
const { start: operatingDayStart } = useOperatingDay();
|
|
|
|
const salesToday = useMemo(() => {
|
|
if (!carts) return null;
|
|
return carts
|
|
.filter((c) => c.status === "paid" && new Date(c.createdAt) >= operatingDayStart)
|
|
.reduce((sum, c) => sum + Number(c.totalPrice), 0);
|
|
}, [carts, operatingDayStart]);
|
|
|
|
const stockByProduct = useMemo(() => {
|
|
const map = new Map<number, number>();
|
|
for (const w of warehouses ?? []) map.set(w.productId, (map.get(w.productId) ?? 0) + w.stock);
|
|
return map;
|
|
}, [warehouses]);
|
|
|
|
const lowStockCount = useMemo(() => {
|
|
if (!products) return null;
|
|
return products.filter((p) => (stockByProduct.get(p.id) ?? 0) <= p.minStock).length;
|
|
}, [products, stockByProduct]);
|
|
|
|
const usersByRole = useMemo(() => {
|
|
const counts: Record<string, number> = {};
|
|
for (const u of users ?? []) counts[u.role] = (counts[u.role] ?? 0) + 1;
|
|
return counts;
|
|
}, [users]);
|
|
|
|
const usersHint = Object.entries(usersByRole)
|
|
.map(([role, count]) => `${count} ${ROLE_LABELS[role] ?? role}${count === 1 ? "" : "s"}`)
|
|
.join(", ");
|
|
|
|
return (
|
|
<div className="flex flex-col gap-8">
|
|
<div>
|
|
<h1 className="text-xl font-semibold text-foreground">Resumen</h1>
|
|
<p className="text-sm text-muted">Estado general del negocio.</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
<StatCard
|
|
icon={iconCart}
|
|
label="Ventas de hoy"
|
|
value={loadingCarts || salesToday === null ? "—" : money(salesToday)}
|
|
hint={salesToday ? "Total cobrado hoy" : "Sin ventas registradas aún"}
|
|
/>
|
|
<StatCard
|
|
icon={iconBox}
|
|
label="Productos activos"
|
|
value={loadingProducts || !products ? "—" : String(products.length)}
|
|
hint={products && products.length > 0 ? "En el catálogo" : "Conectá el catálogo para ver el total"}
|
|
/>
|
|
<StatCard
|
|
icon={iconArchive}
|
|
label="Alertas de stock"
|
|
value={lowStockCount === null ? "—" : String(lowStockCount)}
|
|
hint={lowStockCount ? "Productos bajo el mínimo" : "Sin datos de inventario aún"}
|
|
/>
|
|
<StatCard
|
|
icon={iconUsersGroup}
|
|
label="Usuarios activos"
|
|
value={loadingUsers || !users ? "—" : String(users.length)}
|
|
hint={usersHint || "Sin usuarios"}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<h2 className="mb-3 text-sm font-semibold text-foreground">Accesos rápidos</h2>
|
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
<QuickActionCard href="/admin/inventario" icon={iconArchive} label="Almacenes" description="Stock, entradas y salidas" />
|
|
<QuickActionCard href="/admin/inventario/productos" icon={iconBox} label="Productos" description="Catálogo y precios" />
|
|
<QuickActionCard href="/admin/inventario/categorias" icon={iconTag} label="Categorías" description="Organizá el catálogo" />
|
|
<QuickActionCard href="/admin/proveedores" icon={iconDelivery} label="Proveedores" description="Contactos y pedidos" />
|
|
<QuickActionCard href="/admin/usuarios" icon={iconUsersGroup} label="Usuarios" description="Roles y accesos" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|