@engagebay/engagebay-form-module 1.0.6 → 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 -4
- package/src/form/formfields/BusinessHoursField.tsx +2 -4
- package/src/form/formfields/DatePickerField.tsx +3 -3
- package/src/form/formfields/NumberField.tsx +1 -1
- package/src/form/formfields/Typeahead.tsx +163 -177
- package/src/form/formfields/TypeaheadMultiSelect.tsx +26 -23
- package/src/form/schema/FormFieldSchema.ts +4 -3
- package/src/form/util/RenderListOptions.tsx +75 -43
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",
|
|
@@ -28,13 +28,13 @@ import TimeField from "./formfields/TimeField";
|
|
|
28
28
|
import Typeahead from "./formfields/Typeahead";
|
|
29
29
|
import TypeaheadMultiSelect from "./formfields/TypeaheadMultiSelect";
|
|
30
30
|
import UrlField from "./formfields/UrlField";
|
|
31
|
-
import Typeahead2 from "./formfields/Typeahead2";
|
|
32
31
|
import {
|
|
33
32
|
FieldOptionsSchema,
|
|
34
33
|
FormFieldComponentPropSchema,
|
|
35
34
|
FormFieldType,
|
|
36
35
|
OptionMappingConfig,
|
|
37
36
|
} from "./schema/FormFieldSchema";
|
|
37
|
+
import Typeahead2 from "./formfields/Typeahead2";
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* @property {React.FC<FormFieldComponentPropSchema>} component - React component for a form field.
|
|
@@ -137,12 +137,12 @@ const formFieldComponents: FormComponentSchema = {
|
|
|
137
137
|
[FormFieldType[FormFieldType.TYPEAHEAD]]: {
|
|
138
138
|
component: Typeahead,
|
|
139
139
|
},
|
|
140
|
-
[FormFieldType[FormFieldType.TYPEAHEAD_2]]: {
|
|
141
|
-
component: Typeahead2,
|
|
142
|
-
},
|
|
143
140
|
[FormFieldType[FormFieldType.TYPEAHEAD_MULTI_SELECT]]: {
|
|
144
141
|
component: TypeaheadMultiSelect,
|
|
145
142
|
},
|
|
143
|
+
[FormFieldType[FormFieldType.TYPEAHEAD_2]]: {
|
|
144
|
+
component: Typeahead2,
|
|
145
|
+
},
|
|
146
146
|
[FormFieldType[FormFieldType.COMBO_SELECT]]: {
|
|
147
147
|
component: ComboSelect,
|
|
148
148
|
},
|
|
@@ -21,7 +21,6 @@ import FormField from "../FormField";
|
|
|
21
21
|
import RenderFormField from "../util/RenderFormField";
|
|
22
22
|
import { TrashIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|
23
23
|
import Tippy from "@tippyjs/react";
|
|
24
|
-
import { set } from "lodash";
|
|
25
24
|
|
|
26
25
|
const defaultBusinessHours = {
|
|
27
26
|
MONDAY: {
|
|
@@ -124,9 +123,8 @@ export const BusinessHoursField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
124
123
|
<div className="w-full sm:w-full text-end mr-[2.3em]">
|
|
125
124
|
<button
|
|
126
125
|
type="button"
|
|
127
|
-
className={`text-end text-primary cursor-pointer font-[13px] font-medium ${
|
|
128
|
-
|
|
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,13 +25,13 @@ 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() {
|
|
@@ -28,7 +28,7 @@ const NumberField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
28
28
|
step={props.fieldConfig.decimalAllowed ? 0.01 : 1}
|
|
29
29
|
defaultValue={props.fieldConfig.defaultValue as string}
|
|
30
30
|
className={`form-input ${
|
|
31
|
-
props.fieldConfig.customClassNames?.fieldClassName || "flex-1
|
|
31
|
+
props.fieldConfig.customClassNames?.fieldClassName || "flex-1"
|
|
32
32
|
}`}
|
|
33
33
|
onKeyDown={(e) => {
|
|
34
34
|
props.fieldConfig.onKeyDown && props.fieldConfig.onKeyDown(e);
|
|
@@ -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;
|
|
@@ -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
|
|
@@ -106,7 +106,6 @@ export type FormFieldSchema = {
|
|
|
106
106
|
max?: number;
|
|
107
107
|
min?: number;
|
|
108
108
|
rows?: number;
|
|
109
|
-
allowedMinQueryLength?: number;
|
|
110
109
|
defaultValue?: string | string[] | {} | boolean;
|
|
111
110
|
options?: FieldOptionsSchema[];
|
|
112
111
|
minDate?: Date | null | undefined;
|
|
@@ -129,14 +128,15 @@ export type FormFieldSchema = {
|
|
|
129
128
|
decimalAllowed?: boolean;
|
|
130
129
|
errorMessage?: string;
|
|
131
130
|
submitOnChange?: boolean;
|
|
132
|
-
isMultiple?: boolean;
|
|
133
131
|
formFieldPattern?: FormFieldPatternsImpl[];
|
|
134
132
|
fetchUrl?: string;
|
|
135
|
-
postUrl?: string;
|
|
136
133
|
fetchSavedDataUrl?: string;
|
|
134
|
+
postUrl?: string;
|
|
137
135
|
fileAccept?: string;
|
|
138
136
|
icon?: ReactNode;
|
|
139
137
|
outputFormat?: OutputFormatType;
|
|
138
|
+
isMultiple?: boolean;
|
|
139
|
+
allowedMinQueryLength?: number;
|
|
140
140
|
children?: FormFieldSchema[];
|
|
141
141
|
defaultOptions?: FieldOptionsSchema[];
|
|
142
142
|
optionsConfig?: OptionMappingConfig;
|
|
@@ -205,6 +205,7 @@ export type FieldOptionsSchema = {
|
|
|
205
205
|
isDisabled?: boolean;
|
|
206
206
|
helpText?: string;
|
|
207
207
|
groupName?: string;
|
|
208
|
+
tooltip?: string;
|
|
208
209
|
icon?: React.ReactNode;
|
|
209
210
|
};
|
|
210
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,7 +127,7 @@ 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]);
|
|
@@ -169,11 +169,10 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
169
169
|
key={option.value}
|
|
170
170
|
disabled={option.isDisabled}
|
|
171
171
|
onClick={() => setTimeout(resetToDefault, 300)}
|
|
172
|
-
className={`form-listbox-option ${
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}`}
|
|
172
|
+
className={`form-listbox-option ${selected
|
|
173
|
+
? " bg-gray-100 text-gray-900"
|
|
174
|
+
: "hover:bg-gray-100 text-gray-700"
|
|
175
|
+
}`}
|
|
177
176
|
value={option.value}
|
|
178
177
|
>
|
|
179
178
|
{fieldConfig.fieldOptionWrapper ? (
|
|
@@ -182,17 +181,30 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
182
181
|
<>
|
|
183
182
|
<div className="flex items-center justify-between gap-x-1.5">
|
|
184
183
|
<span
|
|
185
|
-
className={`block truncate w-full ${fieldConfig.customClassNames?.optionClassName} space-x-2 ${
|
|
186
|
-
|
|
187
|
-
}`}
|
|
184
|
+
className={`block truncate w-full ${fieldConfig.customClassNames?.optionClassName} space-x-2 ${selected ? "font-medium" : "font-normal"
|
|
185
|
+
}`}
|
|
188
186
|
>
|
|
189
187
|
{option.icon && (
|
|
190
188
|
<span className="listbox-svg">{option.icon}</span>
|
|
191
189
|
)}
|
|
192
|
-
{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
|
+
)}
|
|
193
205
|
</span>
|
|
194
206
|
{isTypeahead &&
|
|
195
|
-
|
|
207
|
+
!fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
|
|
196
208
|
<input
|
|
197
209
|
type="checkbox"
|
|
198
210
|
className="form-checkbox"
|
|
@@ -208,11 +220,30 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
208
220
|
)
|
|
209
221
|
)}
|
|
210
222
|
</div>
|
|
211
|
-
{option.helpText &&
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
+
))}
|
|
216
247
|
</>
|
|
217
248
|
)}
|
|
218
249
|
</ListboxOption>
|
|
@@ -372,28 +403,28 @@ export function renderListBoxValue(
|
|
|
372
403
|
{values.length > 1
|
|
373
404
|
? `${values.length} selected`
|
|
374
405
|
: listOptions.find((option) => option.value == value[0])?.label ||
|
|
375
|
-
|
|
406
|
+
values[0]}
|
|
376
407
|
</span>
|
|
377
408
|
{getDeleteButton()}
|
|
378
409
|
</span>
|
|
379
410
|
) : (
|
|
380
411
|
Array.isArray(values) &&
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
412
|
+
values.map((opt: any) => {
|
|
413
|
+
const option = listOptions.find((option) => option.value == opt);
|
|
414
|
+
if (!option && values.length == 0) return getPlaceholder();
|
|
384
415
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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>
|
|
392
423
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
424
|
+
{getDeleteButton(opt)}
|
|
425
|
+
</span>
|
|
426
|
+
);
|
|
427
|
+
})
|
|
397
428
|
);
|
|
398
429
|
};
|
|
399
430
|
const getDeleteButton = (option?: string) => {
|
|
@@ -425,9 +456,10 @@ export function renderListBoxValue(
|
|
|
425
456
|
)
|
|
426
457
|
);
|
|
427
458
|
};
|
|
428
|
-
let outputFormat =
|
|
429
|
-
|
|
430
|
-
|
|
459
|
+
let outputFormat =
|
|
460
|
+
fieldConfig.outputFormat != undefined
|
|
461
|
+
? fieldConfig.outputFormat === OutputFormatType.STRING
|
|
462
|
+
: false;
|
|
431
463
|
const getPlaceholder = () => (
|
|
432
464
|
<span className="form-placeholder">
|
|
433
465
|
{fieldConfig.placeholder || "Select any option"}
|