import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { PlusIcon, PencilIcon, TrashIcon, WrenchScrewdriverIcon } from '@heroicons/react/24/outline'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { toast } from 'sonner'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import type { ServiceSection, ServiceItem, ClinicStaff } from '../types'; import { formatRial } from '../lib/utils'; import Modal from '../components/ui/Modal'; import PriceInput from '../components/ui/PriceInput'; import ConfirmDialog from '../components/ui/ConfirmDialog'; import PageHeader from '../components/ui/PageHeader'; import SearchableSelect from '../components/ui/SearchableSelect'; import FeatureGate from '../components/ui/FeatureGate'; const sectionSchema = z.object({ name: z.string().min(1, 'نام بخش الزامی است') }); const itemSchema = z.object({ name: z.string().min(1, 'نام سرویس الزامی است'), price_rials: z.coerce.number().min(0, 'مبلغ نمی‌تواند منفی باشد'), staff_uuid: z.string().optional(), }); type SectionForm = z.infer; type ItemForm = z.infer; const EMPTY_SECTIONS: ServiceSection[] = []; const EMPTY_ITEMS: ServiceItem[] = []; function ClinicServicesPageInner() { const qc = useQueryClient(); const [selectedSection, setSelectedSection] = useState(null); const [sectionModal, setSectionModal] = useState<'create' | ServiceSection | null>(null); const [deleteSection, setDeleteSection] = useState(null); const [itemModal, setItemModal] = useState<'create' | ServiceItem | null>(null); const [deleteItem, setDeleteItem] = useState(null); const { data: sectionsData, isLoading: sectionsLoading } = useQuery>({ queryKey: ['service-sections'], queryFn: () => api.get('/api/v1/service-sections'), }); const { data: itemsData, isLoading: itemsLoading } = useQuery>({ queryKey: ['service-items', selectedSection?.uuid], queryFn: () => api.get(`/api/v1/service-items/${selectedSection!.uuid}`), enabled: !!selectedSection, }); const { data: staffData } = useQuery>({ queryKey: ['staff'], queryFn: () => api.get('/api/v1/staff'), }); const sections = sectionsData?.data ?? EMPTY_SECTIONS; const items = itemsData?.data ?? EMPTY_ITEMS; const staffOptions = (staffData?.data ?? []).filter((s) => s.active).map((s) => ({ value: s.uuid, label: s.full_name, })); const sectionForm = useForm({ resolver: zodResolver(sectionSchema) }); const itemForm = useForm({ resolver: zodResolver(itemSchema) }); const createSection = useMutation({ mutationFn: (body: SectionForm) => api.post('/api/v1/service-section', body), onSuccess: () => { qc.invalidateQueries({ queryKey: ['service-sections'] }); setSectionModal(null); sectionForm.reset(); toast.success('بخش ایجاد شد'); }, onError: (e: any) => toast.error(e.message), }); const editSection = useMutation({ mutationFn: ({ uuid, body }: { uuid: string; body: SectionForm }) => api.patch(`/api/v1/service-section/${uuid}`, body), onSuccess: () => { qc.invalidateQueries({ queryKey: ['service-sections'] }); setSectionModal(null); toast.success('بخش ویرایش شد'); }, onError: (e: any) => toast.error(e.message), }); const delSection = useMutation({ mutationFn: (uuid: string) => api.delete(`/api/v1/service-section/${uuid}`), onSuccess: () => { qc.invalidateQueries({ queryKey: ['service-sections'] }); if (selectedSection?.uuid === deleteSection?.uuid) setSelectedSection(null); setDeleteSection(null); toast.success('بخش حذف شد'); }, onError: (e: any) => { toast.error(e.message); setDeleteSection(null); }, }); const createItem = useMutation({ mutationFn: (body: ItemForm & { section_uuid: string }) => api.post('/api/v1/service-item', body), onSuccess: () => { qc.invalidateQueries({ queryKey: ['service-items', selectedSection?.uuid] }); setItemModal(null); itemForm.reset(); toast.success('سرویس ایجاد شد'); }, onError: (e: any) => toast.error(e.message), }); const editItem = useMutation({ mutationFn: ({ uuid, body }: { uuid: string; body: ItemForm }) => api.patch(`/api/v1/service-item/${uuid}`, body), onSuccess: () => { qc.invalidateQueries({ queryKey: ['service-items', selectedSection?.uuid] }); setItemModal(null); toast.success('سرویس ویرایش شد'); }, onError: (e: any) => toast.error(e.message), }); const delItem = useMutation({ mutationFn: (uuid: string) => api.delete(`/api/v1/service-item/${uuid}`), onSuccess: () => { qc.invalidateQueries({ queryKey: ['service-items', selectedSection?.uuid] }); setDeleteItem(null); toast.success('سرویس حذف شد'); }, onError: (e: any) => { if (e.code === 'ERR_SERVICE_ITEM_IN_USE') { toast.error('این سرویس در پرونده بیمار استفاده شده و قابل حذف نیست'); } else { toast.error(e.message); } setDeleteItem(null); }, }); const openEditSection = (s: ServiceSection) => { sectionForm.reset({ name: s.name }); setSectionModal(s); }; const openEditItem = (item: ServiceItem) => { itemForm.reset({ name: item.name, price_rials: item.price_rials, staff_uuid: item.staff?.uuid ?? '', }); setItemModal(item); }; return ( <>
{/* ستون بخش‌ها */}
بخش‌ها
{sectionsLoading ? (
در حال بارگذاری...
) : sections.length === 0 ? (
بخشی ثبت نشده است
) : ( sections.map((s) => (
setSelectedSection(s)} >
{s.name}
e.stopPropagation()}>
)) )}
{/* ستون آیتم‌ها */}
{!selectedSection ? (
یک بخش انتخاب کنید
تا سرویس‌های آن را مشاهده و مدیریت کنید
) : ( <> {/* toolbar بخش انتخاب‌شده */}
{selectedSection.name} {!itemsLoading && ( {items.length} سرویس )}
{itemsLoading ? (
در حال بارگذاری...
) : items.length === 0 ? (
سرویسی در این بخش وجود ندارد
) : ( {items.map((item, idx) => ( ))}
نام سرویس قیمت پرسنل مسئول عملیات
{item.name} {formatRial(item.price_rials)} {item.staff ? (
{item.staff.full_name.charAt(0)}
{item.staff.full_name}
) : ( )}
)} )}
{/* Modal بخش */} setSectionModal(null)} title={sectionModal === 'create' ? 'بخش جدید' : 'ویرایش بخش'} >
{ if (sectionModal === 'create') createSection.mutate(d); else if (sectionModal !== null && typeof sectionModal === 'object') editSection.mutate({ uuid: sectionModal.uuid, body: d }); })}>
{sectionForm.formState.errors.name && ( {sectionForm.formState.errors.name.message} )}
{/* Modal آیتم */} setItemModal(null)} title={itemModal === 'create' ? 'سرویس جدید' : 'ویرایش سرویس'} >
{ if (itemModal === 'create' && selectedSection) { createItem.mutate({ ...d, section_uuid: selectedSection.uuid }); } else if (itemModal !== null && typeof itemModal === 'object') { editItem.mutate({ uuid: itemModal.uuid, body: d }); } })}>
{itemForm.formState.errors.name && ( {itemForm.formState.errors.name.message} )}
itemForm.setValue('price_rials', v)} placeholder="۸۵,۰۰۰" min={0} />
itemForm.setValue('staff_uuid', v != null ? String(v) : undefined)} placeholder="انتخاب پرسنل (اختیاری)" isClearable />
{/* Confirm حذف بخش */} deleteSection && delSection.mutate(deleteSection.uuid)} onCancel={() => setDeleteSection(null)} loading={delSection.isPending} /> {/* Confirm حذف آیتم */} deleteItem && delItem.mutate(deleteItem.uuid)} onCancel={() => setDeleteItem(null)} loading={delItem.isPending} /> ); } export default function ClinicServicesPage() { return ( ); }