import { useEffect, useRef, useState } from 'react'; import { PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; import Field from './ui/Field'; import Input from './ui/Input'; import Switch from './ui/Switch'; import SearchableSelect from './ui/SearchableSelect'; import type { TreatmentFormField } from '../types'; const TYPE_OPTIONS = [ { value: 'select', label: 'انتخاب از فهرست' }, { value: 'number', label: 'عدد' }, { value: 'text', label: 'متن' }, ]; const MAX_FIELDS = 20; const parseOptions = (raw: string) => raw.split(/[,،]/).map((o) => o.trim()).filter((o) => o !== ''); /** * ورودی متنی گزینههای فیلد `select`. * * متن خام را در state خودش نگه میدارد و آرایه را فقط موقع emit میسازد. اگر مقدار * input مستقیم از آرایه ساخته میشد، کاما و فاصلهٔ انتهایی در همان keystroke حذف * میشد (چون عنصر خالی filter میشود) و کاربر اصلاً نمیتوانست کاما تایپ کند. */ function OptionsInput({ id, options, disabled, onChange }: { id: string; options: TreatmentFormField['options']; disabled?: boolean; onChange: (options: string[]) => void; }) { const [draft, setDraft] = useState(() => (options ?? []).join(', ')); const emitted = useRef(options); // فقط وقتی آرایه از بیرون عوض شود (نه با تایپ خودِ کاربر) متن را همگام کن. useEffect(() => { if (options !== emitted.current) { emitted.current = options; setDraft((options ?? []).join(', ')); } }, [options]); return ( { setDraft(e.target.value); const next = parseOptions(e.target.value); emitted.current = next; onChange(next); }} /> ); } /** * فرمی که اپراتور بعد از درمانِ هر ناحیه با این نوع منبع پر میکند. * * روی نوع منبع تعریف میشود نه روی سرویس، چون خودِ دستگاه تعیین میکند چه چیزی * خواندنی است: لیزر انرژی و پالس و شات دارد، دستگاه RF چیز دیگری. افزودن دستگاه * تازه اینطور تنظیمات است، نه تغییر کد. */ export default function FieldSchemaEditor({ value, onChange, disabled }: { value: TreatmentFormField[]; onChange: (fields: TreatmentFormField[]) => void; disabled?: boolean; }) { const patch = (index: number, changes: Partial) => { onChange(value.map((f, i) => (i === index ? { ...f, ...changes } : f))); }; const add = () => { onChange([...value, { key: '', label: '', type: 'number', required: false, sort_order: value.length }]); }; return ( اپراتور بعد از درمان هر ناحیه این فیلدها را پر میکند. بدون فیلد، فقط دستگاه و زمان ثبت میشود. {value.map((field, index) => ( patch(index, { key: e.target.value })} /> patch(index, { label: e.target.value })} /> patch(index, { type: (v === null ? 'text' : String(v)) as TreatmentFormField['type'] })} ariaLabel="نوع فیلد" /> {field.type === 'select' && ( patch(index, { options })} /> )} patch(index, { required: v })} disabled={disabled} label="الزامی" /> {!disabled && ( onChange(value.filter((_, i) => i !== index))} aria-label={`حذف فیلد ${field.label || index + 1}`} > )} ))} {!disabled && value.length < MAX_FIELDS && ( افزودن فیلد )} ); }
اپراتور بعد از درمان هر ناحیه این فیلدها را پر میکند. بدون فیلد، فقط دستگاه و زمان ثبت میشود.