task: added side scroll tip for rebalance table, added limitations to transaction table in recent activity chart (dashbaord)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { FastifyPluginAsync } from "fastify";
|
||||
import { z } from "zod";
|
||||
import { getUserMidnightFromDateOnly } from "../allocator.js";
|
||||
import { getUserTimezone } from "../services/user-context.js";
|
||||
|
||||
@@ -11,9 +12,11 @@ const monthKey = (date: Date) =>
|
||||
const monthLabel = (date: Date) =>
|
||||
date.toLocaleString("en-US", { month: "short", year: "numeric" });
|
||||
|
||||
function buildMonthBuckets(count: number, now = new Date()) {
|
||||
function buildMonthBuckets(count: number, pageOffset: number, now = new Date()) {
|
||||
const buckets: Array<{ key: string; label: string; start: Date; end: Date }> = [];
|
||||
const current = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
||||
const current = new Date(
|
||||
Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - pageOffset * count, 1)
|
||||
);
|
||||
for (let i = count - 1; i >= 0; i--) {
|
||||
const start = new Date(Date.UTC(current.getUTCFullYear(), current.getUTCMonth() - i, 1));
|
||||
const end = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 1));
|
||||
@@ -23,15 +26,28 @@ function buildMonthBuckets(count: number, now = new Date()) {
|
||||
}
|
||||
|
||||
const dashboardRoutes: FastifyPluginAsync = async (app) => {
|
||||
app.get("/dashboard", async (req) => {
|
||||
app.get("/dashboard", async (req, reply) => {
|
||||
const Query = z.object({
|
||||
trendPage: z.coerce.number().int().min(0).default(0),
|
||||
trendMonths: z.coerce.number().int().min(1).max(12).default(6),
|
||||
});
|
||||
const parsedQuery = Query.safeParse(req.query ?? {});
|
||||
if (!parsedQuery.success) {
|
||||
return reply.code(400).send({ message: "Invalid dashboard query" });
|
||||
}
|
||||
|
||||
const userId = req.userId;
|
||||
const monthsBack = 6;
|
||||
const buckets = buildMonthBuckets(monthsBack);
|
||||
const rangeStart = buckets[0]?.start ?? new Date();
|
||||
const { trendPage, trendMonths } = parsedQuery.data;
|
||||
const now = new Date();
|
||||
const buckets = buildMonthBuckets(trendMonths, trendPage, now);
|
||||
const rangeStart = buckets[0]?.start ?? new Date();
|
||||
const rangeEnd =
|
||||
buckets[buckets.length - 1]?.end ??
|
||||
new Date(rangeStart.getTime() + 31 * DAY_MS);
|
||||
const defaultSixMonthStart = buildMonthBuckets(6, 0, now)[0]?.start ?? rangeStart;
|
||||
const dashboardTxKinds = ["variable_spend", "fixed_payment"];
|
||||
|
||||
const [cats, plans, recentTxs, agg, allocAgg, incomeEvents, firstIncomeEvent, spendTxs, user] = await Promise.all([
|
||||
const [cats, plans, recentTxs, agg, allocAgg, incomeEvents, firstIncomeEvent, spendTxs, earliestSpendTx, user] = await Promise.all([
|
||||
app.prisma.variableCategory.findMany({
|
||||
where: { userId },
|
||||
orderBy: [{ priority: "asc" }, { name: "asc" }],
|
||||
@@ -43,7 +59,7 @@ const dashboardRoutes: FastifyPluginAsync = async (app) => {
|
||||
app.prisma.transaction.findMany({
|
||||
where: { userId, kind: { in: dashboardTxKinds } },
|
||||
orderBy: { occurredAt: "desc" },
|
||||
take: 50,
|
||||
take: 20,
|
||||
select: { id: true, kind: true, amountCents: true, occurredAt: true },
|
||||
}),
|
||||
app.prisma.incomeEvent.aggregate({
|
||||
@@ -57,7 +73,7 @@ const dashboardRoutes: FastifyPluginAsync = async (app) => {
|
||||
app.prisma.incomeEvent.findMany({
|
||||
where: {
|
||||
userId,
|
||||
postedAt: { gte: rangeStart },
|
||||
postedAt: { gte: rangeStart, lt: rangeEnd },
|
||||
},
|
||||
select: { id: true, postedAt: true, amountCents: true, note: true },
|
||||
}),
|
||||
@@ -70,10 +86,18 @@ const dashboardRoutes: FastifyPluginAsync = async (app) => {
|
||||
where: {
|
||||
userId,
|
||||
kind: { in: dashboardTxKinds },
|
||||
occurredAt: { gte: rangeStart },
|
||||
occurredAt: { gte: rangeStart, lt: rangeEnd },
|
||||
},
|
||||
select: { occurredAt: true, amountCents: true },
|
||||
}),
|
||||
app.prisma.transaction.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
kind: { in: dashboardTxKinds },
|
||||
},
|
||||
orderBy: { occurredAt: "asc" },
|
||||
select: { occurredAt: true },
|
||||
}),
|
||||
app.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
@@ -174,7 +198,21 @@ const dashboardRoutes: FastifyPluginAsync = async (app) => {
|
||||
incomeCents: incomeByMonth.get(bucket.key) ?? 0,
|
||||
spendCents: spendByMonth.get(bucket.key) ?? 0,
|
||||
}));
|
||||
|
||||
const hasTransactionsOlderThanSixMonths =
|
||||
!!earliestSpendTx?.occurredAt &&
|
||||
earliestSpendTx.occurredAt.getTime() < defaultSixMonthStart.getTime();
|
||||
const hasOlderTrendWindow =
|
||||
hasTransactionsOlderThanSixMonths &&
|
||||
!!earliestSpendTx?.occurredAt &&
|
||||
earliestSpendTx.occurredAt.getTime() < rangeStart.getTime();
|
||||
const startLabel = buckets[0]?.label ?? "";
|
||||
const endLabel = buckets[buckets.length - 1]?.label ?? "";
|
||||
const rangeLabel =
|
||||
startLabel && endLabel
|
||||
? startLabel === endLabel
|
||||
? startLabel
|
||||
: `${startLabel} - ${endLabel}`
|
||||
: "Trend window";
|
||||
const upcomingPlans = fixedPlans
|
||||
.map((plan) => ({ ...plan, due: getUserMidnightFromDateOnly(userTimezone, plan.dueOn) }))
|
||||
.filter(
|
||||
@@ -279,6 +317,16 @@ const dashboardRoutes: FastifyPluginAsync = async (app) => {
|
||||
fixedExpensePercentage: user?.fixedExpensePercentage ?? 40,
|
||||
},
|
||||
monthlyTrend,
|
||||
trendWindow: {
|
||||
page: trendPage,
|
||||
months: trendMonths,
|
||||
canGoNewer: trendPage > 0,
|
||||
canGoOlder: hasOlderTrendWindow,
|
||||
hasTransactionsOlderThanSixMonths,
|
||||
startLabel,
|
||||
endLabel,
|
||||
label: rangeLabel,
|
||||
},
|
||||
upcomingPlans,
|
||||
savingsTargets,
|
||||
crisis: {
|
||||
|
||||
Reference in New Issue
Block a user