From 969139251dd131e8c9036a17bd08094123109983 Mon Sep 17 00:00:00 2001 From: _null Date: Mon, 4 May 2026 13:14:32 -0500 Subject: [PATCH] calendar --- HISTORY.md | 25 ++ bill-tracker.code-workspace | 8 + client/App.jsx | 4 + client/api.js | 13 + client/components/BillModal.jsx | 2 +- client/components/BillsTableInner.jsx | 371 +++++++++++------ client/components/layout/Sidebar.jsx | 10 +- client/components/ui/alert-dialog.jsx | 2 +- client/components/ui/dialog.jsx | 2 +- client/index.css | 60 +++ client/lib/version.js | 16 +- client/pages/AdminPage.jsx | 10 +- client/pages/AnalyticsPage.jsx | 565 ++++++++++++++++++++++++++ client/pages/BillsPage.jsx | 6 +- client/pages/CalendarPage.jsx | 455 +++++++++++++++++++++ client/pages/CategoriesPage.jsx | 6 +- client/pages/LoginPage.jsx | 2 +- client/pages/SettingsPage.jsx | 4 +- client/pages/StatusPage.jsx | 2 +- client/pages/TrackerPage.jsx | 262 ++++++++++-- img/Selection_1490.png | Bin 0 -> 304294 bytes package-lock.json | 4 +- package.json | 2 +- routes/analytics.js | 276 +++++++++++++ routes/calendar.js | 179 ++++++++ server.js | 2 + 26 files changed, 2096 insertions(+), 192 deletions(-) create mode 100644 bill-tracker.code-workspace create mode 100644 client/pages/AnalyticsPage.jsx create mode 100644 client/pages/CalendarPage.jsx create mode 100644 img/Selection_1490.png create mode 100644 routes/analytics.js create mode 100644 routes/calendar.js diff --git a/HISTORY.md b/HISTORY.md index dbbe8fe..986203f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,23 @@ # Bill Tracker — Changelog +## v0.18.1 + +### Changed +- Added a Calendar page with a month grid for user-owned bills and payments, compact day indicators, a legend, monthly progress summary, and day detail dialog. +- Added a user-scoped `GET /api/calendar` endpoint for one-month calendar data using existing bills, payments, categories, and monthly bill state records without schema changes. +- Calendar status and totals respect monthly actual amount overrides, skipped bills, existing due-day clamping, and existing tracker-style late/missed status behavior. +- Added Calendar to the top navigation after Tracker while preserving the existing desktop and mobile nav behavior. +- Improved mobile and tablet responsive rendering across the top navigation, page headers, dialogs, dense tables, Tracker, Bills, Categories, Settings, Status, Admin, Analytics, and Login views. +- Preserved the current desktop layout by keeping existing desktop-oriented layouts at `lg` and above while adding mobile/tablet stacking, scrolling, and tap-friendly controls below that breakpoint. +- Tablet navigation now uses the compact menu to avoid horizontal overflow; user menu, theme toggle, and admin-only navigation remain reachable. +- Dialogs and destructive confirmations now respect mobile viewport width/height and scroll internally when content is long. +- Dense Tracker, Bills, Admin, Analytics, and import/history style tables use horizontal scrolling or mobile stacking so actions remain reachable on smaller screens. +- Tracker and Bills now use stacked mobile/tablet bill rows below `lg`, reducing sideways scrolling for normal bill review, quick payment, and bill actions while preserving the desktop table layouts. +- Tracker mobile notes stay contained in each bill row, so long notes can truncate or scroll locally without forcing the whole bill list sideways. + +### Notes +- No schema, auth behavior, tracker/payment/bill business logic, admin permissions, or desktop redesign changes were made. + ## v0.18 ### Branding @@ -14,6 +32,13 @@ - Vite now copies only modern React public assets from `client/public`, preventing legacy `public/*.html`, CSS, and JS files from being emitted into `dist`. - No backend, auth, tracker, bills, categories, settings, status, admin, or navigation-link behavior was changed. +### Analytics +- Added a user-scoped Analytics API at `GET /api/analytics/summary` using existing bills, payments, categories, and monthly bill state data without schema changes. +- Added an Analytics page with date range controls, category and bill filters, inactive/skipped toggles, chart visibility toggles, and a line/area trend option. +- Added monthly spending trend, expected vs actual spend, category spending donut, and pay-on-time heatmap views. +- Added print and browser save-as-PDF report output with print CSS that hides navigation, controls, and interactive actions. +- Analytics queries are scoped to the signed-in user and do not accept or expose cross-user aggregation. + ### Security - **OIDC ID token signature verification** now uses `openid-client@5` for full cryptographic validation via JWKS: signature, issuer, audience, expiry, nonce, and `sub` presence — tokens without a valid signature are rejected - **OIDC client cache** invalidation path added; cache is keyed by issuer/client/redirect so Admin panel credential changes pick up a fresh client diff --git a/bill-tracker.code-workspace b/bill-tracker.code-workspace new file mode 100644 index 0000000..bab1b7f --- /dev/null +++ b/bill-tracker.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": ".." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/client/App.jsx b/client/App.jsx index 3a20eb4..1e3e1fc 100644 --- a/client/App.jsx +++ b/client/App.jsx @@ -6,10 +6,12 @@ import { ReleaseNotesDialog } from '@/components/ReleaseNotesDialog'; import LoginPage from '@/pages/LoginPage'; import AdminPage from '@/pages/AdminPage'; import TrackerPage from '@/pages/TrackerPage'; +import CalendarPage from '@/pages/CalendarPage'; import BillsPage from '@/pages/BillsPage'; import CategoriesPage from '@/pages/CategoriesPage'; import SettingsPage from '@/pages/SettingsPage'; import StatusPage from '@/pages/StatusPage'; +import AnalyticsPage from '@/pages/AnalyticsPage'; import ReleaseNotesPage from '@/pages/ReleaseNotesPage'; import DataPage from '@/pages/DataPage'; import ProfilePage from '@/pages/ProfilePage'; @@ -73,8 +75,10 @@ export default function App() { } > } /> + } /> } /> } /> + } /> } /> } /> } /> diff --git a/client/api.js b/client/api.js index 9e43dc8..c976643 100644 --- a/client/api.js +++ b/client/api.js @@ -108,6 +108,9 @@ export const api = { tracker: (y, m) => get(`/tracker?year=${y}&month=${m}`), upcomingBills: (days = 30) => get(`/tracker/upcoming?days=${days}`), + // Calendar + calendar: (y, m) => get(`/calendar?year=${y}&month=${m}`), + // Bills bills: () => get('/bills'), allBills: () => get('/bills?inactive=true'), @@ -141,6 +144,16 @@ export const api = { settings: () => get('/settings'), saveSettings: (data) => put('/settings', data), + // Analytics + analyticsSummary: (params = {}) => { + const qs = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') qs.set(key, String(value)); + }); + const query = qs.toString(); + return get(`/analytics/summary${query ? `?${query}` : ''}`); + }, + // Status status: () => get('/status'), diff --git a/client/components/BillModal.jsx b/client/components/BillModal.jsx index 9917cc8..e648e19 100644 --- a/client/components/BillModal.jsx +++ b/client/components/BillModal.jsx @@ -91,7 +91,7 @@ export default function BillModal({ bill, categories, onClose, onSave }) {
-
+
{/* Name */}
diff --git a/client/components/BillsTableInner.jsx b/client/components/BillsTableInner.jsx index 6cf0e8e..04323d0 100644 --- a/client/components/BillsTableInner.jsx +++ b/client/components/BillsTableInner.jsx @@ -10,138 +10,257 @@ function hasHistoricalVisibility(bill) { return !!bill.has_history_ranges || (visibility && visibility !== 'default'); } +function MobileBillRow({ bill, onEdit, onToggle, onDelete, onHistory }) { + const hasHistory = hasHistoricalVisibility(bill); + + return ( +
+
+
+
+ + {hasHistory && ( + + + + )} +
+ +
+ + {bill.active ? 'Active' : 'Inactive'} + + {!!bill.autopay_enabled && ( + AP + )} + {!!bill.has_2fa && ( + 2FA + )} +
+
+ + + ${Number(bill.expected_amount).toFixed(2)} + +
+ +
+
+

Due

+

Day {bill.due_day}

+
+
+

Category

+

{bill.category_name || '—'}

+
+
+

Cycle

+

{bill.billing_cycle || 'monthly'}

+
+
+ +
+ + {!bill.active && ( + + )} + +
+
+ ); +} + // Accepts row action handlers from BillsPage export default function BillsTableInner({ bills, onEdit, onToggle, onDelete, onHistory }) { return ( - - - - - Bill - Category - Due - Expected - Cycle - Flags - - - - - + <> +
{bills.map((bill) => ( - - - {/* Bill name */} - -
- - {hasHistoricalVisibility(bill) && ( - - - - )} -
-
- - {/* Category */} - - {bill.category_name ? ( - {bill.category_name} - ) : ( - - )} - - - {/* Due day */} - - Day {bill.due_day} - - - {/* Expected amount */} - - - ${Number(bill.expected_amount).toFixed(2)} - - - - {/* Billing cycle — field is billing_cycle, not cycle */} - - - {bill.billing_cycle || 'monthly'} - - - - {/* Flags */} - - {(!!bill.autopay_enabled || !!bill.has_2fa) ? ( -
- {!!bill.autopay_enabled && ( - AP - )} - {!!bill.has_2fa && ( - 2FA - )} -
- ) : ( - - )} -
- - {/* Actions — visible on row hover */} - -
- - {!bill.active && ( - - )} - -
-
- -
+ bill={bill} + onEdit={onEdit} + onToggle={onToggle} + onDelete={onDelete} + onHistory={onHistory} + /> ))} - +
-
+
+ + + + + Bill + Category + Due + Expected + Cycle + Flags + + + + + + {bills.map((bill) => ( + + + {/* Bill name */} + +
+ + {hasHistoricalVisibility(bill) && ( + + + + )} +
+
+ + {/* Category */} + + {bill.category_name ? ( + {bill.category_name} + ) : ( + + )} + + + {/* Due day */} + + Day {bill.due_day} + + + {/* Expected amount */} + + + ${Number(bill.expected_amount).toFixed(2)} + + + + {/* Billing cycle — field is billing_cycle, not cycle */} + + + {bill.billing_cycle || 'monthly'} + + + + {/* Flags */} + + {(!!bill.autopay_enabled || !!bill.has_2fa) ? ( +
+ {!!bill.autopay_enabled && ( + AP + )} + {!!bill.has_2fa && ( + 2FA + )} +
+ ) : ( + + )} +
+ + {/* Actions — visible on row hover */} + +
+ + {!bill.active && ( + + )} + +
+
+ +
+ ))} +
+ +
+
+ ); } diff --git a/client/components/layout/Sidebar.jsx b/client/components/layout/Sidebar.jsx index bf04704..25f3d81 100644 --- a/client/components/layout/Sidebar.jsx +++ b/client/components/layout/Sidebar.jsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { NavLink, useNavigate } from 'react-router-dom'; import { - Activity, ChevronDown, LayoutGrid, LogOut, Menu, Receipt, + Activity, BarChart3, CalendarDays, ChevronDown, LayoutGrid, LogOut, Menu, Receipt, Settings, ShieldCheck, Tag, User, X, } from 'lucide-react'; import { cn } from '@/lib/utils'; @@ -19,8 +19,10 @@ import { const userNavItems = [ { to: '/', icon: LayoutGrid, label: 'Tracker', end: true }, + { to: '/calendar', icon: CalendarDays, label: 'Calendar' }, { to: '/bills', icon: Receipt, label: 'Bills' }, { to: '/categories', icon: Tag, label: 'Categories' }, + { to: '/analytics', icon: BarChart3, label: 'Analytics' }, { to: '/settings', icon: Settings, label: 'Settings' }, { to: '/status', icon: Activity, label: 'Status' }, ]; @@ -130,7 +132,7 @@ export default function Sidebar({ adminMode = false }) {
-
{mobileOpen && ( -
+