Files
clinicpro/assets/admin/pages/ClinicServicesPage.tsx
T
hamedandClaude Opus 4.8 9e491ae6b9 feat(clinic-services): two-page section→services flow matching Figma
Restructure clinic-services from two-pane to Figma two-view flow:
sections card grid (name, edit, activate/deactivate, 'بخش جدید' modal) →
click a section → its services card grid with breadcrumb back. Service
card matches Figma: overflow menu, active badge, base price, average
time badge, staff chip. Add duration_minutes to ServiceItem (migration
+ create/update + form field). Update clinic-services.md.

(Version20260713050014 syncs pre-existing entity/schema drift.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:41:29 +03:30

571 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
PlusIcon, PencilIcon, TrashIcon, WrenchScrewdriverIcon, BanknotesIcon,
ShieldCheckIcon, MagnifyingGlassIcon, EyeIcon, EyeSlashIcon,
EllipsisHorizontalIcon, CheckCircleIcon, XCircleIcon, ChevronRightIcon,
} 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, rialToToman, tomanToRial } 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 ServiceTariffModal from '../components/ServiceTariffModal';
import ServiceInsuranceModal from '../components/ServiceInsuranceModal';
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(),
insurance_covered: z.boolean().optional(),
insurance_price_rials: z.coerce.number().min(0).optional(),
duration_minutes: z.coerce.number().min(0).optional(),
});
type SectionForm = z.infer<typeof sectionSchema>;
type ItemForm = z.infer<typeof itemSchema>;
const EMPTY_SECTIONS: ServiceSection[] = [];
const EMPTY_ITEMS: ServiceItem[] = [];
function Avatar({ name }: { name: string }) {
return (
<div style={{
width: 26, height: 26, borderRadius: '50%',
background: 'linear-gradient(145deg, oklch(0.62 0.15 162), oklch(0.48 0.16 162))',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 11, fontWeight: 700, color: '#fff', flexShrink: 0,
}}>
{name.charAt(0)}
</div>
);
}
function SrvRow({ label, value, strong, danger }: { label: string; value: string; strong?: boolean; danger?: boolean }) {
return (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8, padding: '3px 0' }}>
<span style={{ fontSize: 12, color: 'var(--text-3)', flexShrink: 0 }}>{label}:</span>
<span
style={{
fontSize: 14,
fontWeight: strong ? 800 : 500,
color: danger ? 'var(--danger)' : strong ? 'var(--primary)' : 'var(--text-2)',
textAlign: 'left',
}}
>
{value}
</span>
</div>
);
}
function ClinicServicesPageInner() {
const qc = useQueryClient();
const [selectedSection, setSelectedSection] = useState<ServiceSection | null>(null);
const [sectionModal, setSectionModal] = useState<'create' | ServiceSection | null>(null);
const [deleteSection, setDeleteSection] = useState<ServiceSection | null>(null);
const [itemModal, setItemModal] = useState<'create' | ServiceItem | null>(null);
const [toggleItem, setToggleItem] = useState<ServiceItem | null>(null);
const [tariffItem, setTariffItem] = useState<ServiceItem | null>(null);
const [insuranceItem, setInsuranceItem] = useState<ServiceItem | null>(null);
const [search, setSearch] = useState('');
const [showInactive, setShowInactive] = useState(true);
const [menuOpen, setMenuOpen] = useState<string | null>(null);
const { data: sectionsData, isLoading: sectionsLoading } = useQuery<ApiResponse<ServiceSection[]>>({
queryKey: ['service-sections'],
queryFn: () => api.get('/api/v1/service-sections'),
});
const { data: itemsData, isLoading: itemsLoading } = useQuery<ApiResponse<ServiceItem[]>>({
queryKey: ['service-items', selectedSection?.uuid],
queryFn: () => api.get(`/api/v1/service-items/${selectedSection!.uuid}`),
enabled: !!selectedSection,
});
const { data: staffData } = useQuery<ApiResponse<ClinicStaff[]>>({
queryKey: ['staff'],
queryFn: () => api.get('/api/v1/staff'),
});
const sections = sectionsData?.data ?? EMPTY_SECTIONS;
const allItems = itemsData?.data ?? EMPTY_ITEMS;
const allStaff = staffData?.data ?? [];
const editingStaff = itemModal && typeof itemModal === 'object' ? itemModal.staff : null;
const staffOptions = allStaff
.filter((s) => s.active || s.uuid === editingStaff?.uuid)
.map((s) => ({
value: s.uuid,
label: s.active ? s.full_name : `${s.full_name} (غیرفعال)`,
}));
const items = allItems.filter((it) => {
if (!showInactive && !it.active) return false;
if (search.trim() && !it.name.includes(search.trim())) return false;
return true;
});
const activeCount = allItems.filter((i) => i.active).length;
const sectionForm = useForm<SectionForm>({ resolver: zodResolver(sectionSchema) });
const itemForm = useForm<ItemForm>({ 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 toggleSection = useMutation({
mutationFn: ({ uuid, active }: { uuid: string; active: boolean }) =>
api.patch(`/api/v1/service-section/${uuid}`, { active }),
onSuccess: (_d, v) => {
qc.invalidateQueries({ queryKey: ['service-sections'] });
toast.success(v.active ? 'بخش فعال شد' : 'بخش غیرفعال شد');
},
onError: (e: any) => toast.error(e.message),
});
const createItem = useMutation({
mutationFn: (body: ItemForm & { section_uuid: string }) => api.post('/api/v1/service-item', {
...body,
price_rials: tomanToRial(body.price_rials),
insurance_price_rials: tomanToRial(body.insurance_price_rials ?? 0),
}),
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,
price_rials: tomanToRial(body.price_rials),
insurance_price_rials: tomanToRial(body.insurance_price_rials ?? 0),
}),
onSuccess: () => { qc.invalidateQueries({ queryKey: ['service-items', selectedSection?.uuid] }); setItemModal(null); toast.success('سرویس ویرایش شد'); },
onError: (e: any) => toast.error(e.message),
});
const toggleActive = useMutation({
mutationFn: ({ uuid, active }: { uuid: string; active: boolean }) =>
api.patch(`/api/v1/service-item/${uuid}`, { active }),
onSuccess: (_d, v) => {
qc.invalidateQueries({ queryKey: ['service-items', selectedSection?.uuid] });
setToggleItem(null);
toast.success(v.active ? 'سرویس فعال شد' : 'سرویس غیرفعال شد');
},
onError: (e: any) => { toast.error(e.message); setToggleItem(null); },
});
const openEditSection = (s: ServiceSection) => {
sectionForm.reset({ name: s.name });
setSectionModal(s);
};
const openEditItem = (item: ServiceItem) => {
itemForm.reset({
name: item.name,
price_rials: rialToToman(item.price_rials),
staff_uuid: item.staff?.uuid ?? '',
insurance_covered: item.insurance_covered ?? false,
insurance_price_rials: rialToToman(item.insurance_price_rials ?? 0),
duration_minutes: item.duration_minutes ?? undefined,
});
setItemModal(item);
};
const openCreateItem = () => {
itemForm.reset({ name: '', price_rials: 0, staff_uuid: '', insurance_covered: false, insurance_price_rials: 0, duration_minutes: undefined });
setItemModal('create');
};
return (
<>
<PageHeader title="سرویس‌های کلینیک" description="بخش‌ها، سرویس‌ها، تعرفه و پوشش بیمه را مدیریت کنید" />
{!selectedSection ? (
/* ═════════ نمای بخش‌ها ═════════ */
<>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 12 }}>
<b style={{ fontSize: 16 }}>بخش‌ها</b>
<button className="cp-btn-primary" onClick={() => { sectionForm.reset({ name: '' }); setSectionModal('create'); }}>
<PlusIcon style={{ width: 16 }} /> بخش جدید
</button>
</div>
{sectionsLoading ? (
<div style={{ color: 'var(--text-3)', fontSize: 13, padding: 16 }}>در حال بارگذاری...</div>
) : sections.length === 0 ? (
<div className="card" style={{ padding: '52px 0', textAlign: 'center', color: 'var(--text-3)' }}>
<WrenchScrewdriverIcon style={{ width: 34, margin: '0 auto 12px', display: 'block', opacity: 0.35 }} />
<div style={{ fontWeight: 600, color: 'var(--text-2)', marginBottom: 4 }}>بخشی ثبت نشده است</div>
<button className="cp-btn-primary" style={{ marginTop: 12 }} onClick={() => { sectionForm.reset({ name: '' }); setSectionModal('create'); }}>افزودن بخش</button>
</div>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(210px, 1fr))', gap: 14 }}>
{sections.map((s) => (
<div
key={s.uuid}
onClick={() => setSelectedSection(s)}
style={{
cursor: 'pointer', background: 'var(--surface)', borderRadius: 8,
boxShadow: '0 1px 24.8px rgba(204,204,204,0.18)',
border: '1px solid var(--border)', padding: '18px 14px',
opacity: s.active ? 1 : 0.62,
}}
>
<div style={{ fontWeight: 700, fontSize: 14, color: 'var(--text)', textAlign: 'center', marginBottom: 14 }}>{s.name}</div>
<div style={{ display: 'flex', gap: 6, justifyContent: 'center', borderTop: '1px solid var(--border)', paddingTop: 10 }} onClick={(e) => e.stopPropagation()}>
<button className="btn sm ghost" style={{ display: 'flex', alignItems: 'center', gap: 4, color: 'var(--text-2)' }} onClick={() => openEditSection(s)}>
<PencilIcon style={{ width: 14 }} /> ویرایش
</button>
<button
className="btn sm ghost"
style={{ display: 'flex', alignItems: 'center', gap: 4, color: s.active ? 'var(--danger)' : 'var(--success)' }}
onClick={() => toggleSection.mutate({ uuid: s.uuid, active: !s.active })}
>
{s.active
? <><XCircleIcon style={{ width: 14 }} /> غیرفعال</>
: <><CheckCircleIcon style={{ width: 14 }} /> فعال</>}
</button>
</div>
</div>
))}
</div>
)}
</>
) : (
/* ═════════ نمای سرویس‌های یک بخش ═════════ */
<>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 12 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
<button className="cp-btn-secondary" style={{ height: 36 }} onClick={() => setSelectedSection(null)}>
<ChevronRightIcon style={{ width: 16 }} /> بازگشت
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 14, color: 'var(--text-3)' }}>
<span style={{ cursor: 'pointer' }} onClick={() => setSelectedSection(null)}>بخش‌ها</span>
<span></span>
<b style={{ color: 'var(--text)' }}>{selectedSection.name}</b>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<div style={{ position: 'relative' }}>
<MagnifyingGlassIcon style={{ width: 15, position: 'absolute', insetInlineStart: 9, top: '50%', transform: 'translateY(-50%)', color: 'var(--text-3)', pointerEvents: 'none' }} />
<input className="cp-input" placeholder="جستجوی سرویس" value={search} onChange={(e) => setSearch(e.target.value)} style={{ width: 170, paddingInlineStart: 30, height: 36 }} />
</div>
<button className="btn sm ghost" onClick={() => setShowInactive((v) => !v)} title={showInactive ? 'پنهان‌کردن غیرفعال‌ها' : 'نمایش غیرفعال‌ها'}>
{showInactive ? <EyeIcon style={{ width: 16 }} /> : <EyeSlashIcon style={{ width: 16 }} />}
</button>
<button className="cp-btn-primary" onClick={openCreateItem}>
<PlusIcon style={{ width: 16 }} /> سرویس جدید
</button>
</div>
</div>
{itemsLoading ? (
<div style={{ color: 'var(--text-3)', fontSize: 13, padding: 16 }}>در حال بارگذاری...</div>
) : items.length === 0 ? (
<div className="card" style={{ padding: '52px 0', textAlign: 'center', color: 'var(--text-3)' }}>
<PlusIcon style={{ width: 32, margin: '0 auto 12px', display: 'block', opacity: 0.35 }} />
<div style={{ fontWeight: 600, color: 'var(--text-2)', marginBottom: 4 }}>
{allItems.length === 0 ? 'سرویسی در این بخش وجود ندارد' : 'سرویسی با این فیلتر یافت نشد'}
</div>
{allItems.length === 0 && (
<button className="cp-btn-primary" style={{ marginTop: 12 }} onClick={openCreateItem}>افزودن سرویس</button>
)}
</div>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16 }}>
{items.map((item) => {
const menuItemStyle: React.CSSProperties = {
display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '8px 10px', borderRadius: 8, border: 'none', cursor: 'pointer',
background: 'transparent', color: 'var(--text-2)', fontSize: 13,
fontFamily: 'inherit', textAlign: 'start', whiteSpace: 'nowrap',
};
const chipStyle: React.CSSProperties = {
fontSize: 12, color: 'var(--text-2)', background: 'var(--surface-2)',
border: '1px solid var(--border)', borderRadius: 999, padding: '3px 10px', whiteSpace: 'nowrap',
};
return (
<div
key={item.uuid}
style={{
position: 'relative', background: 'var(--surface)', borderRadius: 8,
boxShadow: '0 1px 24.8px rgba(204,204,204,0.18)',
border: '1px solid var(--border)', padding: 14,
opacity: item.active ? 1 : 0.7,
}}
>
{/* نوار بالا: ⋮ (چپ) + وضعیت و نام (راست) */}
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 8, marginBottom: 14 }}>
<button className="btn sm ghost" style={{ padding: 4 }} onClick={() => setMenuOpen(menuOpen === item.uuid ? null : item.uuid)} title="عملیات">
<EllipsisHorizontalIcon style={{ width: 20 }} />
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
<span className={`badge ${item.active ? 'green' : 'gray'}`} style={{ fontSize: 11 }}>
<span className="bdot" />{item.active ? 'فعال' : 'غیرفعال'}
</span>
<span style={{ fontWeight: 700, fontSize: 14, color: 'var(--text)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.name}</span>
</div>
</div>
{menuOpen === item.uuid && (
<>
<div style={{ position: 'fixed', inset: 0, zIndex: 40 }} onClick={() => setMenuOpen(null)} />
<div style={{ position: 'absolute', top: 40, left: 8, zIndex: 41, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--r-sm)', boxShadow: 'var(--shadow)', padding: 6, minWidth: 176, display: 'flex', flexDirection: 'column', gap: 2 }}>
<button style={menuItemStyle} onClick={() => { setMenuOpen(null); openEditItem(item); }}><PencilIcon style={{ width: 15 }} /> ویرایش</button>
<button style={menuItemStyle} onClick={() => { setMenuOpen(null); setTariffItem(item); }}><BanknotesIcon style={{ width: 15 }} /> تعرفه‌های سالانه</button>
<button style={menuItemStyle} onClick={() => { setMenuOpen(null); setInsuranceItem(item); }}><ShieldCheckIcon style={{ width: 15 }} /> پوشش بیمه</button>
<button style={menuItemStyle} onClick={() => { setMenuOpen(null); setToggleItem(item); }}>{item.active ? <EyeSlashIcon style={{ width: 15 }} /> : <EyeIcon style={{ width: 15 }} />}{item.active ? ' غیرفعال‌کردن' : ' فعال‌کردن'}</button>
</div>
</>
)}
<SrvRow label="قیمت پایه" value={formatRial(item.price_rials)} strong />
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, padding: '3px 0' }}>
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>زمان متوسط:</span>
{item.duration_minutes
? <span className="badge blue" style={{ fontSize: 11 }}>{Number(item.duration_minutes).toLocaleString('fa-IR')} دقیقه</span>
: <span style={{ fontSize: 12, color: 'var(--text-3)' }}></span>}
</div>
{item.insurance_covered && item.insurance_price_rials != null && (
<SrvRow label="سهم بیمار (بیمه)" value={formatRial(item.insurance_price_rials)} />
)}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, padding: '3px 0' }}>
<span style={{ fontSize: 12, color: 'var(--text-3)', flexShrink: 0 }}>پرسنل:</span>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', justifyContent: 'flex-end' }}>
{item.staff
? <span style={chipStyle}>{item.staff.full_name}</span>
: <span style={{ fontSize: 12, color: 'var(--text-3)' }}></span>}
</div>
</div>
</div>
);
})}
</div>
)}
</>
)}
{/* ───────── Modal بخش ───────── */}
<Modal
open={sectionModal !== null}
onClose={() => setSectionModal(null)}
title={sectionModal === 'create' ? 'بخش جدید' : 'ویرایش بخش'}
>
<form onSubmit={sectionForm.handleSubmit((d) => {
if (sectionModal === 'create') createSection.mutate(d);
else if (sectionModal !== null && typeof sectionModal === 'object') editSection.mutate({ uuid: sectionModal.uuid, body: d });
})}>
<div className="field">
<label>نام بخش *</label>
<input {...sectionForm.register('name')} placeholder="مثلاً: تزریقات" autoFocus />
{sectionForm.formState.errors.name && (
<span className="field-error">{sectionForm.formState.errors.name.message}</span>
)}
</div>
<div style={{ display: 'flex', gap: 8, marginTop: 18 }}>
<button type="submit" className="btn primary" disabled={createSection.isPending || editSection.isPending}>ذخیره</button>
<button type="button" className="btn" onClick={() => setSectionModal(null)}>انصراف</button>
</div>
</form>
</Modal>
{/* ───────── Modal سرویس ───────── */}
<Modal
open={itemModal !== null}
onClose={() => setItemModal(null)}
title={itemModal === 'create' ? 'سرویس جدید' : 'ویرایش سرویس'}
size="md"
footer={
<>
<button type="button" className="btn" onClick={() => setItemModal(null)}>انصراف</button>
<button type="submit" form="service-item-form" className="btn primary" disabled={createItem.isPending || editItem.isPending}>
{createItem.isPending || editItem.isPending ? 'در حال ذخیره...' : 'ذخیره سرویس'}
</button>
</>
}
>
<form
id="service-item-form"
onSubmit={itemForm.handleSubmit((d) => {
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 });
}
})}
style={{ display: 'flex', flexDirection: 'column', gap: 20 }}
>
{/* اطلاعات پایه */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
<div>
<label className="field-label">نام سرویس *</label>
<div className="field" style={itemForm.formState.errors.name ? { borderColor: 'var(--danger)' } : undefined}>
<input {...itemForm.register('name')} placeholder="مثلاً: سرم ۵۰۰cc" autoFocus />
</div>
{itemForm.formState.errors.name && (
<span className="field-error">{itemForm.formState.errors.name.message}</span>
)}
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div>
<label className="field-label">قیمت پایه (تومان) *</label>
<div className="field">
<PriceInput
value={itemForm.watch('price_rials') ?? 0}
onChange={(v) => itemForm.setValue('price_rials', v)}
placeholder="۸۵,۰۰۰"
min={0}
/>
</div>
{itemForm.formState.errors.price_rials && (
<span className="field-error">{itemForm.formState.errors.price_rials.message}</span>
)}
</div>
<div>
<label className="field-label">پرسنل مسئول</label>
<SearchableSelect
options={staffOptions}
value={itemForm.watch('staff_uuid') ?? ''}
onChange={(v) => itemForm.setValue('staff_uuid', v != null ? String(v) : '')}
placeholder="انتخاب (اختیاری)"
noOptionsMessage="پرسنلی ثبت نشده"
height={42}
isClearable
/>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div>
<label className="field-label">زمان متوسط (دقیقه)</label>
<div className="field">
<input type="number" min={0} {...itemForm.register('duration_minutes')} placeholder="مثلاً: ۵۰" />
</div>
</div>
</div>
</div>
{/* بیمه */}
<div style={{
border: '1px solid var(--border)', borderRadius: 'var(--r)',
background: 'var(--surface-2)', overflow: 'hidden',
}}>
<label style={{
display: 'flex', alignItems: 'center', gap: 11, cursor: 'pointer',
padding: '13px 16px',
}}>
<ShieldCheckIcon style={{ width: 18, color: 'var(--primary)', flexShrink: 0 }} />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13.5, fontWeight: 600 }}>این خدمت شامل بیمه می‌شود</div>
<div style={{ fontSize: 11.5, color: 'var(--text-3)', marginTop: 2 }}>نشانه‌ی سریع برای فهرست سرویس‌ها</div>
</div>
<span className="switch">
<input
type="checkbox"
checked={itemForm.watch('insurance_covered') ?? false}
onChange={(e) => itemForm.setValue('insurance_covered', e.target.checked)}
/>
<span className="switch-track"><span className="switch-thumb" /></span>
</span>
</label>
{itemForm.watch('insurance_covered') && (
<div style={{ padding: '0 16px 16px', borderTop: '1px solid var(--border)', paddingTop: 14 }}>
<label className="field-label">قیمت تقریبی با بیمه (تومان)</label>
<div className="field" style={{ background: 'var(--surface)' }}>
<PriceInput
value={itemForm.watch('insurance_price_rials') ?? 0}
onChange={(v) => itemForm.setValue('insurance_price_rials', v)}
placeholder="سهم تقریبی بیمار"
min={0}
/>
</div>
<div style={{
fontSize: 11.5, color: 'var(--text-3)', lineHeight: 1.75, marginTop: 10,
display: 'flex', gap: 6,
}}>
<ShieldCheckIcon style={{ width: 14, flexShrink: 0, marginTop: 2, color: 'var(--text-3)' }} />
<span>برای محاسبه‌ی دقیق سهم بیمار و ساخت مطالبات، پوشش هر بیمه‌گر را از دکمه‌ی «پوشش بیمه» در فهرست سرویس‌ها تنظیم کنید.</span>
</div>
</div>
)}
</div>
</form>
</Modal>
<ServiceTariffModal item={tariffItem} onClose={() => setTariffItem(null)} />
<ServiceInsuranceModal item={insuranceItem} onClose={() => setInsuranceItem(null)} />
{/* Confirm حذف بخش */}
<ConfirmDialog
open={!!deleteSection}
title="حذف بخش"
message={`آیا مطمئن هستید که می‌خواهید بخش «${deleteSection?.name}» و همه‌ی سرویس‌های آن را حذف کنید؟`}
confirmLabel="حذف"
onConfirm={() => deleteSection && delSection.mutate(deleteSection.uuid)}
onCancel={() => setDeleteSection(null)}
loading={delSection.isPending}
/>
{/* Confirm فعال/غیرفعال سرویس */}
<ConfirmDialog
open={!!toggleItem}
title={toggleItem?.active ? 'غیرفعال‌کردن سرویس' : 'فعال‌کردن سرویس'}
message={
toggleItem?.active
? `سرویس «${toggleItem?.name}» غیرفعال می‌شود و در پذیرش جدید نمایش داده نمی‌شود. سوابق قبلی حفظ می‌مانند.`
: `سرویس «${toggleItem?.name}» دوباره فعال و قابل انتخاب می‌شود.`
}
confirmLabel={toggleItem?.active ? 'غیرفعال کن' : 'فعال کن'}
onConfirm={() => toggleItem && toggleActive.mutate({ uuid: toggleItem.uuid, active: !toggleItem.active })}
onCancel={() => setToggleItem(null)}
loading={toggleActive.isPending}
/>
</>
);
}
export default function ClinicServicesPage() {
return (
<FeatureGate feature="services">
<ClinicServicesPageInner />
</FeatureGate>
);
}