import { useCallback, useEffect, useState } from "react"; /** * Read the current `?ack=…` query param off `window.location.search`. * Returns `null` when the param is absent or empty. * * `URLSearchParams` is the standard, locale-free way to parse query * strings in the browser. Using it (rather than hand-rolled string * slicing) means we correctly handle multiple params and percent-encoded * characters in ack ids without surprises. * * Param name is `?ack=` — chosen to mirror the existing `?provider=…`, * `?claim=…`, and `?remit=…` drilldown conventions (one-word token, * alphabetical brevity, no collision with the existing `Acks` table * columns). The id value is treated as a string in the URL so deep * links (`/acks?ack=42`) round-trip identically across the app, even * though the backend `Ack.id` is a numeric — `AckDrawer` does the * `Number()` coercion when calling `api.getAck`. */ function readAckId(): string | null { const params = new URLSearchParams(window.location.search); const value = params.get("ack"); return value === "" ? null : value; } /** * Build the URL we want to push/replace into history. * * - `ackId === null` → drop the `?ack=` param, preserving any * other params (e.g. `?page=2&ack=…` keeps `page=2`). * - `ackId !== null` → set the param to the new id, also preserving * any other params. * * We return `pathname + search + hash` (a relative URL) rather than the * full href — `history.pushState` accepts a relative URL and rewriting * only the relative form keeps the document's origin stable. */ function buildUrl(ackId: string | null): string { const url = new URL(window.location.href); if (ackId === null) { url.searchParams.delete("ack"); } else { url.searchParams.set("ack", ackId); } return url.pathname + url.search + url.hash; } /** * Per-ack detail drawer URL state (AckDrawer). * * Mirrors `useProviderDrawerUrlState` / `useRemitDrawerUrlState` but * for the 999-ACK drawer — reads `?ack=` from the URL on mount and * keeps the value in sync with history as the drawer is opened and * closed. * * - `ackId`: the id parsed from the URL (or `null` when the param * is absent). React state so consumers re-render on changes. * - `open(id)`: pushes a NEW history entry with `?ack={id}` — so the * browser Back button returns to the previous page (e.g. the * acks list) and not just to the previously-open ack. * - `setAckId(id)`: REPLACES the current history entry — kept for * symmetry with the other drawer hooks so a future j/k nav * handler doesn't have to special-case ack ids. * - `close()`: pushes a NEW entry that strips the param, so Back * from the closed drawer returns to whatever page the user was * on before opening the drawer. * * The hook subscribes to `popstate` so that browser Back/Forward * (which fire popstate rather than our own pushState) propagate into * the React state. Without this, hitting Back would change the URL * but leave the drawer open on the stale id. */ export function useAckDrawerUrlState(): { ackId: string | null; open: (id: string) => void; close: () => void; setAckId: (id: string) => void; } { const [ackId, setAckIdState] = useState(() => readAckId()); const open = useCallback((id: string) => { window.history.pushState(null, "", buildUrl(id)); setAckIdState(id); }, []); const setAckId = useCallback((id: string) => { window.history.replaceState(null, "", buildUrl(id)); setAckIdState(id); }, []); const close = useCallback(() => { window.history.pushState(null, "", buildUrl(null)); setAckIdState(null); }, []); useEffect(() => { const onPopState = () => { setAckIdState(readAckId()); }; window.addEventListener("popstate", onPopState); return () => window.removeEventListener("popstate", onPopState); }, []); return { ackId, open, close, setAckId }; }