Files
clinicpro/assets/admin/hooks/useUrlState.ts
T
hamed 684cf1f783 feat: implement useUrlState hook for managing URL-based state in admin pages
- Refactor multiple admin pages (BlogsPage, ClinicsPage, DoctorsPage, etc.) to utilize the new useUrlState hook for managing pagination, search, and filter states via URL.
- Ensure that the state persists in the URL, allowing users to return to the same state when navigating back from detail pages.
- Update relevant components to handle state changes appropriately and maintain clean URLs by removing default values.
- Add SlotPicker component for selecting appointment slots based on availability.
- Create tests for useUrlState to validate its functionality and ensure correct behavior when interacting with the URL.
- Update API documentation to reflect changes in appointment creation and slot selection processes.
2026-07-29 21:04:40 +03:30

53 lines
2.5 KiB
TypeScript

import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useSearchParams } from 'react-router-dom';
/**
* وضعیت لیست (جستجو، فیلترها، صفحه، تب) در query string زندگی می‌کند، نه در state
* کامپوننت. دلیلش «بازگشت»‌ است: `navigate(-1)` همان URL قبلی را برمی‌گرداند، پس هر
* چیزی که در URL باشد سرِ جایش برمی‌گردد و هر چیزی که در state باشد با remount می‌پرد.
* سود جانبی: رفرش و اشتراک لینک هم همان نما را می‌دهد.
*
* مقدارِ برابرِ پیش‌فرض از URL حذف می‌شود تا آدرس تمیز بماند.
*/
export function useUrlState<T extends Record<string, string>>(defaults: T) {
const [params, setParams] = useSearchParams();
const state = useMemo(() => {
const out = { ...defaults };
(Object.keys(defaults) as (keyof T)[]).forEach((k) => {
const raw = params.get(String(k));
if (raw !== null) out[k] = raw as T[keyof T];
});
return out;
}, [params, defaults]);
// `setSearchParams` مثل setState صف نمی‌شود؛ دو فراخوانی در یک handler، اگر هر دو از
// `params` فعلی شروع کنند، همدیگر را پاک می‌کنند. این ref آخرین مقدارِ نوشته‌شده را
// نگه می‌دارد تا فراخوانی‌های پشت‌سرهم روی هم انباشته شوند.
const latest = useRef(params);
useEffect(() => { latest.current = params; }, [params]);
/**
* چند کلید را با هم می‌نویسد. `replace` پیش‌فرض است تا تایپ در فیلد جستجو
* تاریخچه را پر نکند و یک بار «بازگشت» کاربر را از صفحه بیرون ببرد.
*/
const setState = useCallback((patch: Partial<T>, opts?: { push?: boolean }) => {
const next = new URLSearchParams(latest.current);
Object.entries(patch).forEach(([k, v]) => {
const value = v === undefined ? '' : String(v);
if (value === '' || value === defaults[k as keyof T]) next.delete(k);
else next.set(k, value);
});
latest.current = next;
setParams(next, { replace: !opts?.push });
}, [setParams, defaults]);
return [state, setState] as const;
}
/** عدد صفحه از URL — همیشه ≥ ۱. */
export function pageOf(value: string): number {
return Math.max(1, Number(value) || 1);
}