feat(analysis): make analysis page work for all currencies (BELO, BLUE, BNA)

- Replace hardcoded BELO analysis with generic AnalysisPage
- Add /quotes/analysis/ route with currency path param
- Add currency selector tabs on analysis page
- Dashboard QuoteCards now navigate to analysis for all currencies
- Old /quotes/belo/analysis redirects to /quotes/analysis/BELO
This commit is contained in:
Jose Selesan
2026-06-12 16:44:20 -03:00
parent 142fd4e33f
commit c0c8bf0945
5 changed files with 79 additions and 17 deletions

View File

@@ -1,4 +1,4 @@
import { Link } from "@tanstack/react-router"; import { Link, useNavigate } from "@tanstack/react-router";
import { subDays } from "date-fns"; import { subDays } from "date-fns";
import { ArrowLeft } from "lucide-react"; import { ArrowLeft } from "lucide-react";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
@@ -15,6 +15,14 @@ import {
import { DatePicker } from "@/components/ui/date-picker"; import { DatePicker } from "@/components/ui/date-picker";
import { useDailyQuotes, useHistoricalMinMax, useQuotes } from "@/lib/queries"; import { useDailyQuotes, useHistoricalMinMax, useQuotes } from "@/lib/queries";
const CURRENCIES = ["BELO", "BLUE", "BNA"] as const;
const CURRENCY_LABELS: Record<string, string> = {
BELO: "BELO",
BLUE: "Dólar Blue",
BNA: "Dólar Oficial",
};
const priceFormatter = new Intl.NumberFormat("es-AR", { const priceFormatter = new Intl.NumberFormat("es-AR", {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
@@ -39,7 +47,8 @@ function formatShortDate(iso: string): string {
return dateFormatter.format(new Date(y, m - 1, d)); return dateFormatter.format(new Date(y, m - 1, d));
} }
export function BeloAnalysisPage() { export function AnalysisPage({ currency }: { currency: string }) {
const navigate = useNavigate();
const today = new Date(); const today = new Date();
const defaultStart = subDays(today, 30); const defaultStart = subDays(today, 30);
@@ -50,10 +59,10 @@ export function BeloAnalysisPage() {
const endDateStr = formatDate(endDate); const endDateStr = formatDate(endDate);
const { data: historical, isLoading: historicalLoading } = const { data: historical, isLoading: historicalLoading } =
useHistoricalMinMax("BELO"); useHistoricalMinMax(currency);
const { data: quotes } = useQuotes(); const { data: quotes } = useQuotes();
const { data: daily, isLoading: dailyLoading } = useDailyQuotes( const { data: daily, isLoading: dailyLoading } = useDailyQuotes(
"BELO", currency,
startDateStr, startDateStr,
endDateStr, endDateStr,
); );
@@ -64,12 +73,12 @@ export function BeloAnalysisPage() {
const firstOfMonthStr = formatDate(firstOfMonth); const firstOfMonthStr = formatDate(firstOfMonth);
const { data: yesterdayQuote } = useDailyQuotes( const { data: yesterdayQuote } = useDailyQuotes(
"BELO", currency,
yesterdayStr, yesterdayStr,
yesterdayStr, yesterdayStr,
); );
const { data: firstDayQuote } = useDailyQuotes( const { data: firstDayQuote } = useDailyQuotes(
"BELO", currency,
firstOfMonthStr, firstOfMonthStr,
firstOfMonthStr, firstOfMonthStr,
); );
@@ -107,8 +116,8 @@ export function BeloAnalysisPage() {
}; };
}, [chartData]); }, [chartData]);
const beloQuote = quotes?.find((q) => q.type === "BELO"); const currentQuote = quotes?.find((q) => q.type === currency);
const currentBuy = beloQuote ? Number(beloQuote.buy) : 0; const currentBuy = currentQuote ? Number(currentQuote.buy) : 0;
const yesterdayBuy = yesterdayQuote?.[0]?.buy ?? null; const yesterdayBuy = yesterdayQuote?.[0]?.buy ?? null;
const prevDayDiff = yesterdayBuy !== null ? currentBuy - yesterdayBuy : null; const prevDayDiff = yesterdayBuy !== null ? currentBuy - yesterdayBuy : null;
@@ -124,6 +133,9 @@ export function BeloAnalysisPage() {
? (monthDiff / firstMonthBuy) * 100 ? (monthDiff / firstMonthBuy) * 100
: null; : null;
const backTo = currency === "BELO" ? "/quotes/belo" : "/quotes";
const backLabel = currency === "BELO" ? "BELO" : "Cotizaciones";
function VariationValue({ function VariationValue({
value, value,
pct, pct,
@@ -152,14 +164,32 @@ export function BeloAnalysisPage() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link <Link
to="/quotes/belo" to={backTo}
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors" className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
> >
<ArrowLeft className="size-4" /> <ArrowLeft className="size-4" />
BELO {backLabel}
</Link> </Link>
<h1 className="text-2xl font-semibold">Análisis</h1> <h1 className="text-2xl font-semibold">Análisis</h1>
</div> </div>
<div className="flex items-center gap-1 rounded-lg border p-0.5">
{CURRENCIES.map((c) => (
<button
key={c}
type="button"
onClick={() =>
navigate({ to: "/quotes/analysis/$currency", params: { currency: c } })
}
className={`px-3 py-1 text-xs rounded-md transition-colors cursor-pointer ${
currency === c
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
>
{CURRENCY_LABELS[c]}
</button>
))}
</div>
</div> </div>
<div className="grid gap-4 md:grid-cols-5"> <div className="grid gap-4 md:grid-cols-5">

View File

@@ -232,7 +232,8 @@ export function BeloPage() {
</span> </span>
)} )}
<Link <Link
to="/quotes/belo/analysis" to="/quotes/analysis/$currency"
params={{ currency: "BELO" }}
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors" className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
> >
<BarChart3 className="h-4 w-4" /> <BarChart3 className="h-4 w-4" />

View File

@@ -173,11 +173,21 @@ export function DashboardPage() {
<QuoteCard <QuoteCard
quote={belo} quote={belo}
title="BELO" title="BELO"
to="/quotes/belo" to="/quotes/analysis/BELO"
variant="minimal"
/>
<QuoteCard
quote={blue}
title="BLUE"
to="/quotes/analysis/BLUE"
variant="minimal"
/>
<QuoteCard
quote={bna}
title="Dolar Oficial"
to="/quotes/analysis/BNA"
variant="minimal" variant="minimal"
/> />
<QuoteCard quote={blue} title="BLUE" variant="minimal" />
<QuoteCard quote={bna} title="Dolar Oficial" variant="minimal" />
</div> </div>
<DashboardExpensesTable /> <DashboardExpensesTable />
</div> </div>

View File

@@ -0,0 +1,20 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import { AnalysisPage } from "@/features/analysis/AnalysisPage";
const VALID_CURRENCIES = ["BELO", "BLUE", "BNA"];
export const Route = createFileRoute(
"/_authenticated/quotes/analysis/$currency",
)({
component: RouteComponent,
loader: ({ params }) => {
if (!VALID_CURRENCIES.includes(params.currency)) {
throw redirect({ to: "/quotes" });
}
},
});
function RouteComponent() {
const { currency } = Route.useParams();
return <AnalysisPage currency={currency} />;
}

View File

@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, redirect } from "@tanstack/react-router";
import { BeloAnalysisPage } from "@/features/belo/BeloAnalysisPage";
export const Route = createFileRoute("/_authenticated/quotes/belo/analysis")({ export const Route = createFileRoute("/_authenticated/quotes/belo/analysis")({
component: BeloAnalysisPage, loader: () => {
throw redirect({ to: "/quotes/analysis/$currency", params: { currency: "BELO" } });
},
}); });