@engagebay/engagebay-form-module 1.0.7 → 1.0.8-beta.2

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/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@engagebay/engagebay-form-module",
3
- "version": "1.0.7",
4
- "description": "Provide base form components to engagebay",
3
+ "version": "1.0.8-beta.2",
4
+ "description": "Provide base form components to reacho",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
7
7
  },
8
- "author": "Engagebay Developer",
8
+ "author": "Reacho Dev",
9
9
  "license": "ISC",
10
10
  "dependencies": {
11
11
  "react-phone-input-2": "^2.15.1",
@@ -16,7 +16,6 @@
16
16
  "@heroicons/react": ">=2.1.5",
17
17
  "@reduxjs/toolkit": ">=2.2.7",
18
18
  "@tippyjs/react": ">=4.2.6",
19
- "@types/lodash": ">=4.17.7",
20
19
  "@types/react-redux": ">=7.1.33",
21
20
  "axios": ">=1.7.2",
22
21
  "clsx": ">=2.1.1",
@@ -32,7 +31,6 @@
32
31
  "@heroicons/react": ">=2.1.5",
33
32
  "@reduxjs/toolkit": ">=2.2.7",
34
33
  "@tippyjs/react": ">=4.2.6",
35
- "@types/lodash": ">=4.17.7",
36
34
  "@types/react-redux": ">=7.1.33",
37
35
  "axios": ">=1.7.2",
38
36
  "clsx": ">=2.1.1",
package/src/api/index.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import axios, {AxiosInstance, AxiosRequestConfig} from "axios";
2
2
  import {FormFieldSchema} from "../form/schema/FormFieldSchema";
3
3
 
4
+ let baseURL;
5
+ try {
6
+ baseURL = (window as any).DEFAULT_HOST ||
7
+ (window as any).parent.DEFAULT_HOST
8
+ } catch (error) {
9
+
10
+ }
11
+
4
12
  const BASE_API: AxiosRequestConfig = {
5
- baseURL:
6
- (window as any).REACHO_BASE_URL ||
7
- (window as any).parent.REACHO_BASE_URL,
13
+ baseURL: baseURL ?? "",
8
14
  timeout: 30000,
9
15
  headers: {
10
16
  "Content-Type": "application/json",
@@ -28,13 +28,13 @@ import TimeField from "./formfields/TimeField";
28
28
  import Typeahead from "./formfields/Typeahead";
29
29
  import TypeaheadMultiSelect from "./formfields/TypeaheadMultiSelect";
30
30
  import UrlField from "./formfields/UrlField";
31
- import Typeahead2 from "./formfields/Typeahead2";
32
31
  import {
33
32
  FieldOptionsSchema,
34
33
  FormFieldComponentPropSchema,
35
34
  FormFieldType,
36
35
  OptionMappingConfig,
37
36
  } from "./schema/FormFieldSchema";
37
+ import Typeahead2 from "./formfields/Typeahead2";
38
38
 
39
39
  /**
40
40
  * @property {React.FC<FormFieldComponentPropSchema>} component - React component for a form field.
@@ -137,12 +137,12 @@ const formFieldComponents: FormComponentSchema = {
137
137
  [FormFieldType[FormFieldType.TYPEAHEAD]]: {
138
138
  component: Typeahead,
139
139
  },
140
- [FormFieldType[FormFieldType.TYPEAHEAD_2]]: {
141
- component: Typeahead2,
142
- },
143
140
  [FormFieldType[FormFieldType.TYPEAHEAD_MULTI_SELECT]]: {
144
141
  component: TypeaheadMultiSelect,
145
142
  },
143
+ [FormFieldType[FormFieldType.TYPEAHEAD_2]]: {
144
+ component: Typeahead2,
145
+ },
146
146
  [FormFieldType[FormFieldType.COMBO_SELECT]]: {
147
147
  component: ComboSelect,
148
148
  },
@@ -21,7 +21,6 @@ import FormField from "../FormField";
21
21
  import RenderFormField from "../util/RenderFormField";
22
22
  import { TrashIcon, XMarkIcon } from "@heroicons/react/24/outline";
23
23
  import Tippy from "@tippyjs/react";
24
- import { set } from "lodash";
25
24
 
26
25
  const defaultBusinessHours = {
27
26
  MONDAY: {
@@ -124,9 +123,8 @@ export const BusinessHoursField: React.FC<FormFieldComponentPropSchema> = (
124
123
  <div className="w-full sm:w-full text-end mr-[2.3em]">
125
124
  <button
126
125
  type="button"
127
- className={`text-end text-primary cursor-pointer font-[13px] font-medium ${
128
- getValues(`${mappedName}.sessions`)?.length > 1 ? "mr-9" : ""
129
- }`}
126
+ className={`text-end text-primary cursor-pointer font-[13px] font-medium ${getValues(`${mappedName}.sessions`)?.length > 1 ? "mr-9" : ""
127
+ }`}
130
128
  onClick={() => {
131
129
  const lastEndTime =
132
130
  getValues(`${mappedName}.sessions`)?.[
@@ -1,32 +1,46 @@
1
1
  import { RegisterOptions } from "react-hook-form";
2
2
  import { useContext, useEffect, useState } from "react";
3
3
  import { FormContext } from "../context/FormContext";
4
- import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
4
+ import {
5
+ FormFieldComponentPropSchema,
6
+ OutputFormatType,
7
+ } from "../schema/FormFieldSchema";
5
8
  import { Checkbox, Field, Input, Label } from "@headlessui/react";
6
9
  import React from "react";
7
10
  import RenderFormField from "../util/RenderFormField";
8
11
  import { handleChange, registerFormField } from "../util";
12
+ import { normalizeCheckboxBooleanValue, normalizeMultiSelectValue } from "../util/normalizeCustomFieldValues";
9
13
  const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
10
14
  props: FormFieldComponentPropSchema
11
15
  ) => {
12
16
  const formContext = useContext(FormContext);
13
17
  const [selectedOption, setSelectedOption] = useState<string[]>(
14
- formContext.getValues(props.fieldConfig.name)
15
- ? formContext.getValues(props.fieldConfig.name)
16
- : []
18
+ normalizeMultiSelectValue(formContext.getValues(props.fieldConfig.name))
17
19
  );
18
20
  const [isChecked, setIsChecked] = useState<boolean>(
19
- formContext.getValues(props.fieldConfig.name)
20
- ? formContext.getValues(props.fieldConfig.name)
21
- : props.fieldConfig.defaultValue
21
+ normalizeCheckboxBooleanValue(
22
+ formContext.getValues(props.fieldConfig.name) ?? props.fieldConfig.defaultValue
23
+ )
22
24
  );
23
25
  useEffect(() => {
24
- if (formContext.getValues(props.fieldConfig.name)) {
25
- setSelectedOption(formContext.getValues(props.fieldConfig.name));
26
+ const current = formContext.getValues(props.fieldConfig.name);
27
+ if (props.fieldConfig.options) {
28
+ setSelectedOption(normalizeMultiSelectValue(current));
29
+ } else {
30
+ setIsChecked(
31
+ normalizeCheckboxBooleanValue(current ?? props.fieldConfig.defaultValue)
32
+ );
26
33
  }
27
34
  }, []);
28
35
  let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
29
36
  let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
37
+ const getOutputValue = (value: any) => {
38
+ if (props.fieldConfig.outputFormat === OutputFormatType.STRING) {
39
+ if (Array.isArray(value)) return JSON.stringify(value);
40
+ if (typeof value === "boolean") return String(value);
41
+ }
42
+ return value;
43
+ };
30
44
  const handleInput = (option: string) => {
31
45
  let tempData: string[] = selectedOption;
32
46
  if (selectedOption.includes(option)) {
@@ -36,7 +50,12 @@ const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
36
50
  tempData.push(option);
37
51
  setSelectedOption([...tempData]);
38
52
  }
39
- handleChange(tempData, formContext, props.fieldConfig, props.onChange);
53
+ handleChange(
54
+ getOutputValue(tempData),
55
+ formContext,
56
+ props.fieldConfig,
57
+ props.onChange
58
+ );
40
59
  };
41
60
  function getInput() {
42
61
  return props.fieldConfig.options ? (
@@ -87,7 +106,7 @@ const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
87
106
  checked={isChecked}
88
107
  onChange={(checked) => {
89
108
  handleChange(
90
- checked,
109
+ getOutputValue(checked),
91
110
  formContext,
92
111
  props.fieldConfig,
93
112
  props.onChange
@@ -1,14 +1,17 @@
1
1
  import { RegisterOptions } from "react-hook-form";
2
- import React, { useContext, useEffect, useRef, useState } from "react";
2
+ import React, { useContext, useEffect } from "react";
3
3
  import Datepicker from "react-tailwindcss-datepicker";
4
4
  import { FormContext } from "../context/FormContext";
5
5
  import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
6
6
  import RenderFormField from "../util/RenderFormField";
7
7
  import { handleChange, registerFormField } from "../util";
8
+ import { normalizeDateFormat, normalizeDateInputValue } from "../util/normalizeCustomFieldValues";
8
9
  import moment from "moment";
9
10
 
11
+
12
+
10
13
  const DatePicker: React.FC<FormFieldComponentPropSchema> = (
11
- props: FormFieldComponentPropSchema
14
+ props: FormFieldComponentPropSchema,
12
15
  ) => {
13
16
  const formContext = useContext(FormContext);
14
17
  const initialDate = formContext.getValues(props.fieldConfig.name)
@@ -25,16 +28,17 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
25
28
  ) {
26
29
  formContext.setValue(
27
30
  props.fieldConfig.name,
28
- props.fieldConfig.defaultValue
31
+ props.fieldConfig.defaultValue,
29
32
  );
30
33
  }
31
34
  }, [props.fieldConfig.forceUpdate]);
32
35
 
33
36
  let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
34
-
37
+ //registerOptions.valueAsDate = true;
35
38
  let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
36
39
 
37
40
  function getInput() {
41
+ const displayFormat = normalizeDateFormat(props.fieldConfig.dateDisplayFormat);
38
42
  const rawStart =
39
43
  initialDate?.startDate ||
40
44
  props.fieldConfig.defaultValue ||
@@ -43,13 +47,9 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
43
47
  initialDate?.endDate ||
44
48
  props.fieldConfig.defaultValue ||
45
49
  "";
46
- const toDate = (v: string | Date | null | undefined): Date | null => {
47
- if (v == null || v === "") return null;
48
- return moment(v).isValid() ? moment(v).toDate() : null;
49
- };
50
50
  const value = {
51
- startDate: toDate(rawStart),
52
- endDate: toDate(rawEnd),
51
+ startDate: normalizeDateInputValue(rawStart, displayFormat),
52
+ endDate: normalizeDateInputValue(rawEnd, displayFormat),
53
53
  };
54
54
  // Calendar opens on selected date's month/year (must be a Date instance)
55
55
  const startFrom = value.startDate instanceof Date ? value.startDate : new Date();
@@ -58,10 +58,11 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
58
58
  value={value}
59
59
  startFrom={startFrom}
60
60
  {...hookProps}
61
+ displayFormat={displayFormat}
61
62
  placeholder={
62
63
  props.fieldConfig.placeholder
63
64
  ? props.fieldConfig.placeholder
64
- : "YYYY-MM-DD"
65
+ : displayFormat
65
66
  }
66
67
  asSingle={true}
67
68
  popoverDirection="down"
@@ -79,7 +80,7 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
79
80
  onChange={(dates) => {
80
81
  let date = null;
81
82
  if (dates?.startDate != null)
82
- date = moment(dates?.startDate).format("YYYY-MM-DD");
83
+ date = moment(dates?.startDate).format(displayFormat);
83
84
 
84
85
  handleChange(date, formContext, props.fieldConfig, props.onChange);
85
86
  }}
@@ -13,6 +13,7 @@ import {
13
13
  FieldOptionsSchema,
14
14
  FormFieldComponentPropSchema,
15
15
  FormFieldType,
16
+ OutputFormatType,
16
17
  } from "../schema/FormFieldSchema";
17
18
  import { FormContext } from "../context/FormContext";
18
19
  import RenderFormField from "../util/RenderFormField";
@@ -20,6 +21,7 @@ import RenderListOptions, {
20
21
  renderListBoxValue,
21
22
  } from "../util/RenderListOptions";
22
23
  import { handleChange, registerFormField } from "../util";
24
+ import { normalizeMultiSelectValue } from "../util/normalizeCustomFieldValues";
23
25
 
24
26
  const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
25
27
  fieldConfig,
@@ -34,9 +36,11 @@ const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
34
36
  fieldConfig.options ? fieldConfig.options : []
35
37
  );
36
38
 
37
- const validSelectedValues = (
38
- formContext.getValues(fieldConfig.name) || []
39
- ).filter(
39
+ const normalizedSelectedValues = normalizeMultiSelectValue(
40
+ formContext.getValues(fieldConfig.name)
41
+ );
42
+
43
+ const validSelectedValues = normalizedSelectedValues.filter(
40
44
  (selectedValue: any) =>
41
45
  fieldConfig.options &&
42
46
  fieldConfig.options.some((option) => option.value === selectedValue)
@@ -53,14 +57,22 @@ const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
53
57
  }, [fieldConfig.options]);
54
58
 
55
59
  function getInput() {
60
+ const getOutputValue = (val: any) => {
61
+ if (fieldConfig.outputFormat === OutputFormatType.STRING) {
62
+ const normalized = normalizeMultiSelectValue(val);
63
+ return JSON.stringify(normalized);
64
+ }
65
+ return val;
66
+ };
67
+
56
68
  return (
57
69
  <Listbox
58
70
  as={"div"}
59
71
  {...hookProps}
60
72
  value={validSelectedValues}
61
73
  defaultValue={fieldConfig.defaultValue}
62
- onChange={(val) =>
63
- handleChange(val, formContext, fieldConfig, onChange)
74
+ onChange={(val: any) =>
75
+ handleChange(getOutputValue(val), formContext, fieldConfig, onChange)
64
76
  }
65
77
  disabled={fieldConfig.disabled}
66
78
  multiple
@@ -1,9 +1,9 @@
1
1
  import React, {
2
- useCallback,
3
- useContext,
4
- useEffect,
5
- useRef,
6
- useState,
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
7
  } from "react";
8
8
 
9
9
  import { Listbox, ListboxButton } from "@headlessui/react";
@@ -12,199 +12,185 @@ import { RegisterOptions } from "react-hook-form";
12
12
  import { FormContext } from "../context/FormContext";
13
13
  import { getListOption, getListOptions } from "../FormFieldUtils";
14
14
  import {
15
- FieldOptionsSchema,
16
- FormFieldComponentPropSchema,
17
- FormFieldType,
18
- OutputFormatType,
15
+ FieldOptionsSchema,
16
+ FormFieldComponentPropSchema,
17
+ FormFieldType,
18
+ OutputFormatType,
19
19
  } from "../schema/FormFieldSchema";
20
20
  import { handleChange, registerFormField } from "../util";
21
21
  import RenderFormField from "../util/RenderFormField";
22
22
  import RenderListOptions, {
23
- renderListBoxValue,
23
+ renderListBoxValue,
24
24
  } from "../util/RenderListOptions";
25
25
 
26
26
  const Typeahead: React.FC<FormFieldComponentPropSchema> = (
27
- props: FormFieldComponentPropSchema
27
+ props: FormFieldComponentPropSchema,
28
28
  ) => {
29
- const dynamicSelectRef = useRef<HTMLUListElement>(null);
30
- const formContext = useContext(FormContext);
31
- let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
32
- let hookProps = formContext.register(
33
- props.fieldConfig.name,
34
- registerOptions
35
- );
36
- const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
37
- const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
38
- []
39
- );
40
- const [loading, setLoading] = useState<boolean>(true);
41
-
42
- useEffect(() => {
43
- if (
44
- !formContext.getValues(props.fieldConfig.name) &&
45
- props.fieldConfig.defaultValue
46
- ) {
47
- formContext.setValue(
48
- props.fieldConfig.name,
49
- props.fieldConfig.defaultValue
50
- );
51
- }
52
- fetchData(undefined);
53
- }, []);
29
+ const dynamicSelectRef = useRef<HTMLUListElement>(null);
30
+ const formContext = useContext(FormContext);
31
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
32
+ let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
33
+ const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
34
+ const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
35
+ [],
36
+ );
37
+ const [loading, setLoading] = useState<boolean>(true);
54
38
 
55
- const fetchData = useCallback(
56
- async (_query: string | undefined) => {
57
- setLoading(true);
58
- try {
59
- if (!props.fieldConfig.fetchUrl) return;
39
+ useEffect(() => {
40
+ if (
41
+ !formContext.getValues(props.fieldConfig.name) &&
42
+ props.fieldConfig.defaultValue
43
+ ) {
44
+ formContext.setValue(
45
+ props.fieldConfig.name,
46
+ props.fieldConfig.defaultValue,
47
+ );
48
+ }
49
+ fetchData(undefined);
50
+ }, []);
60
51
 
61
- let url = props.fieldConfig.fetchUrl;
62
- if (_query) {
63
- url = url.includes("?")
64
- ? url + "&q=" + _query
65
- : url + "?q=" + _query;
66
- }
52
+ const fetchData = useCallback(
53
+ async (_query: string | undefined) => {
54
+ setLoading(true);
55
+ try {
56
+ if (!props.fieldConfig.fetchUrl) return;
67
57
 
68
- let response = await (props.fieldConfig.disableHeaderInFetch
69
- ? axios.get(url)
70
- : formContext.axiosInstance?.get(url));
71
- if (response?.data) {
72
- const data: FieldOptionsSchema[] = getListOptions(
73
- response.data,
74
- props.fieldConfig.optionsConfig
75
- );
76
- setListOptions([...data]);
77
- let value = formContext.getValues(props.fieldConfig.name);
78
- if (
79
- value &&
80
- data.find((i) => i.value !== value)
81
- ) {
82
- if (selectedValues.find((i) => i.value !== value))
83
- fetchValue(value);
84
- }
85
- if (props.fieldConfig.fetchCallback) {
86
- props.fieldConfig.fetchCallback(response);
87
- }
88
- } else {
89
- console.error(response?.statusText);
90
- }
91
- } catch (err) {
92
- } finally {
93
- setLoading(false);
94
- }
95
- },
96
- [props.fieldConfig.fetchUrl]
97
- );
58
+ let url = props.fieldConfig.fetchUrl;
59
+ if (_query) {
60
+ url = url.includes("?") ? url + "&q=" + _query : url + "?q=" + _query;
61
+ }
98
62
 
99
- const fetchValue = async (value: string) => {
100
- try {
101
- if (
102
- props.fieldConfig.ignoreFetchValue ||
103
- !props.fieldConfig.fetchUrl
104
- ) {
105
- return;
106
- }
63
+ let response = await (props.fieldConfig.disableHeaderInFetch
64
+ ? axios.get(url)
65
+ : formContext.axiosInstance?.get(url));
66
+ if (response?.data) {
67
+ const data: FieldOptionsSchema[] = getListOptions(
68
+ response.data,
69
+ props.fieldConfig.optionsConfig,
70
+ );
71
+ setListOptions([...data]);
72
+ let value = formContext.getValues(props.fieldConfig.name);
73
+ if (value && data.find((i) => i.value !== value)) {
74
+ if (selectedValues.find((i) => i.value !== value))
75
+ fetchValue(value);
76
+ }
77
+ if (props.fieldConfig.fetchCallback) {
78
+ props.fieldConfig.fetchCallback(response);
79
+ }
80
+ } else {
81
+ console.error(response?.statusText);
82
+ }
83
+ } catch (err) {
84
+ } finally {
85
+ setLoading(false);
86
+ }
87
+ },
88
+ [props.fieldConfig.fetchUrl],
89
+ );
107
90
 
108
- let url = props.fieldConfig.fetchUrl;
91
+ const fetchValue = async (value: string) => {
92
+ try {
93
+ if (props.fieldConfig.ignoreFetchValue || !props.fieldConfig.fetchUrl) {
94
+ return;
95
+ }
109
96
 
110
- if (value)
111
- url = url.includes("?")
112
- ? url + "&values=" + value
113
- : url + "?values=" + value;
97
+ let url = props.fieldConfig.fetchUrl;
114
98
 
115
- let response = await (
116
- props.fieldConfig.disableHeaderInFetch
117
- ? axios.get(url)
118
- : formContext.axiosInstance?.get(url)
119
- );
120
- if (response?.data) {
121
- const data: FieldOptionsSchema[] = getListOptions(
122
- response?.data,
123
- props.fieldConfig.optionsConfig
124
- );
125
- setSelectedValues([...data]);
126
- }
127
- } catch (err) { }
128
- };
99
+ if (value)
100
+ url = url.includes("?")
101
+ ? url + "&values=" + value
102
+ : url + "?values=" + value;
129
103
 
130
- const updateListOptions = (data: any) => {
131
- const resData: FieldOptionsSchema = getListOption(
132
- data,
133
- props.fieldConfig.optionsConfig
104
+ let response = await (props.fieldConfig.disableHeaderInFetch
105
+ ? axios.get(url)
106
+ : formContext.axiosInstance?.get(url));
107
+ if (response?.data) {
108
+ const data: FieldOptionsSchema[] = getListOptions(
109
+ response?.data,
110
+ props.fieldConfig.optionsConfig,
134
111
  );
135
- setSelectedValues((perv) => [resData]);
136
- formContext.setValue(props.fieldConfig.name, resData.value);
137
- };
112
+ setSelectedValues([...data]);
113
+ }
114
+ } catch (err) {}
115
+ };
138
116
 
139
- function getInput() {
140
- return (
141
- <Listbox
142
- as={"div"}
143
- {...hookProps}
144
- className={`relative form-listbox flex-1`}
145
- value={
146
- formContext.getValues(props.fieldConfig.name)
147
- ? props.fieldConfig.outputFormat ===
148
- OutputFormatType.ARRAY
149
- ? formContext.getValues(props.fieldConfig.name)[0]
150
- : formContext.getValues(props.fieldConfig.name)
151
- : undefined
152
- }
153
- onChange={(val) => {
154
- const currentValue = formContext.getValues(
155
- props.fieldConfig.name
156
- );
117
+ const updateListOptions = (data: any) => {
118
+ const resData: FieldOptionsSchema = getListOption(
119
+ data,
120
+ props.fieldConfig.optionsConfig,
121
+ );
122
+ setSelectedValues((perv) => [resData]);
123
+ formContext.setValue(props.fieldConfig.name, resData.value);
124
+ };
157
125
 
158
- // If the value matches, set it to null, otherwise set it to val
159
- const newValue = currentValue === val ? null : val;
126
+ function getInput() {
127
+ return (
128
+ <Listbox
129
+ as={"div"}
130
+ {...hookProps}
131
+ className={`relative form-listbox flex-1`}
132
+ value={
133
+ formContext.getValues(props.fieldConfig.name)
134
+ ? props.fieldConfig.outputFormat === OutputFormatType.ARRAY
135
+ ? formContext.getValues(props.fieldConfig.name)[0]
136
+ : formContext.getValues(props.fieldConfig.name)
137
+ : undefined
138
+ }
139
+ onChange={(val) => {
140
+ const currentValue = formContext.getValues(props.fieldConfig.name);
160
141
 
161
- const selected = listOptions.find(o => o.value == newValue);
162
- selected && setSelectedValues([selected]);
163
- handleChange(
164
- newValue,
165
- formContext,
166
- props.fieldConfig,
167
- props.onChange
168
- );
169
- }}
170
- name={props.fieldConfig.name}
171
- disabled={props.fieldConfig.disabled}>
172
- <ListboxButton
173
- className={
174
- props.fieldConfig.customClassNames?.fieldClassName
175
- ? "form-listbox-select " +
176
- props.fieldConfig.customClassNames?.fieldClassName
177
- : "form-listbox-select"
178
- }>
179
- {renderListBoxValue(
180
- formContext,
181
- props.fieldConfig,
182
- [...selectedValues, ...listOptions],
183
- props.onChange
184
- )}
185
- </ListboxButton>
186
- <RenderListOptions
187
- formContext={formContext}
188
- onChange={props.onChange}
189
- formField={FormFieldType.TYPEAHEAD}
190
- ref={dynamicSelectRef}
191
- fieldConfig={props.fieldConfig}
192
- listOptions={[...selectedValues, ...listOptions]}
193
- setListOptions={setListOptions}
194
- loading={loading}
195
- setLoading={setLoading}
196
- createCallback={(data) => updateListOptions(data)}
197
- queryCallback={(query) => fetchData(query)}
198
- />
199
- </Listbox>
200
- );
201
- }
202
- if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
203
- return <></>;
204
- }
142
+ // If the value matches, set it to null, otherwise set it to val
143
+ const newValue = currentValue === val ? null : val;
205
144
 
206
- return (
207
- <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
145
+ const selected = listOptions.find((o) => o.value == newValue);
146
+ selected && setSelectedValues([selected]);
147
+ handleChange(
148
+ newValue,
149
+ formContext,
150
+ props.fieldConfig,
151
+ props.onChange,
152
+ );
153
+ }}
154
+ name={props.fieldConfig.name}
155
+ disabled={props.fieldConfig.disabled}
156
+ >
157
+ <ListboxButton
158
+ className={
159
+ props.fieldConfig.customClassNames?.fieldClassName
160
+ ? "form-listbox-select " +
161
+ props.fieldConfig.customClassNames?.fieldClassName
162
+ : "form-listbox-select"
163
+ }
164
+ >
165
+ {renderListBoxValue(
166
+ formContext,
167
+ props.fieldConfig,
168
+ [...selectedValues, ...listOptions],
169
+ props.onChange,
170
+ )}
171
+ </ListboxButton>
172
+ <RenderListOptions
173
+ formContext={formContext}
174
+ onChange={props.onChange}
175
+ formField={FormFieldType.TYPEAHEAD}
176
+ ref={dynamicSelectRef}
177
+ fieldConfig={props.fieldConfig}
178
+ listOptions={[...selectedValues, ...listOptions]}
179
+ setListOptions={setListOptions}
180
+ loading={loading}
181
+ setLoading={setLoading}
182
+ createCallback={(data) => updateListOptions(data)}
183
+ queryCallback={(query) => fetchData(query)}
184
+ />
185
+ </Listbox>
208
186
  );
187
+ }
188
+ if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
189
+ return <></>;
190
+ }
191
+
192
+ return (
193
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
194
+ );
209
195
  };
210
196
  export default Typeahead;
@@ -23,9 +23,10 @@ import RenderListOptions, {
23
23
  } from "../util/RenderListOptions";
24
24
  // import _ from "lodash";
25
25
  import axios from "axios";
26
+ import { getAxiosInstance } from "../../api";
26
27
 
27
28
  const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
28
- props: FormFieldComponentPropSchema
29
+ props: FormFieldComponentPropSchema,
29
30
  ) => {
30
31
  const dynamicSelectRef = useRef<HTMLUListElement>(null);
31
32
  const formContext = useContext(FormContext);
@@ -33,7 +34,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
33
34
  let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
34
35
  const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
35
36
  const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
36
- []
37
+ [],
37
38
  );
38
39
  const [loading, setLoading] = useState<boolean>(true);
39
40
 
@@ -44,7 +45,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
44
45
  ) {
45
46
  formContext.setValue(
46
47
  props.fieldConfig.name,
47
- props.fieldConfig.defaultValue
48
+ props.fieldConfig.defaultValue,
48
49
  );
49
50
  }
50
51
  fetchData(undefined);
@@ -60,14 +61,16 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
60
61
  if (_query) {
61
62
  url = url.includes("?") ? url + "&q=" + _query : url + "?q=" + _query;
62
63
  }
64
+ const axiosInstance = getAxiosInstance(
65
+ formContext.axiosInstance,
66
+ props.fieldConfig,
67
+ );
63
68
 
64
- let response = await (props.fieldConfig.disableHeaderInFetch
65
- ? axios.get(url)
66
- : formContext.axiosInstance?.get(url));
69
+ let response = await axiosInstance.get(url);
67
70
  if (response?.data) {
68
71
  const data: FieldOptionsSchema[] = getListOptions(
69
72
  response?.data,
70
- props.fieldConfig.optionsConfig
73
+ props.fieldConfig.optionsConfig,
71
74
  );
72
75
  setListOptions([...data]);
73
76
  let values = formContext.getValues(props.fieldConfig.name) || [];
@@ -77,7 +80,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
77
80
  (data.length === 0 || // If 'data' is empty
78
81
  (!values.every((v: string) => data.some((i) => i.value === v)) && // Ensure none of 'values' match 'data'
79
82
  !values.every((v: string) =>
80
- selectedValues.some((i) => i.value === v)
83
+ selectedValues.some((i) => i.value === v),
81
84
  ))) // Ensure none of 'values' match 'selectedValues'
82
85
  ) {
83
86
  fetchValue(values); // Call 'fetchValue()' if all conditions are met
@@ -94,7 +97,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
94
97
  setLoading(false);
95
98
  }
96
99
  },
97
- [props.fieldConfig.fetchUrl]
100
+ [props.fieldConfig.fetchUrl],
98
101
  );
99
102
 
100
103
  const fetchValue = async (value: string) => {
@@ -105,7 +108,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
105
108
 
106
109
  let url = props.fieldConfig.fetchUrl;
107
110
 
108
- url = url = url.includes("?")
111
+ url = url.includes("?")
109
112
  ? url + "&values=" + value
110
113
  : url + "?values=" + value;
111
114
 
@@ -115,7 +118,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
115
118
  if (response?.data) {
116
119
  const data: FieldOptionsSchema[] = getListOptions(
117
120
  response?.data,
118
- props.fieldConfig.optionsConfig
121
+ props.fieldConfig.optionsConfig,
119
122
  );
120
123
  let values: any[] = formContext.getValues(props.fieldConfig.name) || [];
121
124
  setSelectedValues(
@@ -123,22 +126,22 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
123
126
  ...data,
124
127
  props.fieldConfig.dropdownFieldConfig?.isSuggestionBox && values
125
128
  ? values
126
- .filter(
127
- (value) =>
128
- data.some((d) => d.value !== value) || data.length == 0
129
- )
130
- .map((val) => ({ label: val, value: val }))
129
+ .filter(
130
+ (value) =>
131
+ data.some((d) => d.value !== value) || data.length == 0,
132
+ )
133
+ .map((val) => ({ label: val, value: val }))
131
134
  : [],
132
- ].flat()
135
+ ].flat(),
133
136
  );
134
137
  }
135
- } catch (err) {}
138
+ } catch (err) { }
136
139
  };
137
140
 
138
141
  const updateListOptions = (data: any) => {
139
142
  const resData: FieldOptionsSchema = getListOption(
140
143
  data,
141
- props.fieldConfig.optionsConfig
144
+ props.fieldConfig.optionsConfig,
142
145
  );
143
146
  setSelectedValues((prev) => [...prev, resData]);
144
147
  let result = formContext.getValues(props.fieldConfig.name) || [];
@@ -160,14 +163,14 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
160
163
  className={"relative form-listbox flex-1 overflow-hidden"}
161
164
  onChange={(selectedOptions) => {
162
165
  const chossenOptions = listOptions.filter((op) =>
163
- selectedOptions.includes(op.value)
166
+ selectedOptions.includes(op.value),
164
167
  );
165
- setSelectedValues((prev) => [...prev, ...chossenOptions]);
168
+ // setSelectedValues((prev) => [...prev, ...chossenOptions]);
166
169
  handleChange(
167
170
  selectedOptions,
168
171
  formContext,
169
172
  props.fieldConfig,
170
- props.onChange
173
+ props.onChange,
171
174
  );
172
175
  }}
173
176
  multiple
@@ -176,7 +179,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
176
179
  className={
177
180
  props.fieldConfig.customClassNames?.fieldClassName
178
181
  ? "form-listbox-select " +
179
- props.fieldConfig.customClassNames?.fieldClassName
182
+ props.fieldConfig.customClassNames?.fieldClassName
180
183
  : "form-listbox-select"
181
184
  }
182
185
  >
@@ -184,7 +187,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
184
187
  formContext,
185
188
  props.fieldConfig,
186
189
  [...selectedValues, ...listOptions],
187
- props.onChange
190
+ props.onChange,
188
191
  )}
189
192
  </ListboxButton>
190
193
  <RenderListOptions
@@ -1,7 +1,7 @@
1
1
  import React, { useContext } from "react";
2
2
  import { RegisterOptions } from "react-hook-form";
3
3
  import { FormContext } from "../context/FormContext";
4
- import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
4
+ import { FormFieldComponentPropSchema, FormFieldPatternsImpl } from "../schema/FormFieldSchema";
5
5
  import { Input } from "@headlessui/react";
6
6
  import RenderFormField from "../util/RenderFormField";
7
7
  import { handleChange, registerFormField } from "../util";
@@ -11,7 +11,12 @@ const UrlField: React.FC<FormFieldComponentPropSchema> = (
11
11
  ) => {
12
12
  const formContext = useContext(FormContext);
13
13
 
14
- let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
14
+ // Apply URL pattern by default unless the consumer has already set a formFieldPattern
15
+ const fieldConfigWithUrlPattern = props.fieldConfig.formFieldPattern
16
+ ? props.fieldConfig
17
+ : { ...props.fieldConfig, formFieldPattern: [FormFieldPatternsImpl.URL] };
18
+
19
+ let registerOptions: RegisterOptions = registerFormField(fieldConfigWithUrlPattern);
15
20
 
16
21
  let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
17
22
 
@@ -47,7 +52,7 @@ const UrlField: React.FC<FormFieldComponentPropSchema> = (
47
52
  };
48
53
 
49
54
  return (
50
- <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
55
+ <RenderFormField fieldConfig={fieldConfigWithUrlPattern} getInput={getInput} />
51
56
  );
52
57
  };
53
58
  export default UrlField;
@@ -106,11 +106,12 @@ export type FormFieldSchema = {
106
106
  max?: number;
107
107
  min?: number;
108
108
  rows?: number;
109
- allowedMinQueryLength?: number;
110
109
  defaultValue?: string | string[] | {} | boolean;
111
110
  options?: FieldOptionsSchema[];
112
111
  minDate?: Date | null | undefined;
113
112
  maxDate?: Date | null | undefined;
113
+ /** Input display format for date pickers (e.g. 'MMMM D, YYYY'). */
114
+ dateDisplayFormat?: string;
114
115
  // ref?:any
115
116
 
116
117
  /**
@@ -129,14 +130,15 @@ export type FormFieldSchema = {
129
130
  decimalAllowed?: boolean;
130
131
  errorMessage?: string;
131
132
  submitOnChange?: boolean;
132
- isMultiple?: boolean;
133
133
  formFieldPattern?: FormFieldPatternsImpl[];
134
134
  fetchUrl?: string;
135
- postUrl?: string;
136
135
  fetchSavedDataUrl?: string;
136
+ postUrl?: string;
137
137
  fileAccept?: string;
138
138
  icon?: ReactNode;
139
139
  outputFormat?: OutputFormatType;
140
+ isMultiple?: boolean;
141
+ allowedMinQueryLength?: number;
140
142
  children?: FormFieldSchema[];
141
143
  defaultOptions?: FieldOptionsSchema[];
142
144
  optionsConfig?: OptionMappingConfig;
@@ -205,6 +207,7 @@ export type FieldOptionsSchema = {
205
207
  isDisabled?: boolean;
206
208
  helpText?: string;
207
209
  groupName?: string;
210
+ tooltip?: string;
208
211
  icon?: React.ReactNode;
209
212
  };
210
213
 
@@ -11,14 +11,14 @@ import { handleChange } from ".";
11
11
  import { LoaderWithText } from "../../util/LoaderWithText";
12
12
  import SVGIcon from "../../util/svg/SVGIcon";
13
13
  import { FormContextType } from "../context/FormContext";
14
- import Tippy from "@tippyjs/react";
15
14
  import {
16
15
  FieldOptionsSchema,
17
16
  FormFieldSchema,
18
17
  FormFieldType,
19
18
  OutputFormatType,
20
19
  } from "../schema/FormFieldSchema";
21
-
20
+ import Tippy from "@tippyjs/react";
21
+ import { normalizeMultiSelectValue } from "./normalizeCustomFieldValues";
22
22
  type RenderListOptionsProps = {
23
23
  formContext: FormContextType;
24
24
  fieldConfig: FormFieldSchema;
@@ -127,7 +127,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
127
127
  }, {});
128
128
 
129
129
  const handleQueryCallback = useCallback(() => {
130
- if (filteredList.length == 0 && isTypeahead) {
130
+ if (filteredList.length < 5 && isTypeahead) {
131
131
  queryCallback && queryCallback(query);
132
132
  }
133
133
  }, [filteredList]);
@@ -154,12 +154,17 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
154
154
 
155
155
  const renderOption = (option: FieldOptionsSchema) => {
156
156
  let selected = false;
157
- if (Array.isArray(formContext.getValues(fieldConfig.name))) {
158
- selected = formContext
159
- .getValues(fieldConfig.name)
160
- .includes(option.value);
157
+ const formValue = formContext.getValues(fieldConfig.name);
158
+ if (formField === FormFieldType.MULTI_SELECT) {
159
+ const normalizedValues = normalizeMultiSelectValue(formValue);
160
+ const normalizedOptionValue =
161
+ typeof option.value === "string"
162
+ ? option.value.trim()
163
+ : String(option.value).trim();
164
+ selected = normalizedValues.includes(normalizedOptionValue);
165
+ } else if (Array.isArray(formValue)) {
166
+ selected = formValue.includes(option.value);
161
167
  } else {
162
- const formValue = formContext.getValues(fieldConfig.name);
163
168
  // const defaultValue = fieldConfig.defaultValue;
164
169
  // selected = formValue ? option.value == formValue : defaultValue == option.value;
165
170
  selected = option.value == formValue;
@@ -189,7 +194,21 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
189
194
  {option.icon && (
190
195
  <span className="listbox-svg">{option.icon}</span>
191
196
  )}
192
- {option.label}
197
+ {option.tooltip ? (
198
+ <Tippy
199
+ content={
200
+ <>
201
+ {option.label}
202
+ <br />
203
+ {option.helpText}
204
+ </>
205
+ }
206
+ >
207
+ <div className="truncate">{option.label}</div>
208
+ </Tippy>
209
+ ) : (
210
+ option.label
211
+ )}
193
212
  </span>
194
213
  {isTypeahead &&
195
214
  !fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
@@ -208,11 +227,30 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
208
227
  )
209
228
  )}
210
229
  </div>
211
- {option.helpText && (
212
- <div className="mt-0 text-xs text-gray-500 font-normal truncate w-full !max-w-[150px]">
213
- {option.helpText}
214
- </div>
215
- )}
230
+ {option.helpText &&
231
+ (option.tooltip ? (
232
+ <Tippy
233
+ content={
234
+ <>
235
+ {option.label}
236
+ <br />
237
+ {option.helpText}
238
+ </>
239
+ }
240
+ >
241
+ <div
242
+ className={`mt-0 text-xs text-gray-500 font-normal truncate w-full ${fieldConfig.customClassNames?.optionClassName}`}
243
+ >
244
+ {option.helpText}
245
+ </div>
246
+ </Tippy>
247
+ ) : (
248
+ <div
249
+ className={`mt-0 text-xs text-gray-500 font-normal truncate w-full ${fieldConfig.customClassNames?.optionClassName}`}
250
+ >
251
+ {option.helpText}
252
+ </div>
253
+ ))}
216
254
  </>
217
255
  )}
218
256
  </ListboxOption>
@@ -291,7 +329,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
291
329
  fill="currentColor"
292
330
  aria-hidden="true"
293
331
  data-slot="icon"
294
- className="h-5 w-5 text-gray-400"
332
+ className="h-4.5 w-4.5 text-gray-400"
295
333
  >
296
334
  <path
297
335
  fillRule="evenodd"
@@ -335,7 +373,7 @@ export function renderListBoxValue(
335
373
  listOptions: FieldOptionsSchema[],
336
374
  onChange?: (value: any) => void,
337
375
  ): JSX.Element {
338
- let value = formContext.getValues(fieldConfig.name);
376
+ let value = normalizeMultiSelectValue(formContext.getValues(fieldConfig.name));
339
377
  const renderAsString = () => {
340
378
  // if (!listOptions) {
341
379
  // return value;
@@ -425,9 +463,10 @@ export function renderListBoxValue(
425
463
  )
426
464
  );
427
465
  };
428
- let outputFormat = fieldConfig.outputFormat
429
- ? fieldConfig.outputFormat === OutputFormatType.ARRAY
430
- : false;
466
+ let outputFormat =
467
+ fieldConfig.outputFormat != undefined
468
+ ? fieldConfig.outputFormat === OutputFormatType.STRING
469
+ : false;
431
470
  const getPlaceholder = () => (
432
471
  <span className="form-placeholder">
433
472
  {fieldConfig.placeholder || "Select any option"}
@@ -0,0 +1,115 @@
1
+ import moment from "moment";
2
+
3
+ const normalizeStringToken = (value: unknown): string => {
4
+ return typeof value === "string" ? value.trim() : String(value).trim();
5
+ };
6
+
7
+ const normalizeStringArray = (values: unknown[]): string[] => {
8
+ return values.map(normalizeStringToken).filter(Boolean);
9
+ };
10
+
11
+ const parseJsonArrayString = (value: string): string[] | null => {
12
+ if (!value.startsWith("[")) return null;
13
+ try {
14
+ const parsed = JSON.parse(value);
15
+ return Array.isArray(parsed) ? normalizeStringArray(parsed) : null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ };
20
+
21
+ export const normalizeCheckboxBooleanValue = (value: any): boolean => {
22
+ if (value === true || value === 1) return true;
23
+ if (value === false || value === 0 || value == null || value === "") {
24
+ return false;
25
+ }
26
+
27
+ if (typeof value === "string") {
28
+ const s = value.trim().toLowerCase();
29
+ if (s === "true" || s === "1" || s === "yes") return true;
30
+ if (s === "false" || s === "0" || s === "no" || s === "") return false;
31
+ }
32
+
33
+ return Boolean(value);
34
+ };
35
+
36
+ export const normalizeMultiSelectValue = (value: any): string[] => {
37
+ if (value == null) return [];
38
+
39
+ if (Array.isArray(value)) return normalizeStringArray(value);
40
+
41
+ if (typeof value === "string") {
42
+ const s = value.trim();
43
+ if (!s) return [];
44
+
45
+ const jsonArray = parseJsonArrayString(s);
46
+ if (jsonArray) return jsonArray;
47
+
48
+ return normalizeStringArray(s.split(","));
49
+ }
50
+
51
+ return normalizeStringArray([value]);
52
+ };
53
+
54
+ export const normalizeDateFormat = (format?: string): string => {
55
+ if (!format) return "YYYY-MM-DD";
56
+ return format
57
+ .replace(/yyyy/g, "YYYY")
58
+ .replace(/yy/g, "YY")
59
+ .replace(/dd/g, "DD")
60
+ .replace(/d/g, "D")
61
+ .replace(/mm/g, "MM");
62
+ };
63
+
64
+ export const normalizeDateInputValue = (
65
+ value: any,
66
+ preferredFormat?: string
67
+ ): Date | null => {
68
+ if (value == null || value === "") return null;
69
+
70
+ if (value instanceof Date) {
71
+ return Number.isNaN(value.getTime()) ? null : value;
72
+ }
73
+
74
+ if (typeof value === "number" && Number.isFinite(value)) {
75
+ const ms = value < 1_000_000_000_000 ? value * 1000 : value;
76
+ const date = new Date(ms);
77
+ return Number.isNaN(date.getTime()) ? null : date;
78
+ }
79
+
80
+ if (typeof value === "string") {
81
+ const s = value.trim();
82
+ if (!s) return null;
83
+
84
+ const numeric = Number(s);
85
+ if (Number.isFinite(numeric)) {
86
+ const ms = numeric < 1_000_000_000_000 ? numeric * 1000 : numeric;
87
+ const date = new Date(ms);
88
+ return Number.isNaN(date.getTime()) ? null : date;
89
+ }
90
+
91
+ const preferred = normalizeDateFormat(preferredFormat);
92
+ const formats = Array.from(
93
+ new Set([
94
+ preferred,
95
+ "YYYY-MM-DD",
96
+ "DD/MM/YYYY",
97
+ "MM/DD/YYYY",
98
+ "DD-MM-YYYY",
99
+ "MM-DD-YYYY",
100
+ ])
101
+ );
102
+ const parsedWithKnownFormats = moment(s, formats, true);
103
+ if (parsedWithKnownFormats.isValid()) {
104
+ return parsedWithKnownFormats.toDate();
105
+ }
106
+
107
+ const date = new Date(s);
108
+ return Number.isNaN(date.getTime()) ? null : date;
109
+ }
110
+
111
+ const date = new Date(value);
112
+ return Number.isNaN(date.getTime()) ? null : date;
113
+ };
114
+
115
+