@engagebay/engagebay-form-module 1.0.7 → 1.0.8-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 -5
- 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/CheckboxField.tsx +30 -11
- package/src/form/formfields/DatePickerField.tsx +13 -12
- package/src/form/formfields/MultipleSelectField.tsx +17 -5
- package/src/form/formfields/Typeahead.tsx +163 -177
- package/src/form/formfields/TypeaheadMultiSelect.tsx +27 -24
- package/src/form/formfields/UrlField.tsx +8 -3
- package/src/form/schema/FormFieldSchema.ts +6 -3
- package/src/form/util/RenderListOptions.tsx +58 -19
- package/src/form/util/normalizeCustomFieldValues.ts +115 -0
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.8-beta.2",
|
|
4
|
+
"description": "Provide base form components to reacho",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
7
|
},
|
|
8
|
-
"author": "
|
|
8
|
+
"author": "Reacho Dev",
|
|
9
9
|
"license": "ISC",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"react-phone-input-2": "^2.15.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`)?.[
|
|
@@ -1,32 +1,46 @@
|
|
|
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 {
|
|
4
|
+
import {
|
|
5
|
+
FormFieldComponentPropSchema,
|
|
6
|
+
OutputFormatType,
|
|
7
|
+
} from "../schema/FormFieldSchema";
|
|
5
8
|
import { Checkbox, Field, Input, Label } from "@headlessui/react";
|
|
6
9
|
import React from "react";
|
|
7
10
|
import RenderFormField from "../util/RenderFormField";
|
|
8
11
|
import { handleChange, registerFormField } from "../util";
|
|
12
|
+
import { normalizeCheckboxBooleanValue, normalizeMultiSelectValue } from "../util/normalizeCustomFieldValues";
|
|
9
13
|
const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
10
14
|
props: FormFieldComponentPropSchema
|
|
11
15
|
) => {
|
|
12
16
|
const formContext = useContext(FormContext);
|
|
13
17
|
const [selectedOption, setSelectedOption] = useState<string[]>(
|
|
14
|
-
formContext.getValues(props.fieldConfig.name)
|
|
15
|
-
? formContext.getValues(props.fieldConfig.name)
|
|
16
|
-
: []
|
|
18
|
+
normalizeMultiSelectValue(formContext.getValues(props.fieldConfig.name))
|
|
17
19
|
);
|
|
18
20
|
const [isChecked, setIsChecked] = useState<boolean>(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
normalizeCheckboxBooleanValue(
|
|
22
|
+
formContext.getValues(props.fieldConfig.name) ?? props.fieldConfig.defaultValue
|
|
23
|
+
)
|
|
22
24
|
);
|
|
23
25
|
useEffect(() => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const current = formContext.getValues(props.fieldConfig.name);
|
|
27
|
+
if (props.fieldConfig.options) {
|
|
28
|
+
setSelectedOption(normalizeMultiSelectValue(current));
|
|
29
|
+
} else {
|
|
30
|
+
setIsChecked(
|
|
31
|
+
normalizeCheckboxBooleanValue(current ?? props.fieldConfig.defaultValue)
|
|
32
|
+
);
|
|
26
33
|
}
|
|
27
34
|
}, []);
|
|
28
35
|
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
29
36
|
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
|
+
};
|
|
30
44
|
const handleInput = (option: string) => {
|
|
31
45
|
let tempData: string[] = selectedOption;
|
|
32
46
|
if (selectedOption.includes(option)) {
|
|
@@ -36,7 +50,12 @@ const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
36
50
|
tempData.push(option);
|
|
37
51
|
setSelectedOption([...tempData]);
|
|
38
52
|
}
|
|
39
|
-
handleChange(
|
|
53
|
+
handleChange(
|
|
54
|
+
getOutputValue(tempData),
|
|
55
|
+
formContext,
|
|
56
|
+
props.fieldConfig,
|
|
57
|
+
props.onChange
|
|
58
|
+
);
|
|
40
59
|
};
|
|
41
60
|
function getInput() {
|
|
42
61
|
return props.fieldConfig.options ? (
|
|
@@ -87,7 +106,7 @@ const CheckboxField: React.FC<FormFieldComponentPropSchema> = (
|
|
|
87
106
|
checked={isChecked}
|
|
88
107
|
onChange={(checked) => {
|
|
89
108
|
handleChange(
|
|
90
|
-
checked,
|
|
109
|
+
getOutputValue(checked),
|
|
91
110
|
formContext,
|
|
92
111
|
props.fieldConfig,
|
|
93
112
|
props.onChange
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { RegisterOptions } from "react-hook-form";
|
|
2
|
-
import React, { useContext, useEffect
|
|
2
|
+
import React, { useContext, useEffect } 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";
|
|
8
9
|
import moment from "moment";
|
|
9
10
|
|
|
11
|
+
|
|
12
|
+
|
|
10
13
|
const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
11
|
-
props: FormFieldComponentPropSchema
|
|
14
|
+
props: FormFieldComponentPropSchema,
|
|
12
15
|
) => {
|
|
13
16
|
const formContext = useContext(FormContext);
|
|
14
17
|
const initialDate = formContext.getValues(props.fieldConfig.name)
|
|
@@ -25,16 +28,17 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
25
28
|
) {
|
|
26
29
|
formContext.setValue(
|
|
27
30
|
props.fieldConfig.name,
|
|
28
|
-
props.fieldConfig.defaultValue
|
|
31
|
+
props.fieldConfig.defaultValue,
|
|
29
32
|
);
|
|
30
33
|
}
|
|
31
34
|
}, [props.fieldConfig.forceUpdate]);
|
|
32
35
|
|
|
33
36
|
let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
|
|
34
|
-
|
|
37
|
+
//registerOptions.valueAsDate = true;
|
|
35
38
|
let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
|
|
36
39
|
|
|
37
40
|
function getInput() {
|
|
41
|
+
const displayFormat = normalizeDateFormat(props.fieldConfig.dateDisplayFormat);
|
|
38
42
|
const rawStart =
|
|
39
43
|
initialDate?.startDate ||
|
|
40
44
|
props.fieldConfig.defaultValue ||
|
|
@@ -43,13 +47,9 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
43
47
|
initialDate?.endDate ||
|
|
44
48
|
props.fieldConfig.defaultValue ||
|
|
45
49
|
"";
|
|
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: normalizeDateInputValue(rawStart, displayFormat),
|
|
52
|
+
endDate: normalizeDateInputValue(rawEnd, displayFormat),
|
|
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,10 +58,11 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
58
58
|
value={value}
|
|
59
59
|
startFrom={startFrom}
|
|
60
60
|
{...hookProps}
|
|
61
|
+
displayFormat={displayFormat}
|
|
61
62
|
placeholder={
|
|
62
63
|
props.fieldConfig.placeholder
|
|
63
64
|
? props.fieldConfig.placeholder
|
|
64
|
-
:
|
|
65
|
+
: displayFormat
|
|
65
66
|
}
|
|
66
67
|
asSingle={true}
|
|
67
68
|
popoverDirection="down"
|
|
@@ -79,7 +80,7 @@ const DatePicker: React.FC<FormFieldComponentPropSchema> = (
|
|
|
79
80
|
onChange={(dates) => {
|
|
80
81
|
let date = null;
|
|
81
82
|
if (dates?.startDate != null)
|
|
82
|
-
date = moment(dates?.startDate).format(
|
|
83
|
+
date = moment(dates?.startDate).format(displayFormat);
|
|
83
84
|
|
|
84
85
|
handleChange(date, formContext, props.fieldConfig, props.onChange);
|
|
85
86
|
}}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
FieldOptionsSchema,
|
|
14
14
|
FormFieldComponentPropSchema,
|
|
15
15
|
FormFieldType,
|
|
16
|
+
OutputFormatType,
|
|
16
17
|
} from "../schema/FormFieldSchema";
|
|
17
18
|
import { FormContext } from "../context/FormContext";
|
|
18
19
|
import RenderFormField from "../util/RenderFormField";
|
|
@@ -20,6 +21,7 @@ import RenderListOptions, {
|
|
|
20
21
|
renderListBoxValue,
|
|
21
22
|
} from "../util/RenderListOptions";
|
|
22
23
|
import { handleChange, registerFormField } from "../util";
|
|
24
|
+
import { normalizeMultiSelectValue } from "../util/normalizeCustomFieldValues";
|
|
23
25
|
|
|
24
26
|
const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
25
27
|
fieldConfig,
|
|
@@ -34,9 +36,11 @@ const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
|
34
36
|
fieldConfig.options ? fieldConfig.options : []
|
|
35
37
|
);
|
|
36
38
|
|
|
37
|
-
const
|
|
38
|
-
formContext.getValues(fieldConfig.name)
|
|
39
|
-
)
|
|
39
|
+
const normalizedSelectedValues = normalizeMultiSelectValue(
|
|
40
|
+
formContext.getValues(fieldConfig.name)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const validSelectedValues = normalizedSelectedValues.filter(
|
|
40
44
|
(selectedValue: any) =>
|
|
41
45
|
fieldConfig.options &&
|
|
42
46
|
fieldConfig.options.some((option) => option.value === selectedValue)
|
|
@@ -53,14 +57,22 @@ const MultipleSelectField: React.FC<FormFieldComponentPropSchema> = ({
|
|
|
53
57
|
}, [fieldConfig.options]);
|
|
54
58
|
|
|
55
59
|
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
|
+
|
|
56
68
|
return (
|
|
57
69
|
<Listbox
|
|
58
70
|
as={"div"}
|
|
59
71
|
{...hookProps}
|
|
60
72
|
value={validSelectedValues}
|
|
61
73
|
defaultValue={fieldConfig.defaultValue}
|
|
62
|
-
onChange={(val) =>
|
|
63
|
-
handleChange(val, formContext, fieldConfig, onChange)
|
|
74
|
+
onChange={(val: any) =>
|
|
75
|
+
handleChange(getOutputValue(val), formContext, fieldConfig, onChange)
|
|
64
76
|
}
|
|
65
77
|
disabled={fieldConfig.disabled}
|
|
66
78
|
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,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
|
-
setSelectedValues((prev) => [...prev, ...chossenOptions]);
|
|
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,11 +106,12 @@ 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;
|
|
113
112
|
maxDate?: Date | null | undefined;
|
|
113
|
+
/** Input display format for date pickers (e.g. 'MMMM D, YYYY'). */
|
|
114
|
+
dateDisplayFormat?: string;
|
|
114
115
|
// ref?:any
|
|
115
116
|
|
|
116
117
|
/**
|
|
@@ -129,14 +130,15 @@ export type FormFieldSchema = {
|
|
|
129
130
|
decimalAllowed?: boolean;
|
|
130
131
|
errorMessage?: string;
|
|
131
132
|
submitOnChange?: boolean;
|
|
132
|
-
isMultiple?: boolean;
|
|
133
133
|
formFieldPattern?: FormFieldPatternsImpl[];
|
|
134
134
|
fetchUrl?: string;
|
|
135
|
-
postUrl?: string;
|
|
136
135
|
fetchSavedDataUrl?: string;
|
|
136
|
+
postUrl?: string;
|
|
137
137
|
fileAccept?: string;
|
|
138
138
|
icon?: ReactNode;
|
|
139
139
|
outputFormat?: OutputFormatType;
|
|
140
|
+
isMultiple?: boolean;
|
|
141
|
+
allowedMinQueryLength?: number;
|
|
140
142
|
children?: FormFieldSchema[];
|
|
141
143
|
defaultOptions?: FieldOptionsSchema[];
|
|
142
144
|
optionsConfig?: OptionMappingConfig;
|
|
@@ -205,6 +207,7 @@ export type FieldOptionsSchema = {
|
|
|
205
207
|
isDisabled?: boolean;
|
|
206
208
|
helpText?: string;
|
|
207
209
|
groupName?: string;
|
|
210
|
+
tooltip?: string;
|
|
208
211
|
icon?: React.ReactNode;
|
|
209
212
|
};
|
|
210
213
|
|
|
@@ -11,14 +11,14 @@ 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";
|
|
21
|
-
|
|
20
|
+
import Tippy from "@tippyjs/react";
|
|
21
|
+
import { normalizeMultiSelectValue } from "./normalizeCustomFieldValues";
|
|
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 < 5 && isTypeahead) {
|
|
131
131
|
queryCallback && queryCallback(query);
|
|
132
132
|
}
|
|
133
133
|
}, [filteredList]);
|
|
@@ -154,12 +154,17 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
154
154
|
|
|
155
155
|
const renderOption = (option: FieldOptionsSchema) => {
|
|
156
156
|
let selected = false;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
const formValue = formContext.getValues(fieldConfig.name);
|
|
158
|
+
if (formField === FormFieldType.MULTI_SELECT) {
|
|
159
|
+
const normalizedValues = normalizeMultiSelectValue(formValue);
|
|
160
|
+
const normalizedOptionValue =
|
|
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);
|
|
161
167
|
} else {
|
|
162
|
-
const formValue = formContext.getValues(fieldConfig.name);
|
|
163
168
|
// const defaultValue = fieldConfig.defaultValue;
|
|
164
169
|
// selected = formValue ? option.value == formValue : defaultValue == option.value;
|
|
165
170
|
selected = option.value == formValue;
|
|
@@ -189,7 +194,21 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
189
194
|
{option.icon && (
|
|
190
195
|
<span className="listbox-svg">{option.icon}</span>
|
|
191
196
|
)}
|
|
192
|
-
{option.
|
|
197
|
+
{option.tooltip ? (
|
|
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
|
+
)}
|
|
193
212
|
</span>
|
|
194
213
|
{isTypeahead &&
|
|
195
214
|
!fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
|
|
@@ -208,11 +227,30 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
208
227
|
)
|
|
209
228
|
)}
|
|
210
229
|
</div>
|
|
211
|
-
{option.helpText &&
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
230
|
+
{option.helpText &&
|
|
231
|
+
(option.tooltip ? (
|
|
232
|
+
<Tippy
|
|
233
|
+
content={
|
|
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
|
+
))}
|
|
216
254
|
</>
|
|
217
255
|
)}
|
|
218
256
|
</ListboxOption>
|
|
@@ -291,7 +329,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
|
|
|
291
329
|
fill="currentColor"
|
|
292
330
|
aria-hidden="true"
|
|
293
331
|
data-slot="icon"
|
|
294
|
-
className="h-5 w-5 text-gray-400"
|
|
332
|
+
className="h-4.5 w-4.5 text-gray-400"
|
|
295
333
|
>
|
|
296
334
|
<path
|
|
297
335
|
fillRule="evenodd"
|
|
@@ -335,7 +373,7 @@ export function renderListBoxValue(
|
|
|
335
373
|
listOptions: FieldOptionsSchema[],
|
|
336
374
|
onChange?: (value: any) => void,
|
|
337
375
|
): JSX.Element {
|
|
338
|
-
let value = formContext.getValues(fieldConfig.name);
|
|
376
|
+
let value = normalizeMultiSelectValue(formContext.getValues(fieldConfig.name));
|
|
339
377
|
const renderAsString = () => {
|
|
340
378
|
// if (!listOptions) {
|
|
341
379
|
// return value;
|
|
@@ -425,9 +463,10 @@ export function renderListBoxValue(
|
|
|
425
463
|
)
|
|
426
464
|
);
|
|
427
465
|
};
|
|
428
|
-
let outputFormat =
|
|
429
|
-
|
|
430
|
-
|
|
466
|
+
let outputFormat =
|
|
467
|
+
fieldConfig.outputFormat != undefined
|
|
468
|
+
? fieldConfig.outputFormat === OutputFormatType.STRING
|
|
469
|
+
: false;
|
|
431
470
|
const getPlaceholder = () => (
|
|
432
471
|
<span className="form-placeholder">
|
|
433
472
|
{fieldConfig.placeholder || "Select any option"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import moment from "moment";
|
|
2
|
+
|
|
3
|
+
const normalizeStringToken = (value: unknown): string => {
|
|
4
|
+
return typeof value === "string" ? value.trim() : String(value).trim();
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const normalizeStringArray = (values: unknown[]): string[] => {
|
|
8
|
+
return values.map(normalizeStringToken).filter(Boolean);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const parseJsonArrayString = (value: string): string[] | null => {
|
|
12
|
+
if (!value.startsWith("[")) return null;
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(value);
|
|
15
|
+
return Array.isArray(parsed) ? normalizeStringArray(parsed) : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const normalizeCheckboxBooleanValue = (value: any): boolean => {
|
|
22
|
+
if (value === true || value === 1) return true;
|
|
23
|
+
if (value === false || value === 0 || value == null || value === "") {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof value === "string") {
|
|
28
|
+
const s = value.trim().toLowerCase();
|
|
29
|
+
if (s === "true" || s === "1" || s === "yes") return true;
|
|
30
|
+
if (s === "false" || s === "0" || s === "no" || s === "") return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Boolean(value);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const normalizeMultiSelectValue = (value: any): string[] => {
|
|
37
|
+
if (value == null) return [];
|
|
38
|
+
|
|
39
|
+
if (Array.isArray(value)) return normalizeStringArray(value);
|
|
40
|
+
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
const s = value.trim();
|
|
43
|
+
if (!s) return [];
|
|
44
|
+
|
|
45
|
+
const jsonArray = parseJsonArrayString(s);
|
|
46
|
+
if (jsonArray) return jsonArray;
|
|
47
|
+
|
|
48
|
+
return normalizeStringArray(s.split(","));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return normalizeStringArray([value]);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const normalizeDateFormat = (format?: string): string => {
|
|
55
|
+
if (!format) return "YYYY-MM-DD";
|
|
56
|
+
return format
|
|
57
|
+
.replace(/yyyy/g, "YYYY")
|
|
58
|
+
.replace(/yy/g, "YY")
|
|
59
|
+
.replace(/dd/g, "DD")
|
|
60
|
+
.replace(/d/g, "D")
|
|
61
|
+
.replace(/mm/g, "MM");
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const normalizeDateInputValue = (
|
|
65
|
+
value: any,
|
|
66
|
+
preferredFormat?: string
|
|
67
|
+
): Date | null => {
|
|
68
|
+
if (value == null || value === "") return null;
|
|
69
|
+
|
|
70
|
+
if (value instanceof Date) {
|
|
71
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
75
|
+
const ms = value < 1_000_000_000_000 ? value * 1000 : value;
|
|
76
|
+
const date = new Date(ms);
|
|
77
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof value === "string") {
|
|
81
|
+
const s = value.trim();
|
|
82
|
+
if (!s) return null;
|
|
83
|
+
|
|
84
|
+
const numeric = Number(s);
|
|
85
|
+
if (Number.isFinite(numeric)) {
|
|
86
|
+
const ms = numeric < 1_000_000_000_000 ? numeric * 1000 : numeric;
|
|
87
|
+
const date = new Date(ms);
|
|
88
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const preferred = normalizeDateFormat(preferredFormat);
|
|
92
|
+
const formats = Array.from(
|
|
93
|
+
new Set([
|
|
94
|
+
preferred,
|
|
95
|
+
"YYYY-MM-DD",
|
|
96
|
+
"DD/MM/YYYY",
|
|
97
|
+
"MM/DD/YYYY",
|
|
98
|
+
"DD-MM-YYYY",
|
|
99
|
+
"MM-DD-YYYY",
|
|
100
|
+
])
|
|
101
|
+
);
|
|
102
|
+
const parsedWithKnownFormats = moment(s, formats, true);
|
|
103
|
+
if (parsedWithKnownFormats.isValid()) {
|
|
104
|
+
return parsedWithKnownFormats.toDate();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const date = new Date(s);
|
|
108
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const date = new Date(value);
|
|
112
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
|