import React, { useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; import PageHeader from '../components/ui/PageHeader'; import DataTable, { type Column } from '../components/ui/DataTable'; import Modal from '../components/ui/Modal'; import SearchableSelect from '../components/ui/SearchableSelect'; import { ActiveBadge } from '../components/ui/StatusBadge'; import { useUrlState } from '../hooks/useUrlState'; import { usePermissions } from '../hooks/usePermissions'; import { useCourseProtocols } from '../hooks/useCourses'; import { api, type ApiResponse } from '../lib/api'; import type { CourseProtocol, ServiceItem } from '../types'; interface StepDraft { session_number: number; energy: string; } interface Draft { uuid?: string; service_uuid: string; session_count: number; min_days: number; ideal_days: number; max_days: number; prefer_same_resource: boolean; steps: StepDraft[]; } const EMPTY: Draft = { service_uuid: '', session_count: 6, min_days: 21, ideal_days: 28, max_days: 45, prefer_same_resource: true, steps: [], }; /** * پروتکل دوره per سرویس. * * سه فاصله سه معنا دارند و ترتیبشان اجباری است؛ فرم همان‌جا می‌گوید، نه اینکه بگذارد * کاربر ذخیره کند و ۴۲۲ بگیرد. */ export default function CourseProtocolsPage() { const { protocols, loading, create, update, deactivate } = useCourseProtocols(); const { can } = usePermissions(); const canManage = can('appointment_settings', 'update'); const [urlState, setUrlState] = useUrlState({ search: '' }); const [draft, setDraft] = useState(null); const { data: servicesData } = useQuery({ queryKey: ['service-items-for-courses'], queryFn: () => api.get>('/api/v1/service-items'), staleTime: 60_000, }); const services = servicesData?.data ?? []; const rows = useMemo(() => { const q = urlState.search.trim(); return protocols.filter((p) => q === '' || p.service_name.includes(q)); }, [protocols, urlState.search]); const orderInvalid = draft !== null && !(draft.min_days <= draft.ideal_days && draft.ideal_days <= draft.max_days); const columns: Column[] = [ { key: 'service_name', header: 'سرویس', render: (p) => {p.service_name}, }, { key: 'session_count', header: 'تعداد جلسه', render: (p) => {p.session_count}, }, { key: 'spacing', header: 'فاصله (روز)', render: (p) => ( حداقل {p.min_days} · ایده‌آل {p.ideal_days} · حداکثر {p.max_days} ), }, { key: 'steps', header: 'پارامتر جلسات', render: (p) => ( {p.steps.length === 0 ? '—' : `${p.steps.length} جلسه پارامتر دارد`} ), }, { key: 'active', header: 'وضعیت', render: (p) => , }, ]; const save = async () => { if (!draft) return; const body = { service_uuid: draft.service_uuid, session_count: draft.session_count, min_days: draft.min_days, ideal_days: draft.ideal_days, max_days: draft.max_days, prefer_same_resource: draft.prefer_same_resource, steps: draft.steps .filter((s) => s.energy.trim() !== '') .map((s) => ({ session_number: s.session_number, params: { energy: Number(s.energy) } })), }; if (draft.uuid) { await update.mutateAsync({ uuid: draft.uuid, body }); } else { await create.mutateAsync(body); } setDraft(null); }; return (
setDraft({ ...EMPTY, steps: [] })}> پروتکل تازه ) : undefined } /> setUrlState({ search: v })} searchPlaceholder="جستجو در پروتکل‌ها..." emptyMessage="هنوز پروتکلی تعریف نشده است" actions={(p) => canManage ? (
{p.active && ( )}
) : null } /> setDraft(null)} footer={ <> } > {draft && (
{!draft.uuid && (
setDraft({ ...draft, service_uuid: String(v ?? '') })} options={services.map((s) => ({ value: s.uuid, label: s.name }))} placeholder="انتخاب سرویس" /> هر سرویس یک پروتکل دارد.
)}
setDraft({ ...draft, session_count: Number(e.target.value) })} /> {draft.session_count < 2 && ( دورهٔ کمتر از دو جلسه همان نوبت تکی است. )}
{([ ['min_days', 'حداقل (روز)'], ['ideal_days', 'ایده‌آل (روز)'], ['max_days', 'حداکثر (روز)'], ] as const).map(([key, label]) => (
setDraft({ ...draft, [key]: Number(e.target.value) })} />
))}
{orderInvalid && ( ترتیب باید حداقل ≤ ایده‌آل ≤ حداکثر باشد. )}

پارامتر جلسات (اختیاری)

{draft.steps.map((step, index) => (
setDraft({ ...draft, steps: draft.steps.map((s, i) => i === index ? { ...s, session_number: Number(e.target.value) } : s, ), }) } /> setDraft({ ...draft, steps: draft.steps.map((s, i) => (i === index ? { ...s, energy: e.target.value } : s)), }) } />
))}
)}
); }