import { useMemo, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { api, ApiError } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import Modal from './ui/Modal'; import Input from './ui/Input'; import SearchableSelect from './ui/SearchableSelect'; import { formatNumber } from '../lib/utils'; import type { TreatmentCaseDetail, TreatmentCaseStatus } from '../types'; const STATUS_OPTIONS: Array<{ value: TreatmentCaseStatus; label: string }> = [ { value: 'active', label: 'در جریان' }, { value: 'completed', label: 'تمام شده' }, { value: 'abandoned', label: 'رها شده' }, ]; interface DoctorRow { uuid: string; name?: string | null; full_name?: string | null } interface StaffRow { uuid: string; full_name: string } /** * ویرایش پروندهٔ درمان. * * پرونده بعد از باز شدن سند است نه فرم، پس فقط چیزهایی اینجا هستند که واقعاً وسط دوره * عوض می‌شوند. سرور جلوی ویرایشی را که سابقه را بازنویسی کند می‌گیرد؛ فرم آن خطا را * نشان می‌دهد، تکرارش نمی‌کند. */ export default function TreatmentCaseEditModal({ caseUuid, onClose }: { caseUuid: string; onClose: () => void; }) { const qc = useQueryClient(); const { data, isLoading, isError, refetch } = useQuery({ queryKey: ['treatment-case', caseUuid], queryFn: () => api.get>(`/api/v1/treatment-case/${caseUuid}`), }); // همان اندپوینتی که صفحهٔ نوبت‌ها می‌خواند: فقط پزشکانِ مجازِ همین محیط. // پاسخش دو لایه تو در تو است (`data.data`) — الگوی شناخته‌شدهٔ همین اندپوینت. const doctorsQ = useQuery>({ queryKey: ['clinic-doctors-lite'], queryFn: () => api.get('/api/v1/my/clinic-doctors'), staleTime: 60_000, }); const staffQ = useQuery>({ queryKey: ['staff-list'], queryFn: () => api.get('/api/v1/staff'), staleTime: 60_000, }); const detail = data?.data; return ( {isLoading ? (
در حال بارگذاری...
) : isError || !detail ? (
خواندن پرونده ناموفق بود.
) : ( { qc.invalidateQueries({ queryKey: ['treatment-cases'] }); qc.invalidateQueries({ queryKey: ['treatment-case', caseUuid] }); onClose(); }} onClose={onClose} /> )}
); } function EditForm({ detail, doctors, doctorsLoading, staff, staffLoading, onSaved, onClose }: { detail: TreatmentCaseDetail; doctors: DoctorRow[]; doctorsLoading: boolean; staff: StaffRow[]; staffLoading: boolean; onSaved: () => void; onClose: () => void; }) { const [status, setStatus] = useState(detail.status); const [supervisor, setSupervisor] = useState(detail.supervisor?.uuid ?? null); const [total, setTotal] = useState(String(detail.total_sessions)); const [areas, setAreas] = useState( detail.areas.map((a) => a.category_uuid).filter((u): u is string => u !== null), ); const [staffUuids, setStaffUuids] = useState(detail.assigned_staff.map((s) => s.uuid)); // ناحیه‌ای که دسته‌اش حذف شده در سابقه هست ولی دیگر قابل انتخاب نیست — باید دیده // شود، وگرنه کاربر فکر می‌کند فرم آن را انداخته است. const orphanAreas = useMemo( () => detail.areas.filter((a) => a.category_uuid === null).map((a) => a.name), [detail.areas], ); const minTotal = detail.completed_sessions; const save = useMutation({ mutationFn: () => api.patch>(`/api/v1/treatment-case/${detail.uuid}`, { status, supervisor_doctor_uuid: supervisor, area_uuids: areas, staff_uuids: staffUuids, total_sessions: Number(total) || 0, }), onSuccess: () => { toast.success('پرونده به‌روزرسانی شد'); onSaved(); }, onError: (e: unknown) => toast.error(e instanceof ApiError ? e.message : 'ویرایش پرونده ناموفق بود'), }); const toggleArea = (uuid: string) => setAreas((prev) => prev.includes(uuid) ? prev.filter((u) => u !== uuid) : [...prev, uuid]); const toggleStaff = (uuid: string) => setStaffUuids((prev) => prev.includes(uuid) ? prev.filter((u) => u !== uuid) : [...prev, uuid]); const totalValid = Number(total) >= 2 && Number(total) <= 60; const valid = areas.length > 0 && totalValid; return (
بیمار: {detail.patient.name || 'بدون نام'} سرویس: {detail.service.name}
{STATUS_OPTIONS.map((o) => ( ))}
({ value: d.uuid, label: d.name ?? d.full_name ?? '' }))} value={supervisor} onChange={(v) => setSupervisor(v === null ? null : String(v))} placeholder="بدون پزشک ناظر" isLoading={doctorsLoading} isClearable height={40} />
{detail.available_areas.map((a) => { const on = areas.includes(a.uuid); return ( ); })}
{areas.length === 0 && حداقل یک ناحیه لازم است} {orphanAreas.length > 0 && ( نواحیِ «{orphanAreas.join('، ')}» در سابقه هستند ولی دسته‌بندی‌شان حذف شده و قابل انتخاب نیستند. )}
{/* «در حال خواندن» با «تعریف نشده» یکی نیست؛ بدون این تفکیک، فهرستِ هنوز نیامده به‌صورت «پرسنلی ندارید» خوانده می‌شد. */} {staffLoading ? ( در حال خواندن فهرست پرسنل… ) : staff.length === 0 ? ( پرسنلی در این محیط تعریف نشده است. ) : (
{staff.map((p) => { const on = staffUuids.includes(p.uuid); return ( ); })}
)} {staffUuids.length === 0 ? 'خالی یعنی هر پرسنلِ مجازِ این سرویس می‌تواند جلسات را انجام دهد.' : 'جلسات این پرونده فقط در صف همین افراد دیده می‌شود.'} {detail.performed_by.length > 0 && ( تا اینجا انجام‌دهنده: {detail.performed_by.map((p) => p.name).join('، ')} )}
setTotal(e.target.value.replace(/\D/g, '').slice(0, 2))} />
{totalValid ? `${formatNumber(minTotal)} جلسه انجام شده. جلسه‌ای که نوبت دارد یا انجام شده حذف نمی‌شود.` : 'تعداد جلسات باید بین ۲ و ۶۰ باشد'}
); }