From 0b5d017b14b89739bfb7a33efd0759a199ed885c Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Fri, 12 Jun 2026 22:58:28 +0330 Subject: [PATCH] feat: enhance DoctorDetailPage with edit form helpers and gender selection options --- assets/admin/pages/DoctorDetailPage.tsx | 396 +++++++++++++++++++----- 1 file changed, 317 insertions(+), 79 deletions(-) diff --git a/assets/admin/pages/DoctorDetailPage.tsx b/assets/admin/pages/DoctorDetailPage.tsx index f7c3c1b4..466168aa 100644 --- a/assets/admin/pages/DoctorDetailPage.tsx +++ b/assets/admin/pages/DoctorDetailPage.tsx @@ -12,6 +12,7 @@ import { BuildingOfficeIcon, MapPinIcon, AcademicCapIcon, UserIcon, PlusIcon, CameraIcon, ChevronDownIcon, ChevronRightIcon, ChevronLeftIcon, CheckCircleIcon, XMarkIcon, ExclamationTriangleIcon, + HeartIcon, CheckIcon, IdentificationIcon, DocumentTextIcon, } from '@heroicons/react/24/outline'; import { StarIcon as StarSolid } from '@heroicons/react/24/solid'; import { toast } from 'sonner'; @@ -1784,6 +1785,221 @@ function ScheduleSection({ doctorUuid }: { doctorUuid: string }) { ); } +// ── Edit form helpers ────────────────────────────────────────────────────── + +const EDIT_DEGREE_OPTIONS = [ + { value: 'general', label: 'پزشک عمومی' }, + { value: 'specialist', label: 'متخصص' }, + { value: 'expert', label: 'فوق تخصص' }, + { value: 'subspecialistplus', label: 'فلوشیپ' }, +]; + +const EDIT_GENDER_OPTIONS = [ + { value: 'man', label: 'مرد', icon: '♂' }, + { value: 'woman', label: 'زن', icon: '♀' }, +]; + +function EditField({ label, required, error, hint, children }: { + label: string; required?: boolean; error?: string; hint?: string; children: React.ReactNode; +}) { + return ( +
+ + {children} + {hint && !error &&

{hint}

} + {error &&

{error}

} +
+ ); +} + +function EditSectionHeader({ icon, title, badge }: { icon: React.ReactNode; title: string; badge?: string }) { + return ( +
+
+ {icon} +
+ {title} + {badge && ( + + {badge} + + )} +
+ ); +} + +interface EditSelectedEntry { parentId: number | null; childId: number } + +function EditSpecialtyPicker({ selected, onChange, specialties }: { + selected: number[]; onChange: (ids: number[]) => void; + specialties: SpecialtyOpt[]; +}) { + const [activeParentId, setActiveParentId] = useState(null); + + const parents = useMemo(() => specialties.filter(s => s.parent_id === null), [specialties]); + const childMap = useMemo(() => { + const m: Record = {}; + specialties.forEach(s => { + if (s.parent_id !== null) { + if (!m[s.parent_id]) m[s.parent_id] = []; + m[s.parent_id].push(s); + } + }); + return m; + }, [specialties]); + + const activeChildren = activeParentId !== null ? (childMap[activeParentId] ?? []) : []; + + const selectedChildOfParent = (parentId: number): number | null => { + const kids = childMap[parentId] ?? []; + return kids.find(k => selected.includes(k.id))?.id ?? null; + }; + + const isRootSelected = (parentId: number): boolean => + !(childMap[parentId]?.length) && selected.includes(parentId); + + const selectChild = (child: SpecialtyOpt) => { + const parentId = child.parent_id!; + if (selected.includes(child.id)) { + onChange([]); + } else { + onChange([parentId, child.id]); + } + }; + + const selectRoot = (root: SpecialtyOpt) => { + if (selected.includes(root.id)) { + onChange([]); + } else { + setActiveParentId(null); + onChange([root.id]); + } + }; + + const removeEntry = () => { onChange([]); setActiveParentId(null); }; + + const chips: EditSelectedEntry[] = useMemo(() => { + return selected + .map(id => { + const s = specialties.find(x => x.id === id); + if (!s) return null; + if (s.parent_id !== null) return { parentId: s.parent_id, childId: s.id }; + const hasSelectedChild = (childMap[s.id] ?? []).some(k => selected.includes(k.id)); + if (!hasSelectedChild && !(childMap[s.id]?.length)) return { parentId: null, childId: s.id }; + return null; + }) + .filter(Boolean) as EditSelectedEntry[]; + }, [selected, specialties, childMap]); + + return ( +
+ {chips.length > 0 && ( +
+ {chips.map(({ parentId, childId }) => { + const child = specialties.find(x => x.id === childId); + const parent = parentId !== null ? specialties.find(x => x.id === parentId) : null; + if (!child) return null; + const label = parent ? `${parent.name} — ${child.name}` : child.name; + return ( + + {label} + + + ); + })} +
+ )} +
+
+
+ گروه تخصصی +
+ {parents.map(p => { + const hasChildren = (childMap[p.id]?.length ?? 0) > 0; + const childSel = hasChildren ? selectedChildOfParent(p.id) : null; + const rootSel = !hasChildren && isRootSelected(p.id); + const isMarked = childSel !== null || rootSel; + const isActive = activeParentId === p.id; + const hasSelection = selected.length > 0; + const isDisabled = hasSelection && !isMarked; + return ( + + ); + })} +
+
+ {activeParentId === null ? ( +
+ + یک گروه تخصصی انتخاب کنید +
+ ) : activeChildren.length === 0 ? ( +
+ زیرمجموعه‌ای یافت نشد +
+ ) : ( + <> +
+ انتخاب تخصص — یک مورد +
+ {activeChildren.map(s => { + const checked = selected.includes(s.id); + return ( + + ); + })} + + )} +
+
+
+ ); +} + // ── Edit schema ──────────────────────────────────────────────────────────── const editSchema = z.object({ @@ -1817,6 +2033,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil const [addrModalOpen, setAddrModalOpen] = useState(false); const [editingAddr, setEditingAddr] = useState(null); const [deletingAddrId, setDeletingAddrId] = useState(null); + const [editGender, setEditGender] = useState<'man' | 'woman' | ''>(''); // ── Queries ── @@ -1866,6 +2083,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil specialties: doctor.specialties.map(s => Number(s.id)), services: doctor.expertise.map(s => Number(s.id)), }); + setEditGender((doctor.gender as any) ?? ''); } }, [doctor, reset]); @@ -1884,7 +2102,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil const updateMut = useMutation({ mutationFn: (body: EditForm) => api.patch>(`/api/v1/doctor/${uuid}`, { title: body.name, - gender: body.gender || undefined, + gender: editGender || undefined, degree: body.degree || undefined, medical_system_code: body.medical_system_code || undefined, mobile_number: body.mobile_number || undefined, @@ -2260,92 +2478,112 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil setEditOpen(false)} footer={ <> - - + } > -
updateMut.mutate(v))} className="space-y-5"> -
-

اطلاعات پایه

-
- - -
-
-
- - ( - field.onChange(v ?? '')} - placeholder="انتخاب کنید" - isClearable - /> - )} /> -
-
- - ( - field.onChange(v ?? '')} - placeholder="انتخاب کنید" - isClearable - /> - )} /> -
-
- - -
-
- - -
-
-
- -