form-craft-package 1.0.0
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/.prettierrc +9 -0
- package/AJV_JSON_Schema_Guide.md +409 -0
- package/README.md +108 -0
- package/index.ts +8 -0
- package/package.json +40 -0
- package/src/ajv/form/form.schema.json +10 -0
- package/src/ajv/form/layout.schema.json +97 -0
- package/src/ajv/form/migration-rules.schema.json +59 -0
- package/src/ajv/master-portal-only/render-conditions/conditions.schema.json +24 -0
- package/src/ajv/master-portal-only/render-conditions/validate.ts +15 -0
- package/src/components/common/button.tsx +72 -0
- package/src/components/common/custom-hooks/use-find-dynamic-form.ts +33 -0
- package/src/components/common/custom-hooks/use-lazy-modal-opener.hook.ts +20 -0
- package/src/components/common/custom-hooks/use-notification.hook.tsx +157 -0
- package/src/components/common/disabled-field-indicator.tsx +20 -0
- package/src/components/common/warning-icon.tsx +10 -0
- package/src/components/form/layout-renderer/1-row/index.tsx +27 -0
- package/src/components/form/layout-renderer/2-col/index.tsx +32 -0
- package/src/components/form/layout-renderer/3-element/1-dynamic-button.tsx +277 -0
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +220 -0
- package/src/components/form/layout-renderer/3-element/index.tsx +73 -0
- package/src/components/index.tsx +2 -0
- package/src/components/modals/form-data-loading.modal.tsx +48 -0
- package/src/constants.ts +15 -0
- package/src/enums.ts +177 -0
- package/src/functions/axios-handler.ts +158 -0
- package/src/functions/data-list-functions.tsx +41 -0
- package/src/functions/form-schema-validator.ts +50 -0
- package/src/functions/get-element-props.ts +20 -0
- package/src/functions/index.ts +56 -0
- package/src/functions/json-handlers.ts +19 -0
- package/src/functions/validations.ts +120 -0
- package/src/types/form-data-list/index.ts +54 -0
- package/src/types/index.ts +124 -0
- package/src/types/layout-elements/element-data-render-logic.ts +56 -0
- package/src/types/layout-elements/field-option-source.ts +14 -0
- package/src/types/layout-elements/index.ts +224 -0
- package/src/types/layout-elements/style.ts +35 -0
- package/src/types/layout-elements/validation.ts +18 -0
- package/tsconfig.json +111 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { lazy, useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import { useNavigate } from 'react-router-dom'
|
|
3
|
+
import { FormInstance } from 'antd'
|
|
4
|
+
import { getButtonRenderProps } from '../../../../functions/get-element-props'
|
|
5
|
+
import { stringifyJSON } from '../../../../functions/json-handlers'
|
|
6
|
+
import { ButtonActionCategoryEnum, FormLoadingModalTypeEnum } from '../../../../enums'
|
|
7
|
+
import { IDataRender_ButtonProps } from '../../../../types'
|
|
8
|
+
import { NEW_FORM_DATA_IDENTIFIER } from '../../../../constants'
|
|
9
|
+
import WarningIcon from '../../../common/warning-icon'
|
|
10
|
+
import { ButtonFP } from '../../../common/button'
|
|
11
|
+
import { useFindDynamiForm } from '../../../common/custom-hooks/use-find-dynamic-form'
|
|
12
|
+
import { constructDynamicFormHref } from '../../../../functions'
|
|
13
|
+
import { useNotification } from '../../../common/custom-hooks/use-notification.hook'
|
|
14
|
+
import client from '../../../../functions/axios-handler'
|
|
15
|
+
import { useLazyModalOpener } from '../../../common/custom-hooks/use-lazy-modal-opener.hook'
|
|
16
|
+
|
|
17
|
+
const FormDataLoadingIndicatorModal = lazy(() => import('../../../modals/form-data-loading.modal'))
|
|
18
|
+
|
|
19
|
+
export const DynamicFormButtonRender = ({
|
|
20
|
+
btnProps,
|
|
21
|
+
formDataId,
|
|
22
|
+
formRef,
|
|
23
|
+
onCustomFunctionCall = () => {},
|
|
24
|
+
}: IDynamicButton) => {
|
|
25
|
+
const navigate = useNavigate()
|
|
26
|
+
const [loading, setLoading] = useState(false)
|
|
27
|
+
const { success, warning, error, confirmModal } = useNotification()
|
|
28
|
+
const formInfo = useFindDynamiForm()
|
|
29
|
+
const { isModalOpen, isPendingTransition, openModal, closeModal } = useLazyModalOpener()
|
|
30
|
+
|
|
31
|
+
const buttonProps = useMemo(() => getButtonRenderProps(btnProps), [btnProps])
|
|
32
|
+
const formId = useMemo(() => formInfo?.id ?? null, [formInfo])
|
|
33
|
+
const baseDynamicUrl = useMemo(() => (formInfo ? constructDynamicFormHref(formInfo.name) : ''), [formInfo])
|
|
34
|
+
|
|
35
|
+
const [dataLoadingType, setDataLoadingType] = useState<FormLoadingModalTypeEnum | undefined>()
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (dataLoadingType !== undefined) openModal()
|
|
39
|
+
}, [dataLoadingType])
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!isModalOpen) setDataLoadingType(undefined)
|
|
43
|
+
}, [isModalOpen])
|
|
44
|
+
|
|
45
|
+
const displayResultMessage = useCallback(
|
|
46
|
+
(isSuccess: boolean = true) => {
|
|
47
|
+
if (!btnProps.messages) return
|
|
48
|
+
|
|
49
|
+
if (isSuccess) {
|
|
50
|
+
if (btnProps.messages?.success) success({ message: btnProps.messages.success })
|
|
51
|
+
} else {
|
|
52
|
+
if (btnProps.messages?.error) error({ message: btnProps.messages.error })
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
[btnProps.messages],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const onDuplicateData = useCallback(async () => {
|
|
59
|
+
let isSuccess = true
|
|
60
|
+
try {
|
|
61
|
+
warning({ message: `Duplicate form data: Form id: ${formId}, Data id: ${formDataId}` })
|
|
62
|
+
// const res = await client.delete(`/formdata/${formId}/${formDataId}`)
|
|
63
|
+
// if (res.status < 300) {
|
|
64
|
+
// navigate(0)
|
|
65
|
+
// }
|
|
66
|
+
} catch (err) {
|
|
67
|
+
isSuccess = false
|
|
68
|
+
console.error(err)
|
|
69
|
+
} finally {
|
|
70
|
+
displayResultMessage(isSuccess)
|
|
71
|
+
}
|
|
72
|
+
}, [formId, formDataId])
|
|
73
|
+
|
|
74
|
+
const onDeleteData = useCallback(async () => {
|
|
75
|
+
let isSuccess = true
|
|
76
|
+
try {
|
|
77
|
+
const res = await client.delete(`/api/formdata/${formId}/${formDataId}`)
|
|
78
|
+
if (res.status < 300) {
|
|
79
|
+
navigate(baseDynamicUrl)
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
isSuccess = false
|
|
83
|
+
console.error(err)
|
|
84
|
+
} finally {
|
|
85
|
+
displayResultMessage(isSuccess)
|
|
86
|
+
}
|
|
87
|
+
}, [baseDynamicUrl])
|
|
88
|
+
|
|
89
|
+
const onPublishData = useCallback(async () => {
|
|
90
|
+
let isSuccess = true
|
|
91
|
+
try {
|
|
92
|
+
const res = await client.put(`/api/formdata/publish/${formId}/${formDataId}`)
|
|
93
|
+
if (res.status < 300) {
|
|
94
|
+
navigate(0)
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
isSuccess = false
|
|
98
|
+
console.error(err)
|
|
99
|
+
} finally {
|
|
100
|
+
displayResultMessage(isSuccess)
|
|
101
|
+
}
|
|
102
|
+
}, [baseDynamicUrl])
|
|
103
|
+
|
|
104
|
+
const onCreateNewData = useCallback(() => {
|
|
105
|
+
formRef!.validateFields().then((values) => {
|
|
106
|
+
setLoading(true)
|
|
107
|
+
const reqData = {
|
|
108
|
+
name: '', // TODO: maybe later, make it dynamic
|
|
109
|
+
data: stringifyJSON(values),
|
|
110
|
+
private: true, // TODO: figure out what this is
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
client
|
|
114
|
+
.post(`/api/formdata/${formId}`, reqData)
|
|
115
|
+
.then((res: any) => {
|
|
116
|
+
if (res.status === 200) {
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
closeModal()
|
|
119
|
+
displayResultMessage()
|
|
120
|
+
navigate(`${baseDynamicUrl}/${res.data}`)
|
|
121
|
+
}, 500)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
.catch(() => displayResultMessage(false))
|
|
125
|
+
.finally(() => setLoading(false))
|
|
126
|
+
})
|
|
127
|
+
}, [formRef, formId, baseDynamicUrl])
|
|
128
|
+
|
|
129
|
+
const onSaveExistingData = useCallback(() => {
|
|
130
|
+
formRef!.validateFields().then((values) => {
|
|
131
|
+
setDataLoadingType(FormLoadingModalTypeEnum.SavingChanges)
|
|
132
|
+
|
|
133
|
+
console.log(values)
|
|
134
|
+
closeModal()
|
|
135
|
+
setLoading(false)
|
|
136
|
+
// setLoading(true)
|
|
137
|
+
// const reqData = {
|
|
138
|
+
// name: 'Dynamic form data', // TODO: maybe later, make it dynamic
|
|
139
|
+
// data: stringifyJSON(values),
|
|
140
|
+
// version: 1, // FIXME: increment
|
|
141
|
+
// private: true, // TODO: figure out what this is
|
|
142
|
+
// }
|
|
143
|
+
|
|
144
|
+
// client
|
|
145
|
+
// .put(`/api/formdata/${formId}/${formDataId}`, reqData)
|
|
146
|
+
// .then((res: any) => {
|
|
147
|
+
// if (res.status === 200) {
|
|
148
|
+
// setTimeout(() => {
|
|
149
|
+
// closeModal()
|
|
150
|
+
// displayResultMessage()
|
|
151
|
+
// }, 500)
|
|
152
|
+
// }
|
|
153
|
+
// })
|
|
154
|
+
// .catch(() => displayResultMessage(false))
|
|
155
|
+
// .finally(() => setLoading(false))
|
|
156
|
+
})
|
|
157
|
+
}, [formRef, formId, formDataId])
|
|
158
|
+
|
|
159
|
+
const onButtonClick = useCallback(() => {
|
|
160
|
+
if (!formInfo) {
|
|
161
|
+
error({ message: 'Form information was not found!' })
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
switch (btnProps.category) {
|
|
166
|
+
case ButtonActionCategoryEnum.CreateNewData:
|
|
167
|
+
navigate(`${baseDynamicUrl}/${NEW_FORM_DATA_IDENTIFIER}`)
|
|
168
|
+
|
|
169
|
+
break
|
|
170
|
+
case ButtonActionCategoryEnum.ViewDataDetails:
|
|
171
|
+
if (formDataId) navigate(`${baseDynamicUrl}/${formDataId}`)
|
|
172
|
+
else error({ message: CUSTOM_ERROR_MESSAGES.DataIdNotFound })
|
|
173
|
+
|
|
174
|
+
break
|
|
175
|
+
case ButtonActionCategoryEnum.DuplicateData:
|
|
176
|
+
if (formDataId) onDuplicateData()
|
|
177
|
+
else error({ message: CUSTOM_ERROR_MESSAGES.DataIdNotFound })
|
|
178
|
+
|
|
179
|
+
break
|
|
180
|
+
case ButtonActionCategoryEnum.DeleteData:
|
|
181
|
+
if (formDataId) onDeleteData()
|
|
182
|
+
else error({ message: CUSTOM_ERROR_MESSAGES.DataIdNotFound })
|
|
183
|
+
|
|
184
|
+
break
|
|
185
|
+
case ButtonActionCategoryEnum.SaveDataChanges:
|
|
186
|
+
if (formRef) {
|
|
187
|
+
if (formDataId === NEW_FORM_DATA_IDENTIFIER) onCreateNewData()
|
|
188
|
+
else onSaveExistingData()
|
|
189
|
+
} else error({ message: CUSTOM_ERROR_MESSAGES.FormInstanceNotFound })
|
|
190
|
+
|
|
191
|
+
break
|
|
192
|
+
case ButtonActionCategoryEnum.PublishDataChanges:
|
|
193
|
+
if (formDataId) onPublishData()
|
|
194
|
+
else error({ message: CUSTOM_ERROR_MESSAGES.DataIdNotFound })
|
|
195
|
+
|
|
196
|
+
break
|
|
197
|
+
case ButtonActionCategoryEnum.ReturnToDataList:
|
|
198
|
+
navigate(baseDynamicUrl)
|
|
199
|
+
|
|
200
|
+
break
|
|
201
|
+
case ButtonActionCategoryEnum.ViewDataVersions:
|
|
202
|
+
// TODO: figure out how to do this
|
|
203
|
+
|
|
204
|
+
break
|
|
205
|
+
case ButtonActionCategoryEnum.CustomFunction:
|
|
206
|
+
onCustomFunctionCall(btnProps.messages ?? defaultMessage)
|
|
207
|
+
|
|
208
|
+
break
|
|
209
|
+
default:
|
|
210
|
+
const errorMessage = `${ButtonActionCategoryEnum.ViewDataDetails} function was not found!`
|
|
211
|
+
console.error(errorMessage)
|
|
212
|
+
warning({ message: errorMessage })
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
}, [baseDynamicUrl, formDataId])
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<>
|
|
219
|
+
<ButtonFP
|
|
220
|
+
{...buttonProps}
|
|
221
|
+
className={getButtonWidth(btnProps?.width)}
|
|
222
|
+
loading={loading || isPendingTransition}
|
|
223
|
+
onClick={() => {
|
|
224
|
+
if (btnProps.confirmation?.enabled)
|
|
225
|
+
confirmModal({
|
|
226
|
+
content: btnProps.confirmation.message,
|
|
227
|
+
okText: btnProps.confirmation.okLabel,
|
|
228
|
+
cancelText: btnProps.confirmation.cancelLabel,
|
|
229
|
+
onOk: onButtonClick,
|
|
230
|
+
})
|
|
231
|
+
else onButtonClick()
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
{btnProps.category === ButtonActionCategoryEnum.SaveDataChanges && !formRef && (
|
|
235
|
+
<WarningIcon tooltip={CUSTOM_ERROR_MESSAGES.FormInstanceNotFound} />
|
|
236
|
+
)}
|
|
237
|
+
{btnProps.category &&
|
|
238
|
+
[
|
|
239
|
+
ButtonActionCategoryEnum.ViewDataDetails,
|
|
240
|
+
ButtonActionCategoryEnum.DuplicateData,
|
|
241
|
+
ButtonActionCategoryEnum.DeleteData,
|
|
242
|
+
ButtonActionCategoryEnum.PublishDataChanges,
|
|
243
|
+
].includes(btnProps.category) &&
|
|
244
|
+
!formDataId && <WarningIcon tooltip={CUSTOM_ERROR_MESSAGES.DataIdNotFound} />}
|
|
245
|
+
{btnProps.label}
|
|
246
|
+
</ButtonFP>
|
|
247
|
+
{isModalOpen && (
|
|
248
|
+
<FormDataLoadingIndicatorModal
|
|
249
|
+
currentType={dataLoadingType}
|
|
250
|
+
closeModal={closeModal}
|
|
251
|
+
errorMessage={btnProps.messages?.error}
|
|
252
|
+
/>
|
|
253
|
+
)}
|
|
254
|
+
</>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
interface IDynamicButton {
|
|
259
|
+
btnProps: IDataRender_ButtonProps
|
|
260
|
+
formDataId?: string
|
|
261
|
+
formRef?: FormInstance
|
|
262
|
+
onCustomFunctionCall?: (messages: IDynamicButtonMessages) => void
|
|
263
|
+
}
|
|
264
|
+
export interface IDynamicButtonMessages {
|
|
265
|
+
success?: string
|
|
266
|
+
error?: string
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const defaultMessage = { success: 'Successfully completed!', error: 'Error occured' }
|
|
270
|
+
|
|
271
|
+
const CUSTOM_ERROR_MESSAGES = {
|
|
272
|
+
DataIdNotFound: 'Data id was not found!',
|
|
273
|
+
FormInstanceNotFound: 'Form ref was not found!',
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const getButtonWidth = (btnPropsWidth?: string) =>
|
|
277
|
+
!btnPropsWidth || btnPropsWidth === '100%' ? 'w-full' : btnPropsWidth === 'auto' ? 'w-min' : `w-[${btnPropsWidth}]`
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Key, ReactNode, Fragment } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Checkbox,
|
|
4
|
+
Col,
|
|
5
|
+
Form,
|
|
6
|
+
Input,
|
|
7
|
+
Select,
|
|
8
|
+
Radio,
|
|
9
|
+
Row,
|
|
10
|
+
InputNumber,
|
|
11
|
+
DatePicker,
|
|
12
|
+
Modal,
|
|
13
|
+
Slider,
|
|
14
|
+
Upload,
|
|
15
|
+
Switch,
|
|
16
|
+
} from 'antd'
|
|
17
|
+
import { FaCaretDown } from 'react-icons/fa'
|
|
18
|
+
import dayjs, { Dayjs } from 'dayjs'
|
|
19
|
+
import { FaUpload } from 'react-icons/fa6'
|
|
20
|
+
import { ElementTypeEnum } from '../../../../enums'
|
|
21
|
+
import { IValidationRule, mapToFormItemRules } from '../../../../functions'
|
|
22
|
+
import { DisabledFieldIndicator } from '../../../common/disabled-field-indicator'
|
|
23
|
+
|
|
24
|
+
interface IFieldsMapperProps {
|
|
25
|
+
fields: IMapperFieldObj[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const LayoutRenderer_FieldElement = ({ fields }: IFieldsMapperProps): JSX.Element => (
|
|
29
|
+
<>
|
|
30
|
+
{fields
|
|
31
|
+
.filter(({ isHidden = false }) => !isHidden)
|
|
32
|
+
.map((field: IMapperFieldObj, fieldIdx: Key) => {
|
|
33
|
+
if (field.name === 'age') console.log(field, mapToFormItemRules(field.validations ?? []))
|
|
34
|
+
return (
|
|
35
|
+
<Fragment key={fieldIdx}>
|
|
36
|
+
<Form.Item
|
|
37
|
+
key={fieldIdx}
|
|
38
|
+
name={field.name}
|
|
39
|
+
label={field.hasNoLabel ? '' : field.label}
|
|
40
|
+
labelAlign="left"
|
|
41
|
+
valuePropName={
|
|
42
|
+
field.type && [ElementTypeEnum.Checkbox, ElementTypeEnum.Switch].includes(field.type)
|
|
43
|
+
? 'checked'
|
|
44
|
+
: 'value'
|
|
45
|
+
}
|
|
46
|
+
rules={mapToFormItemRules(field.validations ?? [])}
|
|
47
|
+
// rules={[
|
|
48
|
+
// { pattern: /^([0-9]{1,2}|100)$/, message: 'Max value exceeded' },
|
|
49
|
+
// { pattern: /^(18|[1-9][0-9]*)$/, message: 'Min value exceeded' },
|
|
50
|
+
// ]}
|
|
51
|
+
>
|
|
52
|
+
{getField(field)}
|
|
53
|
+
</Form.Item>
|
|
54
|
+
{field.disabled && field.type !== ElementTypeEnum.Checkbox && <DisabledFieldIndicator />}
|
|
55
|
+
</Fragment>
|
|
56
|
+
)
|
|
57
|
+
})}
|
|
58
|
+
</>
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const getField = ({
|
|
62
|
+
disabled,
|
|
63
|
+
options = [],
|
|
64
|
+
type = ElementTypeEnum.ShortInput,
|
|
65
|
+
label,
|
|
66
|
+
allowClear = true,
|
|
67
|
+
isMultiValue = false,
|
|
68
|
+
placeholder,
|
|
69
|
+
minRows = 3,
|
|
70
|
+
numberFieldMin,
|
|
71
|
+
numberFieldMax,
|
|
72
|
+
disabledDate,
|
|
73
|
+
}: Partial<IMapperFieldObj>): JSX.Element => {
|
|
74
|
+
placeholder = placeholder && placeholder.length > 0 ? placeholder : typeof label === 'string' ? label : ''
|
|
75
|
+
|
|
76
|
+
switch (type) {
|
|
77
|
+
case ElementTypeEnum.LongInput:
|
|
78
|
+
return <Input.TextArea autoSize={{ minRows }} placeholder={placeholder} disabled={disabled} />
|
|
79
|
+
case ElementTypeEnum.Select:
|
|
80
|
+
return (
|
|
81
|
+
<Select
|
|
82
|
+
placeholder={placeholder}
|
|
83
|
+
suffixIcon={<FaCaretDown />}
|
|
84
|
+
options={options}
|
|
85
|
+
disabled={disabled}
|
|
86
|
+
showSearch
|
|
87
|
+
optionFilterProp="label"
|
|
88
|
+
filterOption={(input, option) =>
|
|
89
|
+
option && typeof option.label === 'string'
|
|
90
|
+
? option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
|
91
|
+
: false
|
|
92
|
+
}
|
|
93
|
+
allowClear={allowClear}
|
|
94
|
+
className="w-full"
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
case ElementTypeEnum.Checkbox:
|
|
98
|
+
return <Checkbox disabled={disabled}>{label}</Checkbox>
|
|
99
|
+
case ElementTypeEnum.Switch:
|
|
100
|
+
return (
|
|
101
|
+
<div className="flex items-center gap-2">
|
|
102
|
+
<Switch disabled={disabled} />
|
|
103
|
+
{label}
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
case ElementTypeEnum.Radio:
|
|
107
|
+
if (isMultiValue)
|
|
108
|
+
return (
|
|
109
|
+
<Checkbox.Group disabled={disabled}>
|
|
110
|
+
<Row gutter={[3, 3]}>
|
|
111
|
+
{options?.map((o, oIdx) => (
|
|
112
|
+
<Col key={oIdx}>
|
|
113
|
+
<Checkbox value={o.value}>{o.label}</Checkbox>
|
|
114
|
+
</Col>
|
|
115
|
+
))}
|
|
116
|
+
</Row>
|
|
117
|
+
</Checkbox.Group>
|
|
118
|
+
)
|
|
119
|
+
return (
|
|
120
|
+
<Radio.Group disabled={disabled} className="w-full">
|
|
121
|
+
<Row gutter={[5, 5]} align="middle">
|
|
122
|
+
{options?.map((o, oIdx) => (
|
|
123
|
+
<Col key={oIdx}>
|
|
124
|
+
<Radio value={o.value}>{o.label}</Radio>
|
|
125
|
+
</Col>
|
|
126
|
+
))}
|
|
127
|
+
</Row>
|
|
128
|
+
</Radio.Group>
|
|
129
|
+
)
|
|
130
|
+
case ElementTypeEnum.NumberInput:
|
|
131
|
+
return (
|
|
132
|
+
<InputNumber
|
|
133
|
+
placeholder={placeholder}
|
|
134
|
+
disabled={disabled}
|
|
135
|
+
className="w-full"
|
|
136
|
+
min={numberFieldMin}
|
|
137
|
+
max={numberFieldMax}
|
|
138
|
+
formatter={(value) => (value ? value.toString().replace(/\D/g, '') : '')}
|
|
139
|
+
// parser={(value) => (value ? parseFloat(value.replace(/\D/g, '')) : undefined)}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
case ElementTypeEnum.Password:
|
|
143
|
+
return <Input.Password placeholder={placeholder} disabled={disabled} />
|
|
144
|
+
case ElementTypeEnum.DatePicker:
|
|
145
|
+
return (
|
|
146
|
+
<DatePicker
|
|
147
|
+
format={datepickerFormats}
|
|
148
|
+
placeholder="MM/DD/YYYY"
|
|
149
|
+
disabled={disabled}
|
|
150
|
+
disabledDate={(now) => (disabledDate ? disabledDate(now) : dayjs(now) < dayjs('01/01/1900'))}
|
|
151
|
+
allowClear={allowClear}
|
|
152
|
+
className="w-full"
|
|
153
|
+
onChange={(e) => {
|
|
154
|
+
if (dayjs(e) < dayjs('01/01/1900'))
|
|
155
|
+
Modal.error({ title: 'Invalid Date!', content: 'The year cannot be less than 1900!', centered: true })
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
)
|
|
159
|
+
case ElementTypeEnum.TimePicker:
|
|
160
|
+
return (
|
|
161
|
+
<DatePicker.TimePicker
|
|
162
|
+
format={datepickerFormats}
|
|
163
|
+
disabled={disabled}
|
|
164
|
+
className="w-full"
|
|
165
|
+
allowClear={allowClear}
|
|
166
|
+
/>
|
|
167
|
+
)
|
|
168
|
+
case ElementTypeEnum.Slider:
|
|
169
|
+
return <Slider disabled={disabled} />
|
|
170
|
+
case ElementTypeEnum.FileUpload:
|
|
171
|
+
return (
|
|
172
|
+
<Upload>
|
|
173
|
+
<div className="cursor-pointer border border-dashed border-neutral border-opacity-50 text-neutral rounded-md flex items-center gap-2 h-[32px] px-2">
|
|
174
|
+
<FaUpload />
|
|
175
|
+
{placeholder}
|
|
176
|
+
</div>
|
|
177
|
+
</Upload>
|
|
178
|
+
)
|
|
179
|
+
case ElementTypeEnum.ShortInput:
|
|
180
|
+
default:
|
|
181
|
+
return <Input placeholder={placeholder} disabled={disabled} />
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface IMapperFieldObj {
|
|
186
|
+
type?: ElementTypeEnum
|
|
187
|
+
validations?: IValidationRule[]
|
|
188
|
+
isHidden?: boolean
|
|
189
|
+
name: string | (string | number)[]
|
|
190
|
+
label?: string | ReactNode
|
|
191
|
+
description?: string
|
|
192
|
+
placeholder?: string
|
|
193
|
+
disabled?: boolean
|
|
194
|
+
options?: ISelectOption[]
|
|
195
|
+
isMultiValue?: boolean
|
|
196
|
+
minRows?: number
|
|
197
|
+
hasNoLabel?: boolean
|
|
198
|
+
allowClear?: boolean
|
|
199
|
+
numberFieldMin?: number
|
|
200
|
+
numberFieldMax?: number
|
|
201
|
+
disabledDate?: (date: Dayjs) => boolean
|
|
202
|
+
}
|
|
203
|
+
interface ISelectOption {
|
|
204
|
+
value: number | boolean | string
|
|
205
|
+
label: string | ReactNode
|
|
206
|
+
}
|
|
207
|
+
const datepickerFormats = [
|
|
208
|
+
'MM/DD/YYYY',
|
|
209
|
+
'MM/DD/YY',
|
|
210
|
+
'YYYY/MM/DD',
|
|
211
|
+
'MMDDYYYY',
|
|
212
|
+
'MMDDYY',
|
|
213
|
+
'YYYYMMDD',
|
|
214
|
+
'MM DD YYYY',
|
|
215
|
+
'MM DD YY',
|
|
216
|
+
'YYYY MM DD',
|
|
217
|
+
'MM-DD/YYYY',
|
|
218
|
+
'MM-DD-YY',
|
|
219
|
+
'YYYY-MM-DD',
|
|
220
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Divider } from 'antd'
|
|
2
|
+
import { ReactElement, ReactNode, useMemo } from 'react'
|
|
3
|
+
import { IDataRender_ButtonProps, IFieldElementSourceManual, IFormLayoutElement } from '../../../../types'
|
|
4
|
+
import { getElementGeneralizedProps } from '../../../../functions/get-element-props'
|
|
5
|
+
import { ElementTypeEnum, FieldValidationEnum } from '../../../../enums'
|
|
6
|
+
import { LayoutRenderer_FieldElement } from './2-field-element'
|
|
7
|
+
import { findMaxAndMinValues } from '../../../../functions'
|
|
8
|
+
|
|
9
|
+
export default function LayoutRendererElement({
|
|
10
|
+
elementData,
|
|
11
|
+
titleComponent,
|
|
12
|
+
renderButton,
|
|
13
|
+
}: {
|
|
14
|
+
elementData: IFormLayoutElement
|
|
15
|
+
titleComponent?: ReactNode
|
|
16
|
+
renderButton?: (props: IDataRender_ButtonProps) => ReactElement
|
|
17
|
+
}) {
|
|
18
|
+
const props = getElementGeneralizedProps(elementData.props)
|
|
19
|
+
const { elementType, key, validations } = elementData
|
|
20
|
+
const { options = [] } = (props.optionSource as IFieldElementSourceManual) ?? {}
|
|
21
|
+
const { maxValue, minValue } = useMemo(() => findMaxAndMinValues(validations), [validations])
|
|
22
|
+
|
|
23
|
+
switch (elementType) {
|
|
24
|
+
case ElementTypeEnum.RichTextEditor:
|
|
25
|
+
// return <FC_FormElement_RichTextEditor placeholder={placeholder} />
|
|
26
|
+
return 'rich editor'
|
|
27
|
+
// case ElementTypeEnum.FileUpload:
|
|
28
|
+
// return (
|
|
29
|
+
// <Upload fileList={[]}>
|
|
30
|
+
// <div className="cursor-pointer border border-dashed border-neutral border-opacity-50 text-neutral rounded-md flex items-center gap-2 h-[32px] px-2">
|
|
31
|
+
// <FaUpload />
|
|
32
|
+
// {placeholder}
|
|
33
|
+
// </div>
|
|
34
|
+
// </Upload>
|
|
35
|
+
// )
|
|
36
|
+
case ElementTypeEnum.ReadOnly:
|
|
37
|
+
// return (
|
|
38
|
+
// <FC_FormElement_ReadOnlyInfo
|
|
39
|
+
// label={props.label}
|
|
40
|
+
// isHardCoded={props.isHardCoded}
|
|
41
|
+
// value={props.value}
|
|
42
|
+
// renderConditions={props.renderConditions}
|
|
43
|
+
// />
|
|
44
|
+
// )
|
|
45
|
+
return 'read only'
|
|
46
|
+
case ElementTypeEnum.Divider:
|
|
47
|
+
return (
|
|
48
|
+
<Divider className={props.className} orientation={props.labelPlacement}>
|
|
49
|
+
{props.label && <span className="text-12">{props.label}</span>}
|
|
50
|
+
</Divider>
|
|
51
|
+
)
|
|
52
|
+
case ElementTypeEnum.Button:
|
|
53
|
+
return renderButton ? renderButton(props) : <></>
|
|
54
|
+
case ElementTypeEnum.TitlePlacement:
|
|
55
|
+
return titleComponent ?? <span className="text-warning">Provide title component</span>
|
|
56
|
+
default:
|
|
57
|
+
return (
|
|
58
|
+
<LayoutRenderer_FieldElement
|
|
59
|
+
fields={[
|
|
60
|
+
{
|
|
61
|
+
...props,
|
|
62
|
+
numberFieldMax: maxValue,
|
|
63
|
+
numberFieldMin: minValue,
|
|
64
|
+
type: elementType,
|
|
65
|
+
name: key ?? '',
|
|
66
|
+
validations,
|
|
67
|
+
options: options.map((op) => ({ value: op.value, label: op.value })),
|
|
68
|
+
},
|
|
69
|
+
]}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Modal, Spin } from 'antd'
|
|
2
|
+
import { ButtonFP } from '../common/button'
|
|
3
|
+
import { FormLoadingModalTypeEnum } from '../../enums'
|
|
4
|
+
|
|
5
|
+
export default function FormDataLoadingIndicatorModal({
|
|
6
|
+
closeModal,
|
|
7
|
+
errorMessage,
|
|
8
|
+
currentType,
|
|
9
|
+
}: {
|
|
10
|
+
closeModal: () => void
|
|
11
|
+
errorMessage?: string
|
|
12
|
+
currentType?: FormLoadingModalTypeEnum
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<Modal
|
|
16
|
+
open
|
|
17
|
+
closable={false}
|
|
18
|
+
maskClosable={false}
|
|
19
|
+
centered
|
|
20
|
+
width={400}
|
|
21
|
+
footer={
|
|
22
|
+
currentType && [FormLoadingModalTypeEnum.ErrorOccured].includes(currentType)
|
|
23
|
+
? [
|
|
24
|
+
<ButtonFP primary onClick={closeModal} className="w-full" key="modal_footer">
|
|
25
|
+
Close Modal
|
|
26
|
+
</ButtonFP>,
|
|
27
|
+
]
|
|
28
|
+
: null
|
|
29
|
+
}
|
|
30
|
+
>
|
|
31
|
+
<div className="text-24 font-semibold text-center flex flex-col items-center gap-4">
|
|
32
|
+
<Spin spinning size="large" />
|
|
33
|
+
{(() => {
|
|
34
|
+
switch (currentType) {
|
|
35
|
+
case FormLoadingModalTypeEnum.SavingChanges:
|
|
36
|
+
return <span className="text-primary">Saving data...</span>
|
|
37
|
+
case FormLoadingModalTypeEnum.ErrorOccured:
|
|
38
|
+
return (
|
|
39
|
+
<span className="text-danger">{errorMessage ?? 'An error occured while performing the action!'}</span>
|
|
40
|
+
)
|
|
41
|
+
default:
|
|
42
|
+
return <></>
|
|
43
|
+
}
|
|
44
|
+
})()}
|
|
45
|
+
</div>
|
|
46
|
+
</Modal>
|
|
47
|
+
)
|
|
48
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CSSLayoutType, FlexAlignItems, FlexDirection } from './enums'
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_FLEX_CONFIG = {
|
|
4
|
+
Container: {
|
|
5
|
+
display: CSSLayoutType.Flex,
|
|
6
|
+
flexDirection: FlexDirection.Row,
|
|
7
|
+
alignItems: FlexAlignItems.Start,
|
|
8
|
+
gap: '10px',
|
|
9
|
+
},
|
|
10
|
+
Item: { flexBasis: '0', flexGrow: 1 },
|
|
11
|
+
}
|
|
12
|
+
export const NEW_FORM_DATA_IDENTIFIER = 'new'
|
|
13
|
+
export enum LOCAL_STORAGE_KEYS_ENUM {
|
|
14
|
+
DynamicForms = '38be231fe41c169037ed04c267ebe070',
|
|
15
|
+
}
|