feat(mobile-dashboard): add light/dark toggle, switch accent to red, bigger numbers

Boss phone dashboard (/) improvements:

- Add a Sun/Moon theme toggle button next to the refresh button.
  Uses the existing next-themes provider so the choice stays in
  sync with the Settings → Mode toggle.
- Replace the cyan accent (cyan-400/cyan-500/*) with red (red-400
  for chrome, red-500 on tinted backgrounds) for stronger contrast
  on both light and dark themes. The active today/week/month tab
  and per-rep/per-driver avatar circles use bg-red-500/15 +
  text-red-500 for high-contrast highlights.
- Bump OrderTotal values from text-xl/font-semibold to
  text-3xl/font-bold, and MiniMetric values from text-sm/font-
  semibold to text-lg/font-bold, so the month-of-loaded/claimed/
  delivered/invoiced counts are easier to read at a glance.
This commit is contained in:
David 2026-09-07 15:02:10 +02:00
parent 20304442a7
commit 43cdf02ae5

View file

@ -3,7 +3,8 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { RefreshCw, X } from "lucide-react";
import { RefreshCw, X, Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import getPocketBase from "@/lib/pocketbase";
import type {
BossDashboardData,
@ -56,6 +57,19 @@ export default function BossDashboardView() {
realtimeStatusRef.current = realtimeStatus;
}, [realtimeStatus]);
// Light/dark toggle — next-themes mounted check avoids SSR/hydration
// mismatch (next-themes returns "light" before mount regardless of the
// persisted value).
const { theme: colorMode, setTheme: setColorMode } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setMounted(true);
}, []);
const toggleColorMode = useCallback(() => {
setColorMode(colorMode === "dark" ? "light" : "dark");
}, [colorMode, setColorMode]);
const fetchData = useCallback(async () => {
// Pre-check auth — if PB has no valid token, redirect via the router so
// we preserve the in-memory state of the dashboard (search, tabs, sheet)
@ -225,7 +239,7 @@ export default function BossDashboardView() {
<header className="flex items-start justify-between gap-3">
<div className="min-w-0">
<h1 className="text-xl font-semibold tracking-tight">
Good {greeting()}, <span className="text-cyan-400">{firstName}</span>.
Good {greeting()}, <span className="text-red-400">{firstName}</span>.
</h1>
<div className="mt-0.5 flex items-center gap-2 text-xs text-muted-foreground">
<span
@ -247,15 +261,29 @@ export default function BossDashboardView() {
</span>
</div>
</div>
<button
type="button"
onClick={() => void fetchData()}
disabled={loading}
aria-label="Refresh"
className="grid h-10 w-10 shrink-0 place-items-center rounded-full border border-cyan-500/30 text-cyan-400 active:bg-cyan-500/10 disabled:opacity-50"
>
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
</button>
<div className="flex items-center gap-2 shrink-0">
<button
type="button"
onClick={toggleColorMode}
aria-label={mounted && colorMode === "dark" ? "Switch to light mode" : "Switch to dark mode"}
className="grid h-10 w-10 place-items-center rounded-full border border-red-500/30 text-red-400 active:bg-red-500/10"
>
{mounted && colorMode === "dark" ? (
<Sun className="h-4 w-4" />
) : (
<Moon className="h-4 w-4" />
)}
</button>
<button
type="button"
onClick={() => void fetchData()}
disabled={loading}
aria-label="Refresh"
className="grid h-10 w-10 shrink-0 place-items-center rounded-full border border-red-500/30 text-red-400 active:bg-red-500/10 disabled:opacity-50"
>
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
</button>
</div>
</header>
{/* Time tab bar */}
@ -269,7 +297,7 @@ export default function BossDashboardView() {
onClick={() => selectWindow(w)}
className={`flex-1 rounded-lg px-3 py-2 text-sm font-medium capitalize transition-colors ${
activeWindow === w
? "bg-cyan-500/15 text-cyan-300"
? "bg-red-500/15 text-red-500"
: "text-muted-foreground hover:text-foreground"
}`}
>
@ -315,13 +343,13 @@ export default function BossDashboardView() {
className="w-full rounded-xl border border-border/60 bg-card p-3 text-left transition-colors hover:bg-card/70 active:bg-card/80 disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="flex items-center gap-3">
<div className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-cyan-500/15 text-cyan-300 text-sm font-semibold">
<div className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-red-500/15 text-red-500 text-sm font-semibold">
{(rep.name[0] ?? "?").toUpperCase()}
</div>
<div className="min-w-0 flex-1">
<div className="text-sm font-medium truncate">{rep.name}</div>
<div className="mt-0.5 grid grid-cols-3 gap-1 text-center">
<MiniMetric label="loaded" value={rep.quotesDoneFor} color="text-cyan-400" />
<MiniMetric label="loaded" value={rep.quotesDoneFor} color="text-red-400" />
<MiniMetric label="claimed" value={rep.quotesLoaded} color="text-emerald-400" />
<MiniMetric label="orders" value={rep.ordersPlaced} color="text-orange-400" />
</div>
@ -388,7 +416,7 @@ export default function BossDashboardView() {
className="flex items-center justify-between gap-3 rounded-xl border border-border/60 bg-card p-3"
>
<div className="flex items-center gap-3 min-w-0">
<div className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-cyan-500/15 text-cyan-300 text-sm font-semibold">
<div className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-red-500/15 text-red-500 text-sm font-semibold">
{(drv.name[0] ?? "?").toUpperCase()}
</div>
<div className="min-w-0">
@ -465,7 +493,7 @@ export default function BossDashboardView() {
}}
className={`flex-1 rounded-md px-3 py-1.5 text-sm font-medium capitalize transition-colors ${
sheetKind === k
? "bg-cyan-500/15 text-cyan-300"
? "bg-red-500/15 text-red-500"
: "text-muted-foreground hover:text-foreground"
}`}
>
@ -569,7 +597,7 @@ function OrderTotal({
<div className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
{label}
</div>
<div className={`mt-1 text-xl font-semibold tabular-nums ${color}`}>
<div className={`mt-1 text-3xl font-bold tabular-nums ${color}`}>
{value.toLocaleString()}
</div>
</div>
@ -587,10 +615,10 @@ function MiniMetric({
}) {
return (
<div>
<div className={`text-sm font-semibold tabular-nums leading-none ${color}`}>
<div className={`text-lg font-bold tabular-nums leading-none ${color}`}>
{value.toLocaleString()}
</div>
<div className="mt-0.5 text-[9px] uppercase tracking-wider text-muted-foreground leading-none">
<div className="mt-1 text-[9px] uppercase tracking-wider text-muted-foreground leading-none">
{label}
</div>
</div>