fix(admin): SQL GROUP BY in reports, hydration in settings, mobile tabs scroll

- reports.ts: wrap window-function arg in SUM() so it operates on the
  per-group aggregate instead of the raw c.id column (PostgreSQL
  rejects window funcs over non-grouped, non-aggregated columns).
- SettingsClient.tsx: read URL hash in useEffect instead of a lazy
  useState initializer to avoid server/client first-render mismatch
  on tab content (was triggering full client re-render in /admin/settings).
- layout.tsx: add pt-16 lg:pt-0 to page-content for mobile header clearance.
- ReportsDashboard.tsx: memoize DateRange on primitive inputs to break
  useEffect → setState → re-render loop caused by buildRange returning
  a new object every call.
- AdminFilterTabs.tsx: add overflow-x-auto so wide tab rows scroll on
  mobile instead of overflowing.
- use-media-query.ts: always start as false for hydration safety; callers
  handle the mobile/unknown branch first.
This commit is contained in:
Nora
2026-06-26 21:43:05 -06:00
parent 137b5e7ffe
commit 6de112467a
6 changed files with 43 additions and 17 deletions
+10 -7
View File
@@ -135,14 +135,17 @@ export default function SettingsClient({
paymentSettings,
currentUser,
}: Props) {
// Initial tab is determined by URL hash if present, otherwise "general".
// No useEffect needed — we read window.location.hash during the lazy
// initializer on the client. On the server we default to "general".
const [activeTab, setActiveTab] = useState<Tab>(() => {
if (typeof window === "undefined") return "general";
// Server renders the default tab; client picks up the URL hash after mount
// so the initial server HTML matches the first client render (avoids
// hydration mismatch). Reading window.location.hash in a lazy initializer
// is unsafe because the server has no hash and would render a different
// tab than the client.
const [activeTab, setActiveTab] = useState<Tab>("general");
useEffect(() => {
const hash = window.location.hash.slice(1);
return TABS.find((t) => t.hash === hash)?.id ?? "general";
});
const tab = TABS.find((t) => t.hash === hash)?.id;
if (tab) setActiveTab(tab);
}, []);
return (
<main className="min-h-screen bg-[var(--admin-bg)]">