From f2c3f20ccf92e9a994a3a796144f6ee47b8adae1 Mon Sep 17 00:00:00 2001 From: Chan Date: Sat, 15 Aug 2026 11:16:59 +0530 Subject: [PATCH] feat(web): let a site set its reporting timezone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `reporting_timezone` has been a per-site column and a field on the settings PATCH since ADR-0044, but nothing in the dashboard could set it — so a site's numbers were always cut on whichever zone the viewer's browser happened to be in, and two people reading the same range could see different days. It sits beside the reporting currency because it answers the same kind of question — what the numbers are expressed in — and because the two are read together when a total looks a day out. Left unset it keeps today's behaviour, so no existing site changes. Co-Authored-By: Claude Opus 5 --- .../components/dashboard/settings-board.tsx | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/apps/web/components/dashboard/settings-board.tsx b/apps/web/components/dashboard/settings-board.tsx index dac35c4..03623c5 100644 --- a/apps/web/components/dashboard/settings-board.tsx +++ b/apps/web/components/dashboard/settings-board.tsx @@ -36,6 +36,7 @@ import { import { TeamSection } from "@/components/dashboard/team-section"; import { WidgetsSection } from "@/components/dashboard/widgets-section"; import { CopyButton } from "@/components/ui/copy-button"; +import { TimezoneSelect } from "@/components/ui/timezone-select"; import { Button } from "@/components/ui/button"; import { SaveButton } from "@/components/ui/save-button"; import { @@ -506,6 +507,18 @@ function General({ site }: { site: SiteSummary }) { ); const [converting, setConverting] = React.useState(false); + /** + * The reporting timezone. `null` means "use the viewer's browser zone", which + * is what an unset site did before this field existed and still does. + * + * It sits beside the currency because it is the same kind of decision — what + * the numbers are expressed in — and because the two are read together when + * someone is working out why a total looks off by a day. + */ + const [timezone, setTimezone] = React.useState( + site.reporting_timezone ?? null + ); + // The tab's two reads, started here so they run in parallel and share one // reveal moment. Their panels take the answers as props. const publicSettings = usePublicDashboard(site.site_id); @@ -522,12 +535,14 @@ function General({ site }: { site: SiteSummary }) { name: name.trim(), domains: nextDomains, reporting_currency: currency, + reporting_timezone: timezone, }); setName(next.name); setDomains(next.domains); setPendingRemoval([]); setCurrency(next.reporting_currency); setAppliedCurrency(next.reporting_currency); + setTimezone(next.reporting_timezone ?? null); } else { setAppliedCurrency(currency); } @@ -742,6 +757,7 @@ function General({ site }: { site: SiteSummary }) { but it is the only field here that restates stored history, and the copy under it changes with the choice rather than sitting still. */} +