From 14f4da34834fb9c418caa467851062cae9d1dac2 Mon Sep 17 00:00:00 2001 From: lorentz Date: Thu, 7 May 2026 07:54:21 -0400 Subject: [PATCH] feat(07.1-04): migrate mobile finance + ticket detail to useUserTimezone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - app/mobile/finance/page.tsx: thread tz through fmtDate, setLastSync, monthLabel — 3 formatter callsites now pass timeZone - app/mobile/tickets/[id]/page.tsx: thread tz through fmtDate (5 callsites) and TimelineCard prop - All toLocaleDateString / toLocaleString calls in both files now render in user.timezone, not browser local zone - Resolves TZ-02 on the directly-reported bug surface (mobile finance + ticket detail) --- app/mobile/finance/page.tsx | 14 ++++++++------ app/mobile/tickets/[id]/page.tsx | 20 +++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/mobile/finance/page.tsx b/app/mobile/finance/page.tsx index 7343a41..6d6b666 100644 --- a/app/mobile/finance/page.tsx +++ b/app/mobile/finance/page.tsx @@ -20,6 +20,7 @@ import { CollapsibleTrigger, } from '@/components/ui/collapsible'; import { Button } from '@/components/ui/button'; +import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; interface AgingBucket { balance: number; count: number; } interface FinanceData { @@ -39,11 +40,12 @@ interface FinanceData { function fmt$(n: number) { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(n); } -function fmtDate(ts: string) { - return new Date(ts).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); +function fmtDate(ts: string, tz: string) { + return new Date(ts).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: tz }); } export default function MobileFinance() { + const tz = useUserTimezone(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -72,7 +74,7 @@ export default function MobileFinance() { if (r.ok) { const d = await r.json(); const ts = d.lastSync?.invoices; - setLastSync(ts ? new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : null); + setLastSync(ts ? new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: tz }) : null); } } catch {} }; @@ -315,7 +317,7 @@ export default function MobileFinance() { amountTone={inv.status === 'Overdue' ? 'destructive' : 'default'} secondary={ <> - #{inv.doc_number} · Due {fmtDate(inv.due_date)} + #{inv.doc_number} · Due {fmtDate(inv.due_date, tz)} {inv.days_overdue > 0 && ( ({inv.days_overdue}d overdue) )} @@ -354,7 +356,7 @@ export default function MobileFinance() { primary={p.customer_ref_name} amount={fmt$(p.total_amt)} amountTone="positive" - secondary={fmtDate(p.txn_date)} + secondary={fmtDate(p.txn_date, tz)} /> )) )} @@ -370,7 +372,7 @@ export default function MobileFinance() {

Revenue — last 12 months

{monthly_revenue.map((m) => { - const monthLabel = new Date(m.month).toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + const monthLabel = new Date(m.month).toLocaleDateString('en-US', { month: 'short', year: 'numeric', timeZone: tz }); return (

{monthLabel}

diff --git a/app/mobile/tickets/[id]/page.tsx b/app/mobile/tickets/[id]/page.tsx index e85b2da..48e3df7 100644 --- a/app/mobile/tickets/[id]/page.tsx +++ b/app/mobile/tickets/[id]/page.tsx @@ -6,6 +6,7 @@ import { ArrowLeft, RefreshCw, Clock, FileText, Timer, CheckCircle2, ChevronDown, ChevronRight, User, Briefcase, AlertCircle, EyeOff, Eye, ExternalLink, Mail, AlignLeft, Code2, } from 'lucide-react'; +import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; const PRIORITY_LABEL: Record = { 1: 'Standard', 2: 'Medium', 3: 'Standard', 4: 'Critical', @@ -45,8 +46,8 @@ type TimelineItem = | { kind: 'time'; ts: string; data: { id: number; hours_worked: string; notes: string; billable: boolean; resource_name: string } } | { kind: 'resolved'; ts: string }; -function fmtDate(ts: string) { - return new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' }); +function fmtDate(ts: string, tz: string) { + return new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: tz }); } function fmtHours(h: string | number) { const n = parseFloat(String(h)); @@ -91,7 +92,7 @@ function renderContent(s: string): React.ReactNode[] { return nodes; } -function TimelineCard({ item, defaultOpen = false }: { item: TimelineItem; defaultOpen?: boolean }) { +function TimelineCard({ item, tz, defaultOpen = false }: { item: TimelineItem; tz: string; defaultOpen?: boolean }) { const [open, setOpen] = useState(defaultOpen); if (item.kind === 'created') { @@ -104,7 +105,7 @@ function TimelineCard({ item, defaultOpen = false }: { item: TimelineItem; defau
-

{fmtDate(item.ts)}

+

{fmtDate(item.ts, tz)}

Ticket created

@@ -120,7 +121,7 @@ function TimelineCard({ item, defaultOpen = false }: { item: TimelineItem; defau
-

{fmtDate(item.ts)}

+

{fmtDate(item.ts, tz)}

Ticket resolved

@@ -138,7 +139,7 @@ function TimelineCard({ item, defaultOpen = false }: { item: TimelineItem; defau
-

{fmtDate(item.ts)}

+

{fmtDate(item.ts, tz)}