From 39785075722042dd4ceb99969a3341690b9adc89 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 30 May 2026 13:19:09 -0500 Subject: [PATCH] feat(tracker): overdue command center with snooze/skip/pay + sidebar badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - migration v0.70 adds snoozed_until TEXT to monthly_bill_state - trackerService: snoozed_until in monthly state fetch + getOverdueCount() - GET /api/tracker/overdue-count endpoint - PUT /bills/:id/monthly-state validates snoozed_until - OverdueCommandCenter component: collapsible, per-bill actions, hides snoozed - useOverdueCount hook (2-min stale, 5-min poll, tab-only) - Sidebar/nav uses overdue count badge on Tracker menu item - Bump v0.33.8.7 → v0.34.0 --- client/api.js | 2 + client/components/layout/NavPill.jsx | 7 +- client/components/layout/Sidebar.jsx | 19 +- .../tracker/OverdueCommandCenter.jsx | 228 ++++++++++++++++++ client/hooks/useQueries.js | 11 + client/lib/version.js | 8 +- client/pages/TrackerPage.jsx | 28 +++ db/database.js | 8 + db/schema.sql | 1 + package.json | 2 +- routes/bills.js | 22 +- routes/tracker.js | 7 +- services/trackerService.js | 47 +++- 13 files changed, 374 insertions(+), 16 deletions(-) create mode 100644 client/components/tracker/OverdueCommandCenter.jsx diff --git a/client/api.js b/client/api.js index 48bd432..16d63f9 100644 --- a/client/api.js +++ b/client/api.js @@ -137,6 +137,8 @@ export const api = { // Tracker tracker: (y, m, params = {}) => get(`/tracker${queryString({ year: y, month: m, ...params })}`), upcomingBills: (days = 30) => get(`/tracker/upcoming?days=${days}`), + overdueCount: () => get('/tracker/overdue-count'), + snoozeOverdue: (id, data) => put(`/bills/${id}/monthly-state`, data), // Calendar calendar: (y, m) => get(`/calendar?year=${y}&month=${m}`), diff --git a/client/components/layout/NavPill.jsx b/client/components/layout/NavPill.jsx index cb5c4e7..8ca4c7a 100644 --- a/client/components/layout/NavPill.jsx +++ b/client/components/layout/NavPill.jsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import { NavLink } from 'react-router-dom'; import { cn } from '@/lib/utils'; -export const NavPill = React.memo(function NavPill({ item, onNavigate }) { +export const NavPill = React.memo(function NavPill({ item, onNavigate, badge }) { const Icon = useMemo(() => item.icon, [item.icon]); const to = useMemo(() => item.to, [item.to]); const end = useMemo(() => item.end, [item.end]); @@ -23,6 +23,11 @@ export const NavPill = React.memo(function NavPill({ item, onNavigate }) { > {label} + {badge > 0 && ( + + {badge > 99 ? '99+' : badge} + + )} ); }); diff --git a/client/components/layout/Sidebar.jsx b/client/components/layout/Sidebar.jsx index 30d8b15..44850bd 100644 --- a/client/components/layout/Sidebar.jsx +++ b/client/components/layout/Sidebar.jsx @@ -7,6 +7,7 @@ import { } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useAuth } from '@/hooks/useAuth'; +import { useOverdueCount } from '@/hooks/useQueries'; import { ThemeToggle } from '@/components/ui/theme-toggle'; import { Button } from '@/components/ui/button'; import { @@ -42,7 +43,7 @@ const trackerItems = [ { to: '/snowball', icon: TrendingDown, label: 'Snowball' }, ]; -function TrackerMenu({ onNavigate }) { +function TrackerMenu({ onNavigate, badge }) { const location = useLocation(); const navigate = useNavigate(); const isTrackerActive = useMemo(() => trackerItems.some(item => ( @@ -65,6 +66,11 @@ function TrackerMenu({ onNavigate }) { > Tracker + {badge > 0 && ( + + {badge > 99 ? '99+' : badge} + + )} @@ -169,6 +175,8 @@ export default function Sidebar({ adminMode = false }) { const [mobileOpen, setMobileOpen] = useState(false); const { user } = useAuth(); const items = useMemo(() => adminMode ? adminNavItems : userNavItems, [adminMode]); + const { data: overdueData } = useOverdueCount(); + const overdueCount = (!adminMode && overdueData?.count) ? overdueData.count : 0; return (
@@ -176,7 +184,7 @@ export default function Sidebar({ adminMode = false }) {