import { useEffect, useState } from 'react'; import { useParams } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { api, ApiError, type ApiResponse } from '../lib/api'; import PageHeader from '../components/ui/PageHeader'; import StatusBadge from '../components/ui/StatusBadge'; import SearchableSelect from '../components/ui/SearchableSelect'; import { formatDate, formatNumber } from '../lib/utils'; import { ArrowUturnRightIcon } from '@heroicons/react/24/outline'; import { useElapsed } from '../hooks/useElapsed'; import type { SessionAreaRecord, StaffSessionDetail, TreatmentDevice, TreatmentFormField } from '../types'; const BASE = '/api/v1/dashboard/staff'; /** * صفحهٔ انجام جلسه. * * فرمِ هر ناحیه از `forms` می‌آید که سرور از روی نوع منبع ساخته — پنل فیلدها را حدس * نمی‌زند، پس افزودن دستگاه تازه در تنظیمات همین‌جا هم ظاهر می‌شود بدون تغییر کد. */ export default function StaffSessionDetailPage() { const { uuid = '' } = useParams(); const qc = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ['staff-session', uuid], queryFn: () => api.get>(`${BASE}/treatment-session/${uuid}`), enabled: uuid !== '', }); const session = data?.data; const [note, setNote] = useState(''); const refresh = () => { qc.invalidateQueries({ queryKey: ['staff-session', uuid] }); qc.invalidateQueries({ queryKey: ['staff-treatment-sessions'] }); }; const fail = (e: unknown, fallback: string) => toast.error(e instanceof ApiError ? e.message : fallback); const startSession = useMutation({ mutationFn: () => api.post>(`${BASE}/treatment-session/${uuid}/start`, {}), onSuccess: () => { toast.success('جلسه شروع شد'); refresh(); }, onError: (e) => fail(e, 'شروع جلسه ناموفق بود'), }); const finishSession = useMutation({ mutationFn: () => api.post>( `${BASE}/treatment-session/${uuid}/finish`, { note: note || undefined }, ), onSuccess: (res) => { const left = res?.data?.unsettled_areas ?? 0; toast.success(left > 0 ? `جلسه بسته شد — ${formatNumber(left)} ناحیه تکمیل نشده بود` : 'جلسه با موفقیت تمام شد'); refresh(); }, onError: (e) => fail(e, 'اتمام جلسه ناموفق بود'), }); const skipArea = useMutation({ mutationFn: (payload: { areaUuid: string; note: string }) => api.post>(`${BASE}/session-area/${payload.areaUuid}/skip`, { note: payload.note || undefined, }), onSuccess: () => { toast.success('این ناحیه صرف‌نظر شد'); refresh(); }, onError: (e) => fail(e, 'صرف‌نظر از ناحیه ناموفق بود'), }); // اشتباهِ حین کار: ناحیه‌ای که زودتر بسته شده تا وقتی جلسه باز است برمی‌گردد. const reopenArea = useMutation({ mutationFn: (areaUuid: string) => api.post>(`${BASE}/session-area/${areaUuid}/reopen`, {}), onSuccess: () => { toast.success('ناحیه دوباره باز شد'); refresh(); }, onError: (e) => fail(e, 'باز کردن ناحیه ناموفق بود'), }); const completeArea = useMutation({ mutationFn: (payload: { areaUuid: string; body: Record }) => api.post>(`${BASE}/session-area/${payload.areaUuid}/complete`, payload.body), onSuccess: () => { toast.success('اطلاعات ناحیه ثبت شد'); refresh(); }, onError: (e) => fail(e, 'ثبت اطلاعات ناحیه ناموفق بود'), }); // پیش از هر return زودهنگام: ترتیب hookها باید در هر رندر یکی باشد. const elapsed = useElapsed(session?.started_at ?? null, session?.finished_at ?? null); if (isLoading) { return
در حال بارگذاری...
; } if (!session) { return
جلسه یافت نشد
; } const areas = session.areas ?? []; const settled = areas.filter((a) => a.status === 'completed' || a.status === 'skipped').length; const started = session.started_at !== null; const finished = session.status === 'done'; return ( <>
{session.case.service.name}
{session.appointment && تاریخ: {formatDate(session.appointment.slot_start)}} {session.performed_by && پرسنل: {session.performed_by.name}} {formatNumber(settled)} از {formatNumber(areas.length)} ناحیه انجام شده {elapsed && ( {session.finished_at === null ? 'در حال انجام: ' : 'مدت جلسه: '}{elapsed} )}
{!started && !finished && ( )}

نواحی این جلسه

{areas.length === 0 && (
برای دیدن نواحی، ابتدا جلسه را شروع کنید.
)} {areas.map((area) => ( skipArea.mutate({ areaUuid: area.uuid, note })} onReopen={() => reopenArea.mutate(area.uuid)} onComplete={(body) => completeArea.mutate({ areaUuid: area.uuid, body })} saving={completeArea.isPending} reopening={reopenArea.isPending} /> ))}
{started && !finished && (

یادداشت و اتمام جلسه