@engagebay/engagebay-form-module 1.0.2-beta.0 → 1.0.2-beta.10
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/DynamicMultiSelect.tsx +0 -1
- package/src/form/formfields/NumberField.tsx +52 -51
- package/src/form/formfields/Typeahead.tsx +163 -177
- package/src/form/formfields/Typeahead2.tsx +283 -0
- package/src/form/formfields/TypeaheadMultiSelect.tsx +9 -9
- package/src/form/schema/FormFieldSchema.ts +5 -0
- package/src/form/util/RenderListOptions.tsx +94 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@engagebay/engagebay-form-module",
|
|
3
|
-
"version": "1.0.2-beta.
|
|
3
|
+
"version": "1.0.2-beta.10",
|
|
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}
|
|
@@ -150,7 +150,6 @@ const DynamicMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
150
150
|
className={`relative form-listbox flex-1 overflow-hidden`}
|
|
151
151
|
>
|
|
152
152
|
<ListboxButton
|
|
153
|
-
static
|
|
154
153
|
className={
|
|
155
154
|
props.fieldConfig.customClassNames?.fieldClassName
|
|
156
155
|
? "form-listbox-select " +
|
|
@@ -1,61 +1,62 @@
|
|
|
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
|
-
|
|
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
|
-
}
|
|
13
|
+
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
14
|
+
registerOptions.valueAsNumber = true;
|
|
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
|
+
value = numericValue.toString();
|
|
52
|
+
handleChange(value, formContext, props.fieldConfig, props.onChange);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
59
55
|
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
60
|
+
);
|
|
60
61
|
};
|
|
61
62
|
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,283 @@
|
|
|
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;
|
|
@@ -23,7 +23,7 @@ import RenderListOptions, {
|
|
|
23
23
|
} from "../util/RenderListOptions";
|
|
24
24
|
// import _ from "lodash";
|
|
25
25
|
import axios from "axios";
|
|
26
|
-
import { getAxiosInstance } from "
|
|
26
|
+
import { getAxiosInstance } from "../../api";
|
|
27
27
|
|
|
28
28
|
const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
29
29
|
props: FormFieldComponentPropSchema,
|
|
@@ -108,7 +108,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
108
108
|
|
|
109
109
|
let url = props.fieldConfig.fetchUrl;
|
|
110
110
|
|
|
111
|
-
url = url
|
|
111
|
+
url = url.includes("?")
|
|
112
112
|
? url + "&values=" + value
|
|
113
113
|
: url + "?values=" + value;
|
|
114
114
|
|
|
@@ -126,16 +126,16 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
126
126
|
...data,
|
|
127
127
|
props.fieldConfig.dropdownFieldConfig?.isSuggestionBox && values
|
|
128
128
|
? values
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
.filter(
|
|
130
|
+
(value) =>
|
|
131
|
+
data.some((d) => d.value !== value) || data.length == 0,
|
|
132
|
+
)
|
|
133
|
+
.map((val) => ({ label: val, value: val }))
|
|
134
134
|
: [],
|
|
135
135
|
].flat(),
|
|
136
136
|
);
|
|
137
137
|
}
|
|
138
|
-
} catch (err) {}
|
|
138
|
+
} catch (err) { }
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
const updateListOptions = (data: any) => {
|
|
@@ -179,7 +179,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
179
179
|
className={
|
|
180
180
|
props.fieldConfig.customClassNames?.fieldClassName
|
|
181
181
|
? "form-listbox-select " +
|
|
182
|
-
|
|
182
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
183
183
|
: "form-listbox-select"
|
|
184
184
|
}
|
|
185
185
|
>
|
|
@@ -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
|
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
FormFieldType,
|
|
18
18
|
OutputFormatType,
|
|
19
19
|
} from "../schema/FormFieldSchema";
|
|
20
|
+
import Tippy from "@tippyjs/react";
|
|
20
21
|
|
|
21
22
|
type RenderListOptionsProps = {
|
|
22
23
|
formContext: FormContextType;
|
|
@@ -45,7 +46,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
45
46
|
queryCallback,
|
|
46
47
|
createCallback,
|
|
47
48
|
},
|
|
48
|
-
ref
|
|
49
|
+
ref,
|
|
49
50
|
) => {
|
|
50
51
|
const [query, setQuery] = useState<string>("");
|
|
51
52
|
const [createdListItems, setCreatedListItems] = useState<
|
|
@@ -90,7 +91,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
90
91
|
.filter((result) => result.label)
|
|
91
92
|
.filter(
|
|
92
93
|
(option, index, self) =>
|
|
93
|
-
index === self.findIndex((obj) => obj.value === option.value)
|
|
94
|
+
index === self.findIndex((obj) => obj.value === option.value),
|
|
94
95
|
),
|
|
95
96
|
]; // removing duplicate values
|
|
96
97
|
|
|
@@ -101,15 +102,15 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
101
102
|
|
|
102
103
|
const filteredList = query
|
|
103
104
|
? resultList.filter((item) => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const normalizedLabel = fieldConfig.dropdownFieldConfig
|
|
106
|
+
?.isCaseSensitive
|
|
107
|
+
? item.label
|
|
108
|
+
: item.label.toLowerCase();
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
return normalizedLabel
|
|
111
|
+
.replace(/\s+/g, "")
|
|
112
|
+
.includes(caseSensitive.replace(/\s+/g, ""));
|
|
113
|
+
})
|
|
113
114
|
: resultList;
|
|
114
115
|
|
|
115
116
|
let nullGroupOptions: any[] = [];
|
|
@@ -126,13 +127,15 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
126
127
|
}, {});
|
|
127
128
|
|
|
128
129
|
const handleQueryCallback = useCallback(() => {
|
|
129
|
-
if (filteredList.length
|
|
130
|
+
if (filteredList.length < 5 && isTypeahead) {
|
|
130
131
|
queryCallback && queryCallback(query);
|
|
131
132
|
}
|
|
132
133
|
}, [filteredList]);
|
|
133
134
|
|
|
134
135
|
useEffect(() => {
|
|
135
|
-
|
|
136
|
+
if (formField == FormFieldType.TYPEAHEAD_2)
|
|
137
|
+
queryCallback && queryCallback(query);
|
|
138
|
+
else handleQueryCallback();
|
|
136
139
|
}, [query]);
|
|
137
140
|
|
|
138
141
|
let enableCreateFlag =
|
|
@@ -142,6 +145,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
142
145
|
|
|
143
146
|
let validTypeaheadFields = [
|
|
144
147
|
FormFieldType.TYPEAHEAD,
|
|
148
|
+
FormFieldType.TYPEAHEAD_2,
|
|
145
149
|
FormFieldType.TYPEAHEAD_MULTI_SELECT,
|
|
146
150
|
];
|
|
147
151
|
let isTypeahead: boolean = validTypeaheadFields.indexOf(formField) > -1;
|
|
@@ -161,16 +165,14 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
161
165
|
selected = option.value == formValue;
|
|
162
166
|
}
|
|
163
167
|
return (
|
|
164
|
-
<
|
|
165
|
-
static
|
|
168
|
+
<ListboxOption
|
|
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
|
|
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>
|
|
@@ -319,7 +353,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
319
353
|
</div>
|
|
320
354
|
</ListboxOptions>
|
|
321
355
|
);
|
|
322
|
-
}
|
|
356
|
+
},
|
|
323
357
|
);
|
|
324
358
|
|
|
325
359
|
export default RenderListOptions;
|
|
@@ -327,7 +361,7 @@ export function renderListBoxValue(
|
|
|
327
361
|
formContext: FormContextType,
|
|
328
362
|
fieldConfig: FormFieldSchema,
|
|
329
363
|
listOptions: FieldOptionsSchema[],
|
|
330
|
-
onChange?: (value: any) => void
|
|
364
|
+
onChange?: (value: any) => void,
|
|
331
365
|
): JSX.Element {
|
|
332
366
|
let value = formContext.getValues(fieldConfig.name);
|
|
333
367
|
const renderAsString = () => {
|
|
@@ -339,10 +373,17 @@ export function renderListBoxValue(
|
|
|
339
373
|
return icon ? (
|
|
340
374
|
<span className="flex items-center fs-8 whitespace-nowrap text-gray-500">
|
|
341
375
|
<span>{icon}</span>
|
|
342
|
-
|
|
376
|
+
|
|
377
|
+
<Tippy content={label} delay={500}>
|
|
378
|
+
<span className="block truncate">{label}</span>
|
|
379
|
+
</Tippy>
|
|
343
380
|
</span>
|
|
381
|
+
) : label ? (
|
|
382
|
+
<Tippy content={<>{label}</>} delay={500}>
|
|
383
|
+
<span className="block truncate">{label}</span>
|
|
384
|
+
</Tippy>
|
|
344
385
|
) : (
|
|
345
|
-
|
|
386
|
+
getPlaceholder()
|
|
346
387
|
);
|
|
347
388
|
};
|
|
348
389
|
const renderAsArray = () => {
|
|
@@ -359,23 +400,28 @@ export function renderListBoxValue(
|
|
|
359
400
|
{values.length > 1
|
|
360
401
|
? `${values.length} selected`
|
|
361
402
|
: listOptions.find((option) => option.value == value[0])?.label ||
|
|
362
|
-
|
|
403
|
+
values[0]}
|
|
363
404
|
</span>
|
|
364
405
|
{getDeleteButton()}
|
|
365
406
|
</span>
|
|
366
407
|
) : (
|
|
367
408
|
Array.isArray(values) &&
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
409
|
+
values.map((opt: any) => {
|
|
410
|
+
const option = listOptions.find((option) => option.value == opt);
|
|
411
|
+
if (!option && values.length == 0) return getPlaceholder();
|
|
371
412
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
413
|
+
return (
|
|
414
|
+
<span key={option?.value} className="form-selected-badge">
|
|
415
|
+
<Tippy content={option?.label} delay={500}>
|
|
416
|
+
<span className="form-selected-badge-name">
|
|
417
|
+
{option?.label}
|
|
418
|
+
</span>
|
|
419
|
+
</Tippy>
|
|
420
|
+
|
|
421
|
+
{getDeleteButton(opt)}
|
|
422
|
+
</span>
|
|
423
|
+
);
|
|
424
|
+
})
|
|
379
425
|
);
|
|
380
426
|
};
|
|
381
427
|
const getDeleteButton = (option?: string) => {
|
|
@@ -407,9 +453,10 @@ export function renderListBoxValue(
|
|
|
407
453
|
)
|
|
408
454
|
);
|
|
409
455
|
};
|
|
410
|
-
let outputFormat =
|
|
411
|
-
|
|
412
|
-
|
|
456
|
+
let outputFormat =
|
|
457
|
+
fieldConfig.outputFormat != undefined
|
|
458
|
+
? fieldConfig.outputFormat === OutputFormatType.STRING
|
|
459
|
+
: false;
|
|
413
460
|
const getPlaceholder = () => (
|
|
414
461
|
<span className="form-placeholder">
|
|
415
462
|
{fieldConfig.placeholder || "Select any option"}
|