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