diff --git a/assets/admin/pages/PatientDetailPage.test.tsx b/assets/admin/pages/PatientDetailPage.test.tsx index 647890a5..bad053b4 100644 --- a/assets/admin/pages/PatientDetailPage.test.tsx +++ b/assets/admin/pages/PatientDetailPage.test.tsx @@ -66,4 +66,13 @@ describe('PatientDetailPage (پرونده تب‌دار)', () => { expect(await screen.findByRole('button', { name: /آپلود فایل جدید/ })).toBeInTheDocument(); expect(await screen.findByText('هنوز فایلی ضمیمه نشده است.')).toBeInTheDocument(); }); + + it('opens the add-exam modal on the medical-record tab', async () => { + renderDetail(); + await screen.findByText('ساغر صابری'); + fireEvent.click(screen.getByText('پرونده پزشکی')); + fireEvent.click(await screen.findByRole('button', { name: /ثبت معاینه جدید/ })); + // modal opens with a title field + expect(await screen.findByPlaceholderText('مثلاً: معاینه اولیه')).toBeInTheDocument(); + }); }); diff --git a/assets/admin/pages/PatientDetailPage.tsx b/assets/admin/pages/PatientDetailPage.tsx index eb9eaa53..e2bd7f6a 100644 --- a/assets/admin/pages/PatientDetailPage.tsx +++ b/assets/admin/pages/PatientDetailPage.tsx @@ -7,12 +7,16 @@ import { PhoneArrowUpRightIcon, PaperClipIcon, ClipboardDocumentListIcon, ArrowUpTrayIcon, TrashIcon, DocumentIcon, } from '@heroicons/react/24/outline'; +import { PlusIcon } from '@heroicons/react/24/outline'; import { toast } from 'sonner'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import type { PatientRecord } from '../types'; import { useAuthStore } from '../stores/authStore'; import { formatDate } from '../lib/utils'; +import Modal from '../components/ui/Modal'; +import ConfirmDialog from '../components/ui/ConfirmDialog'; +import PersianDateInput from '../components/ui/PersianDateInput'; type TabKey = 'services' | 'info' | 'appointments' | 'payments' | 'wallet' | 'messages' | 'callcenter' | 'attach' | 'records'; @@ -125,6 +129,8 @@ export default function PatientDetailPage() { row={(a) => ({ title: a.service_name || a.doctor_name || 'نوبت', meta: a.date ? formatDate(a.date) : (a.starts_at ? formatDate(a.starts_at) : ''), badge: a.status_label || a.status })} /> ) : tab === 'attach' ? ( + ) : tab === 'records' ? ( + ) : ( t.key === tab)!.label} /> )} @@ -214,6 +220,110 @@ function AttachmentsTab({ uuid }: { uuid: string }) { ); } +interface MedicalItem { uuid: string; title: string; body?: string | null; recorded_at: number } + +/** پرونده پزشکی — medical exam entries: list + add/edit modal + delete. */ +function MedicalRecordsTab({ uuid }: { uuid: string }) { + const qc = useQueryClient(); + const [modal, setModal] = useState<'create' | MedicalItem | null>(null); + const [delTarget, setDelTarget] = useState(null); + const [title, setTitle] = useState(''); + const [date, setDate] = useState(''); + const [body, setBody] = useState(''); + + const { data, isLoading } = useQuery>({ + queryKey: ['patient-medical', uuid], + queryFn: () => api.get(`/api/v1/patient/${uuid}/medical-records`), + }); + const items = data?.data ?? []; + const invalidate = () => qc.invalidateQueries({ queryKey: ['patient-medical', uuid] }); + + const open = (m?: MedicalItem) => { + setTitle(m?.title ?? ''); setBody(m?.body ?? ''); + setDate(m?.recorded_at ? new Date(m.recorded_at * 1000).toISOString().slice(0, 10) : ''); + setModal(m ?? 'create'); + }; + + const save = useMutation({ + mutationFn: () => { + const payload = { title, body: body || null, recorded_at: date ? Math.floor(new Date(date).getTime() / 1000) : undefined }; + return modal === 'create' + ? api.post(`/api/v1/patient/${uuid}/medical-record`, payload) + : api.patch(`/api/v1/patient/medical-record/${(modal as MedicalItem).uuid}`, payload); + }, + onSuccess: () => { invalidate(); setModal(null); toast.success('ذخیره شد'); }, + onError: (e: any) => toast.error(e.message), + }); + const del = useMutation({ + mutationFn: (u: string) => api.delete(`/api/v1/patient/medical-record/${u}`), + onSuccess: () => { invalidate(); setDelTarget(null); toast.success('حذف شد'); }, + onError: (e: any) => { toast.error(e.message); setDelTarget(null); }, + }); + + return ( +
+
+ +
+ + {isLoading ? ( +
در حال بارگذاری...
+ ) : items.length === 0 ? ( +
معاینه‌ای ثبت نشده است.
+ ) : ( +
+ {items.map((m) => ( +
+
+
+
{m.title}
+
{formatDate(m.recorded_at)}
+ {m.body &&
{m.body}
} +
+
+ + +
+
+
+ ))} +
+ )} + + setModal(null)} title={modal === 'create' ? 'ثبت معاینه جدید' : 'ویرایش معاینه'}> +
+
+ +
setTitle(e.target.value)} placeholder="مثلاً: معاینه اولیه" autoFocus />
+
+
+ + +
+
+ +