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={
<>
-
-