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.
Files changed (40) hide show
  1. package/.prettierrc +9 -0
  2. package/AJV_JSON_Schema_Guide.md +409 -0
  3. package/README.md +108 -0
  4. package/index.ts +8 -0
  5. package/package.json +40 -0
  6. package/src/ajv/form/form.schema.json +10 -0
  7. package/src/ajv/form/layout.schema.json +97 -0
  8. package/src/ajv/form/migration-rules.schema.json +59 -0
  9. package/src/ajv/master-portal-only/render-conditions/conditions.schema.json +24 -0
  10. package/src/ajv/master-portal-only/render-conditions/validate.ts +15 -0
  11. package/src/components/common/button.tsx +72 -0
  12. package/src/components/common/custom-hooks/use-find-dynamic-form.ts +33 -0
  13. package/src/components/common/custom-hooks/use-lazy-modal-opener.hook.ts +20 -0
  14. package/src/components/common/custom-hooks/use-notification.hook.tsx +157 -0
  15. package/src/components/common/disabled-field-indicator.tsx +20 -0
  16. package/src/components/common/warning-icon.tsx +10 -0
  17. package/src/components/form/layout-renderer/1-row/index.tsx +27 -0
  18. package/src/components/form/layout-renderer/2-col/index.tsx +32 -0
  19. package/src/components/form/layout-renderer/3-element/1-dynamic-button.tsx +277 -0
  20. package/src/components/form/layout-renderer/3-element/2-field-element.tsx +220 -0
  21. package/src/components/form/layout-renderer/3-element/index.tsx +73 -0
  22. package/src/components/index.tsx +2 -0
  23. package/src/components/modals/form-data-loading.modal.tsx +48 -0
  24. package/src/constants.ts +15 -0
  25. package/src/enums.ts +177 -0
  26. package/src/functions/axios-handler.ts +158 -0
  27. package/src/functions/data-list-functions.tsx +41 -0
  28. package/src/functions/form-schema-validator.ts +50 -0
  29. package/src/functions/get-element-props.ts +20 -0
  30. package/src/functions/index.ts +56 -0
  31. package/src/functions/json-handlers.ts +19 -0
  32. package/src/functions/validations.ts +120 -0
  33. package/src/types/form-data-list/index.ts +54 -0
  34. package/src/types/index.ts +124 -0
  35. package/src/types/layout-elements/element-data-render-logic.ts +56 -0
  36. package/src/types/layout-elements/field-option-source.ts +14 -0
  37. package/src/types/layout-elements/index.ts +224 -0
  38. package/src/types/layout-elements/style.ts +35 -0
  39. package/src/types/layout-elements/validation.ts +18 -0
  40. 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,2 @@
1
+ export * from './form/layout-renderer/1-row'
2
+ export * from './form/layout-renderer/3-element/1-dynamic-button'
@@ -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
+ }
@@ -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
+ }