import { useEffect, useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { PlusIcon, TrashIcon, XMarkIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import Switch from './ui/Switch';
import SearchableSelect from './ui/SearchableSelect';
import Input from './ui/Input';
import ConfirmDialog from './ui/ConfirmDialog';
import { api, ApiError, type ApiResponse } from '../lib/api';
interface ProtocolStep {
step_number: number;
offset_days: number;
}
interface ProtocolStaff {
uuid: string;
name: string;
}
interface TreatmentProtocol {
uuid: string;
/** آیا هیچ منبعی این سرویس را ارائه میدهد؛ نبودش رزرو را قفل نمیکند ولی باید دیده شود. */
service_has_resources?: boolean;
active: boolean;
total_sessions: number;
supervisor: { uuid: string; name: string } | null;
steps: ProtocolStep[];
staff: ProtocolStaff[];
}
interface StaffRow {
uuid: string;
full_name: string;
active?: boolean;
}
interface DoctorRow {
uuid: string;
name: string;
}
/** پیشفرضِ روشنکردن سوییچ: کوتاهترین دورهای که معنی دارد. */
const DEFAULT_STEPS: ProtocolStep[] = [
{ step_number: 1, offset_days: 0 },
{ step_number: 2, offset_days: 30 },
];
const MIN_STEPS = 2;
/** خط جداکنندهٔ بخشها — سه بخشِ مستقل نباید بههم چسبیده به نظر برسند. */
function Divider() {
return
;
}
/**
* «طول درمان» یک سرویس.
*
* وجودِ پروتکل خودش سوییچ است — سرویس بدون پروتکل تکجلسهای است — پس روشنکردن یعنی
* ساختن و خاموشکردن یعنی حذف. چون خاموشکردن برگشتناپذیر است و گامها و پرسنل را با
* خودش میبرد، با تأیید صریح انجام میشود نه با یک کلیک.
*
* فاصلهٔ هر گام از **جلسهٔ قبل** است، نه از شروع دوره، چون فاصلهٔ درمان به آخرین جلسه
* گره خورده نه به روز باز شدن پرونده.
*/
export default function TreatmentProtocolTab({ serviceUuid, canEdit }: {
serviceUuid: string;
canEdit: boolean;
}) {
const qc = useQueryClient();
const { data, isLoading, isError, refetch } = useQuery({
queryKey: ['treatment-protocol', serviceUuid],
queryFn: () => api.get>(
`/api/v1/service-item/${serviceUuid}/treatment-protocol`,
),
});
const { data: staffData } = useQuery({
queryKey: ['staff-list-for-protocol'],
queryFn: () => api.get>('/api/v1/staff'),
staleTime: 60_000,
});
const { data: doctorData } = useQuery({
queryKey: ['my-clinic-doctors'],
queryFn: () => api.get>('/api/v1/my/clinic-doctors'),
staleTime: 60_000,
});
const protocol = data?.data ?? null;
const [enabled, setEnabled] = useState(false);
const [steps, setSteps] = useState(DEFAULT_STEPS);
const [staffUuids, setStaffUuids] = useState([]);
const [supervisor, setSupervisor] = useState(null);
const [confirmOff, setConfirmOff] = useState(false);
useEffect(() => {
setEnabled(protocol !== null);
setSteps(protocol?.steps?.length ? protocol.steps : DEFAULT_STEPS);
setStaffUuids(protocol?.staff?.map((s) => s.uuid) ?? []);
setSupervisor(protocol?.supervisor?.uuid ?? null);
}, [protocol]);
const staffOptions = (staffData?.data ?? [])
.filter((s) => s.active !== false)
.map((s) => ({ value: s.uuid, label: s.full_name }));
const doctorOptions = (doctorData?.data?.data ?? []).map((d) => ({ value: d.uuid, label: d.name }));
const save = useMutation({
mutationFn: () => api.put>(
`/api/v1/service-item/${serviceUuid}/treatment-protocol`,
{
steps: steps.map((s, i) => ({ step_number: i + 1, offset_days: s.offset_days })),
staff_uuids: staffUuids,
supervisor_doctor_uuid: supervisor,
},
),
onSuccess: () => {
toast.success('طول درمان ذخیره شد');
qc.invalidateQueries({ queryKey: ['treatment-protocol', serviceUuid] });
},
onError: (e) => toast.error(e instanceof ApiError ? e.message : 'ذخیرهٔ طول درمان ناموفق بود'),
});
const remove = useMutation({
mutationFn: () => api.delete>(`/api/v1/service-item/${serviceUuid}/treatment-protocol`),
onSuccess: () => {
toast.success('طول درمان خاموش شد');
setConfirmOff(false);
qc.invalidateQueries({ queryKey: ['treatment-protocol', serviceUuid] });
},
onError: (e) => toast.error(e instanceof ApiError ? e.message : 'خاموشکردن ناموفق بود'),
});
/**
* روشنکردن بیخطر است و فوری اعمال میشود؛ خاموشکردن پروتکل را با همهٔ گامها و
* پرسنلش پاک میکند، پس تأیید میخواهد.
*/
const toggle = (on: boolean) => {
if (on) {
setEnabled(true);
return;
}
if (protocol === null) {
setEnabled(false);
return;
}
setConfirmOff(true);
};
const setOffset = (index: number, value: number) => {
setSteps((prev) => prev.map((s, i) => (i === index ? { ...s, offset_days: value } : s)));
};
const addStep = () => {
setSteps((prev) => [
...prev,
{ step_number: prev.length + 1, offset_days: prev[prev.length - 1]?.offset_days || 30 },
]);
};
const removeStep = (index: number) => {
setSteps((prev) => (prev.length <= MIN_STEPS ? prev : prev.filter((_, i) => i !== index)));
};
const addStaff = (uuid: string | number | null) => {
const id = uuid === null ? '' : String(uuid);
if (id && !staffUuids.includes(id)) setStaffUuids((prev) => [...prev, id]);
};
if (isLoading) {
return
در حال بارگذاری...
;
}
/**
* بدون این، شکستِ کوئری `protocol = null` میداد و صفحه سوییچِ خاموش نشان میداد —
* یعنی به مدیر میگفت این سرویس تکجلسهای است، در حالی که فقط خواندن شکست خورده.
*/
if (isError) {
return (
طول درمان این سرویس خوانده نشد
تا وقتی خطا برطرف نشده، وضعیت واقعی این سرویس معلوم نیست.
هیچ دستگاهی به این سرویس وصل نیست
رزرو قفل نمیشود، ولی منشی میتواند این سرویس را روی هر منبعی ثبت کند و اپراتور فرم
دستگاه درست را نمیبیند. در «منابع» مشخص کنید کدام دستگاهها این سرویس را میدهند.
)}
{enabled && (
<>
جلسات دوره{' '}
({steps.length} جلسه)
فاصلهٔ هر جلسه از جلسهٔ قبل حساب میشود، نه از شروع دوره. اگر بیمار دیر بیاید،
بقیهٔ دوره هم جابهجا میشود.
{steps.map((step, index) => (
جلسهٔ {index + 1}
{index === 0 ? (
شروع دوره — همان روز اولین نوبت
) : (
<>
setOffset(index, Number(e.target.value || 0))}
aria-label={`فاصلهٔ جلسهٔ ${index + 1} از جلسهٔ قبل به روز`}
style={{ width: 96 }}
/>
روز بعد از جلسهٔ قبل
>
)}
{canEdit && index > 0 && (
)}
))}
{canEdit && (
)}
پرسنل مجاز
منشی هنگام رزرو فقط از میان همینها انتخاب میکند.
{canEdit && (
!staffUuids.includes(String(o.value)))}
value={null}
onChange={addStaff}
placeholder="افزودن پرسنل..."
ariaLabel="افزودن پرسنل مجاز"
/>
)}
{staffUuids.length === 0 ? (
حداقل یک پرسنل الزامی است — بدون آن ذخیره نمیشود.
) : (