From 9e8064e101d4927c33c1d32afe63e4fdc164523e Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Fri, 12 Jun 2026 19:32:56 +0330 Subject: [PATCH] feat: enhance SpecialtyPicker to support parent-child relationship for specialties --- assets/admin/pages/CategoriesPage.tsx | 11 +++- assets/admin/pages/DoctorFormPage.tsx | 89 +++++++++++++++++++++------ 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/assets/admin/pages/CategoriesPage.tsx b/assets/admin/pages/CategoriesPage.tsx index 9d31b5fd..6b9f066e 100644 --- a/assets/admin/pages/CategoriesPage.tsx +++ b/assets/admin/pages/CategoriesPage.tsx @@ -457,9 +457,16 @@ function SpecialtiesTab() { queryFn: () => api.get>(`/api/v1/admin/specialties?page=${page}&limit=20&search=${encodeURIComponent(search)}`), }); + const { data: rootData } = useQuery({ + queryKey: ['admin-specialties-roots'], + queryFn: () => api.get>('/api/v1/admin/specialties?limit=100'), + staleTime: 5 * 60 * 1000, + }); + const items = data?.data ?? []; const total = data?.meta?.totalRecords ?? 0; - const parentMap = Object.fromEntries(items.map((s) => [s.id, s.name])); + const rootItems = (rootData?.data ?? []).filter((s) => s.parent_id === null); + const parentMap = Object.fromEntries((rootData?.data ?? []).map((s) => [s.id, s.name])); const { register, handleSubmit, reset, control, formState: { errors } } = useForm({ resolver: zodResolver(specialtySchema), @@ -491,7 +498,7 @@ function SpecialtiesTab() { reset({ name: s.name, weight: String(s.weight), status: String(s.status), parent_id: s.parent_id ?? null }); }; - const parentOptions = items.filter((s) => s.parent_id === null).map((s) => ({ value: s.id, label: s.name })); + const parentOptions = rootItems.map((s) => ({ value: s.id, label: s.name })); const columns: Column[] = [ { key: 'id', header: 'شناسه', render: (s) => {s.id} }, diff --git a/assets/admin/pages/DoctorFormPage.tsx b/assets/admin/pages/DoctorFormPage.tsx index 2ee6d16b..5aca2f03 100644 --- a/assets/admin/pages/DoctorFormPage.tsx +++ b/assets/admin/pages/DoctorFormPage.tsx @@ -12,7 +12,7 @@ import { formatNumber } from '../lib/utils'; // ── Types ──────────────────────────────────────────────────────────────── -interface SpecialtyOption { id: number; uuid: string; name: string } +interface SpecialtyOption { id: number; uuid: string; name: string; parent_id: number | null } // ── Schema ─────────────────────────────────────────────────────────────── @@ -47,21 +47,26 @@ function SpecialtyPicker({ selected, onChange, specialties }: { selected: number[]; onChange: (ids: number[]) => void; specialties: SpecialtyOption[]; }) { - const [q, setQ] = useState(''); - const filtered = useMemo(() => specialties.filter(s => s.name.includes(q)), [specialties, q]); + const [activeParentId, setActiveParentId] = useState(null); + + const parents = useMemo( + () => specialties.filter(s => s.parent_id === null), + [specialties] + ); + + const children = useMemo( + () => activeParentId !== null ? specialties.filter(s => s.parent_id === activeParentId) : [], + [specialties, activeParentId] + ); const toggle = (id: number) => onChange(selected.includes(id) ? selected.filter(x => x !== id) : [...selected, id]); return (
-
- setQ(e.target.value)} - placeholder="جستجوی تخصص..." - className="cp-input text-sm h-8" /> -
+ {/* Selected chips */} {selected.length > 0 && ( -
+
{selected.map(id => { const s = specialties.find(x => x.id === id); if (!s) return null; @@ -74,15 +79,63 @@ function SpecialtyPicker({ selected, onChange, specialties }: { })}
)} -
- {filtered.slice(0, 60).map(s => ( - - ))} - {filtered.length === 0 &&

نتیجه‌ای یافت نشد

} + +
+ {/* Parents column */} +
+
+ گروه تخصص +
+ {parents.map(p => ( + + ))} +
+ + {/* Children column */} +
+ {activeParentId === null ? ( +
+ یک گروه تخصص را از سمت راست انتخاب کنید +
+ ) : children.length === 0 ? ( +
+ زیرمجموعه‌ای یافت نشد +
+ ) : ( + <> +
+ انتخاب تخصص +
+ {children.map(s => ( + + ))} + + )} +
);