task: added side scroll tip for rebalance table, added limitations to transaction table in recent activity chart (dashbaord)
All checks were successful
Deploy / deploy (push) Successful in 1m28s
Security Tests / security-non-db (push) Successful in 20s
Security Tests / security-db (push) Successful in 26s

This commit is contained in:
2026-03-25 23:27:59 -05:00
parent 86e217c040
commit cb87c906c8
4 changed files with 304 additions and 63 deletions

View File

@@ -22,8 +22,14 @@ import { isoToDateString, dateStringToUTCMidnight, getTodayInTimezone, getBrowse
export default function DashboardPage() {
const session = useAuthSession();
const shouldLoadDashboard = !!session.data?.userId;
const { data, isLoading, isError, error, refetch } = useDashboard(
const trendMonths = 6;
const [trendPage, setTrendPage] = useState(0);
const { data, isLoading, isError, isFetching, error, refetch } = useDashboard(
shouldLoadDashboard,
{
trendPage,
trendMonths,
}
);
const navigate = useNavigate();
const { push } = useToast();
@@ -479,6 +485,12 @@ export default function DashboardPage() {
const hasCategories = data.variableCategories.length > 0;
const hasPlans = data.fixedPlans.length > 0;
const hasTx = data.recentTransactions.length > 0;
const hasTransactionsOlderThanSixMonths =
data.trendWindow?.hasTransactionsOlderThanSixMonths ?? false;
const useTrendWindowPagination = hasTransactionsOlderThanSixMonths;
const trendWindowLabel = data.trendWindow?.label ?? "Last 6 months";
const canTrendNewer = data.trendWindow?.canGoNewer ?? trendPage > 0;
const canTrendOlder = data.trendWindow?.canGoOlder ?? true;
const greetingName =
data.user.displayName ||
@@ -887,12 +899,25 @@ export default function DashboardPage() {
)}
{activeTab === "trend" && (
<section className="grid gap-6 lg:grid-cols-2">
<MonthlyTrendChart data={data.monthlyTrend} />
<RecentTransactionsPanel
transactions={data.recentTransactions}
hasData={hasTx}
/>
<section className="space-y-4">
{useTrendWindowPagination ? (
<TrendWindowControls
label={trendWindowLabel}
canGoOlder={canTrendOlder}
canGoNewer={canTrendNewer}
busy={isFetching}
onOlder={() => setTrendPage((p) => p + 1)}
onNewer={() => setTrendPage((p) => Math.max(0, p - 1))}
/>
) : null}
<section className="grid gap-6 lg:grid-cols-2">
<MonthlyTrendChart data={data.monthlyTrend} />
<RecentTransactionsPanel
transactions={data.recentTransactions}
hasData={hasTx}
rangeLabel={useTrendWindowPagination ? trendWindowLabel : undefined}
/>
</section>
</section>
)}
</section>
@@ -1077,10 +1102,21 @@ export default function DashboardPage() {
)}
{activeTab === "trend" && (
<section className="space-y-4">
{useTrendWindowPagination ? (
<TrendWindowControls
label={trendWindowLabel}
canGoOlder={canTrendOlder}
canGoNewer={canTrendNewer}
busy={isFetching}
onOlder={() => setTrendPage((p) => p + 1)}
onNewer={() => setTrendPage((p) => Math.max(0, p - 1))}
/>
) : null}
<MonthlyTrendChart data={data.monthlyTrend} />
<RecentTransactionsPanel
transactions={data.recentTransactions}
hasData={hasTx}
rangeLabel={useTrendWindowPagination ? trendWindowLabel : undefined}
/>
</section>
)}
@@ -1118,6 +1154,44 @@ function AnalyticsTabButton({
);
}
function TrendWindowControls({
label,
canGoOlder,
canGoNewer,
busy,
onOlder,
onNewer,
}: {
label: string;
canGoOlder: boolean;
canGoNewer: boolean;
busy: boolean;
onOlder: () => void;
onNewer: () => void;
}) {
return (
<div className="row items-center gap-2 rounded-xl border px-3 py-2 bg-[--color-panel]">
<button
type="button"
className="btn"
onClick={onNewer}
disabled={!canGoNewer || busy}
>
Newer
</button>
<div className="text-sm muted ml-1">{label}</div>
<button
type="button"
className="btn ml-auto"
onClick={onOlder}
disabled={!canGoOlder || busy}
>
Older
</button>
</div>
);
}
function UpcomingPlanAlerts({
plans,
userTimezone,
@@ -1128,18 +1202,18 @@ function UpcomingPlanAlerts({
if (plans.length === 0) return null;
return (
<section className="rounded-xl border border-amber-300/70 bg-amber-50/70 p-4 space-y-3 dark:border-amber-500/40 dark:bg-amber-500/10">
<div className="flex items-center gap-2 text-sm font-semibold text-amber-900 dark:text-amber-100">
<div className="flex items-center gap-2 text-sm font-semibold text-amber-900 dark:text-amber-50">
Upcoming plan alerts (next 14 days)
</div>
<ul className="space-y-2">
{plans.map((plan) => (
<li
key={plan.id}
className="flex flex-col gap-2 rounded-lg bg-amber-100/60 px-3 py-2 text-sm text-amber-900 dark:bg-[--color-ink]/20 dark:text-[--color-text] sm:flex-row sm:items-center sm:justify-between"
className="flex flex-col gap-2 rounded-lg bg-amber-100/60 px-3 py-2 text-sm text-amber-900 dark:bg-amber-200/15 dark:text-amber-50 sm:flex-row sm:items-center sm:justify-between"
>
<div>
<div className="font-medium text-amber-950 dark:text-white">{plan.name}</div>
<div className="text-xs muted">
<div className="text-xs text-amber-700 dark:text-amber-200">
Due{" "}
{formatDateInTimezone(plan.dueOn, userTimezone, {
month: "short",
@@ -1148,7 +1222,7 @@ function UpcomingPlanAlerts({
</div>
</div>
<div className="text-right">
<div className="text-xs muted">Remaining</div>
<div className="text-xs text-amber-700 dark:text-amber-200">Remaining</div>
<Money cents={plan.remainingCents} />
</div>
</li>
@@ -1203,14 +1277,20 @@ function SavingsGoalsPanel({
function RecentTransactionsPanel({
transactions,
hasData,
rangeLabel,
}: {
transactions: DashboardResponse["recentTransactions"];
hasData: boolean;
rangeLabel?: string;
}) {
const title = rangeLabel ? "Transactions in window" : "Recent transactions";
const visibleTransactions = transactions.slice(0, 10);
if (!hasData) {
return (
<div className="space-y-3">
<h2 className="font-semibold">Recent transactions</h2>
<h2 className="font-semibold">{title}</h2>
{rangeLabel ? <div className="text-xs muted">{rangeLabel}</div> : null}
<EmptyState
message="No transactions yet"
actionLabel="Record one"
@@ -1221,7 +1301,8 @@ function RecentTransactionsPanel({
}
return (
<div className="space-y-3">
<h2 className="font-semibold">Recent transactions</h2>
<h2 className="font-semibold">{title}</h2>
{rangeLabel ? <div className="text-xs muted">{rangeLabel}</div> : null}
{/* Desktop table view */}
<div className="hidden sm:block border rounded-xl overflow-x-auto">
@@ -1234,7 +1315,7 @@ function RecentTransactionsPanel({
</tr>
</thead>
<tbody>
{transactions.map((tx) => (
{visibleTransactions.map((tx) => (
<tr key={tx.id} className="border-t">
<td className="p-2">
{new Date(tx.occurredAt).toLocaleString()}
@@ -1253,7 +1334,7 @@ function RecentTransactionsPanel({
{/* Mobile card view */}
<div className="sm:hidden space-y-2">
{transactions.map((tx) => (
{visibleTransactions.map((tx) => (
<div key={tx.id} className="border rounded-xl bg-[--color-panel] p-3">
<div className="flex items-center justify-between">
<div className="flex-1 min-w-0">
@@ -1276,6 +1357,11 @@ function RecentTransactionsPanel({
</div>
))}
</div>
<div>
<Link to="/records" className="btn text-sm">
View more
</Link>
</div>
</div>
);
}