@engagebay/engagebay-form-module 1.0.6 → 1.0.7-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +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/formfields/UrlField.tsx +8 -3
- package/src/form/schema/FormFieldSchema.ts +4 -3
- package/src/form/util/RenderListOptions.tsx +46 -12
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.2",
|
|
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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useContext } from "react";
|
|
2
2
|
import { RegisterOptions } from "react-hook-form";
|
|
3
3
|
import { FormContext } from "../context/FormContext";
|
|
4
|
-
import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
|
|
4
|
+
import { FormFieldComponentPropSchema, FormFieldPatternsImpl } from "../schema/FormFieldSchema";
|
|
5
5
|
import { Input } from "@headlessui/react";
|
|
6
6
|
import RenderFormField from "../util/RenderFormField";
|
|
7
7
|
import { handleChange, registerFormField } from "../util";
|
|
@@ -11,7 +11,12 @@ const UrlField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
11
11
|
) => {
|
|
12
12
|
const formContext = useContext(FormContext);
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// Apply URL pattern by default unless the consumer has already set a formFieldPattern
|
|
15
|
+
const fieldConfigWithUrlPattern = props.fieldConfig.formFieldPattern
|
|
16
|
+
? props.fieldConfig
|
|
17
|
+
: { ...props.fieldConfig, formFieldPattern: [FormFieldPatternsImpl.URL] };
|
|
18
|
+
|
|
19
|
+
let registerOptions: RegisterOptions = registerFormField(fieldConfigWithUrlPattern);
|
|
15
20
|
|
|
16
21
|
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
17
22
|
|
|
@@ -47,7 +52,7 @@ const UrlField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
47
52
|
};
|
|
48
53
|
|
|
49
54
|
return (
|
|
50
|
-
<RenderFormField fieldConfig={
|
|
55
|
+
<RenderFormField fieldConfig={fieldConfigWithUrlPattern} getInput={getInput} />
|
|
51
56
|
);
|
|
52
57
|
};
|
|
53
58
|
export default UrlField;
|
|
@@ -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;
|
|
@@ -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]);
|
|
@@ -189,7 +189,21 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
189
189
|
{option.icon && (
|
|
190
190
|
<span className="listbox-svg">{option.icon}</span>
|
|
191
191
|
)}
|
|
192
|
-
{option.
|
|
192
|
+
{option.tooltip ? (
|
|
193
|
+
<Tippy
|
|
194
|
+
content={
|
|
195
|
+
<>
|
|
196
|
+
{option.label}
|
|
197
|
+
<br />
|
|
198
|
+
{option.helpText}
|
|
199
|
+
</>
|
|
200
|
+
}
|
|
201
|
+
>
|
|
202
|
+
<div className="truncate">{option.label}</div>
|
|
203
|
+
</Tippy>
|
|
204
|
+
) : (
|
|
205
|
+
option.label
|
|
206
|
+
)}
|
|
193
207
|
</span>
|
|
194
208
|
{isTypeahead &&
|
|
195
209
|
!fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
|
|
@@ -208,11 +222,30 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
208
222
|
)
|
|
209
223
|
)}
|
|
210
224
|
</div>
|
|
211
|
-
{option.helpText &&
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
225
|
+
{option.helpText &&
|
|
226
|
+
(option.tooltip ? (
|
|
227
|
+
<Tippy
|
|
228
|
+
content={
|
|
229
|
+
<>
|
|
230
|
+
{option.label}
|
|
231
|
+
<br />
|
|
232
|
+
{option.helpText}
|
|
233
|
+
</>
|
|
234
|
+
}
|
|
235
|
+
>
|
|
236
|
+
<div
|
|
237
|
+
className={`mt-0 text-xs text-gray-500 font-normal truncate w-full ${fieldConfig.customClassNames?.optionClassName}`}
|
|
238
|
+
>
|
|
239
|
+
{option.helpText}
|
|
240
|
+
</div>
|
|
241
|
+
</Tippy>
|
|
242
|
+
) : (
|
|
243
|
+
<div
|
|
244
|
+
className={`mt-0 text-xs text-gray-500 font-normal truncate w-full ${fieldConfig.customClassNames?.optionClassName}`}
|
|
245
|
+
>
|
|
246
|
+
{option.helpText}
|
|
247
|
+
</div>
|
|
248
|
+
))}
|
|
216
249
|
</>
|
|
217
250
|
)}
|
|
218
251
|
</ListboxOption>
|
|
@@ -291,7 +324,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
291
324
|
fill="currentColor"
|
|
292
325
|
aria-hidden="true"
|
|
293
326
|
data-slot="icon"
|
|
294
|
-
className="h-5 w-5 text-gray-400"
|
|
327
|
+
className="h-4.5 w-4.5 text-gray-400"
|
|
295
328
|
>
|
|
296
329
|
<path
|
|
297
330
|
fillRule="evenodd"
|
|
@@ -425,9 +458,10 @@ export function renderListBoxValue(
|
|
|
425
458
|
)
|
|
426
459
|
);
|
|
427
460
|
};
|
|
428
|
-
let outputFormat =
|
|
429
|
-
|
|
430
|
-
|
|
461
|
+
let outputFormat =
|
|
462
|
+
fieldConfig.outputFormat != undefined
|
|
463
|
+
? fieldConfig.outputFormat === OutputFormatType.STRING
|
|
464
|
+
: false;
|
|
431
465
|
const getPlaceholder = () => (
|
|
432
466
|
<span className="form-placeholder">
|
|
433
467
|
{fieldConfig.placeholder || "Select any option"}
|