@engagebay/engagebay-form-module 1.0.8-beta.6 → 1.0.9
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 +5 -3
- package/src/api/index.ts +3 -9
- package/src/form/FormFieldUtils.ts +4 -4
- package/src/form/formfields/BusinessHoursField.tsx +6 -4
- package/src/form/formfields/CheckboxField.tsx +11 -30
- package/src/form/formfields/DatePickerField.tsx +12 -13
- package/src/form/formfields/FileUploadField.tsx +6 -8
- package/src/form/formfields/MultipleSelectField.tsx +5 -17
- package/src/form/formfields/Typeahead.tsx +177 -163
- package/src/form/formfields/Typeahead2.tsx +1 -6
- package/src/form/formfields/TypeaheadMultiSelect.tsx +23 -26
- package/src/form/schema/FormFieldSchema.ts +4 -7
- package/src/form/util/RenderListOptions.tsx +18 -57
- package/src/form/util/normalizeCustomFieldValues.ts +0 -149
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@engagebay/engagebay-form-module",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Provide base form components to
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"description": "Provide base form components to engagebay",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
7
|
},
|
|
8
|
-
"author": "
|
|
8
|
+
"author": "Engagebay Developer",
|
|
9
9
|
"license": "ISC",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"react-phone-input-2": "^2.15.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",
|
|
@@ -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";
|
|
31
32
|
import {
|
|
32
33
|
FieldOptionsSchema,
|
|
33
34
|
FormFieldComponentPropSchema,
|
|
34
35
|
FormFieldType,
|
|
35
36
|
OptionMappingConfig,
|
|
36
37
|
} 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_MULTI_SELECT]]: {
|
|
141
|
-
component: TypeaheadMultiSelect,
|
|
142
|
-
},
|
|
143
140
|
[FormFieldType[FormFieldType.TYPEAHEAD_2]]: {
|
|
144
141
|
component: Typeahead2,
|
|
145
142
|
},
|
|
143
|
+
[FormFieldType[FormFieldType.TYPEAHEAD_MULTI_SELECT]]: {
|
|
144
|
+
component: TypeaheadMultiSelect,
|
|
145
|
+
},
|
|
146
146
|
[FormFieldType[FormFieldType.COMBO_SELECT]]: {
|
|
147
147
|
component: ComboSelect,
|
|
148
148
|
},
|
|
@@ -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`)?.[
|
|
@@ -200,7 +202,7 @@ export const BusinessHoursField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
200
202
|
disableDefaultWrapper: true,
|
|
201
203
|
defaultValue: "08:30",
|
|
202
204
|
customClassNames: {
|
|
203
|
-
fieldClassName: "
|
|
205
|
+
fieldClassName: "cursor-pointer bg-blue-100",
|
|
204
206
|
},
|
|
205
207
|
},
|
|
206
208
|
{
|
|
@@ -210,7 +212,7 @@ export const BusinessHoursField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
210
212
|
disableDefaultWrapper: true,
|
|
211
213
|
defaultValue: "18:00",
|
|
212
214
|
customClassNames: {
|
|
213
|
-
fieldClassName: "
|
|
215
|
+
fieldClassName: "cursor-pointer bg-blue-100",
|
|
214
216
|
},
|
|
215
217
|
},
|
|
216
218
|
],
|
|
@@ -1,46 +1,32 @@
|
|
|
1
1
|
import { RegisterOptions } from "react-hook-form";
|
|
2
2
|
import { useContext, useEffect, useState } from "react";
|
|
3
3
|
import { FormContext } from "../context/FormContext";
|
|
4
|
-
import {
|
|
5
|
-
FormFieldComponentPropSchema,
|
|
6
|
-
OutputFormatType,
|
|
7
|
-
} from "../schema/FormFieldSchema";
|
|
4
|
+
import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
|
|
8
5
|
import { Checkbox, Field, Input, Label } from "@headlessui/react";
|
|
9
6
|
import React from "react";
|
|
10
7
|
import RenderFormField from "../util/RenderFormField";
|
|
11
8
|
import { handleChange, registerFormField } from "../util";
|
|
12
|
-
import { normalizeCheckboxBooleanValue, normalizeMultiSelectValue } from "../util/normalizeCustomFieldValues";
|
|
13
9
|
const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
14
10
|
props: FormFieldComponentPropSchema
|
|
15
11
|
) => {
|
|
16
12
|
const formContext = useContext(FormContext);
|
|
17
13
|
const [selectedOption, setSelectedOption] = useState<string[]>(
|
|
18
|
-
|
|
14
|
+
formContext.getValues(props.fieldConfig.name)
|
|
15
|
+
? formContext.getValues(props.fieldConfig.name)
|
|
16
|
+
: []
|
|
19
17
|
);
|
|
20
18
|
const [isChecked, setIsChecked] = useState<boolean>(
|
|
21
|
-
|
|
22
|
-
formContext.getValues(props.fieldConfig.name)
|
|
23
|
-
|
|
19
|
+
formContext.getValues(props.fieldConfig.name)
|
|
20
|
+
? formContext.getValues(props.fieldConfig.name)
|
|
21
|
+
: props.fieldConfig.defaultValue
|
|
24
22
|
);
|
|
25
23
|
useEffect(() => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
setSelectedOption(normalizeMultiSelectValue(current));
|
|
29
|
-
} else {
|
|
30
|
-
setIsChecked(
|
|
31
|
-
normalizeCheckboxBooleanValue(current ?? props.fieldConfig.defaultValue)
|
|
32
|
-
);
|
|
24
|
+
if (formContext.getValues(props.fieldConfig.name)) {
|
|
25
|
+
setSelectedOption(formContext.getValues(props.fieldConfig.name));
|
|
33
26
|
}
|
|
34
27
|
}, []);
|
|
35
28
|
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
36
29
|
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
37
|
-
const getOutputValue = (value: any) => {
|
|
38
|
-
if (props.fieldConfig.outputFormat === OutputFormatType.STRING) {
|
|
39
|
-
if (Array.isArray(value)) return JSON.stringify(value);
|
|
40
|
-
if (typeof value === "boolean") return String(value);
|
|
41
|
-
}
|
|
42
|
-
return value;
|
|
43
|
-
};
|
|
44
30
|
const handleInput = (option: string) => {
|
|
45
31
|
let tempData: string[] = selectedOption;
|
|
46
32
|
if (selectedOption.includes(option)) {
|
|
@@ -50,12 +36,7 @@ const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
50
36
|
tempData.push(option);
|
|
51
37
|
setSelectedOption([...tempData]);
|
|
52
38
|
}
|
|
53
|
-
handleChange(
|
|
54
|
-
getOutputValue(tempData),
|
|
55
|
-
formContext,
|
|
56
|
-
props.fieldConfig,
|
|
57
|
-
props.onChange
|
|
58
|
-
);
|
|
39
|
+
handleChange(tempData, formContext, props.fieldConfig, props.onChange);
|
|
59
40
|
};
|
|
60
41
|
function getInput() {
|
|
61
42
|
return props.fieldConfig.options ? (
|
|
@@ -106,7 +87,7 @@ const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
106
87
|
checked={isChecked}
|
|
107
88
|
onChange={(checked) => {
|
|
108
89
|
handleChange(
|
|
109
|
-
|
|
90
|
+
checked,
|
|
110
91
|
formContext,
|
|
111
92
|
props.fieldConfig,
|
|
112
93
|
props.onChange
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { RegisterOptions } from "react-hook-form";
|
|
2
|
-
import React, { useContext, useEffect } from "react";
|
|
2
|
+
import React, { useContext, useEffect, useRef, useState } from "react";
|
|
3
3
|
import Datepicker from "react-tailwindcss-datepicker";
|
|
4
4
|
import { FormContext } from "../context/FormContext";
|
|
5
5
|
import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
|
|
6
6
|
import RenderFormField from "../util/RenderFormField";
|
|
7
7
|
import { handleChange, registerFormField } from "../util";
|
|
8
|
-
import { normalizeDateFormat, normalizeDateInputValue } from "../util/normalizeCustomFieldValues";
|
|
9
8
|
import moment from "moment";
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
10
|
const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
14
|
-
props: FormFieldComponentPropSchema
|
|
11
|
+
props: FormFieldComponentPropSchema
|
|
15
12
|
) => {
|
|
16
13
|
const formContext = useContext(FormContext);
|
|
17
14
|
const initialDate = formContext.getValues(props.fieldConfig.name)
|
|
@@ -28,17 +25,16 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
28
25
|
) {
|
|
29
26
|
formContext.setValue(
|
|
30
27
|
props.fieldConfig.name,
|
|
31
|
-
props.fieldConfig.defaultValue
|
|
28
|
+
props.fieldConfig.defaultValue
|
|
32
29
|
);
|
|
33
30
|
}
|
|
34
31
|
}, [props.fieldConfig.forceUpdate]);
|
|
35
32
|
|
|
36
33
|
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
37
|
-
|
|
34
|
+
|
|
38
35
|
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
39
36
|
|
|
40
37
|
function getInput() {
|
|
41
|
-
const displayFormat = normalizeDateFormat(props.fieldConfig.dateDisplayFormat);
|
|
42
38
|
const rawStart =
|
|
43
39
|
initialDate?.startDate ||
|
|
44
40
|
props.fieldConfig.defaultValue ||
|
|
@@ -47,9 +43,13 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
47
43
|
initialDate?.endDate ||
|
|
48
44
|
props.fieldConfig.defaultValue ||
|
|
49
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
50
|
const value = {
|
|
51
|
-
startDate:
|
|
52
|
-
endDate:
|
|
51
|
+
startDate: toDate(rawStart),
|
|
52
|
+
endDate: toDate(rawEnd),
|
|
53
53
|
};
|
|
54
54
|
// Calendar opens on selected date's month/year (must be a Date instance)
|
|
55
55
|
const startFrom = value.startDate instanceof Date ? value.startDate : new Date();
|
|
@@ -58,11 +58,10 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
58
58
|
value={value}
|
|
59
59
|
startFrom={startFrom}
|
|
60
60
|
{...hookProps}
|
|
61
|
-
displayFormat={displayFormat}
|
|
62
61
|
placeholder={
|
|
63
62
|
props.fieldConfig.placeholder
|
|
64
63
|
? props.fieldConfig.placeholder
|
|
65
|
-
:
|
|
64
|
+
: "YYYY-MM-DD"
|
|
66
65
|
}
|
|
67
66
|
asSingle={true}
|
|
68
67
|
popoverDirection="down"
|
|
@@ -80,7 +79,7 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
80
79
|
onChange={(dates) => {
|
|
81
80
|
let date = null;
|
|
82
81
|
if (dates?.startDate != null)
|
|
83
|
-
date = moment(dates?.startDate).format(
|
|
82
|
+
date = moment(dates?.startDate).format("YYYY-MM-DD");
|
|
84
83
|
|
|
85
84
|
handleChange(date, formContext, props.fieldConfig, props.onChange);
|
|
86
85
|
}}
|
|
@@ -9,8 +9,6 @@ import { Description } from "@headlessui/react";
|
|
|
9
9
|
import React from "react";
|
|
10
10
|
import RenderFormField from "../util/RenderFormField";
|
|
11
11
|
import clsx from "clsx";
|
|
12
|
-
import { XMarkIcon } from "@heroicons/react/20/solid";
|
|
13
|
-
import { normalizeFileFieldUrls } from "../util/normalizeCustomFieldValues";
|
|
14
12
|
const FileUploadField: React.FC<FormFieldComponentPropSchema> = (
|
|
15
13
|
props: FormFieldComponentPropSchema
|
|
16
14
|
) => {
|
|
@@ -105,7 +103,7 @@ const FileUploadField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
105
103
|
<Description>Loading...</Description>
|
|
106
104
|
) : (
|
|
107
105
|
props.fieldConfig.fieldContainer ? <props.fieldConfig.fieldContainer /> :
|
|
108
|
-
<div className={"flex flex-col items-center justify-center pt-5 pb-6
|
|
106
|
+
<div className={"flex flex-col items-center justify-center pt-5 pb-6"}>
|
|
109
107
|
{file ? (
|
|
110
108
|
<>
|
|
111
109
|
{isImageFile(file) ? (
|
|
@@ -127,19 +125,19 @@ const FileUploadField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
127
125
|
</div>
|
|
128
126
|
) : (
|
|
129
127
|
<div className="flex">
|
|
130
|
-
<span className="m-2
|
|
131
|
-
|
|
128
|
+
<span className="m-2">
|
|
129
|
+
{file?.name}
|
|
132
130
|
</span>{" "}
|
|
133
131
|
<button
|
|
134
132
|
onClick={(e) => {
|
|
135
133
|
e.preventDefault();
|
|
136
134
|
formContext.setValue(
|
|
137
|
-
props.fieldConfig.name,
|
|
135
|
+
props.fieldConfig.name,cnull
|
|
138
136
|
);
|
|
139
137
|
setFile(null);
|
|
140
138
|
}}
|
|
141
|
-
className="btn btn-sm ">
|
|
142
|
-
|
|
139
|
+
className="btn btn-outline-dark btn-sm ">
|
|
140
|
+
X
|
|
143
141
|
</button>
|
|
144
142
|
</div>
|
|
145
143
|
)}
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
FieldOptionsSchema,
|
|
14
14
|
FormFieldComponentPropSchema,
|
|
15
15
|
FormFieldType,
|
|
16
|
-
OutputFormatType,
|
|
17
16
|
} from "../schema/FormFieldSchema";
|
|
18
17
|
import { FormContext } from "../context/FormContext";
|
|
19
18
|
import RenderFormField from "../util/RenderFormField";
|
|
@@ -21,7 +20,6 @@ import RenderListOptions, {
|
|
|
21
20
|
renderListBoxValue,
|
|
22
21
|
} from "../util/RenderListOptions";
|
|
23
22
|
import { handleChange, registerFormField } from "../util";
|
|
24
|
-
import { normalizeMultiSelectValue } from "../util/normalizeCustomFieldValues";
|
|
25
23
|
|
|
26
24
|
const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
27
25
|
fieldConfig,
|
|
@@ -36,11 +34,9 @@ const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
|
36
34
|
fieldConfig.options ? fieldConfig.options : []
|
|
37
35
|
);
|
|
38
36
|
|
|
39
|
-
const
|
|
40
|
-
formContext.getValues(fieldConfig.name)
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
const validSelectedValues = normalizedSelectedValues.filter(
|
|
37
|
+
const validSelectedValues = (
|
|
38
|
+
formContext.getValues(fieldConfig.name) || []
|
|
39
|
+
).filter(
|
|
44
40
|
(selectedValue: any) =>
|
|
45
41
|
fieldConfig.options &&
|
|
46
42
|
fieldConfig.options.some((option) => option.value === selectedValue)
|
|
@@ -57,22 +53,14 @@ const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
|
57
53
|
}, [fieldConfig.options]);
|
|
58
54
|
|
|
59
55
|
function getInput() {
|
|
60
|
-
const getOutputValue = (val: any) => {
|
|
61
|
-
if (fieldConfig.outputFormat === OutputFormatType.STRING) {
|
|
62
|
-
const normalized = normalizeMultiSelectValue(val);
|
|
63
|
-
return JSON.stringify(normalized);
|
|
64
|
-
}
|
|
65
|
-
return val;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
56
|
return (
|
|
69
57
|
<Listbox
|
|
70
58
|
as={"div"}
|
|
71
59
|
{...hookProps}
|
|
72
60
|
value={validSelectedValues}
|
|
73
61
|
defaultValue={fieldConfig.defaultValue}
|
|
74
|
-
onChange={(val
|
|
75
|
-
handleChange(
|
|
62
|
+
onChange={(val) =>
|
|
63
|
+
handleChange(val, formContext, fieldConfig, onChange)
|
|
76
64
|
}
|
|
77
65
|
disabled={fieldConfig.disabled}
|
|
78
66
|
multiple
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
useCallback,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
7
|
} from "react";
|
|
8
8
|
|
|
9
9
|
import { Listbox, ListboxButton } from "@headlessui/react";
|
|
@@ -12,185 +12,199 @@ import { RegisterOptions } from "react-hook-form";
|
|
|
12
12
|
import { FormContext } from "../context/FormContext";
|
|
13
13
|
import { getListOption, getListOptions } from "../FormFieldUtils";
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
FieldOptionsSchema,
|
|
16
|
+
FormFieldComponentPropSchema,
|
|
17
|
+
FormFieldType,
|
|
18
|
+
OutputFormatType,
|
|
19
19
|
} from "../schema/FormFieldSchema";
|
|
20
20
|
import { handleChange, registerFormField } from "../util";
|
|
21
21
|
import RenderFormField from "../util/RenderFormField";
|
|
22
22
|
import RenderListOptions, {
|
|
23
|
-
|
|
23
|
+
renderListBoxValue,
|
|
24
24
|
} from "../util/RenderListOptions";
|
|
25
25
|
|
|
26
26
|
const Typeahead: React.FC<FormFieldComponentPropSchema> = (
|
|
27
|
-
|
|
27
|
+
props: FormFieldComponentPropSchema
|
|
28
28
|
) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
|
|
34
|
-
const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
|
|
35
|
-
[],
|
|
36
|
-
);
|
|
37
|
-
const [loading, setLoading] = useState<boolean>(true);
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (
|
|
41
|
-
!formContext.getValues(props.fieldConfig.name) &&
|
|
42
|
-
props.fieldConfig.defaultValue
|
|
43
|
-
) {
|
|
44
|
-
formContext.setValue(
|
|
29
|
+
const dynamicSelectRef = useRef<HTMLUListElement>(null);
|
|
30
|
+
const formContext = useContext(FormContext);
|
|
31
|
+
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
32
|
+
let hookProps = formContext.register(
|
|
45
33
|
props.fieldConfig.name,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
async (_query: string | undefined) => {
|
|
54
|
-
setLoading(true);
|
|
55
|
-
try {
|
|
56
|
-
if (!props.fieldConfig.fetchUrl) return;
|
|
34
|
+
registerOptions
|
|
35
|
+
);
|
|
36
|
+
const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
|
|
37
|
+
const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
|
|
38
|
+
[]
|
|
39
|
+
);
|
|
40
|
+
const [loading, setLoading] = useState<boolean>(true);
|
|
57
41
|
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (
|
|
44
|
+
!formContext.getValues(props.fieldConfig.name) &&
|
|
45
|
+
props.fieldConfig.defaultValue
|
|
46
|
+
) {
|
|
47
|
+
formContext.setValue(
|
|
48
|
+
props.fieldConfig.name,
|
|
49
|
+
props.fieldConfig.defaultValue
|
|
50
|
+
);
|
|
61
51
|
}
|
|
52
|
+
fetchData(undefined);
|
|
53
|
+
}, []);
|
|
62
54
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
55
|
+
const fetchData = useCallback(
|
|
56
|
+
async (_query: string | undefined) => {
|
|
57
|
+
setLoading(true);
|
|
58
|
+
try {
|
|
59
|
+
if (!props.fieldConfig.fetchUrl) return;
|
|
60
|
+
|
|
61
|
+
let url = props.fieldConfig.fetchUrl;
|
|
62
|
+
if (_query) {
|
|
63
|
+
url = url.includes("?")
|
|
64
|
+
? url + "&q=" + _query
|
|
65
|
+
: url + "?q=" + _query;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let response = await (props.fieldConfig.disableHeaderInFetch
|
|
69
|
+
? axios.get(url)
|
|
70
|
+
: formContext.axiosInstance?.get(url));
|
|
71
|
+
if (response?.data) {
|
|
72
|
+
const data: FieldOptionsSchema[] = getListOptions(
|
|
73
|
+
response.data,
|
|
74
|
+
props.fieldConfig.optionsConfig
|
|
75
|
+
);
|
|
76
|
+
setListOptions([...data]);
|
|
77
|
+
let value = formContext.getValues(props.fieldConfig.name);
|
|
78
|
+
if (
|
|
79
|
+
value &&
|
|
80
|
+
data.find((i) => i.value !== value)
|
|
81
|
+
) {
|
|
82
|
+
if (selectedValues.find((i) => i.value !== value))
|
|
83
|
+
fetchValue(value);
|
|
84
|
+
}
|
|
85
|
+
if (props.fieldConfig.fetchCallback) {
|
|
86
|
+
props.fieldConfig.fetchCallback(response);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
console.error(response?.statusText);
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
} finally {
|
|
93
|
+
setLoading(false);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[props.fieldConfig.fetchUrl]
|
|
97
|
+
);
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
const fetchValue = async (value: string) => {
|
|
100
|
+
try {
|
|
101
|
+
if (
|
|
102
|
+
props.fieldConfig.ignoreFetchValue ||
|
|
103
|
+
!props.fieldConfig.fetchUrl
|
|
104
|
+
) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
let url = props.fieldConfig.fetchUrl;
|
|
98
109
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
if (value)
|
|
111
|
+
url = url.includes("?")
|
|
112
|
+
? url + "&values=" + value
|
|
113
|
+
: url + "?values=" + value;
|
|
103
114
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
let response = await (
|
|
116
|
+
props.fieldConfig.disableHeaderInFetch
|
|
117
|
+
? axios.get(url)
|
|
118
|
+
: formContext.axiosInstance?.get(url)
|
|
119
|
+
);
|
|
120
|
+
if (response?.data) {
|
|
121
|
+
const data: FieldOptionsSchema[] = getListOptions(
|
|
122
|
+
response?.data,
|
|
123
|
+
props.fieldConfig.optionsConfig
|
|
124
|
+
);
|
|
125
|
+
setSelectedValues([...data]);
|
|
126
|
+
}
|
|
127
|
+
} catch (err) { }
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const updateListOptions = (data: any) => {
|
|
131
|
+
const resData: FieldOptionsSchema = getListOption(
|
|
132
|
+
data,
|
|
133
|
+
props.fieldConfig.optionsConfig
|
|
111
134
|
);
|
|
112
|
-
setSelectedValues([
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
};
|
|
135
|
+
setSelectedValues((perv) => [resData]);
|
|
136
|
+
formContext.setValue(props.fieldConfig.name, resData.value);
|
|
137
|
+
};
|
|
116
138
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
139
|
+
function getInput() {
|
|
140
|
+
return (
|
|
141
|
+
<Listbox
|
|
142
|
+
as={"div"}
|
|
143
|
+
{...hookProps}
|
|
144
|
+
className={`relative form-listbox flex-1`}
|
|
145
|
+
value={
|
|
146
|
+
formContext.getValues(props.fieldConfig.name)
|
|
147
|
+
? props.fieldConfig.outputFormat ===
|
|
148
|
+
OutputFormatType.ARRAY
|
|
149
|
+
? formContext.getValues(props.fieldConfig.name)[0]
|
|
150
|
+
: formContext.getValues(props.fieldConfig.name)
|
|
151
|
+
: undefined
|
|
152
|
+
}
|
|
153
|
+
onChange={(val) => {
|
|
154
|
+
const currentValue = formContext.getValues(
|
|
155
|
+
props.fieldConfig.name
|
|
156
|
+
);
|
|
125
157
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<Listbox
|
|
129
|
-
as={"div"}
|
|
130
|
-
{...hookProps}
|
|
131
|
-
className={`relative form-listbox flex-1`}
|
|
132
|
-
value={
|
|
133
|
-
formContext.getValues(props.fieldConfig.name)
|
|
134
|
-
? props.fieldConfig.outputFormat === OutputFormatType.ARRAY
|
|
135
|
-
? formContext.getValues(props.fieldConfig.name)[0]
|
|
136
|
-
: formContext.getValues(props.fieldConfig.name)
|
|
137
|
-
: undefined
|
|
138
|
-
}
|
|
139
|
-
onChange={(val) => {
|
|
140
|
-
const currentValue = formContext.getValues(props.fieldConfig.name);
|
|
158
|
+
// If the value matches, set it to null, otherwise set it to val
|
|
159
|
+
const newValue = currentValue === val ? null : val;
|
|
141
160
|
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
const selected = listOptions.find(o => o.value == newValue);
|
|
162
|
+
// selected && setSelectedValues([selected]);
|
|
163
|
+
handleChange(
|
|
164
|
+
newValue,
|
|
165
|
+
formContext,
|
|
166
|
+
props.fieldConfig,
|
|
167
|
+
props.onChange
|
|
168
|
+
);
|
|
169
|
+
}}
|
|
170
|
+
name={props.fieldConfig.name}
|
|
171
|
+
disabled={props.fieldConfig.disabled}>
|
|
172
|
+
<ListboxButton
|
|
173
|
+
className={
|
|
174
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
175
|
+
? "form-listbox-select " +
|
|
176
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
177
|
+
: "form-listbox-select"
|
|
178
|
+
}>
|
|
179
|
+
{renderListBoxValue(
|
|
180
|
+
formContext,
|
|
181
|
+
props.fieldConfig,
|
|
182
|
+
[...selectedValues, ...listOptions],
|
|
183
|
+
props.onChange
|
|
184
|
+
)}
|
|
185
|
+
</ListboxButton>
|
|
186
|
+
<RenderListOptions
|
|
187
|
+
formContext={formContext}
|
|
188
|
+
onChange={props.onChange}
|
|
189
|
+
formField={FormFieldType.TYPEAHEAD}
|
|
190
|
+
ref={dynamicSelectRef}
|
|
191
|
+
fieldConfig={props.fieldConfig}
|
|
192
|
+
listOptions={[...selectedValues, ...listOptions]}
|
|
193
|
+
setListOptions={setListOptions}
|
|
194
|
+
loading={loading}
|
|
195
|
+
setLoading={setLoading}
|
|
196
|
+
createCallback={(data) => updateListOptions(data)}
|
|
197
|
+
queryCallback={(query) => fetchData(query)}
|
|
198
|
+
/>
|
|
199
|
+
</Listbox>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
|
|
203
|
+
return <></>;
|
|
204
|
+
}
|
|
144
205
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
handleChange(
|
|
148
|
-
newValue,
|
|
149
|
-
formContext,
|
|
150
|
-
props.fieldConfig,
|
|
151
|
-
props.onChange,
|
|
152
|
-
);
|
|
153
|
-
}}
|
|
154
|
-
name={props.fieldConfig.name}
|
|
155
|
-
disabled={props.fieldConfig.disabled}
|
|
156
|
-
>
|
|
157
|
-
<ListboxButton
|
|
158
|
-
className={
|
|
159
|
-
props.fieldConfig.customClassNames?.fieldClassName
|
|
160
|
-
? "form-listbox-select " +
|
|
161
|
-
props.fieldConfig.customClassNames?.fieldClassName
|
|
162
|
-
: "form-listbox-select"
|
|
163
|
-
}
|
|
164
|
-
>
|
|
165
|
-
{renderListBoxValue(
|
|
166
|
-
formContext,
|
|
167
|
-
props.fieldConfig,
|
|
168
|
-
[...selectedValues, ...listOptions],
|
|
169
|
-
props.onChange,
|
|
170
|
-
)}
|
|
171
|
-
</ListboxButton>
|
|
172
|
-
<RenderListOptions
|
|
173
|
-
formContext={formContext}
|
|
174
|
-
onChange={props.onChange}
|
|
175
|
-
formField={FormFieldType.TYPEAHEAD}
|
|
176
|
-
ref={dynamicSelectRef}
|
|
177
|
-
fieldConfig={props.fieldConfig}
|
|
178
|
-
listOptions={[...selectedValues, ...listOptions]}
|
|
179
|
-
setListOptions={setListOptions}
|
|
180
|
-
loading={loading}
|
|
181
|
-
setLoading={setLoading}
|
|
182
|
-
createCallback={(data) => updateListOptions(data)}
|
|
183
|
-
queryCallback={(query) => fetchData(query)}
|
|
184
|
-
/>
|
|
185
|
-
</Listbox>
|
|
206
|
+
return (
|
|
207
|
+
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
186
208
|
);
|
|
187
|
-
}
|
|
188
|
-
if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
|
|
189
|
-
return <></>;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
<RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
|
|
194
|
-
);
|
|
195
209
|
};
|
|
196
210
|
export default Typeahead;
|
|
@@ -199,12 +199,7 @@ const Typeahead2: React.FC<FormFieldComponentPropSchema> = (
|
|
|
199
199
|
: result.includes(resData.value)
|
|
200
200
|
? [...result.filter((v: string) => v != resData.value), resData.value]
|
|
201
201
|
: [...result, resData.value];
|
|
202
|
-
|
|
203
|
-
result,
|
|
204
|
-
formContext,
|
|
205
|
-
props.fieldConfig,
|
|
206
|
-
props.onChange,
|
|
207
|
-
);
|
|
202
|
+
formContext.setValue(props.fieldConfig.name, result);
|
|
208
203
|
};
|
|
209
204
|
|
|
210
205
|
const getInput = () => {
|
|
@@ -23,10 +23,9 @@ import RenderListOptions, {
|
|
|
23
23
|
} from "../util/RenderListOptions";
|
|
24
24
|
// import _ from "lodash";
|
|
25
25
|
import axios from "axios";
|
|
26
|
-
import { getAxiosInstance } from "../../api";
|
|
27
26
|
|
|
28
27
|
const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
29
|
-
props: FormFieldComponentPropSchema
|
|
28
|
+
props: FormFieldComponentPropSchema
|
|
30
29
|
) => {
|
|
31
30
|
const dynamicSelectRef = useRef<HTMLUListElement>(null);
|
|
32
31
|
const formContext = useContext(FormContext);
|
|
@@ -34,7 +33,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
34
33
|
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
35
34
|
const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
|
|
36
35
|
const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
|
|
37
|
-
[]
|
|
36
|
+
[]
|
|
38
37
|
);
|
|
39
38
|
const [loading, setLoading] = useState<boolean>(true);
|
|
40
39
|
|
|
@@ -45,7 +44,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
45
44
|
) {
|
|
46
45
|
formContext.setValue(
|
|
47
46
|
props.fieldConfig.name,
|
|
48
|
-
props.fieldConfig.defaultValue
|
|
47
|
+
props.fieldConfig.defaultValue
|
|
49
48
|
);
|
|
50
49
|
}
|
|
51
50
|
fetchData(undefined);
|
|
@@ -61,16 +60,14 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
61
60
|
if (_query) {
|
|
62
61
|
url = url.includes("?") ? url + "&q=" + _query : url + "?q=" + _query;
|
|
63
62
|
}
|
|
64
|
-
const axiosInstance = getAxiosInstance(
|
|
65
|
-
formContext.axiosInstance,
|
|
66
|
-
props.fieldConfig,
|
|
67
|
-
);
|
|
68
63
|
|
|
69
|
-
let response = await
|
|
64
|
+
let response = await (props.fieldConfig.disableHeaderInFetch
|
|
65
|
+
? axios.get(url)
|
|
66
|
+
: formContext.axiosInstance?.get(url));
|
|
70
67
|
if (response?.data) {
|
|
71
68
|
const data: FieldOptionsSchema[] = getListOptions(
|
|
72
69
|
response?.data,
|
|
73
|
-
props.fieldConfig.optionsConfig
|
|
70
|
+
props.fieldConfig.optionsConfig
|
|
74
71
|
);
|
|
75
72
|
setListOptions([...data]);
|
|
76
73
|
let values = formContext.getValues(props.fieldConfig.name) || [];
|
|
@@ -80,7 +77,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
80
77
|
(data.length === 0 || // If 'data' is empty
|
|
81
78
|
(!values.every((v: string) => data.some((i) => i.value === v)) && // Ensure none of 'values' match 'data'
|
|
82
79
|
!values.every((v: string) =>
|
|
83
|
-
selectedValues.some((i) => i.value === v)
|
|
80
|
+
selectedValues.some((i) => i.value === v)
|
|
84
81
|
))) // Ensure none of 'values' match 'selectedValues'
|
|
85
82
|
) {
|
|
86
83
|
fetchValue(values); // Call 'fetchValue()' if all conditions are met
|
|
@@ -97,7 +94,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
97
94
|
setLoading(false);
|
|
98
95
|
}
|
|
99
96
|
},
|
|
100
|
-
[props.fieldConfig.fetchUrl]
|
|
97
|
+
[props.fieldConfig.fetchUrl]
|
|
101
98
|
);
|
|
102
99
|
|
|
103
100
|
const fetchValue = async (value: string) => {
|
|
@@ -108,7 +105,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
108
105
|
|
|
109
106
|
let url = props.fieldConfig.fetchUrl;
|
|
110
107
|
|
|
111
|
-
url = url.includes("?")
|
|
108
|
+
url = url = url.includes("?")
|
|
112
109
|
? url + "&values=" + value
|
|
113
110
|
: url + "?values=" + value;
|
|
114
111
|
|
|
@@ -118,7 +115,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
118
115
|
if (response?.data) {
|
|
119
116
|
const data: FieldOptionsSchema[] = getListOptions(
|
|
120
117
|
response?.data,
|
|
121
|
-
props.fieldConfig.optionsConfig
|
|
118
|
+
props.fieldConfig.optionsConfig
|
|
122
119
|
);
|
|
123
120
|
let values: any[] = formContext.getValues(props.fieldConfig.name) || [];
|
|
124
121
|
setSelectedValues(
|
|
@@ -126,22 +123,22 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
126
123
|
...data,
|
|
127
124
|
props.fieldConfig.dropdownFieldConfig?.isSuggestionBox && values
|
|
128
125
|
? values
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
.filter(
|
|
127
|
+
(value) =>
|
|
128
|
+
data.some((d) => d.value !== value) || data.length == 0
|
|
129
|
+
)
|
|
130
|
+
.map((val) => ({ label: val, value: val }))
|
|
134
131
|
: [],
|
|
135
|
-
].flat()
|
|
132
|
+
].flat()
|
|
136
133
|
);
|
|
137
134
|
}
|
|
138
|
-
} catch (err) {
|
|
135
|
+
} catch (err) {}
|
|
139
136
|
};
|
|
140
137
|
|
|
141
138
|
const updateListOptions = (data: any) => {
|
|
142
139
|
const resData: FieldOptionsSchema = getListOption(
|
|
143
140
|
data,
|
|
144
|
-
props.fieldConfig.optionsConfig
|
|
141
|
+
props.fieldConfig.optionsConfig
|
|
145
142
|
);
|
|
146
143
|
setSelectedValues((prev) => [...prev, resData]);
|
|
147
144
|
let result = formContext.getValues(props.fieldConfig.name) || [];
|
|
@@ -163,14 +160,14 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
163
160
|
className={"relative form-listbox flex-1 overflow-hidden"}
|
|
164
161
|
onChange={(selectedOptions) => {
|
|
165
162
|
const chossenOptions = listOptions.filter((op) =>
|
|
166
|
-
selectedOptions.includes(op.value)
|
|
163
|
+
selectedOptions.includes(op.value)
|
|
167
164
|
);
|
|
168
165
|
// setSelectedValues((prev) => [...prev, ...chossenOptions]);
|
|
169
166
|
handleChange(
|
|
170
167
|
selectedOptions,
|
|
171
168
|
formContext,
|
|
172
169
|
props.fieldConfig,
|
|
173
|
-
props.onChange
|
|
170
|
+
props.onChange
|
|
174
171
|
);
|
|
175
172
|
}}
|
|
176
173
|
multiple
|
|
@@ -179,7 +176,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
179
176
|
className={
|
|
180
177
|
props.fieldConfig.customClassNames?.fieldClassName
|
|
181
178
|
? "form-listbox-select " +
|
|
182
|
-
|
|
179
|
+
props.fieldConfig.customClassNames?.fieldClassName
|
|
183
180
|
: "form-listbox-select"
|
|
184
181
|
}
|
|
185
182
|
>
|
|
@@ -187,7 +184,7 @@ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
|
|
|
187
184
|
formContext,
|
|
188
185
|
props.fieldConfig,
|
|
189
186
|
[...selectedValues, ...listOptions],
|
|
190
|
-
props.onChange
|
|
187
|
+
props.onChange
|
|
191
188
|
)}
|
|
192
189
|
</ListboxButton>
|
|
193
190
|
<RenderListOptions
|
|
@@ -106,12 +106,11 @@ export type FormFieldSchema = {
|
|
|
106
106
|
max?: number;
|
|
107
107
|
min?: number;
|
|
108
108
|
rows?: number;
|
|
109
|
+
allowedMinQueryLength?: number;
|
|
109
110
|
defaultValue?: string | string[] | {} | boolean;
|
|
110
111
|
options?: FieldOptionsSchema[];
|
|
111
112
|
minDate?: Date | null | undefined;
|
|
112
113
|
maxDate?: Date | null | undefined;
|
|
113
|
-
/** Input display format for date pickers (e.g. 'MMMM D, YYYY'). */
|
|
114
|
-
dateDisplayFormat?: string;
|
|
115
114
|
// ref?:any
|
|
116
115
|
|
|
117
116
|
/**
|
|
@@ -130,15 +129,14 @@ export type FormFieldSchema = {
|
|
|
130
129
|
decimalAllowed?: boolean;
|
|
131
130
|
errorMessage?: string;
|
|
132
131
|
submitOnChange?: boolean;
|
|
132
|
+
isMultiple?: boolean;
|
|
133
133
|
formFieldPattern?: FormFieldPatternsImpl[];
|
|
134
134
|
fetchUrl?: string;
|
|
135
|
-
fetchSavedDataUrl?: string;
|
|
136
135
|
postUrl?: string;
|
|
136
|
+
fetchSavedDataUrl?: string;
|
|
137
137
|
fileAccept?: string;
|
|
138
138
|
icon?: ReactNode;
|
|
139
139
|
outputFormat?: OutputFormatType;
|
|
140
|
-
isMultiple?: boolean;
|
|
141
|
-
allowedMinQueryLength?: number;
|
|
142
140
|
children?: FormFieldSchema[];
|
|
143
141
|
defaultOptions?: FieldOptionsSchema[];
|
|
144
142
|
optionsConfig?: OptionMappingConfig;
|
|
@@ -207,7 +205,6 @@ export type FieldOptionsSchema = {
|
|
|
207
205
|
isDisabled?: boolean;
|
|
208
206
|
helpText?: string;
|
|
209
207
|
groupName?: string;
|
|
210
|
-
tooltip?: string;
|
|
211
208
|
icon?: React.ReactNode;
|
|
212
209
|
};
|
|
213
210
|
|
|
@@ -243,7 +240,7 @@ export class FormFieldPatternsImpl implements FormFieldPatterns {
|
|
|
243
240
|
static readonly EMAIL = new FormFieldPatternsImpl(
|
|
244
241
|
"EMAIL",
|
|
245
242
|
"Please enter a valid email address",
|
|
246
|
-
/^(
|
|
243
|
+
/^([A-Za-z0-9\._%+\-]+@[A-Za-z0-9\.\-]+\.[A-Za-z]{2,})$/
|
|
247
244
|
);
|
|
248
245
|
// static readonly PASSWORD = new FormFieldPatternsImpl(
|
|
249
246
|
// "PASSWORD",
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
FormFieldType,
|
|
19
19
|
OutputFormatType,
|
|
20
20
|
} from "../schema/FormFieldSchema";
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
type RenderListOptionsProps = {
|
|
23
23
|
formContext: FormContextType;
|
|
24
24
|
fieldConfig: FormFieldSchema;
|
|
@@ -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 == 0 && isTypeahead) {
|
|
131
131
|
queryCallback && queryCallback(query);
|
|
132
132
|
}
|
|
133
133
|
}, [filteredList]);
|
|
@@ -154,17 +154,12 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
154
154
|
|
|
155
155
|
const renderOption = (option: FieldOptionsSchema) => {
|
|
156
156
|
let selected = false;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
typeof option.value === "string"
|
|
162
|
-
? option.value.trim()
|
|
163
|
-
: String(option.value).trim();
|
|
164
|
-
selected = normalizedValues.includes(normalizedOptionValue);
|
|
165
|
-
} else if (Array.isArray(formValue)) {
|
|
166
|
-
selected = formValue.includes(option.value);
|
|
157
|
+
if (Array.isArray(formContext.getValues(fieldConfig.name))) {
|
|
158
|
+
selected = formContext
|
|
159
|
+
.getValues(fieldConfig.name)
|
|
160
|
+
.includes(option.value);
|
|
167
161
|
} else {
|
|
162
|
+
const formValue = formContext.getValues(fieldConfig.name);
|
|
168
163
|
// const defaultValue = fieldConfig.defaultValue;
|
|
169
164
|
// selected = formValue ? option.value == formValue : defaultValue == option.value;
|
|
170
165
|
selected = option.value == formValue;
|
|
@@ -194,21 +189,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
194
189
|
{option.icon && (
|
|
195
190
|
<span className="listbox-svg">{option.icon}</span>
|
|
196
191
|
)}
|
|
197
|
-
{option.
|
|
198
|
-
<Tippy
|
|
199
|
-
content={
|
|
200
|
-
<>
|
|
201
|
-
{option.label}
|
|
202
|
-
<br />
|
|
203
|
-
{option.helpText}
|
|
204
|
-
</>
|
|
205
|
-
}
|
|
206
|
-
>
|
|
207
|
-
<div className="truncate">{option.label}</div>
|
|
208
|
-
</Tippy>
|
|
209
|
-
) : (
|
|
210
|
-
option.label
|
|
211
|
-
)}
|
|
192
|
+
{option.label}
|
|
212
193
|
</span>
|
|
213
194
|
{isTypeahead &&
|
|
214
195
|
!fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
|
|
@@ -227,30 +208,11 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
227
208
|
)
|
|
228
209
|
)}
|
|
229
210
|
</div>
|
|
230
|
-
{option.helpText &&
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
{option.label}
|
|
236
|
-
<br />
|
|
237
|
-
{option.helpText}
|
|
238
|
-
</>
|
|
239
|
-
}
|
|
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
|
-
</Tippy>
|
|
247
|
-
) : (
|
|
248
|
-
<div
|
|
249
|
-
className={`mt-0 text-xs text-gray-500 font-normal truncate w-full ${fieldConfig.customClassNames?.optionClassName}`}
|
|
250
|
-
>
|
|
251
|
-
{option.helpText}
|
|
252
|
-
</div>
|
|
253
|
-
))}
|
|
211
|
+
{option.helpText && (
|
|
212
|
+
<div className="mt-0 text-xs text-gray-500 font-normal truncate w-full !max-w-[150px]">
|
|
213
|
+
{option.helpText}
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
254
216
|
</>
|
|
255
217
|
)}
|
|
256
218
|
</ListboxOption>
|
|
@@ -373,7 +335,7 @@ export function renderListBoxValue(
|
|
|
373
335
|
listOptions: FieldOptionsSchema[],
|
|
374
336
|
onChange?: (value: any) => void,
|
|
375
337
|
): JSX.Element {
|
|
376
|
-
let value =
|
|
338
|
+
let value = formContext.getValues(fieldConfig.name);
|
|
377
339
|
const renderAsString = () => {
|
|
378
340
|
// if (!listOptions) {
|
|
379
341
|
// return value;
|
|
@@ -463,10 +425,9 @@ export function renderListBoxValue(
|
|
|
463
425
|
)
|
|
464
426
|
);
|
|
465
427
|
};
|
|
466
|
-
let outputFormat =
|
|
467
|
-
fieldConfig.outputFormat
|
|
468
|
-
|
|
469
|
-
: false;
|
|
428
|
+
let outputFormat = fieldConfig.outputFormat
|
|
429
|
+
? fieldConfig.outputFormat === OutputFormatType.ARRAY
|
|
430
|
+
: false;
|
|
470
431
|
const getPlaceholder = () => (
|
|
471
432
|
<span className="form-placeholder">
|
|
472
433
|
{fieldConfig.placeholder || "Select any option"}
|
|
@@ -476,7 +437,7 @@ export function renderListBoxValue(
|
|
|
476
437
|
if (!value && !listOptions) {
|
|
477
438
|
return getPlaceholder();
|
|
478
439
|
}
|
|
479
|
-
if (Array.isArray(value)) {
|
|
440
|
+
if (!outputFormat && Array.isArray(value)) {
|
|
480
441
|
return renderAsArray();
|
|
481
442
|
}
|
|
482
443
|
return renderAsString();
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import moment from "moment";
|
|
2
|
-
import { FormFieldSchema, OutputFormatType } from "../schema/FormFieldSchema";
|
|
3
|
-
|
|
4
|
-
const normalizeStringToken = (value: unknown): string => {
|
|
5
|
-
return typeof value === "string" ? value.trim() : String(value).trim();
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
const normalizeStringArray = (values: unknown[]): string[] => {
|
|
9
|
-
return values.map(normalizeStringToken).filter(Boolean);
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const parseJsonArrayString = (value: string): string[] | null => {
|
|
13
|
-
if (!value.startsWith("[")) return null;
|
|
14
|
-
try {
|
|
15
|
-
const parsed = JSON.parse(value);
|
|
16
|
-
return Array.isArray(parsed) ? normalizeStringArray(parsed) : null;
|
|
17
|
-
} catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const normalizeCheckboxBooleanValue = (value: any): boolean => {
|
|
23
|
-
if (value === true || value === 1) return true;
|
|
24
|
-
if (value === false || value === 0 || value == null || value === "") {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (typeof value === "string") {
|
|
29
|
-
const s = value.trim().toLowerCase();
|
|
30
|
-
if (s === "true" || s === "1" || s === "yes") return true;
|
|
31
|
-
if (s === "false" || s === "0" || s === "no" || s === "") return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return Boolean(value);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const normalizeMultiSelectValue = (value: any): string[] => {
|
|
38
|
-
if (value == null) return [];
|
|
39
|
-
|
|
40
|
-
if (Array.isArray(value)) return normalizeStringArray(value);
|
|
41
|
-
|
|
42
|
-
if (typeof value === "string") {
|
|
43
|
-
const s = value.trim();
|
|
44
|
-
if (!s) return [];
|
|
45
|
-
|
|
46
|
-
const jsonArray = parseJsonArrayString(s);
|
|
47
|
-
if (jsonArray) return jsonArray;
|
|
48
|
-
|
|
49
|
-
return normalizeStringArray(s.split(","));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return normalizeStringArray([value]);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const normalizeDateFormat = (format?: string): string => {
|
|
56
|
-
if (!format) return "YYYY-MM-DD";
|
|
57
|
-
return format
|
|
58
|
-
.replace(/yyyy/g, "YYYY")
|
|
59
|
-
.replace(/yy/g, "YY")
|
|
60
|
-
.replace(/dd/g, "DD")
|
|
61
|
-
.replace(/d/g, "D")
|
|
62
|
-
.replace(/mm/g, "MM");
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export const normalizeDateInputValue = (
|
|
66
|
-
value: any,
|
|
67
|
-
preferredFormat?: string
|
|
68
|
-
): Date | null => {
|
|
69
|
-
if (value == null || value === "") return null;
|
|
70
|
-
|
|
71
|
-
if (value instanceof Date) {
|
|
72
|
-
return Number.isNaN(value.getTime()) ? null : value;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
76
|
-
const ms = value < 1_000_000_000_000 ? value * 1000 : value;
|
|
77
|
-
const date = new Date(ms);
|
|
78
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (typeof value === "string") {
|
|
82
|
-
const s = value.trim();
|
|
83
|
-
if (!s) return null;
|
|
84
|
-
|
|
85
|
-
const numeric = Number(s);
|
|
86
|
-
if (Number.isFinite(numeric)) {
|
|
87
|
-
const ms = numeric < 1_000_000_000_000 ? numeric * 1000 : numeric;
|
|
88
|
-
const date = new Date(ms);
|
|
89
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const preferred = normalizeDateFormat(preferredFormat);
|
|
93
|
-
const formats = Array.from(
|
|
94
|
-
new Set([
|
|
95
|
-
preferred,
|
|
96
|
-
"YYYY-MM-DD",
|
|
97
|
-
"DD/MM/YYYY",
|
|
98
|
-
"MM/DD/YYYY",
|
|
99
|
-
"DD-MM-YYYY",
|
|
100
|
-
"MM-DD-YYYY",
|
|
101
|
-
])
|
|
102
|
-
);
|
|
103
|
-
const parsedWithKnownFormats = moment(s, formats, true);
|
|
104
|
-
if (parsedWithKnownFormats.isValid()) {
|
|
105
|
-
return parsedWithKnownFormats.toDate();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const date = new Date(s);
|
|
109
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const date = new Date(value);
|
|
113
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
export const normalizeMultiSelectValueForStringArrayValues = (fieldConfig: FormFieldSchema, value: any): string[] => {
|
|
118
|
-
if (fieldConfig.outputFormat === OutputFormatType.STRING) {
|
|
119
|
-
return normalizeMultiSelectValue(value);
|
|
120
|
-
}
|
|
121
|
-
return value;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
export const normalizeFileFieldUrls = (value: any): string[] => {
|
|
126
|
-
if (value == null) return [];
|
|
127
|
-
|
|
128
|
-
if (Array.isArray(value)) {
|
|
129
|
-
return value.map((v) => String(v).trim()).filter(Boolean);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (typeof value === 'string') {
|
|
133
|
-
const s = value.trim();
|
|
134
|
-
if (!s || s === '[]') return [];
|
|
135
|
-
if (s.startsWith('[')) {
|
|
136
|
-
try {
|
|
137
|
-
const parsed = JSON.parse(s);
|
|
138
|
-
if (Array.isArray(parsed)) {
|
|
139
|
-
return parsed.map((v) => String(v).trim()).filter(Boolean);
|
|
140
|
-
}
|
|
141
|
-
} catch {
|
|
142
|
-
// fall through — treat as single URL
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return [s];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return [];
|
|
149
|
-
};
|