import React, { useEffect, useState } from 'react'; import { XMarkIcon } from '@heroicons/react/24/outline'; import Modal from '../ui/Modal'; import { useQuery } from '@tanstack/react-query'; import SearchableSelect from '../ui/SearchableSelect'; import Field from '../ui/Field'; import Input from '../ui/Input'; import Switch from '../ui/Switch'; import { api, type ApiResponse } from '../../lib/api'; import type { ClinicResource, ResourcePayload, ResourceType } from '../../types'; import { useResourceDetail } from '../../hooks/useResources'; import { formatNumber } from '../../lib/utils'; /** کلیدهای شناخته‌شدهٔ ویژگی — قرارداد است نه اجبار؛ سرور هر کلید snake_case را می‌پذیرد. */ const KNOWN_ATTRIBUTES = ['gender', 'device_model', 'floor', 'brand']; type AttributeRow = { key: string; value: string }; interface Props { open: boolean; resource: ClinicResource | null; types: ResourceType[]; saving: boolean; onClose: () => void; onSave: (payload: ResourcePayload) => void; } export default function ResourceFormModal({ open, resource, types, saving, onClose, onSave, }: Props) { // شمار نوبت‌های آینده فقط برای منبعِ موجود معنا دارد و فقط وقتی مودال باز است. const { upcomingAppointments: upcoming } = useResourceDetail(open ? resource?.uuid : undefined); // پزشکان محیط جاری برای انتخاب ناظر — همان اندپوینت احرازشده‌ای که صفحهٔ نوبت‌ها // استفاده می‌کند، تا منشی فقط پزشکان مجازش را ببیند. const doctorsQuery = useQuery>({ queryKey: ['booking-doctors'], queryFn: () => api.get('/api/v1/my/clinic-doctors'), enabled: open, staleTime: 60_000, }); const doctors = doctorsQuery.data?.data?.data ?? []; const doctorsLoading = doctorsQuery.isLoading; const [name, setName] = useState(''); const [typeUuid, setTypeUuid] = useState(null); const [supervisorUuid, setSupervisorUuid] = useState(null); const [capacity, setCapacity] = useState('1'); const [setupMinutes, setSetupMinutes] = useState('0'); const [cleanupMinutes, setCleanupMinutes] = useState('0'); const [active, setActive] = useState(true); const [attributes, setAttributes] = useState([]); useEffect(() => { if (!open) return; setName(resource?.name ?? ''); setTypeUuid(resource?.type_uuid ?? null); setSupervisorUuid(resource?.supervisor?.uuid ?? null); setCapacity(String(resource?.capacity ?? 1)); setSetupMinutes(String(resource?.setup_minutes ?? 0)); setCleanupMinutes(String(resource?.cleanup_minutes ?? 0)); setActive(resource?.active ?? true); setAttributes( Object.entries(resource?.attributes ?? {}).map(([key, value]) => ({ key, value: String(value) })), ); }, [open, resource]); const isEdit = resource !== null; const parsedCapacity = Number(capacity); const invalid = name.trim() === '' || !supervisorUuid || (!isEdit && !typeUuid) || !Number.isFinite(parsedCapacity) || parsedCapacity < 1; const submit = () => { const attrs: Record = {}; attributes.forEach(({ key, value }) => { if (key.trim() !== '') attrs[key.trim()] = value; }); const payload: ResourcePayload = { name: name.trim(), capacity: parsedCapacity, setup_minutes: Number(setupMinutes) || 0, cleanup_minutes: Number(cleanupMinutes) || 0, attributes: attrs, active, supervisor_doctor_uuid: supervisorUuid!, }; // نوع فقط هنگام ساخت فرستاده می‌شود؛ جفت محیطِ منبع از آدرسِ محیط مشتق می‌شود و // جابه‌جا کردنش یعنی همان منبع در محیط دیگری ظاهر شود. آدرس پرسیده نمی‌شود: // منابع دامنهٔ شعبه ندارند و سرور آدرسِ خودِ کلینیک را برمی‌دارد. if (!isEdit) { payload.type_uuid = typeUuid!; } onSave(payload); }; return (
setName(e.target.value)} placeholder="لیزر آلکساندرایت ۱" />
({ value: t.uuid, label: t.name }))} value={typeUuid} onChange={(v) => setTypeUuid(v ? String(v) : null)} placeholder="نوع را انتخاب کنید" isDisabled={isEdit} height={38} /> {/* ناظر برخلاف نوع در ویرایش هم قابل تغییر است: پزشکِ مسئولِ یک دستگاه عوض می‌شود، ولی محیطِ منبع نه. */} ({ value: d.uuid, label: d.name }))} value={supervisorUuid} onChange={(v) => setSupervisorUuid(v ? String(v) : null)} placeholder="پزشک ناظر را انتخاب کنید" isLoading={doctorsLoading} height={38} />
{isEdit && (

نوع منبع پس از ساخت تغییر نمی‌کند؛ برای تغییرش، منبع تازه بسازید.

)}
{/* `numeric` به‌جای `type="number"`: ورودیِ عددیِ فارسی در `type="number"` نامعتبر است و مرورگر رشتهٔ خالی می‌دهد — یعنی «۳» تایپ‌شده به ۰ می‌رسید. `Input numeric` ارقام را زنده به لاتین برمی‌گرداند. */} setCapacity(e.target.value)} /> setSetupMinutes(e.target.value)} /> setCleanupMinutes(e.target.value)} />

ظرفیت یعنی چند بیمار هم‌زمان — اتاق تزریق سه‌تخته یک منبع با ظرفیت ۳ است، نه سه منبع. آماده‌سازی و تمیزکاری جزو نوبت بیمار نیستند ولی منبع را اشغال می‌کنند.

ویژگی‌ها (اختیاری)
{attributes.map((row, index) => (
setAttributes((a) => a.map((r, i) => (i === index ? { ...r, key: e.target.value } : r))) } style={{ flex: 1 }} /> setAttributes((a) => a.map((r, i) => (i === index ? { ...r, value: e.target.value } : r))) } style={{ flex: 1 }} /> {/* دکمهٔ فقط-آیکون → `mini-btn`، نه `btn secondary sm` با یک ✕ متنی */}
))} {KNOWN_ATTRIBUTES.map((k) =>
{/* غیرفعال‌کردن نوبت‌های ثبت‌شده را لغو نمی‌کند؛ فقط از جستجوی وقتِ بعدی حذف می‌شود. پس این هشدار است نه مانع — ولی اپراتور باید بداند چند بیمار روی منبعی نوبت دارند که دارد خاموش می‌شود. */} {!active && upcoming > 0 && ( این منبع {formatNumber(upcoming)} نوبت آیندهٔ فعال دارد. غیرفعال‌کردن آن‌ها را لغو نمی‌کند؛ فقط این منبع دیگر در جستجوی وقت نمی‌آید. )}
); }