From 0f24016bcfd89cd7153d459924b9b4c10c19f20f Mon Sep 17 00:00:00 2001 From: null Date: Sun, 5 Jul 2026 15:57:35 -0500 Subject: [PATCH] feat(settings): first day of week (plan Tier 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New per-user 'week_start' setting (0=Sun default / 1=Mon), registered in USER_SETTING_KEYS + USER_SETTING_DEFAULTS. - CalendarPage reads it and offsets the month grid: leading-blank count (getDay()-weekStart+7)%7 and a rotated weekday header. Purely presentational. - Settings: Sun/Mon Select in the new "Regional" section. Also aligns the grace_period_days client default (3→5) with the server seed. Co-Authored-By: Claude Opus 4.8 --- client/pages/CalendarPage.tsx | 14 +++++++++++--- client/pages/SettingsPage.tsx | 17 ++++++++++++++++- services/userSettings.js | 2 ++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/client/pages/CalendarPage.tsx b/client/pages/CalendarPage.tsx index 947a9be..528a9b4 100644 --- a/client/pages/CalendarPage.tsx +++ b/client/pages/CalendarPage.tsx @@ -18,6 +18,7 @@ import { api } from '@/api'; import { cn, fmtDate, todayStr, errMessage } from '@/lib/utils'; import { formatUSD, asDollars } from '@/lib/money'; import { isPaidStatus } from '@/lib/trackerUtils'; +import { useSettings } from '@/hooks/useQueries'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; @@ -395,13 +396,17 @@ function DayIndicators({ day, moneyMarker }: { day: CalDay; moneyMarker: MoneyMa ); } -function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers }: { +function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers, weekStart = 0 }: { data: CalendarData; selectedDate?: string; onSelectDay: (day: CalDay) => void; moneyMarkers?: Record; + weekStart?: number; }) { - const firstWeekday = new Date(data.year, data.month - 1, 1).getDay(); + // Offset the leading blanks + rotate the header so the week can start on + // Sunday (0) or Monday (1) per the user's setting. + const firstWeekday = (new Date(data.year, data.month - 1, 1).getDay() - weekStart + 7) % 7; + const orderedWeekdays = [...WEEKDAYS.slice(weekStart), ...WEEKDAYS.slice(0, weekStart)]; const cells: ({ type: 'blank'; key: string } | { type: 'day'; key: string; day: CalDay })[] = [ ...Array.from({ length: firstWeekday }, (_, index) => ({ type: 'blank' as const, key: `blank-${index}` })), ...data.days.map(day => ({ type: 'day' as const, key: day.date, day })), @@ -411,7 +416,7 @@ function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers }: { return (
- {WEEKDAYS.map(day => ( + {orderedWeekdays.map(day => (
{day}
@@ -911,6 +916,8 @@ export default function CalendarPage() { })(); const [year, setYear] = useState(initial.year); const [month, setMonth] = useState(initial.month); + const { data: settingsData } = useSettings(); + const weekStart = Number((settingsData as Record | undefined)?.week_start) === 1 ? 1 : 0; const [data, setData] = useState(null); const [summaryData, setSummaryData] = useState(null); const [snowballProjection, setSnowballProjection] = useState(null); @@ -1063,6 +1070,7 @@ export default function CalendarPage() { <> { diff --git a/client/pages/SettingsPage.tsx b/client/pages/SettingsPage.tsx index 9dde636..aeb51f4 100644 --- a/client/pages/SettingsPage.tsx +++ b/client/pages/SettingsPage.tsx @@ -314,6 +314,7 @@ function LinkImportToggle() { interface SettingsState { currency: string; date_format: string; + week_start: string; grace_period_days: number | string; drift_threshold_pct: string; tracker_show_bank_projection_banner: string; @@ -332,7 +333,8 @@ export default function SettingsPage() { const DEFAULTS: SettingsState = { currency: 'USD', date_format: 'MM/DD/YYYY', - grace_period_days: 3, + week_start: '0', + grace_period_days: 5, drift_threshold_pct: '5', tracker_show_bank_projection_banner: 'true', tracker_bank_projection_banner_snoozed_until: '', @@ -363,6 +365,7 @@ export default function SettingsPage() { const buildPayload = (s: SettingsState) => ({ currency: s.currency, date_format: s.date_format, + week_start: s.week_start, grace_period_days: s.grace_period_days, drift_threshold_pct: s.drift_threshold_pct, tracker_show_bank_projection_banner: s.tracker_show_bank_projection_banner, @@ -476,6 +479,18 @@ export default function SettingsPage() { + + + + {/* Tracker Layout */} diff --git a/services/userSettings.js b/services/userSettings.js index 1ccd0bb..d896229 100644 --- a/services/userSettings.js +++ b/services/userSettings.js @@ -5,6 +5,7 @@ const { getDb, getSetting } = require('../db/database'); const USER_SETTING_KEYS = [ 'currency', 'date_format', + 'week_start', 'grace_period_days', 'notify_days_before', 'drift_threshold_pct', @@ -29,6 +30,7 @@ const USER_SETTING_KEYS = [ ]; const USER_SETTING_DEFAULTS = { + week_start: '0', search_bars_collapsed: 'false', bank_auto_categorize_merchants: 'true', tracker_show_bank_projection_banner: 'true',