import { useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { ChevronDownIcon, ClipboardDocumentListIcon, CpuChipIcon } from '@heroicons/react/24/outline'; import { api } from '../../lib/api'; import type { ApiResponse } from '../../lib/api'; import StatusBadge from '../ui/StatusBadge'; import NewAppointmentModal from '../appointments/NewAppointmentModal'; import { useResourceBookingServices } from '../../hooks/useResourceBookingServices'; import { useClinicContext } from '../../hooks/useClinicContext'; import { formatDateTime, formatNumber } from '../../lib/utils'; import type { TreatmentCaseSummary, SessionAreaRecord } from '../../types'; interface PlanSession { uuid: string; session_number: number; total_sessions: number; status: string; planned_at: number | null; is_estimate: boolean; appointment: { uuid: string; slot_start: number } | null; performed_by: { uuid: string; name: string } | null; areas?: SessionAreaRecord[]; } interface PlanResponse { case: TreatmentCaseSummary; /** دستگاهِ دوره — همان که جلسهٔ قبل رویش انجام شد. */ resource: { uuid: string; name: string } | null; sessions: PlanSession[]; } const CASE_STATUS_LABEL: Record = { active: 'در جریان', completed: 'تمام شده', abandoned: 'رها شده', }; /** `planned_at` (ثانیه) → `YYYY-MM-DD` میلادی، همان چیزی که مودال می‌خواهد. */ function isoDay(ts: number): string { const d = new Date(ts * 1000); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; } /** * درمان‌های چندجلسه‌ایِ همین بیمار، با تقویم کل دوره. * * تاریخ‌ها از `/plan` می‌آیند و بخشی‌شان تخمینی‌اند — همان‌جا برچسب می‌خورند تا با * نوبتِ ثبت‌شده اشتباه گرفته نشوند. کارت نوبت نیست؛ دکمه‌اش نوبت می‌سازد. */ export default function PatientTreatmentTab({ recordUuid }: { recordUuid: string }) { const { data, isLoading, isError, refetch } = useQuery({ queryKey: ['patient-treatment-cases', recordUuid], queryFn: () => api.get>( `/api/v1/treatment-cases?record=${encodeURIComponent(recordUuid)}`, ), enabled: recordUuid !== '', staleTime: 30_000, }); const cases = data?.data ?? []; if (isLoading) { return (
{[0, 1].map((i) =>
)}
); } if (isError) { return (
خواندن درمان‌های این بیمار ناموفق بود.
); } if (cases.length === 0) { return (
این بیمار درمان چندجلسه‌ای ندارد.
پرونده وقتی ساخته می‌شود که نوبتِ سرویسی با «طول درمان» قطعی شود.
); } return (
{cases.map((c) => )}
); } function CasePlan({ summary }: { summary: TreatmentCaseSummary }) { // دورهٔ باز پیش‌فرض باز است و دورهٔ بسته جمع — کارِ پیشِ رو مهم‌تر از سابقه است. const [open, setOpen] = useState(summary.status === 'active'); const { data, isLoading, isError, refetch } = useQuery({ queryKey: ['treatment-case-plan', summary.uuid], queryFn: () => api.get>(`/api/v1/treatment-case/${summary.uuid}/plan`), enabled: open, staleTime: 30_000, }); const sessions = data?.data?.sessions ?? []; return (
{open && (
{isLoading ? (
در حال خواندن تقویم دوره…
) : isError ? (
خواندن تقویم این دوره ناموفق بود.
) : (
{sessions.map((s) => ( ))}
)}
)}
); } function SessionCard({ session: s, summary, resource }: { session: PlanSession; summary: TreatmentCaseSummary; resource: { uuid: string; name: string } | null; }) { const [booking, setBooking] = useState(false); const qc = useQueryClient(); const clinicUuid = useClinicContext(); const supervisor = summary.supervisor; // سرویس‌ها از تقویم منبع می‌آیند نه از برنامهٔ پزشک — دورهٔ لیزر روی دستگاه رزرو // می‌شود و `useDoctorBookingServices` برای این پزشک چیزی برنمی‌گرداند. const { services } = useResourceBookingServices(booking ? resource?.uuid : null); const settled = ['done', 'cancelled', 'no_show'].includes(s.status); const bookable = !settled && s.appointment === null && summary.status === 'active'; const areas = s.areas ?? []; return (
جلسهٔ {formatNumber(s.session_number)} از {formatNumber(s.total_sessions)} {s.planned_at === null ? '—' : formatDateTime(s.planned_at)} {/* تخمین با واقعیت یکی نیست و باید در خودِ کارت معلوم باشد. */} {s.is_estimate && ( تخمینی )} {s.performed_by && ( پرسنل: {s.performed_by.name} )}
{/* آنچه واقعاً انجام شد: ناحیه، دستگاه و خوانده‌هایش. */} {areas.length > 0 && (
{areas.map((a) => (
{a.area.name} {a.resource && ( {a.resource.name} )} {a.parameters && Object.entries(a.parameters).map(([k, v]) => ( {k}: {String(v)} ))} {a.note && یادداشت: {a.note}}
))}
)} {bookable && ( /* بدون دستگاه، فرم سرویسی برای انتخاب ندارد؛ دکمهٔ خاموشِ بی‌توضیح بدتر از نبودنش است، پس دلیلش نوشته می‌شود. */ resource === null ? ( هنوز هیچ جلسه‌ای از این دوره روی دستگاهی انجام نشده — نوبت را از صفحهٔ نوبت‌ها ثبت کنید. ) : ( ) )} {booking && resource && ( setBooking(false)} onSuccess={() => { qc.invalidateQueries({ queryKey: ['treatment-case-plan', summary.uuid] }); qc.invalidateQueries({ queryKey: ['patient-treatment-cases'] }); }} /> )}
); }