From fdc3ebdac99f70c95975f9ed4bf90f92e75b7e8c Mon Sep 17 00:00:00 2001 From: "Claude (auto-fix)" Date: Fri, 19 Jun 2026 12:07:53 +0530 Subject: [PATCH] fix(dashboard): count all POs approved this month, not just current MGR_APPROVED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manager dashboard "Approved This Month" card only counted POs whose current status is MGR_APPROVED, so approvals that had already moved on to payment, delivery, or closure dropped out of the count. Managers could not see what happened to the POs they approved this month. - Count every PO whose `approvedAt` falls in the current month across all post-approval statuses (MGR_APPROVED → ... → CLOSED). `approvedAt` is set once at approval and persists, so it is the correct anchor. - Introduce a shared `POST_APPROVAL_STATUSES` constant (includes the previously-omitted PARTIALLY_CLOSED). This also fixes Total Approved Spend and the vessel/monthly breakdowns, which were silently dropping partially-received POs. - Make the card a link into /history with an approval-date filter applied (?approvedFrom=) so a click shows the full set with each PO's current status, as requested. - Add `approvedFrom`/`approvedTo` filtering to the history page, its filter UI, and the reports export route so the deep-link and exports stay in sync. Scope note: the count remains org-wide, consistent with every other card on the manager dashboard. Adds an integration test covering the moved-on case and the date window. Fixes #32 --- App/app/(portal)/dashboard/page.tsx | 15 ++- App/app/(portal)/history/history-filters.tsx | 18 ++- App/app/(portal)/history/page.tsx | 16 ++- App/app/api/reports/export/route.ts | 12 ++ App/lib/utils.ts | 12 ++ .../integration/approved-this-month.test.ts | 105 ++++++++++++++++++ 6 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 App/tests/integration/approved-this-month.test.ts diff --git a/App/app/(portal)/dashboard/page.tsx b/App/app/(portal)/dashboard/page.tsx index 3da8778..b4ca973 100644 --- a/App/app/(portal)/dashboard/page.tsx +++ b/App/app/(portal)/dashboard/page.tsx @@ -3,7 +3,7 @@ import { db } from "@/lib/db"; import { StatCard } from "@/components/dashboard/stat-card"; import { SpendCharts } from "@/components/dashboard/spend-charts"; import { PoStatusBadge } from "@/components/po/po-status-badge"; -import { formatCurrency, formatDate } from "@/lib/utils"; +import { formatCurrency, formatDate, POST_APPROVAL_STATUSES } from "@/lib/utils"; import { FileText, Clock, CheckCircle, DollarSign } from "lucide-react"; import Link from "next/link"; import type { Metadata } from "next"; @@ -110,11 +110,14 @@ async function ManagerDashboard() { const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); const twelveMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 11, 1); - const approvedStatuses = ["MGR_APPROVED", "SENT_FOR_PAYMENT", "PARTIALLY_PAID", "PAID_DELIVERED", "CLOSED"] as const; + const approvedStatuses = POST_APPROVAL_STATUSES; const [awaitingCount, approvedThisMonth, totalSpendResult, recentApproved, vesselBreakdown, monthlyPos] = await Promise.all([ db.purchaseOrder.count({ where: { status: "MGR_REVIEW" } }), - db.purchaseOrder.count({ where: { status: "MGR_APPROVED", approvedAt: { gte: startOfMonth } } }), + // POs approved this month — including those that have since moved past + // MGR_APPROVED into payment/delivery/closure. `approvedAt` is set once at + // approval and persists, so filter on it across all post-approval statuses. + db.purchaseOrder.count({ where: { status: { in: [...approvedStatuses] }, approvedAt: { gte: startOfMonth } } }), db.purchaseOrder.aggregate({ _sum: { totalAmount: true }, where: { status: { in: [...approvedStatuses] } }, @@ -144,6 +147,10 @@ async function ManagerDashboard() { const totalSpend = Number(totalSpendResult._sum.totalAmount ?? 0); + // Local YYYY-MM-DD for the first of this month, used to deep-link the + // "Approved This Month" card into the history page filtered by approval date. + const startOfMonthParam = `${startOfMonth.getFullYear()}-${String(startOfMonth.getMonth() + 1).padStart(2, "0")}-01`; + // Build monthly series for last 12 months const monthlyMap: Record = {}; for (let i = 11; i >= 0; i--) { @@ -174,7 +181,7 @@ async function ManagerDashboard() {

Dashboard

- +
diff --git a/App/app/(portal)/history/history-filters.tsx b/App/app/(portal)/history/history-filters.tsx index 16b4132..a12cc92 100644 --- a/App/app/(portal)/history/history-filters.tsx +++ b/App/app/(portal)/history/history-filters.tsx @@ -27,6 +27,8 @@ export function HistoryFilters({ vessels }: Props) { const [dateFrom, setDateFrom] = useState(sp.get("dateFrom") ?? ""); const [dateTo, setDateTo] = useState(sp.get("dateTo") ?? ""); + const [approvedFrom, setApprovedFrom] = useState(sp.get("approvedFrom") ?? ""); + const [approvedTo, setApprovedTo] = useState(sp.get("approvedTo") ?? ""); const [vesselId, setVesselId] = useState(sp.get("vesselId") ?? ""); const [status, setStatus] = useState(sp.get("status") ?? ""); @@ -34,17 +36,19 @@ export function HistoryFilters({ vessels }: Props) { const params = new URLSearchParams(); if (dateFrom) params.set("dateFrom", dateFrom); if (dateTo) params.set("dateTo", dateTo); + if (approvedFrom) params.set("approvedFrom", approvedFrom); + if (approvedTo) params.set("approvedTo", approvedTo); if (vesselId) params.set("vesselId", vesselId); if (status) params.set("status", status); router.push(`/history?${params.toString()}`); } function clear() { - setDateFrom(""); setDateTo(""); setVesselId(""); setStatus(""); + setDateFrom(""); setDateTo(""); setApprovedFrom(""); setApprovedTo(""); setVesselId(""); setStatus(""); router.push("/history"); } - const hasFilters = dateFrom || dateTo || vesselId || status; + const hasFilters = dateFrom || dateTo || approvedFrom || approvedTo || vesselId || status; return (
@@ -59,6 +63,16 @@ export function HistoryFilters({ vessels }: Props) { setDateTo(e.target.value)} className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20" />
+
+ + setApprovedFrom(e.target.value)} + className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20" /> +
+
+ + setApprovedTo(e.target.value)} + className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20" /> +