Overhaul the app's color system by replacing all hardcoded Tailwind color classes with semantic theme variables to ensure consistent theming across all components, pages, and tests. Additional changes: - Add assigned date support for order item status updates: add `assignedDate` field to the order item status PATCH API, implement server-side normalization/validation, and add corresponding tests - Refactor purchase order PDF workflows to use the consolidated `purchase_orders.documents` field instead of the legacy separate `purchase_order_pdfs` collection - Fix quote number parsing and formatting to support both dashed (Q-YYYY-MM-NNN) and dashless (QYYYYMMNNN) formats - Rename `driver_pool` item status to `collection_pool` across the codebase - Add null handling for order item quantities and pricing to display "—" for missing values - Improve filter tab styling: make mobile tabs scrollable, prevent text wrapping, hide scrollbar - Add new utility scripts, API routes, migrations, and polish small UI/UX fixes including tsconfig updates and theme hook improvements
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { DEFAULT_THEME_ID, getThemeById } from "@/lib/themes";
|
|
|
|
const STORAGE_KEY = "wfdash-theme";
|
|
|
|
/**
|
|
* useThemeId — reads / writes the active visual theme id.
|
|
*
|
|
* The theme id is applied to <html> via the `data-theme` attribute; the
|
|
* matching tokens live in src/styles/themes.css.
|
|
*
|
|
* On the first render the value is `DEFAULT_THEME_ID` — the bootstrap
|
|
* script in app/layout.tsx applies the persisted value before hydration,
|
|
* then a tiny effect in this hook syncs the React state to whatever the
|
|
* DOM already says. This is the only safe way to avoid a hydration
|
|
* mismatch without rendering nothing on the first frame.
|
|
*/
|
|
export function useThemeId() {
|
|
const [themeId, setThemeIdState] = useState<string>(DEFAULT_THEME_ID);
|
|
|
|
useEffect(() => {
|
|
// Pull whatever the bootstrap script already set on <html> so the
|
|
// first React render matches the DOM. Intentionally a setState-in-effect:
|
|
// we need to read the DOM after hydration, not before.
|
|
const fromDom = document.documentElement.getAttribute("data-theme");
|
|
if (fromDom && fromDom !== themeId) {
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
setThemeIdState(fromDom);
|
|
}
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
const setThemeId = useCallback((id: string) => {
|
|
const t = getThemeById(id);
|
|
setThemeIdState(t.id);
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, t.id);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
if (typeof document !== "undefined") {
|
|
const root = document.documentElement;
|
|
root.setAttribute("data-theme", t.id);
|
|
root.style.setProperty("--radius", t.radius);
|
|
root.style.setProperty("--font-heading", t.fontHeading);
|
|
root.style.setProperty("--font-body", t.fontBody);
|
|
}
|
|
}, []);
|
|
|
|
return { themeId, setThemeId };
|
|
}
|