WFDASH-WEBAPP/src/lib/use-boolean-preference.ts
Dave 17580b5280 feat(invoicing): implement detailed view toggle for supplier and PO number
- Added a switch to toggle the visibility of supplier names and PO numbers in the invoicing page, enhancing user experience by allowing operators to view detailed information as needed.
- Updated the `RowIdentity` component to conditionally render supplier and PO details based on the toggle state.
- Introduced localStorage persistence for the detailed view preference, ensuring user settings are retained across sessions.
- Enhanced tests to verify the functionality of the detailed view toggle and its impact on displayed data.
2026-08-16 11:01:52 +02:00

92 lines
3 KiB
TypeScript

"use client";
import { useCallback, useState } from "react";
/**
* localStorage-backed persistence for a boolean UI preference.
*
* Mirror of `useSplitRatio` for boolean-shaped state. Used by the
* invoicing page's "Detailed" toggle so each operator's preferred
* row density sticks across reloads.
*
* SSR-safe: `localStorage` is gated on `typeof window`. The hook
* reads the stored value lazily on first render and ignores writes
* during SSR so server-rendered HTML matches the client's initial
* paint (avoids hydration warnings).
*
* Read semantics: a stored value of `"true"` parses to `true`;
* anything else (including `"false"`, an unparseable string, or a
* throwing `localStorage`) is treated as "nothing stored" and the
* caller's `initial` wins. This is the conservative pick — if a
* future migration changes the storage format, a stale string falls
* back to the default rather than mis-interpreted as `false`.
*/
const isBrowser = typeof window !== "undefined";
function readStored(key: string): boolean | null {
if (!isBrowser) return null;
try {
const raw = window.localStorage.getItem(key);
if (raw === null) return null;
if (raw === "true") return true;
if (raw === "false") return false;
return null;
} catch {
// Safari private mode / quota errors / SecurityError — treat as no value.
return null;
}
}
function writeStored(key: string, value: boolean): void {
if (!isBrowser) return;
try {
window.localStorage.setItem(key, value ? "true" : "false");
} catch {
// Best-effort persistence. A failed write must not break the UI —
// the in-memory state is the source of truth for the current session.
}
}
export type UseBooleanPreferenceOptions = {
/** localStorage key. Namespace it per-feature to avoid collisions. */
storageKey: string;
/** Value to use when nothing is stored. */
initial: boolean;
};
export type UseBooleanPreferenceResult = {
value: boolean;
setValue: (next: boolean) => void;
};
/**
* Persistent boolean preference hook.
*
* Returns the current value and a setter. The setter persists the
* new value synchronously, so flipping the toggle and reloading the
* page lands on the chosen state on the very first render (no
* flicker — `useState`'s initialiser reads localStorage).
*/
export function useBooleanPreference({
storageKey,
initial,
}: UseBooleanPreferenceOptions): UseBooleanPreferenceResult {
// Lazy init from localStorage so the very first render uses the
// stored value when present. We don't need a follow-up `useEffect`
// to re-read on mount — `useState`'s initialiser already runs once
// per mount with the current localStorage value.
const [value, setValueState] = useState<boolean>(() => {
const stored = readStored(storageKey);
return stored ?? initial;
});
const setValue = useCallback(
(next: boolean) => {
setValueState(next);
writeStored(storageKey, next);
},
[storageKey],
);
return { value, setValue };
}