formanitor 0.0.27 → 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) => {
@@ -4794,6 +4799,174 @@ var SmartTextareaWidget = ({ fieldId }) => {
4794
4799
  showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
4795
4800
  ] });
4796
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
+ };
4797
4970
  var DEFAULT_DRAFT = {
4798
4971
  gpal: { G: 0, P: 0, A: 0, L: 0 },
4799
4972
  lmp: "",
@@ -7147,6 +7320,8 @@ var FieldRenderer = ({ fieldId }) => {
7147
7320
  return /* @__PURE__ */ jsxRuntime.jsx(FollowupWidget, { fieldId });
7148
7321
  case "smart_textarea":
7149
7322
  return /* @__PURE__ */ jsxRuntime.jsx(SmartTextareaWidget, { fieldId });
7323
+ case "diagnosis_textarea":
7324
+ return /* @__PURE__ */ jsxRuntime.jsx(DiagnosisTextareaWidget, { fieldId });
7150
7325
  case "obstetric_history":
7151
7326
  return /* @__PURE__ */ jsxRuntime.jsx(ObstetricHistoryWidget, { fieldId });
7152
7327
  case "eye_prescription":