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:
@@ -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">
|
||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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} />;
|
||||||
|
}
|
||||||
@@ -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" } });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user