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.cjs CHANGED
@@ -430,6 +430,11 @@ var FormStore = class {
430
430
  result[field.id] = raw?.text ?? null;
431
431
  return;
432
432
  }
433
+ if (field.type === "diagnosis_textarea") {
434
+ const raw = values[field.id];
435
+ result[field.id] = typeof raw === "string" ? raw : raw && typeof raw === "object" ? raw.text ?? null : null;
436
+ return;
437
+ }
433
438
  if (field.type === "ophthal_diagnosis") {
434
439
  const raw = values[field.id];
435
440
  const formatEye = (eyeData, eyeLabel) => {
@@ -3360,8 +3365,9 @@ var AddInvestigationField = ({
3360
3365
  const debounceRef = React13.useRef(null);
3361
3366
  const valueRef = React13.useRef([]);
3362
3367
  const searchInputRef = React13.useRef(null);
3363
- valueRef.current = value;
3364
- const addedIds = new Set(value.map((inv) => inv.id));
3368
+ const safeValue = Array.isArray(value) ? value : [];
3369
+ valueRef.current = safeValue;
3370
+ const addedIds = new Set(safeValue.map((inv) => inv.id));
3365
3371
  React13.useEffect(() => {
3366
3372
  if (!open) {
3367
3373
  setQuery("");
@@ -3404,7 +3410,7 @@ var AddInvestigationField = ({
3404
3410
  }, ADD_FEEDBACK_MS2);
3405
3411
  }
3406
3412
  function removeInvestigation(id) {
3407
- onChange(value.filter((inv) => inv.id !== id));
3413
+ onChange(safeValue.filter((inv) => inv.id !== id));
3408
3414
  }
3409
3415
  function handleRecentlyUsedToggle(result) {
3410
3416
  if (addedIds.has(result.id)) {
@@ -3416,10 +3422,10 @@ var AddInvestigationField = ({
3416
3422
  async function handleFrequentItemToggle(item) {
3417
3423
  const id = String(item.id);
3418
3424
  const addedById = addedIds.has(id);
3419
- const addedByName = value.some((inv) => inv.name === item.name);
3425
+ const addedByName = safeValue.some((inv) => inv.name === item.name);
3420
3426
  if (addedById || addedByName) {
3421
3427
  if (addedById) removeInvestigation(id);
3422
- else onChange(value.filter((inv) => inv.name !== item.name));
3428
+ else onChange(safeValue.filter((inv) => inv.name !== item.name));
3423
3429
  return;
3424
3430
  }
3425
3431
  setAddingFrequentId(id);
@@ -3455,7 +3461,7 @@ var AddInvestigationField = ({
3455
3461
  const limitedFrequentItems = frequentItems.slice(0, 25);
3456
3462
  const frequentItemChips = limitedFrequentItems.length > 0 ? limitedFrequentItems.map((item) => {
3457
3463
  const id = String(item.id);
3458
- const isAdded = value.some((inv) => inv.id === id) || value.some((inv) => inv.name === item.name);
3464
+ const isAdded = safeValue.some((inv) => inv.id === id) || safeValue.some((inv) => inv.name === item.name);
3459
3465
  const isAdding = addingFrequentId === id;
3460
3466
  return /* @__PURE__ */ jsxRuntime.jsxs(
3461
3467
  "button",
@@ -3489,7 +3495,7 @@ var AddInvestigationField = ({
3489
3495
  }
3490
3496
  )
3491
3497
  ] }),
3492
- value.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: value.map((inv) => /* @__PURE__ */ jsxRuntime.jsxs(
3498
+ safeValue.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: safeValue.map((inv) => /* @__PURE__ */ jsxRuntime.jsxs(
3493
3499
  "div",
3494
3500
  {
3495
3501
  className: "flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50",
@@ -3556,10 +3562,11 @@ var AddInvestigationWidget = ({ fieldId }) => {
3556
3562
  const { value, setValue } = useField(fieldId);
3557
3563
  const handlers = useFieldHandlers(fieldId);
3558
3564
  const { investigations: frequentInvestigations } = useFrequentItems();
3565
+ const safeValue = Array.isArray(value) ? value : [];
3559
3566
  return /* @__PURE__ */ jsxRuntime.jsx(
3560
3567
  AddInvestigationField,
3561
3568
  {
3562
- value: value ?? [],
3569
+ value: safeValue,
3563
3570
  onChange: setValue,
3564
3571
  onSearch: handlers.onSearch,
3565
3572
  recentlyUsed: handlers.recentlyUsed,
@@ -3596,9 +3603,10 @@ var AddProcedureField = ({
3596
3603
  const debounceRef = React13.useRef(null);
3597
3604
  const valueRef = React13.useRef([]);
3598
3605
  const searchInputRef = React13.useRef(null);
3599
- valueRef.current = value;
3606
+ const safeValue = Array.isArray(value) ? value : [];
3607
+ valueRef.current = safeValue;
3600
3608
  const addedIds = new Set(
3601
- value.filter((proc) => !proc.is_custom).map((proc) => proc.id)
3609
+ safeValue.filter((proc) => !proc.is_custom).map((proc) => proc.id)
3602
3610
  );
3603
3611
  React13.useEffect(() => {
3604
3612
  if (!open) {
@@ -3642,10 +3650,10 @@ var AddProcedureField = ({
3642
3650
  }, ADD_FEEDBACK_MS3);
3643
3651
  }
3644
3652
  function removeAt(index) {
3645
- onChange(value.filter((_, i) => i !== index));
3653
+ onChange(safeValue.filter((_, i) => i !== index));
3646
3654
  }
3647
3655
  function removeBySearchId(id) {
3648
- onChange(value.filter((proc) => proc.is_custom || proc.id !== id));
3656
+ onChange(safeValue.filter((proc) => proc.is_custom || proc.id !== id));
3649
3657
  }
3650
3658
  function handleAddCustom() {
3651
3659
  const trimmed = query.trim();
@@ -3665,10 +3673,10 @@ var AddProcedureField = ({
3665
3673
  async function handleFrequentItemToggle(item) {
3666
3674
  const id = String(item.id);
3667
3675
  const addedById = addedIds.has(id);
3668
- const addedByName = value.some((proc) => proc.name === item.name);
3676
+ const addedByName = safeValue.some((proc) => proc.name === item.name);
3669
3677
  if (addedById || addedByName) {
3670
3678
  if (addedById) removeBySearchId(id);
3671
- else onChange(value.filter((proc) => proc.name !== item.name));
3679
+ else onChange(safeValue.filter((proc) => proc.name !== item.name));
3672
3680
  return;
3673
3681
  }
3674
3682
  setAddingFrequentId(id);
@@ -3704,7 +3712,7 @@ var AddProcedureField = ({
3704
3712
  const limitedFrequentItems = frequentItems.slice(0, 25);
3705
3713
  const frequentItemChips = limitedFrequentItems.length > 0 ? limitedFrequentItems.map((item) => {
3706
3714
  const id = String(item.id);
3707
- const isAdded = value.some((proc) => !proc.is_custom && proc.id === id) || value.some((proc) => proc.name === item.name);
3715
+ const isAdded = safeValue.some((proc) => !proc.is_custom && proc.id === id) || safeValue.some((proc) => proc.name === item.name);
3708
3716
  const isAdding = addingFrequentId === id;
3709
3717
  return /* @__PURE__ */ jsxRuntime.jsxs(
3710
3718
  "button",
@@ -3738,7 +3746,7 @@ var AddProcedureField = ({
3738
3746
  }
3739
3747
  )
3740
3748
  ] }),
3741
- value.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: value.map((proc, index) => /* @__PURE__ */ jsxRuntime.jsxs(
3749
+ safeValue.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: safeValue.map((proc, index) => /* @__PURE__ */ jsxRuntime.jsxs(
3742
3750
  "div",
3743
3751
  {
3744
3752
  className: "flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50",
@@ -3818,10 +3826,11 @@ var AddProcedureWidget = ({ fieldId }) => {
3818
3826
  const { value, setValue } = useField(fieldId);
3819
3827
  const handlers = useFieldHandlers(fieldId);
3820
3828
  const { procedures: frequentProcedures } = useFrequentItems();
3829
+ const safeValue = Array.isArray(value) ? value : [];
3821
3830
  return /* @__PURE__ */ jsxRuntime.jsx(
3822
3831
  AddProcedureField,
3823
3832
  {
3824
- value: value ?? [],
3833
+ value: safeValue,
3825
3834
  onChange: setValue,
3826
3835
  onSearch: handlers.onSearch,
3827
3836
  recentlyUsed: handlers.recentlyUsed,
@@ -4790,6 +4799,174 @@ var SmartTextareaWidget = ({ fieldId }) => {
4790
4799
  showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
4791
4800
  ] });
4792
4801
  };
4802
+ function normalizeDiagnosisGrades(payload) {
4803
+ const result = [];
4804
+ const pushGroup = (conditionRaw, gradesRaw) => {
4805
+ const condition = typeof conditionRaw === "string" ? conditionRaw.trim() : "";
4806
+ if (!condition) return;
4807
+ if (!Array.isArray(gradesRaw)) return;
4808
+ const grades = Array.from(
4809
+ new Set(
4810
+ gradesRaw.map((grade) => typeof grade === "string" ? grade.trim() : "").filter(Boolean)
4811
+ )
4812
+ );
4813
+ if (grades.length === 0) return;
4814
+ result.push({ condition, grades });
4815
+ };
4816
+ const asArrayResponse = payload;
4817
+ if (Array.isArray(asArrayResponse?.diagnosisGrades)) {
4818
+ asArrayResponse.diagnosisGrades.forEach((item) => {
4819
+ pushGroup(item?.condition, item?.grades);
4820
+ });
4821
+ return result;
4822
+ }
4823
+ const asMapResponse = payload;
4824
+ const mapLike = asMapResponse?.field === "diagnosisgrade" && asMapResponse?.value && typeof asMapResponse.value === "object" ? asMapResponse.value : null;
4825
+ if (mapLike) {
4826
+ Object.entries(mapLike).forEach(([condition, grades]) => {
4827
+ pushGroup(condition, grades);
4828
+ });
4829
+ }
4830
+ return result;
4831
+ }
4832
+ var DiagnosisTextareaWidget = ({ fieldId }) => {
4833
+ const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
4834
+ const { state } = useForm();
4835
+ const handler = useFieldHandlers(fieldId);
4836
+ const def = fieldDef;
4837
+ const [gradeGroups, setGradeGroups] = React13__namespace.default.useState([]);
4838
+ const [isLoadingGrades, setIsLoadingGrades] = React13__namespace.default.useState(false);
4839
+ const normalized = React13__namespace.default.useMemo(() => {
4840
+ const raw = value;
4841
+ if (typeof raw === "string") {
4842
+ return { text: raw, selectedGradesByCondition: {} };
4843
+ }
4844
+ if (!raw || typeof raw !== "object") {
4845
+ return { text: "", selectedGradesByCondition: {} };
4846
+ }
4847
+ const text = typeof raw.text === "string" ? raw.text : "";
4848
+ const mapRaw = raw.selectedGradesByCondition;
4849
+ let selectedGradesByCondition = {};
4850
+ if (mapRaw && typeof mapRaw === "object" && !Array.isArray(mapRaw)) {
4851
+ const entries = Object.entries(mapRaw);
4852
+ for (const [condition, gradeRaw] of entries) {
4853
+ if (typeof condition !== "string") continue;
4854
+ if (typeof gradeRaw === "string" && gradeRaw.trim()) {
4855
+ selectedGradesByCondition[condition] = gradeRaw;
4856
+ }
4857
+ }
4858
+ }
4859
+ const sg = raw.selectedGrade;
4860
+ if (sg && typeof sg === "object" && typeof sg.condition === "string" && typeof sg.grade === "string") {
4861
+ const condition = sg.condition.trim();
4862
+ const grade = sg.grade.trim();
4863
+ if (condition && grade && !selectedGradesByCondition[condition]) {
4864
+ selectedGradesByCondition = { ...selectedGradesByCondition, [condition]: grade };
4865
+ }
4866
+ }
4867
+ return { text, selectedGradesByCondition };
4868
+ }, [value]);
4869
+ const textValue = normalized.text;
4870
+ const showError = !!error && (touched || state.submitAttempted);
4871
+ React13__namespace.default.useEffect(() => {
4872
+ const query = textValue.trim();
4873
+ const fetchGrades = handler.onFetchDiagnosisGrades;
4874
+ const debounceMs = def?.debounceDelay ?? 1e3;
4875
+ if (!query || !fetchGrades) {
4876
+ setGradeGroups([]);
4877
+ setIsLoadingGrades(false);
4878
+ return;
4879
+ }
4880
+ let isActive = true;
4881
+ const timer = window.setTimeout(async () => {
4882
+ setIsLoadingGrades(true);
4883
+ try {
4884
+ const response = await fetchGrades(query);
4885
+ if (!isActive) return;
4886
+ setGradeGroups(normalizeDiagnosisGrades(response));
4887
+ } catch {
4888
+ if (!isActive) return;
4889
+ setGradeGroups([]);
4890
+ } finally {
4891
+ if (isActive) setIsLoadingGrades(false);
4892
+ }
4893
+ }, debounceMs);
4894
+ return () => {
4895
+ isActive = false;
4896
+ window.clearTimeout(timer);
4897
+ };
4898
+ }, [textValue, handler.onFetchDiagnosisGrades, def?.debounceDelay]);
4899
+ if (!def) return null;
4900
+ const theme = getThemeConfig("textarea", def.theme);
4901
+ const wrapperClass = theme?.wrapperClassName ?? "space-y-2";
4902
+ const labelClass = theme?.labelClassName;
4903
+ const inputClass = cn(theme?.inputClassName, showError && "border-red-500");
4904
+ const toggleSelectedGrade = (condition, grade) => {
4905
+ if (disabled) return;
4906
+ const current = normalized.selectedGradesByCondition[condition];
4907
+ const nextForCondition = current === grade ? "" : grade;
4908
+ const nextMap = { ...normalized.selectedGradesByCondition };
4909
+ if (!nextForCondition) {
4910
+ delete nextMap[condition];
4911
+ } else {
4912
+ nextMap[condition] = nextForCondition;
4913
+ }
4914
+ setValue({ text: normalized.text, selectedGradesByCondition: nextMap });
4915
+ setTouched();
4916
+ };
4917
+ const labelContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4918
+ def.label,
4919
+ " ",
4920
+ def.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
4921
+ ] });
4922
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClass, children: [
4923
+ theme ? /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, className: labelClass, children: labelContent }) : /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, children: labelContent }),
4924
+ /* @__PURE__ */ jsxRuntime.jsx(
4925
+ Textarea,
4926
+ {
4927
+ id: fieldId,
4928
+ value: textValue,
4929
+ onChange: (e) => setValue({ text: e.target.value, selectedGradesByCondition: normalized.selectedGradesByCondition }),
4930
+ onBlur: setTouched,
4931
+ disabled,
4932
+ placeholder: def.placeholder,
4933
+ className: inputClass,
4934
+ ...def.height != null ? { height: def.height } : {}
4935
+ }
4936
+ ),
4937
+ isLoadingGrades && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Checking diagnosis grades..." }),
4938
+ !isLoadingGrades && gradeGroups.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
4939
+ "div",
4940
+ {
4941
+ className: "mt-2 space-y-3 rounded-lg border border-border bg-muted/30 p-3",
4942
+ role: "group",
4943
+ "aria-label": "Diagnosis grade options",
4944
+ children: gradeGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
4945
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium leading-none text-muted-foreground", children: [
4946
+ group.condition,
4947
+ ":"
4948
+ ] }),
4949
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: group.grades.map((grade) => /* @__PURE__ */ jsxRuntime.jsx(
4950
+ "button",
4951
+ {
4952
+ type: "button",
4953
+ onClick: () => toggleSelectedGrade(group.condition, grade),
4954
+ disabled,
4955
+ className: cn(
4956
+ "inline-flex min-h-8 items-center rounded-full border px-3 py-1 text-xs font-medium leading-none transition-colors",
4957
+ 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",
4958
+ disabled && "cursor-not-allowed opacity-60"
4959
+ ),
4960
+ children: grade
4961
+ },
4962
+ `${group.condition}-${grade}`
4963
+ )) })
4964
+ ] }, group.condition))
4965
+ }
4966
+ ),
4967
+ showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
4968
+ ] });
4969
+ };
4793
4970
  var DEFAULT_DRAFT = {
4794
4971
  gpal: { G: 0, P: 0, A: 0, L: 0 },
4795
4972
  lmp: "",
@@ -7143,6 +7320,8 @@ var FieldRenderer = ({ fieldId }) => {
7143
7320
  return /* @__PURE__ */ jsxRuntime.jsx(FollowupWidget, { fieldId });
7144
7321
  case "smart_textarea":
7145
7322
  return /* @__PURE__ */ jsxRuntime.jsx(SmartTextareaWidget, { fieldId });
7323
+ case "diagnosis_textarea":
7324
+ return /* @__PURE__ */ jsxRuntime.jsx(DiagnosisTextareaWidget, { fieldId });
7146
7325
  case "obstetric_history":
7147
7326
  return /* @__PURE__ */ jsxRuntime.jsx(ObstetricHistoryWidget, { fieldId });
7148
7327
  case "eye_prescription":