Comprehensive changes including: - Refactor picking slip line handling for multiple document types across print endpoints - Add new `/api/v1/stock-purchases/picking-slip` API for stock booking picking slips - Implement reusable Splitter component and useSplitRatio hook for resizable panels - Add floating/fullscreen invoice viewer to collection confirm page - Add real-time stock booking update event for invoicing tab - Add full test coverage for all new components and APIs - Include temporary chat attachment images for testing purposes
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useState } from "react";
|
|
|
|
/**
|
|
* localStorage-backed persistence for a numeric ratio in [min, max].
|
|
*
|
|
* Used by the collection-confirm PO card to remember the operator's
|
|
* split-view ratio (items table ↔ invoice preview) across reloads.
|
|
* The ratio is a fraction of the parent container width — e.g. 0.42
|
|
* means "items table takes 42% of the card body width".
|
|
*
|
|
* 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).
|
|
*
|
|
* Clamping is the hook's job — callers shouldn't have to defend
|
|
* against stale localStorage values that fell outside the new bounds
|
|
* (e.g. after a layout refactor narrowed the min).
|
|
*/
|
|
const isBrowser = typeof window !== "undefined";
|
|
|
|
function readStored(key: string): number | null {
|
|
if (!isBrowser) return null;
|
|
try {
|
|
const raw = window.localStorage.getItem(key);
|
|
if (raw === null) return null;
|
|
const parsed = Number.parseFloat(raw);
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
} catch {
|
|
// Safari private mode / quota errors / SecurityError — treat as no value.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function writeStored(key: string, value: number): void {
|
|
if (!isBrowser) return;
|
|
try {
|
|
window.localStorage.setItem(key, String(value));
|
|
} 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 UseSplitRatioOptions = {
|
|
/** localStorage key. Namespace it per-feature to avoid collisions. */
|
|
storageKey: string;
|
|
/** Initial ratio when nothing is stored (default 0.42 — items ≈ preview). */
|
|
initial: number;
|
|
/** Inclusive lower bound. The hook clamps writes + reads to this. */
|
|
min?: number;
|
|
/** Inclusive upper bound. The hook clamps writes + reads to this. */
|
|
max?: number;
|
|
};
|
|
|
|
export type UseSplitRatioResult = {
|
|
ratio: number;
|
|
setRatio: (next: number) => void;
|
|
};
|
|
|
|
/**
|
|
* Persistent split-ratio hook.
|
|
*
|
|
* Returns the current ratio and a setter. The setter clamps to the
|
|
* configured min/max before persisting, so the splitter can pass
|
|
* raw cursor positions without worrying about overflow.
|
|
*/
|
|
export function useSplitRatio({
|
|
storageKey,
|
|
initial,
|
|
min = 0.15,
|
|
max = 0.85,
|
|
}: UseSplitRatioOptions): UseSplitRatioResult {
|
|
const clamp = useCallback(
|
|
(value: number) => Math.min(Math.max(value, min), max),
|
|
[min, max],
|
|
);
|
|
|
|
// Lazy init from localStorage so the very first render uses the
|
|
// stored value when present (no flicker on a reload). 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, so a hot-reload or fresh page load lands on
|
|
// the right value on the very first render.
|
|
const [ratio, setRatioState] = useState<number>(() => {
|
|
const stored = readStored(storageKey);
|
|
if (stored === null) return clamp(initial);
|
|
return clamp(stored);
|
|
});
|
|
|
|
const setRatio = useCallback(
|
|
(next: number) => {
|
|
const clamped = clamp(next);
|
|
setRatioState(clamped);
|
|
writeStored(storageKey, clamped);
|
|
},
|
|
[clamp, storageKey],
|
|
);
|
|
|
|
return { ratio, setRatio };
|
|
}
|