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