@engagebay/engagebay-form-module 1.0.5 → 1.0.7-beta.1
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 +1 -3
- package/src/api/index.ts +9 -3
- package/src/form/FormFieldUtils.ts +4 -0
- package/src/form/formfields/BusinessHoursField.tsx +2 -4
- package/src/form/formfields/DatePickerField.tsx +24 -16
- package/src/form/formfields/NumberField.tsx +55 -50
- package/src/form/formfields/Typeahead.tsx +163 -177
- package/src/form/formfields/Typeahead2.tsx +284 -0
- package/src/form/formfields/TypeaheadMultiSelect.tsx +26 -23
- package/src/form/schema/FormFieldSchema.ts +5 -0
- package/src/form/util/RenderListOptions.tsx +83 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@engagebay/engagebay-form-module",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7-beta.1",
|
|
4
4
|
"description": "Provide base form components to reacho",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 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",
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
FormFieldType,
|
|
35
35
|
OptionMappingConfig,
|
|
36
36
|
} from "./schema/FormFieldSchema";
|
|
37
|
+
import Typeahead2 from "./formfields/Typeahead2";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* @property {React.FC<FormFieldComponentPropSchema>} component - React component for a form field.
|
|
@@ -139,6 +140,9 @@ const formFieldComponents: FormComponentSchema = {
|
|
|
139
140
|
[FormFieldType[FormFieldType.TYPEAHEAD_MULTI_SELECT]]: {
|
|
140
141
|
component: TypeaheadMultiSelect,
|
|
141
142
|
},
|
|
143
|
+
[FormFieldType[FormFieldType.TYPEAHEAD_2]]: {
|
|
144
|
+
component: Typeahead2,
|
|
145
|
+
},
|
|
142
146
|
[FormFieldType[FormFieldType.COMBO_SELECT]]: {
|
|
143
147
|
component: ComboSelect,
|
|
144
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
|
-
|
|
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`)?.[
|
|
@@ -8,7 +8,7 @@ import { handleChange, registerFormField } from "../util";
|
|
|
8
8
|
import moment from "moment";
|
|
9
9
|
|
|
10
10
|
const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
11
|
-
props: FormFieldComponentPropSchema
|
|
11
|
+
props: FormFieldComponentPropSchema,
|
|
12
12
|
) => {
|
|
13
13
|
const formContext = useContext(FormContext);
|
|
14
14
|
const initialDate = formContext.getValues(props.fieldConfig.name)
|
|
@@ -25,24 +25,38 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
25
25
|
) {
|
|
26
26
|
formContext.setValue(
|
|
27
27
|
props.fieldConfig.name,
|
|
28
|
-
props.fieldConfig.defaultValue
|
|
28
|
+
props.fieldConfig.defaultValue,
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
}, [props.fieldConfig.forceUpdate]);
|
|
32
32
|
|
|
33
33
|
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
34
|
-
|
|
34
|
+
//registerOptions.valueAsDate = true;
|
|
35
35
|
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
36
36
|
|
|
37
37
|
function getInput() {
|
|
38
|
+
const rawStart =
|
|
39
|
+
initialDate?.startDate ||
|
|
40
|
+
props.fieldConfig.defaultValue ||
|
|
41
|
+
"";
|
|
42
|
+
const rawEnd =
|
|
43
|
+
initialDate?.endDate ||
|
|
44
|
+
props.fieldConfig.defaultValue ||
|
|
45
|
+
"";
|
|
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
|
+
const value = {
|
|
51
|
+
startDate: toDate(rawStart),
|
|
52
|
+
endDate: toDate(rawEnd),
|
|
53
|
+
};
|
|
54
|
+
// Calendar opens on selected date's month/year (must be a Date instance)
|
|
55
|
+
const startFrom = value.startDate instanceof Date ? value.startDate : new Date();
|
|
38
56
|
return (
|
|
39
57
|
<Datepicker
|
|
40
|
-
value={
|
|
41
|
-
|
|
42
|
-
startDate: props.fieldConfig.defaultValue,
|
|
43
|
-
endDate: props.fieldConfig.defaultValue,
|
|
44
|
-
}
|
|
45
|
-
}
|
|
58
|
+
value={value}
|
|
59
|
+
startFrom={startFrom}
|
|
46
60
|
{...hookProps}
|
|
47
61
|
placeholder={
|
|
48
62
|
props.fieldConfig.placeholder
|
|
@@ -53,13 +67,7 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
53
67
|
popoverDirection="down"
|
|
54
68
|
useRange={false}
|
|
55
69
|
inputName={props.fieldConfig.name}
|
|
56
|
-
key={
|
|
57
|
-
props.fieldConfig.name +
|
|
58
|
-
"_" +
|
|
59
|
-
initialDate.startDate +
|
|
60
|
-
"_" +
|
|
61
|
-
initialDate.endDate
|
|
62
|
-
}
|
|
70
|
+
key={props.fieldConfig.name + "_" + rawStart + "_" + rawEnd}
|
|
63
71
|
containerClassName={"relative"}
|
|
64
72
|
minDate={props.fieldConfig.minDate}
|
|
65
73
|
maxDate={props.fieldConfig.maxDate}
|
|
@@ -1,61 +1,66 @@
|
|
|
1
|
-
import React, {useContext} from "react";
|
|
2
|
-
import {RegisterOptions} from "react-hook-form";
|
|
3
|
-
import {FormContext} from "../context/FormContext";
|
|
4
|
-
import {FormFieldComponentPropSchema} from "../schema/FormFieldSchema";
|
|
5
|
-
import {handleChange, registerFormField} from "../util";
|
|
1
|
+
import React, { useContext } from "react";
|
|
2
|
+
import { RegisterOptions } from "react-hook-form";
|
|
3
|
+
import { FormContext } from "../context/FormContext";
|
|
4
|
+
import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
|
|
5
|
+
import { handleChange, registerFormField } from "../util";
|
|
6
6
|
import RenderFormField from "../util/RenderFormField";
|
|
7
7
|
|
|
8
8
|
const NumberField: React.FC<FormFieldComponentPropSchema> = (
|
|
9
|
-
|
|
9
|
+
props: FormFieldComponentPropSchema,
|
|
10
10
|
) => {
|
|
11
|
-
|
|
11
|
+
const formContext = useContext(FormContext);
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function getInput() {
|
|
18
|
-
return (
|
|
19
|
-
<input
|
|
20
|
-
type="number"
|
|
21
|
-
{...hookProps}
|
|
22
|
-
placeholder={props.fieldConfig?.placeholder}
|
|
23
|
-
readOnly={props.fieldConfig?.readOnly}
|
|
24
|
-
disabled={props.fieldConfig?.disabled}
|
|
25
|
-
autoComplete={props.fieldConfig?.autoComplete}
|
|
26
|
-
min={props.fieldConfig?.min}
|
|
27
|
-
max={props.fieldConfig?.max}
|
|
28
|
-
step={props.fieldConfig.decimalAllowed ? 0.01 : 1}
|
|
29
|
-
defaultValue={props.fieldConfig.defaultValue as string}
|
|
30
|
-
className={`form-input ${
|
|
31
|
-
props.fieldConfig.customClassNames?.fieldClassName || "flex-1 !w-60"
|
|
32
|
-
}`}
|
|
33
|
-
onKeyDown={(e) => {
|
|
34
|
-
props.fieldConfig.onKeyDown && props.fieldConfig.onKeyDown(e);
|
|
35
|
-
}}
|
|
36
|
-
onChange={(e) => {
|
|
37
|
-
let value = e.target.value;
|
|
38
|
-
let numericValue = parseFloat(value);
|
|
39
|
-
if (props.fieldConfig.max !== undefined && numericValue > props.fieldConfig.max) {
|
|
40
|
-
numericValue = props.fieldConfig.max;
|
|
41
|
-
}
|
|
42
|
-
if (props.fieldConfig.min !== undefined && numericValue < props.fieldConfig.min) {
|
|
43
|
-
numericValue = props.fieldConfig.min;
|
|
44
|
-
}
|
|
45
|
-
value = numericValue.toString();
|
|
46
|
-
handleChange(
|
|
47
|
-
value,
|
|
48
|
-
formContext,
|
|
49
|
-
props.fieldConfig,
|
|
50
|
-
props.onChange
|
|
51
|
-
);
|
|
52
|
-
}}
|
|
53
|
-
/>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
15
|
+
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
56
16
|
|
|
17
|
+
function getInput() {
|
|
57
18
|
return (
|
|
58
|
-
|
|
19
|
+
<input
|
|
20
|
+
type="number"
|
|
21
|
+
{...hookProps}
|
|
22
|
+
placeholder={props.fieldConfig?.placeholder}
|
|
23
|
+
readOnly={props.fieldConfig?.readOnly}
|
|
24
|
+
disabled={props.fieldConfig?.disabled}
|
|
25
|
+
autoComplete={props.fieldConfig?.autoComplete}
|
|
26
|
+
min={props.fieldConfig?.min}
|
|
27
|
+
max={props.fieldConfig?.max}
|
|
28
|
+
step={props.fieldConfig.decimalAllowed ? 0.01 : 1}
|
|
29
|
+
defaultValue={props.fieldConfig.defaultValue as string}
|
|
30
|
+
className={`form-input ${
|
|
31
|
+
props.fieldConfig.customClassNames?.fieldClassName || "flex-1"
|
|
32
|
+
}`}
|
|
33
|
+
onKeyDown={(e) => {
|
|
34
|
+
props.fieldConfig.onKeyDown && props.fieldConfig.onKeyDown(e);
|
|
35
|
+
}}
|
|
36
|
+
onChange={(e) => {
|
|
37
|
+
let value = e.target.value;
|
|
38
|
+
let numericValue = parseFloat(value);
|
|
39
|
+
if (
|
|
40
|
+
props.fieldConfig.max !== undefined &&
|
|
41
|
+
numericValue > props.fieldConfig.max
|
|
42
|
+
) {
|
|
43
|
+
numericValue = props.fieldConfig.max;
|
|
44
|
+
}
|
|
45
|
+
if (
|
|
46
|
+
props.fieldConfig.min !== undefined &&
|
|
47
|
+
numericValue < props.fieldConfig.min
|
|
48
|
+
) {
|
|
49
|
+
numericValue = props.fieldConfig.min;
|
|
50
|
+
}
|
|
51
|
+
handleChange(
|
|
52
|
+
numericValue,
|
|
53
|
+
formContext,
|
|
54
|
+
props.fieldConfig,
|
|
55
|
+
props.onChange,
|
|
56
|
+
);
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
59
|
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
64
|
+
);
|
|
60
65
|
};
|
|
61
66
|
export default NumberField;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
+
renderListBoxValue,
|
|
24
24
|
} from "../util/RenderListOptions";
|
|
25
25
|
|
|
26
26
|
const Typeahead: React.FC<FormFieldComponentPropSchema> = (
|
|
27
|
-
|
|
27
|
+
props: FormFieldComponentPropSchema,
|
|
28
28
|
) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
52
|
+
const fetchData = useCallback(
|
|
53
|
+
async (_query: string | undefined) => {
|
|
54
|
+
setLoading(true);
|
|
55
|
+
try {
|
|
56
|
+
if (!props.fieldConfig.fetchUrl) return;
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
91
|
+
const fetchValue = async (value: string) => {
|
|
92
|
+
try {
|
|
93
|
+
if (props.fieldConfig.ignoreFetchValue || !props.fieldConfig.fetchUrl) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
109
96
|
|
|
110
|
-
|
|
111
|
-
url = url.includes("?")
|
|
112
|
-
? url + "&values=" + value
|
|
113
|
-
: url + "?values=" + value;
|
|
97
|
+
let url = props.fieldConfig.fetchUrl;
|
|
114
98
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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(
|
|
136
|
-
|
|
137
|
-
}
|
|
112
|
+
setSelectedValues([...data]);
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {}
|
|
115
|
+
};
|
|
138
116
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
import { Listbox, ListboxButton } from "@headlessui/react";
|
|
11
|
+
import { RegisterOptions } from "react-hook-form";
|
|
12
|
+
|
|
13
|
+
import { FormContext } from "../context/FormContext";
|
|
14
|
+
import { getListOption, getListOptions } from "../FormFieldUtils";
|
|
15
|
+
import {
|
|
16
|
+
FieldOptionsSchema,
|
|
17
|
+
FormFieldComponentPropSchema,
|
|
18
|
+
FormFieldType,
|
|
19
|
+
} from "../schema/FormFieldSchema";
|
|
20
|
+
import { handleChange, registerFormField } from "../util";
|
|
21
|
+
import RenderFormField from "../util/RenderFormField";
|
|
22
|
+
import RenderListOptions, {
|
|
23
|
+
renderListBoxValue,
|
|
24
|
+
} from "../util/RenderListOptions";
|
|
25
|
+
import axios from "axios";
|
|
26
|
+
import { getAxiosInstance } from "../../api";
|
|
27
|
+
|
|
28
|
+
const Typeahead2: React.FC<FormFieldComponentPropSchema> = (
|
|
29
|
+
props: FormFieldComponentPropSchema,
|
|
30
|
+
) => {
|
|
31
|
+
const dynamicSelectRef = useRef<HTMLUListElement>(null);
|
|
32
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
33
|
+
const formContext = useContext(FormContext);
|
|
34
|
+
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
35
|
+
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
36
|
+
const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
|
|
37
|
+
const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
|
|
38
|
+
[],
|
|
39
|
+
);
|
|
40
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
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
|
+
let values: any | any[] | undefined =
|
|
53
|
+
formContext.getValues(props.fieldConfig.name) ||
|
|
54
|
+
props.fieldConfig.defaultValue;
|
|
55
|
+
|
|
56
|
+
if (values && selectedValues.length < 1) {
|
|
57
|
+
if (props.fieldConfig.fetchSavedDataUrl) {
|
|
58
|
+
fetchValue(values);
|
|
59
|
+
} else {
|
|
60
|
+
setSelectedValues(
|
|
61
|
+
Array.isArray(values)
|
|
62
|
+
? values.map((val: any) => ({ label: val, value: val }))
|
|
63
|
+
: [values],
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
const fetchData = useMemo(() => {
|
|
72
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
73
|
+
|
|
74
|
+
return (_query: string | undefined) => {
|
|
75
|
+
// Clear the previous timer
|
|
76
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
77
|
+
|
|
78
|
+
// Set a new timer
|
|
79
|
+
timeoutId = setTimeout(async () => {
|
|
80
|
+
// 1. Cancel the previous request if it's still pending
|
|
81
|
+
if (abortControllerRef.current) {
|
|
82
|
+
abortControllerRef.current.abort();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 2. Create a new controller for the current request
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
abortControllerRef.current = controller;
|
|
88
|
+
const minLength = props.fieldConfig?.allowedMinQueryLength ?? 3;
|
|
89
|
+
|
|
90
|
+
if (!_query || _query.length < minLength) {
|
|
91
|
+
setListOptions([]);
|
|
92
|
+
setLoading(false);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
setLoading(true);
|
|
96
|
+
try {
|
|
97
|
+
if (!props.fieldConfig.fetchUrl) return;
|
|
98
|
+
|
|
99
|
+
let url = props.fieldConfig.fetchUrl;
|
|
100
|
+
|
|
101
|
+
url = url.includes("?") ? url + "&q=" + _query : url + "?q=" + _query;
|
|
102
|
+
|
|
103
|
+
const axiosInstance = getAxiosInstance(
|
|
104
|
+
formContext.axiosInstance,
|
|
105
|
+
props.fieldConfig,
|
|
106
|
+
);
|
|
107
|
+
const response = await axiosInstance.get(url);
|
|
108
|
+
|
|
109
|
+
if (controller === abortControllerRef.current) {
|
|
110
|
+
if (response?.data) {
|
|
111
|
+
const data: FieldOptionsSchema[] = getListOptions(
|
|
112
|
+
response?.data,
|
|
113
|
+
props.fieldConfig.optionsConfig,
|
|
114
|
+
);
|
|
115
|
+
setListOptions([...data]);
|
|
116
|
+
|
|
117
|
+
if (props.fieldConfig.fetchCallback) {
|
|
118
|
+
props.fieldConfig.fetchCallback(response);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error("Fetch error:", err);
|
|
124
|
+
} finally {
|
|
125
|
+
// Only stop loading if this was the "active" request
|
|
126
|
+
if (controller === abortControllerRef.current) {
|
|
127
|
+
setLoading(false);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}, 250);
|
|
131
|
+
};
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
// Cleanup on unmount
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
return () => {
|
|
137
|
+
abortControllerRef.current?.abort();
|
|
138
|
+
};
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
const fetchValue = async (value: string | any | any[]) => {
|
|
142
|
+
try {
|
|
143
|
+
if (
|
|
144
|
+
props.fieldConfig.ignoreFetchValue ||
|
|
145
|
+
!props.fieldConfig.fetchSavedDataUrl
|
|
146
|
+
) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let url = props.fieldConfig.fetchSavedDataUrl;
|
|
151
|
+
|
|
152
|
+
// url = url.includes("?")
|
|
153
|
+
// ? url + "&values=" + value
|
|
154
|
+
// : url + "?values=" + value;
|
|
155
|
+
|
|
156
|
+
let response = await (props.fieldConfig.disableHeaderInFetch
|
|
157
|
+
? axios.post(url, Array.isArray(value) ? value : [value])
|
|
158
|
+
: formContext.axiosInstance?.post(
|
|
159
|
+
url,
|
|
160
|
+
Array.isArray(value) ? value : [value],
|
|
161
|
+
));
|
|
162
|
+
if (response?.data) {
|
|
163
|
+
const data: FieldOptionsSchema[] = getListOptions(
|
|
164
|
+
response?.data,
|
|
165
|
+
props.fieldConfig.optionsConfig,
|
|
166
|
+
);
|
|
167
|
+
let values: any[] = formContext.getValues(props.fieldConfig.name) || [];
|
|
168
|
+
setSelectedValues(
|
|
169
|
+
[
|
|
170
|
+
...data,
|
|
171
|
+
props.fieldConfig.dropdownFieldConfig?.isSuggestionBox &&
|
|
172
|
+
values &&
|
|
173
|
+
Array.isArray(values)
|
|
174
|
+
? values
|
|
175
|
+
.filter(
|
|
176
|
+
(value) =>
|
|
177
|
+
data.some((d) => d.value !== value) || data.length == 0,
|
|
178
|
+
)
|
|
179
|
+
.map((val) => ({ label: val, value: val }))
|
|
180
|
+
: [],
|
|
181
|
+
].flat(),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
} catch (err) { }
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const updateListOptions = (data: any) => {
|
|
188
|
+
const resData: FieldOptionsSchema = getListOption(
|
|
189
|
+
data,
|
|
190
|
+
props.fieldConfig.optionsConfig,
|
|
191
|
+
);
|
|
192
|
+
setListOptions([]);
|
|
193
|
+
setSelectedValues((prev) =>
|
|
194
|
+
props.fieldConfig.isMultiple ? [...prev, resData] : [resData],
|
|
195
|
+
);
|
|
196
|
+
let result = formContext.getValues(props.fieldConfig.name) || [];
|
|
197
|
+
result = !props.fieldConfig.isMultiple
|
|
198
|
+
? resData.value
|
|
199
|
+
: result.includes(resData.value)
|
|
200
|
+
? [...result.filter((v: string) => v != resData.value), resData.value]
|
|
201
|
+
: [...result, resData.value];
|
|
202
|
+
formContext.setValue(props.fieldConfig.name, result);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const getInput = () => {
|
|
206
|
+
return (
|
|
207
|
+
<Listbox
|
|
208
|
+
as={"div"}
|
|
209
|
+
{...hookProps}
|
|
210
|
+
value={
|
|
211
|
+
props.fieldConfig.isMultiple
|
|
212
|
+
? formContext.getValues(props.fieldConfig.name)
|
|
213
|
+
? formContext.getValues(props.fieldConfig.name)
|
|
214
|
+
: []
|
|
215
|
+
: formContext.getValues(props.fieldConfig.name)
|
|
216
|
+
}
|
|
217
|
+
name={props.fieldConfig.name}
|
|
218
|
+
defaultValue={props.fieldConfig.defaultValue}
|
|
219
|
+
key={props.fieldConfig.name}
|
|
220
|
+
className={"relative form-listbox flex-1 overflow-hidden"}
|
|
221
|
+
onChange={(selectedOptions) => {
|
|
222
|
+
let currentValue = formContext.getValues(props.fieldConfig.name);
|
|
223
|
+
const newValue =
|
|
224
|
+
currentValue === selectedOptions ? null : selectedOptions;
|
|
225
|
+
|
|
226
|
+
if (props.fieldConfig.isMultiple) {
|
|
227
|
+
currentValue = listOptions.filter((op) =>
|
|
228
|
+
selectedOptions.includes(op.value),
|
|
229
|
+
);
|
|
230
|
+
setSelectedValues((prev) => [...prev, ...currentValue]);
|
|
231
|
+
} else {
|
|
232
|
+
const selected = listOptions.find((o) => o.value == newValue);
|
|
233
|
+
selected && setSelectedValues([selected]);
|
|
234
|
+
}
|
|
235
|
+
setListOptions([]);
|
|
236
|
+
handleChange(
|
|
237
|
+
newValue,
|
|
238
|
+
formContext,
|
|
239
|
+
props.fieldConfig,
|
|
240
|
+
props.onChange,
|
|
241
|
+
);
|
|
242
|
+
}}
|
|
243
|
+
multiple={props.fieldConfig.isMultiple}
|
|
244
|
+
>
|
|
245
|
+
<ListboxButton
|
|
246
|
+
className={
|
|
247
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
248
|
+
? "form-listbox-select " +
|
|
249
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
250
|
+
: "form-listbox-select"
|
|
251
|
+
}
|
|
252
|
+
>
|
|
253
|
+
{renderListBoxValue(
|
|
254
|
+
formContext,
|
|
255
|
+
props.fieldConfig,
|
|
256
|
+
[...selectedValues, ...listOptions],
|
|
257
|
+
props.onChange,
|
|
258
|
+
)}
|
|
259
|
+
</ListboxButton>
|
|
260
|
+
<RenderListOptions
|
|
261
|
+
formContext={formContext}
|
|
262
|
+
onChange={props.onChange}
|
|
263
|
+
formField={FormFieldType.TYPEAHEAD_2}
|
|
264
|
+
ref={dynamicSelectRef}
|
|
265
|
+
fieldConfig={props.fieldConfig}
|
|
266
|
+
listOptions={listOptions}
|
|
267
|
+
setListOptions={setListOptions}
|
|
268
|
+
loading={loading}
|
|
269
|
+
setLoading={setLoading}
|
|
270
|
+
createCallback={(data) => updateListOptions(data)}
|
|
271
|
+
queryCallback={(query) => fetchData(query)}
|
|
272
|
+
/>
|
|
273
|
+
</Listbox>
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
|
|
277
|
+
return <></>;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
282
|
+
);
|
|
283
|
+
};
|
|
284
|
+
export default Typeahead2;
|
|
@@ -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 (
|
|
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
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
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
|
-
|
|
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
|
|
@@ -37,6 +37,7 @@ export enum FormFieldType {
|
|
|
37
37
|
DYNAMIC_MULTI_SELECT = "DYNAMIC_MULTI_SELECT",
|
|
38
38
|
|
|
39
39
|
TYPEAHEAD = "TYPEAHEAD",
|
|
40
|
+
TYPEAHEAD_2 = "TYPEAHEAD_2",
|
|
40
41
|
TYPEAHEAD_MULTI_SELECT = "TYPEAHEAD_MULTI_SELECT",
|
|
41
42
|
PHONE_NUMBER_INPUT = "PHONE_NUMBER_INPUT",
|
|
42
43
|
SWITCH = "SWITCH",
|
|
@@ -129,10 +130,13 @@ export type FormFieldSchema = {
|
|
|
129
130
|
submitOnChange?: boolean;
|
|
130
131
|
formFieldPattern?: FormFieldPatternsImpl[];
|
|
131
132
|
fetchUrl?: string;
|
|
133
|
+
fetchSavedDataUrl?: string;
|
|
132
134
|
postUrl?: string;
|
|
133
135
|
fileAccept?: string;
|
|
134
136
|
icon?: ReactNode;
|
|
135
137
|
outputFormat?: OutputFormatType;
|
|
138
|
+
isMultiple?: boolean;
|
|
139
|
+
allowedMinQueryLength?: number;
|
|
136
140
|
children?: FormFieldSchema[];
|
|
137
141
|
defaultOptions?: FieldOptionsSchema[];
|
|
138
142
|
optionsConfig?: OptionMappingConfig;
|
|
@@ -201,6 +205,7 @@ export type FieldOptionsSchema = {
|
|
|
201
205
|
isDisabled?: boolean;
|
|
202
206
|
helpText?: string;
|
|
203
207
|
groupName?: string;
|
|
208
|
+
tooltip?: string;
|
|
204
209
|
icon?: React.ReactNode;
|
|
205
210
|
};
|
|
206
211
|
|
|
@@ -11,13 +11,13 @@ 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";
|
|
20
|
+
import Tippy from "@tippyjs/react";
|
|
21
21
|
|
|
22
22
|
type RenderListOptionsProps = {
|
|
23
23
|
formContext: FormContextType;
|
|
@@ -102,15 +102,15 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
102
102
|
|
|
103
103
|
const filteredList = query
|
|
104
104
|
? resultList.filter((item) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
const normalizedLabel = fieldConfig.dropdownFieldConfig
|
|
106
|
+
?.isCaseSensitive
|
|
107
|
+
? item.label
|
|
108
|
+
: item.label.toLowerCase();
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
return normalizedLabel
|
|
111
|
+
.replace(/\s+/g, "")
|
|
112
|
+
.includes(caseSensitive.replace(/\s+/g, ""));
|
|
113
|
+
})
|
|
114
114
|
: resultList;
|
|
115
115
|
|
|
116
116
|
let nullGroupOptions: any[] = [];
|
|
@@ -127,13 +127,15 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
127
127
|
}, {});
|
|
128
128
|
|
|
129
129
|
const handleQueryCallback = useCallback(() => {
|
|
130
|
-
if (filteredList.length
|
|
130
|
+
if (filteredList.length < 5 && isTypeahead) {
|
|
131
131
|
queryCallback && queryCallback(query);
|
|
132
132
|
}
|
|
133
133
|
}, [filteredList]);
|
|
134
134
|
|
|
135
135
|
useEffect(() => {
|
|
136
|
-
|
|
136
|
+
if (formField == FormFieldType.TYPEAHEAD_2)
|
|
137
|
+
queryCallback && queryCallback(query);
|
|
138
|
+
else handleQueryCallback();
|
|
137
139
|
}, [query]);
|
|
138
140
|
|
|
139
141
|
let enableCreateFlag =
|
|
@@ -143,6 +145,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
143
145
|
|
|
144
146
|
let validTypeaheadFields = [
|
|
145
147
|
FormFieldType.TYPEAHEAD,
|
|
148
|
+
FormFieldType.TYPEAHEAD_2,
|
|
146
149
|
FormFieldType.TYPEAHEAD_MULTI_SELECT,
|
|
147
150
|
];
|
|
148
151
|
let isTypeahead: boolean = validTypeaheadFields.indexOf(formField) > -1;
|
|
@@ -166,11 +169,10 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
166
169
|
key={option.value}
|
|
167
170
|
disabled={option.isDisabled}
|
|
168
171
|
onClick={() => setTimeout(resetToDefault, 300)}
|
|
169
|
-
className={`form-listbox-option ${
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}`}
|
|
172
|
+
className={`form-listbox-option ${selected
|
|
173
|
+
? " bg-gray-100 text-gray-900"
|
|
174
|
+
: "hover:bg-gray-100 text-gray-700"
|
|
175
|
+
}`}
|
|
174
176
|
value={option.value}
|
|
175
177
|
>
|
|
176
178
|
{fieldConfig.fieldOptionWrapper ? (
|
|
@@ -179,17 +181,30 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
179
181
|
<>
|
|
180
182
|
<div className="flex items-center justify-between gap-x-1.5">
|
|
181
183
|
<span
|
|
182
|
-
className={`block truncate w-full ${fieldConfig.customClassNames?.optionClassName} space-x-2 ${
|
|
183
|
-
|
|
184
|
-
}`}
|
|
184
|
+
className={`block truncate w-full ${fieldConfig.customClassNames?.optionClassName} space-x-2 ${selected ? "font-medium" : "font-normal"
|
|
185
|
+
}`}
|
|
185
186
|
>
|
|
186
187
|
{option.icon && (
|
|
187
188
|
<span className="listbox-svg">{option.icon}</span>
|
|
188
189
|
)}
|
|
189
|
-
{option.
|
|
190
|
+
{option.tooltip ? (
|
|
191
|
+
<Tippy
|
|
192
|
+
content={
|
|
193
|
+
<>
|
|
194
|
+
{option.label}
|
|
195
|
+
<br />
|
|
196
|
+
{option.helpText}
|
|
197
|
+
</>
|
|
198
|
+
}
|
|
199
|
+
>
|
|
200
|
+
<div className="truncate">{option.label}</div>
|
|
201
|
+
</Tippy>
|
|
202
|
+
) : (
|
|
203
|
+
option.label
|
|
204
|
+
)}
|
|
190
205
|
</span>
|
|
191
206
|
{isTypeahead &&
|
|
192
|
-
|
|
207
|
+
!fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
|
|
193
208
|
<input
|
|
194
209
|
type="checkbox"
|
|
195
210
|
className="form-checkbox"
|
|
@@ -205,11 +220,30 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
205
220
|
)
|
|
206
221
|
)}
|
|
207
222
|
</div>
|
|
208
|
-
{option.helpText &&
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
223
|
+
{option.helpText &&
|
|
224
|
+
(option.tooltip ? (
|
|
225
|
+
<Tippy
|
|
226
|
+
content={
|
|
227
|
+
<>
|
|
228
|
+
{option.label}
|
|
229
|
+
<br />
|
|
230
|
+
{option.helpText}
|
|
231
|
+
</>
|
|
232
|
+
}
|
|
233
|
+
>
|
|
234
|
+
<div
|
|
235
|
+
className={`mt-0 text-xs text-gray-500 font-normal truncate w-full ${fieldConfig.customClassNames?.optionClassName}`}
|
|
236
|
+
>
|
|
237
|
+
{option.helpText}
|
|
238
|
+
</div>
|
|
239
|
+
</Tippy>
|
|
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
|
+
))}
|
|
213
247
|
</>
|
|
214
248
|
)}
|
|
215
249
|
</ListboxOption>
|
|
@@ -217,7 +251,9 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
217
251
|
};
|
|
218
252
|
|
|
219
253
|
const renderList = useMemo(() => {
|
|
220
|
-
if (filteredList.length === 0 && (!enableCreateFlag || query === ""))
|
|
254
|
+
if (filteredList.length === 0 && (!enableCreateFlag || query === "")) {
|
|
255
|
+
if (formField == FormFieldType.TYPEAHEAD_2 && query === "")
|
|
256
|
+
return <></>;
|
|
221
257
|
return (
|
|
222
258
|
<div className="form-listbox-option text-center">
|
|
223
259
|
<span className="empty-content text-gray-600 font-normal">
|
|
@@ -225,6 +261,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
225
261
|
</span>
|
|
226
262
|
</div>
|
|
227
263
|
);
|
|
264
|
+
}
|
|
228
265
|
|
|
229
266
|
return (
|
|
230
267
|
<>
|
|
@@ -366,28 +403,28 @@ export function renderListBoxValue(
|
|
|
366
403
|
{values.length > 1
|
|
367
404
|
? `${values.length} selected`
|
|
368
405
|
: listOptions.find((option) => option.value == value[0])?.label ||
|
|
369
|
-
|
|
406
|
+
values[0]}
|
|
370
407
|
</span>
|
|
371
408
|
{getDeleteButton()}
|
|
372
409
|
</span>
|
|
373
410
|
) : (
|
|
374
411
|
Array.isArray(values) &&
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
412
|
+
values.map((opt: any) => {
|
|
413
|
+
const option = listOptions.find((option) => option.value == opt);
|
|
414
|
+
if (!option && values.length == 0) return getPlaceholder();
|
|
378
415
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
416
|
+
return (
|
|
417
|
+
<span key={option?.value} className="form-selected-badge">
|
|
418
|
+
<Tippy content={option?.label} delay={500}>
|
|
419
|
+
<span className="form-selected-badge-name">
|
|
420
|
+
{option?.label}
|
|
421
|
+
</span>
|
|
422
|
+
</Tippy>
|
|
386
423
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
424
|
+
{getDeleteButton(opt)}
|
|
425
|
+
</span>
|
|
426
|
+
);
|
|
427
|
+
})
|
|
391
428
|
);
|
|
392
429
|
};
|
|
393
430
|
const getDeleteButton = (option?: string) => {
|
|
@@ -419,9 +456,10 @@ export function renderListBoxValue(
|
|
|
419
456
|
)
|
|
420
457
|
);
|
|
421
458
|
};
|
|
422
|
-
let outputFormat =
|
|
423
|
-
|
|
424
|
-
|
|
459
|
+
let outputFormat =
|
|
460
|
+
fieldConfig.outputFormat != undefined
|
|
461
|
+
? fieldConfig.outputFormat === OutputFormatType.STRING
|
|
462
|
+
: false;
|
|
425
463
|
const getPlaceholder = () => (
|
|
426
464
|
<span className="form-placeholder">
|
|
427
465
|
{fieldConfig.placeholder || "Select any option"}
|