Files
personal-admin-2026/apps/frontend/src/features/dashboard/DashboardPage.tsx

138 lines
5.1 KiB
TypeScript

import { Minus, RefreshCw, TrendingDown, TrendingUp } from "lucide-react";
import { RelativeTime } from "@/lib/time";
import type { Quote } from "@/lib/api";
import { useQuotes, useFetchQuotes, useMonthlyPayedTotal } from "@/lib/queries";
import { useNavigate } from "@tanstack/react-router";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
const priceFormatter = new Intl.NumberFormat("es-AR", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
function QuoteCard({ quote, title, to, variant }: { quote: Quote | undefined; title: string; to?: string; variant?: "default" | "minimal" }) {
const navigate = useNavigate();
const diff = quote ? Number(quote.difference) : 0;
const pct = quote ? Number(quote.percentage) : 0;
const isUp = diff > 0;
const isDown = diff < 0;
return (
<div
className="rounded-lg border p-4 cursor-pointer"
onClick={() => to && navigate({ to })}
onKeyDown={(e) => { if (e.key === "Enter" && to) navigate({ to }); }}
role={to ? "button" : undefined}
tabIndex={to ? 0 : undefined}
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5">
<p className="text-sm text-muted-foreground">{title}</p>
{quote && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="cursor-pointer inline-flex">
{isUp ? (
<TrendingUp className="h-4 w-4 text-emerald-500" />
) : isDown ? (
<TrendingDown className="h-4 w-4 text-red-500" />
) : (
<Minus className="h-4 w-4 text-muted-foreground/50" />
)}
</TooltipTrigger>
<TooltipContent>
<p>
{isUp
? `Subió $${priceFormatter.format(diff)} (${pct.toFixed(2)}%)`
: isDown
? `Bajó $${priceFormatter.format(Math.abs(diff))} (${pct.toFixed(2)}%)`
: "Sin cambios"}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
{quote && (
<span className="text-xs text-muted-foreground">
<RelativeTime date={quote.timeStamp} />
</span>
)}
</div>
{quote ? (
variant === "minimal" ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="cursor-default">
<p className="text-2xl font-bold tabular-nums text-left">${priceFormatter.format(Number(quote.buy))}</p>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>Compra: ${priceFormatter.format(Number(quote.buy))}</p>
<p>Venta: ${priceFormatter.format(Number(quote.sell))}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<div className="space-y-1">
<p className="text-sm">
Compra: <span className="text-lg font-bold">${priceFormatter.format(Number(quote.buy))}</span>
</p>
<p className="text-sm">
Venta: <span className="text-lg font-bold">${priceFormatter.format(Number(quote.sell))}</span>
</p>
</div>
)
) : (
<p className="text-sm text-muted-foreground">Sin datos</p>
)}
</div>
);
}
export function DashboardPage() {
const { data: quotes, isLoading } = useQuotes();
const { mutate: doFetchQuotes, isPending } = useFetchQuotes();
const belo = quotes?.find((q) => q.type === "BELO");
const blue = quotes?.find((q) => q.type === "BLUE");
const now = new Date();
const { data: monthlyExpenses } = useMonthlyPayedTotal(now.getFullYear(), now.getMonth() + 1);
const isFetching = isLoading || isPending;
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-semibold">Panel</h1>
<button
type="button"
disabled={isFetching}
onClick={() => doFetchQuotes()}
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
>
<RefreshCw className={`h-4 w-4 ${isFetching ? "animate-spin" : ""}`} />
Actualizar
</button>
</div>
<div className="grid gap-4 md:grid-cols-4">
<div className="rounded-lg border p-4">
<p className="text-sm text-muted-foreground">Total ingresos</p>
<p className="text-2xl font-bold">$0.00</p>
</div>
<div className="rounded-lg border p-4">
<p className="text-sm text-muted-foreground">Gastos del mes</p>
<p className="text-2xl font-bold">${priceFormatter.format(monthlyExpenses?.total ?? 0)}</p>
</div>
<QuoteCard quote={belo} title="BELO" to="/quotes/belo" variant="minimal" />
<QuoteCard quote={blue} title="BLUE" variant="minimal" />
</div>
</div>
);
}