formanitor 0.0.26 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- type FieldType = 'text' | 'number' | 'textarea' | 'richtext' | 'select' | 'multiselect' | 'radio' | 'repeatable' | 'checkbox' | 'image_upload' | 'signature' | 'date' | 'datetime' | 'editable_table' | 'static_text' | 'medications' | 'investigations' | 'procedures' | 'vitals' | 'hidden_vitals' | 'referral' | 'followup' | string;
3
+ type FieldType = 'text' | 'number' | 'textarea' | 'diagnosis_textarea' | 'richtext' | 'select' | 'multiselect' | 'radio' | 'repeatable' | 'checkbox' | 'image_upload' | 'signature' | 'date' | 'datetime' | 'editable_table' | 'static_text' | 'medications' | 'investigations' | 'procedures' | 'vitals' | 'hidden_vitals' | 'referral' | 'followup' | string;
4
4
  interface ValidationRule {
5
5
  min?: number;
6
6
  max?: number;
@@ -209,6 +209,20 @@ interface SmartTextareaValue {
209
209
  text: string;
210
210
  extractedKeywords: ExtractedKeywordMatch[];
211
211
  }
212
+ /** Selected diagnosis grade chip (single-selection). */
213
+ interface DiagnosisGradeSelection {
214
+ condition: string;
215
+ grade: string;
216
+ }
217
+ /**
218
+ * Structured value for diagnosis_textarea fields.
219
+ * - `text` is the textarea content (what will be submitted).
220
+ * - `selectedGradesByCondition` stores one selected chip per condition for later metadata/preview use.
221
+ */
222
+ interface DiagnosisTextareaValue {
223
+ text: string;
224
+ selectedGradesByCondition: Record<string, string>;
225
+ }
212
226
  /**
213
227
  * Smart textarea that performs client-side NLP keyword extraction and searches
214
228
  * matched terms against backend master tables via `fieldHandlers[fieldId].onSearch`.
@@ -223,6 +237,19 @@ interface SmartTextareaFieldDef extends BaseFieldDef {
223
237
  /** Debounce delay in ms before NLP + search triggers. Default: 1000. */
224
238
  debounceDelay?: number;
225
239
  }
240
+ /**
241
+ * Diagnosis textarea that can display condition-wise grade chips based on
242
+ * asynchronous API response.
243
+ *
244
+ * Value shape in form state can be plain text (legacy) or `DiagnosisTextareaValue`.
245
+ */
246
+ interface DiagnosisTextareaFieldDef extends BaseFieldDef {
247
+ type: 'diagnosis_textarea';
248
+ /** Height in pixels. If omitted, uses default min height (80px). */
249
+ height?: number;
250
+ /** Debounce delay in ms before API call triggers. Default: 700. */
251
+ debounceDelay?: number;
252
+ }
226
253
  interface MedicationFrequentItem {
227
254
  id: string;
228
255
  name: string;
@@ -269,7 +296,7 @@ interface DoctorFrequentItems {
269
296
  investigations?: InvestigationFrequentItem[];
270
297
  procedures?: ProcedureFrequentItem[];
271
298
  }
272
- type FieldDef = TextFieldDef | RichTextFieldDef | NumberFieldDef | SelectFieldDef | RadioFieldDef | CheckboxFieldDef | RepeatableFieldDef | ImageUploadFieldDef | SignatureFieldDef | EditableTableFieldDef | StaticTextFieldDef | MedicationsFieldDef | InvestigationsFieldDef | ProceduresFieldDef | DifferentialDiagnosisFieldDef | VitalsFieldDef | ReferralFieldDef | FollowupFieldDef | SmartTextareaFieldDef | BaseFieldDef;
299
+ type FieldDef = TextFieldDef | RichTextFieldDef | NumberFieldDef | SelectFieldDef | RadioFieldDef | CheckboxFieldDef | RepeatableFieldDef | ImageUploadFieldDef | SignatureFieldDef | EditableTableFieldDef | StaticTextFieldDef | MedicationsFieldDef | InvestigationsFieldDef | ProceduresFieldDef | DifferentialDiagnosisFieldDef | VitalsFieldDef | ReferralFieldDef | FollowupFieldDef | SmartTextareaFieldDef | DiagnosisTextareaFieldDef | BaseFieldDef;
273
300
  interface Condition {
274
301
  field?: string;
275
302
  op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'in' | 'contains';
@@ -542,6 +569,11 @@ type FieldHandler = {
542
569
  label: string;
543
570
  value: any;
544
571
  }[]>;
572
+ /**
573
+ * Called by diagnosis_textarea widgets to fetch condition-wise grades
574
+ * from diagnosis text input.
575
+ */
576
+ onFetchDiagnosisGrades?: (query: string) => Promise<any>;
545
577
  };
546
578
  type FieldHandlersMap = Record<string, FieldHandler>;
547
579
  declare function useFieldHandlers(fieldId: string): FieldHandler;
@@ -860,4 +892,4 @@ declare const SmartTextareaWidget: React.FC<{
860
892
  fieldId: string;
861
893
  }>;
862
894
 
863
- export { type AIDifferentialDiagnosisItem, AddInvestigationField, type AddInvestigationFieldProps, AddMedicationField, type AddMedicationFieldProps, type BaseFieldDef, type CheckboxFieldDef, type ColumnLayout, type Computation, type Condition, type CreateUploadHandlerOptions, type DataSource, DifferentialDiagnosis, type DifferentialDiagnosisFieldDef, type DifferentialDiagnosisItem, type DifferentialDiagnosisProps, type DoctorFrequentItems, DynamicForm, EMAIL_REGEX, type EditableTableColumnDef, type EditableTableFieldDef, type EventDefinition, type ExtractedKeywordMatch, type FieldDef, type FieldHandler, type FieldHandlersMap, type FieldPrefill, FieldRenderer, type FieldState, type FieldType, type FollowupFieldDef, type FollowupValue, FormControls, type FormControlsProps, type FormDef, type FormErrors, FormProvider, type FormState, FormStore, type FormValues, type FormulaComputation, type FormulaExpr, type ImageUploadFieldDef, ImageUploadWidget, type Investigation, type InvestigationFrequentItem, type InvestigationSearchResult, type InvestigationsFieldDef, type LayoutNode, type MatchedCondition, type Medication, type MedicationFrequentItem, type MedicationSearchResult, type MedicationsFieldDef, type NumberFieldDef, type Permission, type PresignedUploadResponse, type ProcedureFrequentItem, type ProceduresFieldDef, type RadioFieldDef, ReadOnlyForm, ReadOnlyImageUpload, ReadOnlySignature, ReadOnlyTable, type ReferralFieldDef, type ReferralItem, type RepeatableFieldDef, type RichTextFieldDef, RichTextWidget, type Rule, type RuleAction, type RuleResult, type ScoreComputation, type ScoreRange, type Section, type SectionChild, type SelectFieldDef, type SelectOption, type SignatureFieldDef, SignatureUploadWidget, SmartForm, type SmartFormProps, type SmartKeywordsResult, type SmartTextareaFieldDef, type SmartTextareaValue, SmartTextareaWidget, type StaticTextFieldDef, type StoreConfig, type SubmissionData, type SumComputation, type TextFieldDef, type ValidationRule, type VitalsFieldDef, type WebhookDef, type Workflow, type WorkflowTransition, createUploadHandler, evaluateRules, useField, useFieldHandlers, useForm, useFormStore, useFrequentItems, useSmartKeywords, validateField, validateForm };
895
+ export { type AIDifferentialDiagnosisItem, AddInvestigationField, type AddInvestigationFieldProps, AddMedicationField, type AddMedicationFieldProps, type BaseFieldDef, type CheckboxFieldDef, type ColumnLayout, type Computation, type Condition, type CreateUploadHandlerOptions, type DataSource, type DiagnosisGradeSelection, type DiagnosisTextareaFieldDef, type DiagnosisTextareaValue, DifferentialDiagnosis, type DifferentialDiagnosisFieldDef, type DifferentialDiagnosisItem, type DifferentialDiagnosisProps, type DoctorFrequentItems, DynamicForm, EMAIL_REGEX, type EditableTableColumnDef, type EditableTableFieldDef, type EventDefinition, type ExtractedKeywordMatch, type FieldDef, type FieldHandler, type FieldHandlersMap, type FieldPrefill, FieldRenderer, type FieldState, type FieldType, type FollowupFieldDef, type FollowupValue, FormControls, type FormControlsProps, type FormDef, type FormErrors, FormProvider, type FormState, FormStore, type FormValues, type FormulaComputation, type FormulaExpr, type ImageUploadFieldDef, ImageUploadWidget, type Investigation, type InvestigationFrequentItem, type InvestigationSearchResult, type InvestigationsFieldDef, type LayoutNode, type MatchedCondition, type Medication, type MedicationFrequentItem, type MedicationSearchResult, type MedicationsFieldDef, type NumberFieldDef, type Permission, type PresignedUploadResponse, type ProcedureFrequentItem, type ProceduresFieldDef, type RadioFieldDef, ReadOnlyForm, ReadOnlyImageUpload, ReadOnlySignature, ReadOnlyTable, type ReferralFieldDef, type ReferralItem, type RepeatableFieldDef, type RichTextFieldDef, RichTextWidget, type Rule, type RuleAction, type RuleResult, type ScoreComputation, type ScoreRange, type Section, type SectionChild, type SelectFieldDef, type SelectOption, type SignatureFieldDef, SignatureUploadWidget, SmartForm, type SmartFormProps, type SmartKeywordsResult, type SmartTextareaFieldDef, type SmartTextareaValue, SmartTextareaWidget, type StaticTextFieldDef, type StoreConfig, type SubmissionData, type SumComputation, type TextFieldDef, type ValidationRule, type VitalsFieldDef, type WebhookDef, type Workflow, type WorkflowTransition, createUploadHandler, evaluateRules, useField, useFieldHandlers, useForm, useFormStore, useFrequentItems, useSmartKeywords, validateField, validateForm };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- type FieldType = 'text' | 'number' | 'textarea' | 'richtext' | 'select' | 'multiselect' | 'radio' | 'repeatable' | 'checkbox' | 'image_upload' | 'signature' | 'date' | 'datetime' | 'editable_table' | 'static_text' | 'medications' | 'investigations' | 'procedures' | 'vitals' | 'hidden_vitals' | 'referral' | 'followup' | string;
3
+ type FieldType = 'text' | 'number' | 'textarea' | 'diagnosis_textarea' | 'richtext' | 'select' | 'multiselect' | 'radio' | 'repeatable' | 'checkbox' | 'image_upload' | 'signature' | 'date' | 'datetime' | 'editable_table' | 'static_text' | 'medications' | 'investigations' | 'procedures' | 'vitals' | 'hidden_vitals' | 'referral' | 'followup' | string;
4
4
  interface ValidationRule {
5
5
  min?: number;
6
6
  max?: number;
@@ -209,6 +209,20 @@ interface SmartTextareaValue {
209
209
  text: string;
210
210
  extractedKeywords: ExtractedKeywordMatch[];
211
211
  }
212
+ /** Selected diagnosis grade chip (single-selection). */
213
+ interface DiagnosisGradeSelection {
214
+ condition: string;
215
+ grade: string;
216
+ }
217
+ /**
218
+ * Structured value for diagnosis_textarea fields.
219
+ * - `text` is the textarea content (what will be submitted).
220
+ * - `selectedGradesByCondition` stores one selected chip per condition for later metadata/preview use.
221
+ */
222
+ interface DiagnosisTextareaValue {
223
+ text: string;
224
+ selectedGradesByCondition: Record<string, string>;
225
+ }
212
226
  /**
213
227
  * Smart textarea that performs client-side NLP keyword extraction and searches
214
228
  * matched terms against backend master tables via `fieldHandlers[fieldId].onSearch`.
@@ -223,6 +237,19 @@ interface SmartTextareaFieldDef extends BaseFieldDef {
223
237
  /** Debounce delay in ms before NLP + search triggers. Default: 1000. */
224
238
  debounceDelay?: number;
225
239
  }
240
+ /**
241
+ * Diagnosis textarea that can display condition-wise grade chips based on
242
+ * asynchronous API response.
243
+ *
244
+ * Value shape in form state can be plain text (legacy) or `DiagnosisTextareaValue`.
245
+ */
246
+ interface DiagnosisTextareaFieldDef extends BaseFieldDef {
247
+ type: 'diagnosis_textarea';
248
+ /** Height in pixels. If omitted, uses default min height (80px). */
249
+ height?: number;
250
+ /** Debounce delay in ms before API call triggers. Default: 700. */
251
+ debounceDelay?: number;
252
+ }
226
253
  interface MedicationFrequentItem {
227
254
  id: string;
228
255
  name: string;
@@ -269,7 +296,7 @@ interface DoctorFrequentItems {
269
296
  investigations?: InvestigationFrequentItem[];
270
297
  procedures?: ProcedureFrequentItem[];
271
298
  }
272
- type FieldDef = TextFieldDef | RichTextFieldDef | NumberFieldDef | SelectFieldDef | RadioFieldDef | CheckboxFieldDef | RepeatableFieldDef | ImageUploadFieldDef | SignatureFieldDef | EditableTableFieldDef | StaticTextFieldDef | MedicationsFieldDef | InvestigationsFieldDef | ProceduresFieldDef | DifferentialDiagnosisFieldDef | VitalsFieldDef | ReferralFieldDef | FollowupFieldDef | SmartTextareaFieldDef | BaseFieldDef;
299
+ type FieldDef = TextFieldDef | RichTextFieldDef | NumberFieldDef | SelectFieldDef | RadioFieldDef | CheckboxFieldDef | RepeatableFieldDef | ImageUploadFieldDef | SignatureFieldDef | EditableTableFieldDef | StaticTextFieldDef | MedicationsFieldDef | InvestigationsFieldDef | ProceduresFieldDef | DifferentialDiagnosisFieldDef | VitalsFieldDef | ReferralFieldDef | FollowupFieldDef | SmartTextareaFieldDef | DiagnosisTextareaFieldDef | BaseFieldDef;
273
300
  interface Condition {
274
301
  field?: string;
275
302
  op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'in' | 'contains';
@@ -542,6 +569,11 @@ type FieldHandler = {
542
569
  label: string;
543
570
  value: any;
544
571
  }[]>;
572
+ /**
573
+ * Called by diagnosis_textarea widgets to fetch condition-wise grades
574
+ * from diagnosis text input.
575
+ */
576
+ onFetchDiagnosisGrades?: (query: string) => Promise<any>;
545
577
  };
546
578
  type FieldHandlersMap = Record<string, FieldHandler>;
547
579
  declare function useFieldHandlers(fieldId: string): FieldHandler;
@@ -860,4 +892,4 @@ declare const SmartTextareaWidget: React.FC<{
860
892
  fieldId: string;
861
893
  }>;
862
894
 
863
- export { type AIDifferentialDiagnosisItem, AddInvestigationField, type AddInvestigationFieldProps, AddMedicationField, type AddMedicationFieldProps, type BaseFieldDef, type CheckboxFieldDef, type ColumnLayout, type Computation, type Condition, type CreateUploadHandlerOptions, type DataSource, DifferentialDiagnosis, type DifferentialDiagnosisFieldDef, type DifferentialDiagnosisItem, type DifferentialDiagnosisProps, type DoctorFrequentItems, DynamicForm, EMAIL_REGEX, type EditableTableColumnDef, type EditableTableFieldDef, type EventDefinition, type ExtractedKeywordMatch, type FieldDef, type FieldHandler, type FieldHandlersMap, type FieldPrefill, FieldRenderer, type FieldState, type FieldType, type FollowupFieldDef, type FollowupValue, FormControls, type FormControlsProps, type FormDef, type FormErrors, FormProvider, type FormState, FormStore, type FormValues, type FormulaComputation, type FormulaExpr, type ImageUploadFieldDef, ImageUploadWidget, type Investigation, type InvestigationFrequentItem, type InvestigationSearchResult, type InvestigationsFieldDef, type LayoutNode, type MatchedCondition, type Medication, type MedicationFrequentItem, type MedicationSearchResult, type MedicationsFieldDef, type NumberFieldDef, type Permission, type PresignedUploadResponse, type ProcedureFrequentItem, type ProceduresFieldDef, type RadioFieldDef, ReadOnlyForm, ReadOnlyImageUpload, ReadOnlySignature, ReadOnlyTable, type ReferralFieldDef, type ReferralItem, type RepeatableFieldDef, type RichTextFieldDef, RichTextWidget, type Rule, type RuleAction, type RuleResult, type ScoreComputation, type ScoreRange, type Section, type SectionChild, type SelectFieldDef, type SelectOption, type SignatureFieldDef, SignatureUploadWidget, SmartForm, type SmartFormProps, type SmartKeywordsResult, type SmartTextareaFieldDef, type SmartTextareaValue, SmartTextareaWidget, type StaticTextFieldDef, type StoreConfig, type SubmissionData, type SumComputation, type TextFieldDef, type ValidationRule, type VitalsFieldDef, type WebhookDef, type Workflow, type WorkflowTransition, createUploadHandler, evaluateRules, useField, useFieldHandlers, useForm, useFormStore, useFrequentItems, useSmartKeywords, validateField, validateForm };
895
+ export { type AIDifferentialDiagnosisItem, AddInvestigationField, type AddInvestigationFieldProps, AddMedicationField, type AddMedicationFieldProps, type BaseFieldDef, type CheckboxFieldDef, type ColumnLayout, type Computation, type Condition, type CreateUploadHandlerOptions, type DataSource, type DiagnosisGradeSelection, type DiagnosisTextareaFieldDef, type DiagnosisTextareaValue, DifferentialDiagnosis, type DifferentialDiagnosisFieldDef, type DifferentialDiagnosisItem, type DifferentialDiagnosisProps, type DoctorFrequentItems, DynamicForm, EMAIL_REGEX, type EditableTableColumnDef, type EditableTableFieldDef, type EventDefinition, type ExtractedKeywordMatch, type FieldDef, type FieldHandler, type FieldHandlersMap, type FieldPrefill, FieldRenderer, type FieldState, type FieldType, type FollowupFieldDef, type FollowupValue, FormControls, type FormControlsProps, type FormDef, type FormErrors, FormProvider, type FormState, FormStore, type FormValues, type FormulaComputation, type FormulaExpr, type ImageUploadFieldDef, ImageUploadWidget, type Investigation, type InvestigationFrequentItem, type InvestigationSearchResult, type InvestigationsFieldDef, type LayoutNode, type MatchedCondition, type Medication, type MedicationFrequentItem, type MedicationSearchResult, type MedicationsFieldDef, type NumberFieldDef, type Permission, type PresignedUploadResponse, type ProcedureFrequentItem, type ProceduresFieldDef, type RadioFieldDef, ReadOnlyForm, ReadOnlyImageUpload, ReadOnlySignature, ReadOnlyTable, type ReferralFieldDef, type ReferralItem, type RepeatableFieldDef, type RichTextFieldDef, RichTextWidget, type Rule, type RuleAction, type RuleResult, type ScoreComputation, type ScoreRange, type Section, type SectionChild, type SelectFieldDef, type SelectOption, type SignatureFieldDef, SignatureUploadWidget, SmartForm, type SmartFormProps, type SmartKeywordsResult, type SmartTextareaFieldDef, type SmartTextareaValue, SmartTextareaWidget, type StaticTextFieldDef, type StoreConfig, type SubmissionData, type SumComputation, type TextFieldDef, type ValidationRule, type VitalsFieldDef, type WebhookDef, type Workflow, type WorkflowTransition, createUploadHandler, evaluateRules, useField, useFieldHandlers, useForm, useFormStore, useFrequentItems, useSmartKeywords, validateField, validateForm };
package/dist/index.mjs CHANGED
@@ -395,6 +395,11 @@ var FormStore = class {
395
395
  result[field.id] = raw?.text ?? null;
396
396
  return;
397
397
  }
398
+ if (field.type === "diagnosis_textarea") {
399
+ const raw = values[field.id];
400
+ result[field.id] = typeof raw === "string" ? raw : raw && typeof raw === "object" ? raw.text ?? null : null;
401
+ return;
402
+ }
398
403
  if (field.type === "ophthal_diagnosis") {
399
404
  const raw = values[field.id];
400
405
  const formatEye = (eyeData, eyeLabel) => {
@@ -3325,8 +3330,9 @@ var AddInvestigationField = ({
3325
3330
  const debounceRef = useRef(null);
3326
3331
  const valueRef = useRef([]);
3327
3332
  const searchInputRef = useRef(null);
3328
- valueRef.current = value;
3329
- const addedIds = new Set(value.map((inv) => inv.id));
3333
+ const safeValue = Array.isArray(value) ? value : [];
3334
+ valueRef.current = safeValue;
3335
+ const addedIds = new Set(safeValue.map((inv) => inv.id));
3330
3336
  useEffect(() => {
3331
3337
  if (!open) {
3332
3338
  setQuery("");
@@ -3369,7 +3375,7 @@ var AddInvestigationField = ({
3369
3375
  }, ADD_FEEDBACK_MS2);
3370
3376
  }
3371
3377
  function removeInvestigation(id) {
3372
- onChange(value.filter((inv) => inv.id !== id));
3378
+ onChange(safeValue.filter((inv) => inv.id !== id));
3373
3379
  }
3374
3380
  function handleRecentlyUsedToggle(result) {
3375
3381
  if (addedIds.has(result.id)) {
@@ -3381,10 +3387,10 @@ var AddInvestigationField = ({
3381
3387
  async function handleFrequentItemToggle(item) {
3382
3388
  const id = String(item.id);
3383
3389
  const addedById = addedIds.has(id);
3384
- const addedByName = value.some((inv) => inv.name === item.name);
3390
+ const addedByName = safeValue.some((inv) => inv.name === item.name);
3385
3391
  if (addedById || addedByName) {
3386
3392
  if (addedById) removeInvestigation(id);
3387
- else onChange(value.filter((inv) => inv.name !== item.name));
3393
+ else onChange(safeValue.filter((inv) => inv.name !== item.name));
3388
3394
  return;
3389
3395
  }
3390
3396
  setAddingFrequentId(id);
@@ -3420,7 +3426,7 @@ var AddInvestigationField = ({
3420
3426
  const limitedFrequentItems = frequentItems.slice(0, 25);
3421
3427
  const frequentItemChips = limitedFrequentItems.length > 0 ? limitedFrequentItems.map((item) => {
3422
3428
  const id = String(item.id);
3423
- const isAdded = value.some((inv) => inv.id === id) || value.some((inv) => inv.name === item.name);
3429
+ const isAdded = safeValue.some((inv) => inv.id === id) || safeValue.some((inv) => inv.name === item.name);
3424
3430
  const isAdding = addingFrequentId === id;
3425
3431
  return /* @__PURE__ */ jsxs(
3426
3432
  "button",
@@ -3454,7 +3460,7 @@ var AddInvestigationField = ({
3454
3460
  }
3455
3461
  )
3456
3462
  ] }),
3457
- value.length > 0 && /* @__PURE__ */ jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: value.map((inv) => /* @__PURE__ */ jsxs(
3463
+ safeValue.length > 0 && /* @__PURE__ */ jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: safeValue.map((inv) => /* @__PURE__ */ jsxs(
3458
3464
  "div",
3459
3465
  {
3460
3466
  className: "flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50",
@@ -3521,10 +3527,11 @@ var AddInvestigationWidget = ({ fieldId }) => {
3521
3527
  const { value, setValue } = useField(fieldId);
3522
3528
  const handlers = useFieldHandlers(fieldId);
3523
3529
  const { investigations: frequentInvestigations } = useFrequentItems();
3530
+ const safeValue = Array.isArray(value) ? value : [];
3524
3531
  return /* @__PURE__ */ jsx(
3525
3532
  AddInvestigationField,
3526
3533
  {
3527
- value: value ?? [],
3534
+ value: safeValue,
3528
3535
  onChange: setValue,
3529
3536
  onSearch: handlers.onSearch,
3530
3537
  recentlyUsed: handlers.recentlyUsed,
@@ -3561,9 +3568,10 @@ var AddProcedureField = ({
3561
3568
  const debounceRef = useRef(null);
3562
3569
  const valueRef = useRef([]);
3563
3570
  const searchInputRef = useRef(null);
3564
- valueRef.current = value;
3571
+ const safeValue = Array.isArray(value) ? value : [];
3572
+ valueRef.current = safeValue;
3565
3573
  const addedIds = new Set(
3566
- value.filter((proc) => !proc.is_custom).map((proc) => proc.id)
3574
+ safeValue.filter((proc) => !proc.is_custom).map((proc) => proc.id)
3567
3575
  );
3568
3576
  useEffect(() => {
3569
3577
  if (!open) {
@@ -3607,10 +3615,10 @@ var AddProcedureField = ({
3607
3615
  }, ADD_FEEDBACK_MS3);
3608
3616
  }
3609
3617
  function removeAt(index) {
3610
- onChange(value.filter((_, i) => i !== index));
3618
+ onChange(safeValue.filter((_, i) => i !== index));
3611
3619
  }
3612
3620
  function removeBySearchId(id) {
3613
- onChange(value.filter((proc) => proc.is_custom || proc.id !== id));
3621
+ onChange(safeValue.filter((proc) => proc.is_custom || proc.id !== id));
3614
3622
  }
3615
3623
  function handleAddCustom() {
3616
3624
  const trimmed = query.trim();
@@ -3630,10 +3638,10 @@ var AddProcedureField = ({
3630
3638
  async function handleFrequentItemToggle(item) {
3631
3639
  const id = String(item.id);
3632
3640
  const addedById = addedIds.has(id);
3633
- const addedByName = value.some((proc) => proc.name === item.name);
3641
+ const addedByName = safeValue.some((proc) => proc.name === item.name);
3634
3642
  if (addedById || addedByName) {
3635
3643
  if (addedById) removeBySearchId(id);
3636
- else onChange(value.filter((proc) => proc.name !== item.name));
3644
+ else onChange(safeValue.filter((proc) => proc.name !== item.name));
3637
3645
  return;
3638
3646
  }
3639
3647
  setAddingFrequentId(id);
@@ -3669,7 +3677,7 @@ var AddProcedureField = ({
3669
3677
  const limitedFrequentItems = frequentItems.slice(0, 25);
3670
3678
  const frequentItemChips = limitedFrequentItems.length > 0 ? limitedFrequentItems.map((item) => {
3671
3679
  const id = String(item.id);
3672
- const isAdded = value.some((proc) => !proc.is_custom && proc.id === id) || value.some((proc) => proc.name === item.name);
3680
+ const isAdded = safeValue.some((proc) => !proc.is_custom && proc.id === id) || safeValue.some((proc) => proc.name === item.name);
3673
3681
  const isAdding = addingFrequentId === id;
3674
3682
  return /* @__PURE__ */ jsxs(
3675
3683
  "button",
@@ -3703,7 +3711,7 @@ var AddProcedureField = ({
3703
3711
  }
3704
3712
  )
3705
3713
  ] }),
3706
- value.length > 0 && /* @__PURE__ */ jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: value.map((proc, index) => /* @__PURE__ */ jsxs(
3714
+ safeValue.length > 0 && /* @__PURE__ */ jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: safeValue.map((proc, index) => /* @__PURE__ */ jsxs(
3707
3715
  "div",
3708
3716
  {
3709
3717
  className: "flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50",
@@ -3783,10 +3791,11 @@ var AddProcedureWidget = ({ fieldId }) => {
3783
3791
  const { value, setValue } = useField(fieldId);
3784
3792
  const handlers = useFieldHandlers(fieldId);
3785
3793
  const { procedures: frequentProcedures } = useFrequentItems();
3794
+ const safeValue = Array.isArray(value) ? value : [];
3786
3795
  return /* @__PURE__ */ jsx(
3787
3796
  AddProcedureField,
3788
3797
  {
3789
- value: value ?? [],
3798
+ value: safeValue,
3790
3799
  onChange: setValue,
3791
3800
  onSearch: handlers.onSearch,
3792
3801
  recentlyUsed: handlers.recentlyUsed,
@@ -4755,6 +4764,174 @@ var SmartTextareaWidget = ({ fieldId }) => {
4755
4764
  showError && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-500", children: error })
4756
4765
  ] });
4757
4766
  };
4767
+ function normalizeDiagnosisGrades(payload) {
4768
+ const result = [];
4769
+ const pushGroup = (conditionRaw, gradesRaw) => {
4770
+ const condition = typeof conditionRaw === "string" ? conditionRaw.trim() : "";
4771
+ if (!condition) return;
4772
+ if (!Array.isArray(gradesRaw)) return;
4773
+ const grades = Array.from(
4774
+ new Set(
4775
+ gradesRaw.map((grade) => typeof grade === "string" ? grade.trim() : "").filter(Boolean)
4776
+ )
4777
+ );
4778
+ if (grades.length === 0) return;
4779
+ result.push({ condition, grades });
4780
+ };
4781
+ const asArrayResponse = payload;
4782
+ if (Array.isArray(asArrayResponse?.diagnosisGrades)) {
4783
+ asArrayResponse.diagnosisGrades.forEach((item) => {
4784
+ pushGroup(item?.condition, item?.grades);
4785
+ });
4786
+ return result;
4787
+ }
4788
+ const asMapResponse = payload;
4789
+ const mapLike = asMapResponse?.field === "diagnosisgrade" && asMapResponse?.value && typeof asMapResponse.value === "object" ? asMapResponse.value : null;
4790
+ if (mapLike) {
4791
+ Object.entries(mapLike).forEach(([condition, grades]) => {
4792
+ pushGroup(condition, grades);
4793
+ });
4794
+ }
4795
+ return result;
4796
+ }
4797
+ var DiagnosisTextareaWidget = ({ fieldId }) => {
4798
+ const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
4799
+ const { state } = useForm();
4800
+ const handler = useFieldHandlers(fieldId);
4801
+ const def = fieldDef;
4802
+ const [gradeGroups, setGradeGroups] = React13__default.useState([]);
4803
+ const [isLoadingGrades, setIsLoadingGrades] = React13__default.useState(false);
4804
+ const normalized = React13__default.useMemo(() => {
4805
+ const raw = value;
4806
+ if (typeof raw === "string") {
4807
+ return { text: raw, selectedGradesByCondition: {} };
4808
+ }
4809
+ if (!raw || typeof raw !== "object") {
4810
+ return { text: "", selectedGradesByCondition: {} };
4811
+ }
4812
+ const text = typeof raw.text === "string" ? raw.text : "";
4813
+ const mapRaw = raw.selectedGradesByCondition;
4814
+ let selectedGradesByCondition = {};
4815
+ if (mapRaw && typeof mapRaw === "object" && !Array.isArray(mapRaw)) {
4816
+ const entries = Object.entries(mapRaw);
4817
+ for (const [condition, gradeRaw] of entries) {
4818
+ if (typeof condition !== "string") continue;
4819
+ if (typeof gradeRaw === "string" && gradeRaw.trim()) {
4820
+ selectedGradesByCondition[condition] = gradeRaw;
4821
+ }
4822
+ }
4823
+ }
4824
+ const sg = raw.selectedGrade;
4825
+ if (sg && typeof sg === "object" && typeof sg.condition === "string" && typeof sg.grade === "string") {
4826
+ const condition = sg.condition.trim();
4827
+ const grade = sg.grade.trim();
4828
+ if (condition && grade && !selectedGradesByCondition[condition]) {
4829
+ selectedGradesByCondition = { ...selectedGradesByCondition, [condition]: grade };
4830
+ }
4831
+ }
4832
+ return { text, selectedGradesByCondition };
4833
+ }, [value]);
4834
+ const textValue = normalized.text;
4835
+ const showError = !!error && (touched || state.submitAttempted);
4836
+ React13__default.useEffect(() => {
4837
+ const query = textValue.trim();
4838
+ const fetchGrades = handler.onFetchDiagnosisGrades;
4839
+ const debounceMs = def?.debounceDelay ?? 1e3;
4840
+ if (!query || !fetchGrades) {
4841
+ setGradeGroups([]);
4842
+ setIsLoadingGrades(false);
4843
+ return;
4844
+ }
4845
+ let isActive = true;
4846
+ const timer = window.setTimeout(async () => {
4847
+ setIsLoadingGrades(true);
4848
+ try {
4849
+ const response = await fetchGrades(query);
4850
+ if (!isActive) return;
4851
+ setGradeGroups(normalizeDiagnosisGrades(response));
4852
+ } catch {
4853
+ if (!isActive) return;
4854
+ setGradeGroups([]);
4855
+ } finally {
4856
+ if (isActive) setIsLoadingGrades(false);
4857
+ }
4858
+ }, debounceMs);
4859
+ return () => {
4860
+ isActive = false;
4861
+ window.clearTimeout(timer);
4862
+ };
4863
+ }, [textValue, handler.onFetchDiagnosisGrades, def?.debounceDelay]);
4864
+ if (!def) return null;
4865
+ const theme = getThemeConfig("textarea", def.theme);
4866
+ const wrapperClass = theme?.wrapperClassName ?? "space-y-2";
4867
+ const labelClass = theme?.labelClassName;
4868
+ const inputClass = cn(theme?.inputClassName, showError && "border-red-500");
4869
+ const toggleSelectedGrade = (condition, grade) => {
4870
+ if (disabled) return;
4871
+ const current = normalized.selectedGradesByCondition[condition];
4872
+ const nextForCondition = current === grade ? "" : grade;
4873
+ const nextMap = { ...normalized.selectedGradesByCondition };
4874
+ if (!nextForCondition) {
4875
+ delete nextMap[condition];
4876
+ } else {
4877
+ nextMap[condition] = nextForCondition;
4878
+ }
4879
+ setValue({ text: normalized.text, selectedGradesByCondition: nextMap });
4880
+ setTouched();
4881
+ };
4882
+ const labelContent = /* @__PURE__ */ jsxs(Fragment, { children: [
4883
+ def.label,
4884
+ " ",
4885
+ def.required && /* @__PURE__ */ jsx("span", { className: "text-red-500", children: "*" })
4886
+ ] });
4887
+ return /* @__PURE__ */ jsxs("div", { className: wrapperClass, children: [
4888
+ theme ? /* @__PURE__ */ jsx(Label, { htmlFor: fieldId, className: labelClass, children: labelContent }) : /* @__PURE__ */ jsx(Label, { htmlFor: fieldId, children: labelContent }),
4889
+ /* @__PURE__ */ jsx(
4890
+ Textarea,
4891
+ {
4892
+ id: fieldId,
4893
+ value: textValue,
4894
+ onChange: (e) => setValue({ text: e.target.value, selectedGradesByCondition: normalized.selectedGradesByCondition }),
4895
+ onBlur: setTouched,
4896
+ disabled,
4897
+ placeholder: def.placeholder,
4898
+ className: inputClass,
4899
+ ...def.height != null ? { height: def.height } : {}
4900
+ }
4901
+ ),
4902
+ isLoadingGrades && /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Checking diagnosis grades..." }),
4903
+ !isLoadingGrades && gradeGroups.length > 0 && /* @__PURE__ */ jsx(
4904
+ "div",
4905
+ {
4906
+ className: "mt-2 space-y-3 rounded-lg border border-border bg-muted/30 p-3",
4907
+ role: "group",
4908
+ "aria-label": "Diagnosis grade options",
4909
+ children: gradeGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
4910
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium leading-none text-muted-foreground", children: [
4911
+ group.condition,
4912
+ ":"
4913
+ ] }),
4914
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: group.grades.map((grade) => /* @__PURE__ */ jsx(
4915
+ "button",
4916
+ {
4917
+ type: "button",
4918
+ onClick: () => toggleSelectedGrade(group.condition, grade),
4919
+ disabled,
4920
+ className: cn(
4921
+ "inline-flex min-h-8 items-center rounded-full border px-3 py-1 text-xs font-medium leading-none transition-colors",
4922
+ normalized.selectedGradesByCondition[group.condition] === grade ? "border-primary bg-primary/15 text-primary shadow-sm" : "border-border/80 bg-background text-foreground hover:bg-muted/80",
4923
+ disabled && "cursor-not-allowed opacity-60"
4924
+ ),
4925
+ children: grade
4926
+ },
4927
+ `${group.condition}-${grade}`
4928
+ )) })
4929
+ ] }, group.condition))
4930
+ }
4931
+ ),
4932
+ showError && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-500", children: error })
4933
+ ] });
4934
+ };
4758
4935
  var DEFAULT_DRAFT = {
4759
4936
  gpal: { G: 0, P: 0, A: 0, L: 0 },
4760
4937
  lmp: "",
@@ -7108,6 +7285,8 @@ var FieldRenderer = ({ fieldId }) => {
7108
7285
  return /* @__PURE__ */ jsx(FollowupWidget, { fieldId });
7109
7286
  case "smart_textarea":
7110
7287
  return /* @__PURE__ */ jsx(SmartTextareaWidget, { fieldId });
7288
+ case "diagnosis_textarea":
7289
+ return /* @__PURE__ */ jsx(DiagnosisTextareaWidget, { fieldId });
7111
7290
  case "obstetric_history":
7112
7291
  return /* @__PURE__ */ jsx(ObstetricHistoryWidget, { fieldId });
7113
7292
  case "eye_prescription":