import { useState } from 'react'; import type { Coding } from 'fhir/r4'; import { ArrowLeft, PlusCircle, Trash2 } from 'lucide-react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../../../../components/ui/dialog'; import { InputControl } from '../../../../components/ui/input-control'; import { SelectControl } from '../../../../components/ui/select-control'; import { CheckboxControl } from '../../../../components/ui/checkbox-control'; import { LoincSearch } from '../../../../components'; import FieldTypeAssist from '../../../../components/FieldTypeAssist/FieldTypeAssist'; import type { ExtendedQuestionnaireItem, IValueSet } from '../../../../services'; import { ANSWER_TYPE_LABELS, buildNewField, isChoiceFieldType, NAMED_FIELD_TYPES, namedFieldType } from './builderFields'; export interface LoincSelection { type: 'question' | 'group'; item: ExtendedQuestionnaireItem; } interface Props { formTitle: string; /** A clinical question is added only with a standard code, so its basics offer no plain Add field. */ clinical?: boolean; /** The group the field joins, when it is added inside one. */ destination?: string; itemControls: IValueSet[]; onAdd: (field: ExtendedQuestionnaireItem) => void; /** `intoDestination` false puts the import at the end of the form, outside every group. */ onImport: (selection: LoincSelection, intoDestination: boolean) => void; onClose: () => void; } const typeLabel = (type: string) => ANSWER_TYPE_LABELS[type] ?? type; const answerLabel = (value: unknown): string => { if (!value || typeof value !== 'object') return String(value ?? ''); const { display, code, score } = value as { display?: string; code?: string; score?: number | string | null }; const scored = score !== undefined && score !== null && score !== '' ? 'score ' + score : undefined; return [display ?? code, display && code ? code : undefined, scored].filter(Boolean).join(' · '); }; // The imported panel as it will land: groups, each question with its type and required state, and coded answers with any score. const PanelStructure = ({ item }: { item: ExtendedQuestionnaireItem }) => { const children = item.item ?? []; const answers = item.type === 'group' ? [] : item.answerOption ?? []; const line = item.type === 'group' ? item.text : item.text + ' — ' + typeLabel(item.type) + (item.required ? ', required' : ''); return (
  • {line} {(children.length > 0 || answers.length > 0) && ( )}
  • ); }; // One modal for a new field: basics first, then the catalogue when the author chooses manually. // Nothing reaches the form until Add field or a catalogue import; the X leaves the form as it was. const AddFieldDialog = ({ formTitle, clinical = false, destination, itemControls, onAdd, onImport, onClose }: Props) => { const [step, setStep] = useState<'basics' | 'manual' | 'review'>('basics'); const [reviewing, setReviewing] = useState(null); const [insertInto, setInsertInto] = useState<'destination' | 'end'>(destination ? 'destination' : 'end'); const [label, setLabel] = useState(''); const [fieldTypeKey, setFieldTypeKey] = useState('string'); const [required, setRequired] = useState(false); const [help, setHelp] = useState(''); const [choices, setChoices] = useState(['', '']); const [code, setCode] = useState(undefined); const [unit, setUnit] = useState(undefined); const [assisting, setAssisting] = useState(false); const [catalogue, setCatalogue] = useState<'question' | 'panel'>('question'); const type = namedFieldType(fieldTypeKey)?.type ?? 'string'; const isQuestion = type !== 'display' && type !== 'group'; const isChoice = isChoiceFieldType(fieldTypeKey); const needsCode = clinical && isQuestion && !code; const complete = label.trim() !== '' && !needsCode && (!isChoice || choices.some((choice) => choice.trim() !== '')); const add = () => { if (!complete) return; onAdd(buildNewField({ label, fieldTypeKey, required, help, choices, code, unit }, itemControls)); }; return ( { if (!open) onClose(); }}> {step === 'review' ? 'Review selected LOINC panel' : step === 'manual' ? 'Choose a standard field' : 'Add field'} {formTitle} · New field{destination ? ` in ${destination}` : ''} {step === 'basics' ? (
    setLabel(value ?? '')}/> ({ label: named.label, value: named.key }))} onChange={setFieldTypeKey}/> {isChoice && (
    Answer choices {choices.map((choice, index) => (
    setChoices((current) => current.map((entry, at) => (at === index ? value ?? '' : entry)))}/>
    {choices.length > 1 && ( )}
    ))}
    )} {isQuestion && ( )} {type !== 'display' && ( setHelp(value ?? '')}/> )} {code && (

    Standard definition: {code.display} · LOINC {code.code}

    )} {isQuestion && (

    Find the right standard field

    Get suggestions for the answer format and matching LOINC definition, or choose one yourself. Nothing is added until you review and confirm.

    )}

    The X closes this setup without adding the field. Your existing form is unchanged.

    ) : step === 'review' && reviewing ? (

    {reviewing.item.text} · LOINC {reviewing.item.code?.[0]?.code}

    setInsertInto(value === 'destination' ? 'destination' : 'end')}/>

    The panel is added as one group with its questions. Its codes, answer choices and any scores are kept.

    ) : (
    { if (selection.type !== 'group') { onImport(selection, true); return; } setReviewing(selection); setStep('review'); }}/>
    )} {!(step === 'basics' && needsCode) && {step === 'review' ? ( <> ) : step === 'manual' ? ( ) : ( )} }
    {assisting && ( { // Every list presentation carries an item control, so a list answer takes the first one // rather than none: otherwise a coded list question stayed Short text. const suggested = NAMED_FIELD_TYPES.find((named) => named.type === match.answerType && !named.itemControl) ?? NAMED_FIELD_TYPES.find((named) => named.type === match.answerType); if (suggested) setFieldTypeKey(suggested.key); setCode({ system: 'http://loinc.org', code: match.code, display: match.display }); setUnit(match.unit ? { system: 'http://unitsofmeasure.org', code: match.unit, display: match.unit } : undefined); setAssisting(false); }} onClose={() => setAssisting(false)}/> )}
    ); }; export default AddFieldDialog;