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.
This commit is contained in:
hamed
2026-07-29 21:04:40 +03:30
parent e0e8fbd1e4
commit 684cf1f783
20 changed files with 668 additions and 79 deletions
+33 -6
View File
@@ -39,11 +39,38 @@ const toEpoch = (isoDate: string, time: string) => tehranWallClockToUnix(isoDate
*/
export async function findRecordUuid(mobile: string): Promise<string | null> {
const res: any = await api.get(
`/api/v1/patient?search=${encodeURIComponent(mobile)}&limit=1`,
`/api/v1/patients?search=${encodeURIComponent(mobile)}&limit=1`,
);
return res?.data?.[0]?.uuid ?? null;
}
/**
* پروندهٔ بیمارِ یک نوبت — اگر هنوز ساخته نشده باشد، از روی خودِ نوبت ساخته می‌شود.
* «ثبت سرویس» روی نوبتی که صاحبش هنوز پرونده ندارد نباید بن‌بست باشد؛ ساخت پرونده
* روی سرور idempotent است و پروندهٔ موجود را برمی‌گرداند.
*/
export async function ensureRecordUuid(appointment: {
patient_mobile?: string | null;
patient_name?: string | null;
patient_national_code?: string | null;
}): Promise<string | null> {
const mobile = (appointment.patient_mobile ?? "").trim();
if (mobile === "") return null;
const found = await findRecordUuid(mobile);
if (found) return found;
const res: any = await api.post("/api/v1/patient", {
mobile,
name: (appointment.patient_name ?? "").trim(),
...(appointment.patient_national_code
? { national_code: appointment.patient_national_code }
: {}),
});
return res?.data?.uuid ?? null;
}
/**
* «شارژ کیف پول» accent link (appointment create/edit forms) — deep-links the
* patient's wallet tab, where the manual top-up modal lives.
@@ -120,14 +147,14 @@ export default function AppointmentActionsMenu({
const goToServiceRegistration = async () => {
setOpen(false);
try {
const recordUuid = await findRecordUuid(appointment.patient_mobile);
const recordUuid = await ensureRecordUuid(appointment);
if (!recordUuid) {
toast.error("پرونده‌ای برای این بیمار یافت نشد");
toast.error("این نوبت شماره تماس ندارد؛ ابتدا نوبت را ویرایش کنید");
return;
}
navigate(`/admin/patients/${recordUuid}/session/new`);
} catch {
toast.error("خطا در یافتن پرونده بیمار");
} catch (e: any) {
toast.error(e?.message || "خطا در یافتن پرونده بیمار");
}
};
@@ -672,7 +699,7 @@ export function ReplaceAppointmentModal({
queryKey: ["replace-patients", patientSearch],
queryFn: () =>
api.get(
`/api/v1/patient?search=${encodeURIComponent(patientSearch)}&limit=10`,
`/api/v1/patients?search=${encodeURIComponent(patientSearch)}&limit=10`,
),
enabled: patientSearch.trim().length >= 2,
});