form-craft-package 1.10.11 → 1.10.12-dev.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-craft-package",
3
- "version": "1.10.11",
3
+ "version": "1.10.12-dev.0",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -8,6 +8,7 @@ export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues
8
8
  const isPublic = Form.useWatch(FormPreservedItemKeys.IsPublic, { form: formRef, preserve: true })
9
9
  const formNotifications = Form.useWatch(FormPreservedItemKeys.FormNotifications, { form: formRef, preserve: true })
10
10
  const isUserForm = Form.useWatch(FormPreservedItemKeys.IsUserForm, { form: formRef, preserve: true })
11
+ const userFormNotificationId = Form.useWatch(FormPreservedItemKeys.UserFormNotificationId, { form: formRef, preserve: true })
11
12
  const formTemplateReports = Form.useWatch(FormPreservedItemKeys.FormTemplateReports, {
12
13
  form: formRef,
13
14
  preserve: true,
@@ -37,6 +38,7 @@ export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues
37
38
  duplicateDataMatches && Object.values(duplicateDataMatches).some((result) => result),
38
39
  [FormPreservedItemKeys.DuplicateCheckPending]: isDuplicateCheckPending,
39
40
  [FormPreservedItemKeys.IsUserForm]: isUserForm,
41
+ [FormPreservedItemKeys.UserFormNotificationId]: userFormNotificationId,
40
42
  }
41
43
  }
42
44
 
@@ -1,15 +1,15 @@
1
1
  import { FaPencilAlt, FaSlash } from 'react-icons/fa'
2
2
 
3
3
  export const DisabledFieldIndicator = ({
4
- top = 30,
5
- right = 15,
4
+ bottom = 8,
5
+ right = 8,
6
6
  left,
7
7
  }: {
8
- top?: number
8
+ bottom?: number
9
9
  right?: number
10
10
  left?: number
11
11
  }) => (
12
- <div className="absolute cursor-nodrop" style={{ top, right, left, color: '#a1a1a1' }}>
12
+ <div className="absolute cursor-nodrop" style={{ bottom, right, left, color: '#a1a1a1' }}>
13
13
  <div className="absolute">
14
14
  <FaPencilAlt />
15
15
  </div>
@@ -1,4 +1,4 @@
1
- import { Form } from 'antd'
1
+ import { Form, Spin } from 'antd'
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
3
3
  import { IDndLayoutStructure_Responsive, IFormDataDetailsFetchConfig } from '../../../types'
4
4
  import { LayoutRendererRow } from '../layout-renderer/1-row'
@@ -62,12 +62,13 @@ function FormDataDetailsComponentChild({
62
62
  const location = useLocation()
63
63
  const [formDataRef] = Form.useForm()
64
64
  const [isNotFound, setIsNotFound] = useState(false)
65
+ const [isDataLoading, setIsDataLoading] = useState(false)
65
66
  const originalTzFieldsRef = useRef<string[]>([])
66
67
  useManyToManyConnector(formDataRef)
67
68
 
68
69
  const currentBreakpoint = useGetCurrentBreakpoint()
69
70
 
70
- const { cachedConfig, isConfigLoading } = useCacheFormLayoutConfig(formId, formKey)
71
+ const { cachedConfig } = useCacheFormLayoutConfig(formId, formKey)
71
72
 
72
73
  useSetHiddenNodes(cachedConfig?.detailsConfig, formDataRef, formDataId, {
73
74
  submissionTemplateReportConfig: cachedConfig?.detailsConfig?.submissionTemplateReportConfig,
@@ -137,14 +138,18 @@ function FormDataDetailsComponentChild({
137
138
  }
138
139
  }
139
140
 
140
- if (isNewFormDataPage(formDataIdTemp) || !isValidMongoDbId(formDataIdTemp))
141
+ if (isNewFormDataPage(formDataIdTemp) || !isValidMongoDbId(formDataIdTemp)) {
141
142
  formDataRef.setFieldValue([FormPreservedItemKeys.IsDetailsDataSet], true)
142
- else {
143
+ setIsDataLoading(false)
144
+ } else {
143
145
  if (!formIdTemp) {
144
146
  console.error('Form ID is required to fetch form data')
147
+ setIsDataLoading(false)
145
148
  return
146
149
  }
147
150
 
151
+ setIsDataLoading(true)
152
+
148
153
  client
149
154
  // .get(`/api/formdata/${formIdTemp}/${formDataIdTemp}`)
150
155
  .post(`/api/report/${formIdTemp}/${formDataIdTemp}`, { joins: extendedJoins })
@@ -179,6 +184,7 @@ function FormDataDetailsComponentChild({
179
184
  } else setIsNotFound(true)
180
185
  })
181
186
  .catch(() => setIsNotFound(true))
187
+ .finally(() => setIsDataLoading(false))
182
188
  }
183
189
  },
184
190
  [formDataId, formDataRef, breadcrumbs],
@@ -212,7 +218,11 @@ function FormDataDetailsComponentChild({
212
218
  useEffect(() => {
213
219
  if (!cachedConfig) return
214
220
  // const BaskhuuAjiltnuudFormId = 22
215
- // formDataRef.setFieldValue(FormPreservedItemKeys.IsUserForm, cachedConfig.id === BaskhuuAjiltnuudFormId)
221
+ // formDataRef.setFieldsValue({
222
+ // [FormPreservedItemKeys.IsUserForm]: cachedConfig.id === BaskhuuAjiltnuudFormId,
223
+ // [FormPreservedItemKeys.UserFormNotificationId]:
224
+ // cachedConfig.detailsConfig.userFormNotificationConfig?.notificationId,
225
+ // })
216
226
 
217
227
  formDataRef.setFieldValue(FormPreservedItemKeys.IsUserForm, false)
218
228
  }, [cachedConfig])
@@ -244,9 +254,34 @@ function FormDataDetailsComponentChild({
244
254
  return deviceLayout
245
255
  }, [cachedConfig?.detailsConfig, currentBreakpoint])
246
256
 
247
- if (isConfigLoading || !cachedConfig) return <FormDataListSkeleton_Details />
257
+ const shouldShowLayoutSkeleton = !cachedConfig?.detailsConfig || !layout.length
258
+ const skeletonTransitionRef = useRef<number>()
259
+ const [renderSkeleton, setRenderSkeleton] = useState(true)
260
+ const [firstLayoutRendered, setFirstLayoutRendered] = useState(false)
261
+
262
+ useEffect(() => {
263
+ if (shouldShowLayoutSkeleton) {
264
+ if (skeletonTransitionRef.current) cancelAnimationFrame(skeletonTransitionRef.current)
265
+ setRenderSkeleton(true)
266
+ return
267
+ }
268
+ skeletonTransitionRef.current = requestAnimationFrame(() => setRenderSkeleton(false))
269
+ return () => {
270
+ if (skeletonTransitionRef.current) cancelAnimationFrame(skeletonTransitionRef.current)
271
+ }
272
+ }, [shouldShowLayoutSkeleton])
273
+
274
+ useEffect(() => {
275
+ if (!renderSkeleton || firstLayoutRendered || shouldShowLayoutSkeleton) return
276
+ const timer = setTimeout(() => {
277
+ setRenderSkeleton(false)
278
+ setFirstLayoutRendered(true)
279
+ }, 250)
280
+ return () => clearTimeout(timer)
281
+ }, [renderSkeleton, firstLayoutRendered, shouldShowLayoutSkeleton])
248
282
 
249
283
  if (isNotFound) return <NotFound />
284
+ if (renderSkeleton || shouldShowLayoutSkeleton) return <FormDataListSkeleton_Details />
250
285
 
251
286
  return (
252
287
  <>
@@ -258,48 +293,50 @@ function FormDataDetailsComponentChild({
258
293
  duplicateCheckConfig={cachedConfig.detailsConfig.duplicateCheckConfig}
259
294
  onCustomFunctionCall={onCustomFunctionCall}
260
295
  />
261
- <Form
262
- layout="vertical"
263
- name="dynamic_form_data_form"
264
- form={formDataRef}
265
- className={ELEMENTS_DEFAULT_CLASS.DataDetailsForm}
266
- onValuesChange={(_changed, allValues) => {
267
- handleDisabledSet(allValues, currentBreakpoint, cachedConfig?.detailsConfig, formDataId)
268
- handleHiddenSet(allValues, currentBreakpoint, cachedConfig?.detailsConfig, formDataId)
269
- }}
270
- >
271
- {layout.map((row, rowIdx) => (
272
- <LayoutRendererRow
273
- key={rowIdx}
274
- rowData={row}
275
- isTopLevel
276
- formContext={{
277
- detailPageFormId: formId,
278
- formId,
279
- formKey,
280
- formDataId,
281
- formRef: formDataRef,
282
- companyKey,
283
- }}
284
- customComponents={customComponents}
285
- elements={cachedConfig.detailsConfig.elements}
286
- renderButton={(btnProps) => (
287
- <DynamicFormButtonRender
288
- displayStateProps={btnProps as IDynamicButton_DisplayStateProps}
289
- formContext={{
290
- detailPageFormId: formId,
291
- formId,
292
- formKey,
293
- formDataId,
294
- formRef: formDataRef,
295
- companyKey,
296
- }}
297
- onCustomFunctionCall={onCustomFunctionCall}
298
- />
299
- )}
300
- />
301
- ))}
302
- </Form>
296
+ <Spin spinning={isDataLoading}>
297
+ <Form
298
+ layout="vertical"
299
+ name="dynamic_form_data_form"
300
+ form={formDataRef}
301
+ className={ELEMENTS_DEFAULT_CLASS.DataDetailsForm}
302
+ onValuesChange={(_changed, allValues) => {
303
+ handleDisabledSet(allValues, currentBreakpoint, cachedConfig?.detailsConfig, formDataId)
304
+ handleHiddenSet(allValues, currentBreakpoint, cachedConfig?.detailsConfig, formDataId)
305
+ }}
306
+ >
307
+ {layout.map((row, rowIdx) => (
308
+ <LayoutRendererRow
309
+ key={rowIdx}
310
+ rowData={row}
311
+ isTopLevel
312
+ formContext={{
313
+ detailPageFormId: formId,
314
+ formId,
315
+ formKey,
316
+ formDataId,
317
+ formRef: formDataRef,
318
+ companyKey,
319
+ }}
320
+ customComponents={customComponents}
321
+ elements={cachedConfig.detailsConfig.elements}
322
+ renderButton={(btnProps) => (
323
+ <DynamicFormButtonRender
324
+ displayStateProps={btnProps as IDynamicButton_DisplayStateProps}
325
+ formContext={{
326
+ detailPageFormId: formId,
327
+ formId,
328
+ formKey,
329
+ formDataId,
330
+ formRef: formDataRef,
331
+ companyKey,
332
+ }}
333
+ onCustomFunctionCall={onCustomFunctionCall}
334
+ />
335
+ )}
336
+ />
337
+ ))}
338
+ </Form>
339
+ </Spin>
303
340
  </>
304
341
  )
305
342
  }
@@ -1,15 +1,14 @@
1
1
  import { FormInstance } from 'antd'
2
2
  import { getGridContainerStyle, getColumnStyle } from '../../../../functions/forms'
3
3
  import LayoutRendererCol from '../2-col'
4
- import { memo, ReactElement, useLayoutEffect, useMemo, useState } from 'react'
4
+ import { memo, ReactElement, useMemo } from 'react'
5
5
  import { LayoutRowConditionalHeaderRenderer } from './header-render'
6
6
  import { LayoutRowRepeatableRenderer } from './repeatable-render'
7
7
  import { IDndLayoutElement, IDndLayoutRow, IFormJoin } from '../../../../types'
8
8
  import { IDynamicButton_DisplayStateProps } from '../3-element/1-dynamic-button'
9
9
  import { ELEMENTS_DEFAULT_CLASS } from '../../../../constants'
10
10
  import { useHiddenIds } from '../../../common/custom-hooks/use-node-condition.hook/use-node-condition.hook'
11
- import FormDataListSkeleton_Details from '../../../common/loading-skeletons/details'
12
- import { ICustomComponents } from '../3-element'
11
+ import { ICustomComponents } from '../3-element'
13
12
 
14
13
  export const LayoutRendererRow = memo(
15
14
  ({
@@ -22,12 +21,7 @@ export const LayoutRendererRow = memo(
22
21
  customComponents,
23
22
  renderButton,
24
23
  }: ILayoutRendererRow) => {
25
- const hiddenIds = useHiddenIds()
26
- const [hydrated, setHydrated] = useState(false)
27
-
28
- useLayoutEffect(() => {
29
- setTimeout(() => setHydrated(true), 500)
30
- }, [])
24
+ const hiddenIds = useHiddenIds()
31
25
 
32
26
  const gridStyle = useMemo(() => {
33
27
  const baseStyle = { ...(rowData.style || {}), ...getGridContainerStyle(rowData.display) }
@@ -50,10 +44,8 @@ export const LayoutRendererRow = memo(
50
44
  return { ...baseStyle, gridTemplateColumns: template }
51
45
  }, [rowData, hiddenIds])
52
46
 
53
- if (!hydrated && isTopLevel) return <FormDataListSkeleton_Details />
54
-
55
- return (
56
- <div
47
+ return (
48
+ <div
57
49
  style={{ display: hiddenIds.includes(rowData.id) ? 'none' : undefined }}
58
50
  id={rowData.id}
59
51
  className={ELEMENTS_DEFAULT_CLASS.LayoutRowContainer}
@@ -2,17 +2,22 @@ import { useCallback } from 'react'
2
2
  import { useFormPreservedItemValues } from '../../../../common/custom-hooks/use-preserved-form-items.hook'
3
3
  import { toMongoDbExtendedJSON } from '../../../../../functions/forms/extended-json-handlers'
4
4
  import client from '../../../../../api/client'
5
- import { isNewFormDataPage, replaceTemplateReportAndUpload } from '../../../../../functions/forms'
6
5
  import { useNotification, useTranslation } from '../../../../common/custom-hooks'
7
6
  import { useNavigate } from 'react-router-dom'
8
7
  import { IFormContext } from '../../1-row'
9
8
  import { IOnSuccessFunctions } from '.'
10
- import { IFormTemplateReport, ISystemRole, SystemRolePermissionEnum } from '../../../../../types'
9
+ import { IFormNotification, IFormTemplateReport, ISystemRole, SystemRolePermissionEnum } from '../../../../../types'
11
10
  import { useBreadcrumb } from '../../../../common/custom-hooks/use-breadcrumb.hook'
12
11
  import { FORM_SUBMISSION_PDF_KEY } from '../../../../../constants'
12
+ import {
13
+ buildNotificationRequests,
14
+ isNewFormDataPage,
15
+ replaceTemplateReportAndUpload,
16
+ } from '../../../../../functions/forms'
13
17
  import {
14
18
  FormLoadingModalTypeEnum,
15
19
  LOCAL_STORAGE_KEYS_ENUM,
20
+ NotificationTypeEnum,
16
21
  TranslationTextTypeEnum,
17
22
  UserFormPlaceholders,
18
23
  } from '../../../../../enums'
@@ -38,8 +43,15 @@ export const useCreateDataWithPdfActions = ({
38
43
  const { t } = useTranslation(formId)
39
44
  const navigate = useNavigate()
40
45
  const { errorModal, warning } = useNotification()
41
- const { baseServerUrl, submissionTemplateReportConfig, formTemplateReports, isPublic, isUserForm } =
42
- useFormPreservedItemValues(formRef)
46
+ const {
47
+ baseServerUrl,
48
+ submissionTemplateReportConfig,
49
+ formTemplateReports,
50
+ isPublic,
51
+ isUserForm,
52
+ userFormNotificationId,
53
+ formNotifications,
54
+ } = useFormPreservedItemValues(formRef)
43
55
  const { breadcrumbs } = useBreadcrumb()
44
56
 
45
57
  const onCreateNewData = useCallback(
@@ -60,13 +72,61 @@ export const useCreateDataWithPdfActions = ({
60
72
  selectedRoles.includes(r.name) ? [...accR, ...r.permissions] : accR,
61
73
  [],
62
74
  )
75
+ const showUserNotificationError = (content: string) => {
76
+ setDataLoadingType(FormLoadingModalTypeEnum.ErrorOccured)
77
+ errorModal({
78
+ title: 'User notification error',
79
+ content,
80
+ })
81
+ onCreateError()
82
+ }
83
+
84
+ if (!formId) {
85
+ showUserNotificationError('Form ID is missing for user notification.')
86
+ return
87
+ }
88
+
89
+ const formNotificationsForCurrentForm = formNotifications?.[formId]
90
+ const userFormNotification = formNotificationsForCurrentForm?.notifications?.find(
91
+ (n: IFormNotification) => n.id === userFormNotificationId,
92
+ )
93
+
94
+ if (!userFormNotificationId || !formNotificationsForCurrentForm || !userFormNotification) {
95
+ showUserNotificationError('User notification is required. Please configure it before submitting.')
96
+ return
97
+ }
98
+
99
+ const notifConfigRes = await client.get(`/api/notificationconfig/${userFormNotificationId}/data`)
100
+ if (notifConfigRes.status !== 200 || typeof notifConfigRes.data.data !== 'string') {
101
+ showUserNotificationError('Failed to fetch user notification configuration.')
102
+ return
103
+ }
104
+
105
+ const domain = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.Domain)
106
+ const fileBaseUrl = baseServerUrl && domain ? `${baseServerUrl}/api/attachment/${domain}` : undefined
107
+
108
+ const notificationRequests = await buildNotificationRequests({
109
+ notificationId: userFormNotificationId,
110
+ notificationConfig: notifConfigRes.data.data,
111
+ formNotification: userFormNotification,
112
+ replacementData: { Data: submissionValues },
113
+ formId,
114
+ fileBaseUrl,
115
+ })
116
+
117
+ const emailNotification = notificationRequests.find((req) => req.type === NotificationTypeEnum.Email)?.data
118
+ if (!emailNotification) {
119
+ showUserNotificationError('Unable to build the user notification payload.')
120
+ return
121
+ }
122
+
63
123
  const userPayload = {
64
124
  email: values?.[UserFormPlaceholders.EMAIL_FIELD_KEY],
65
125
  phone: values?.[UserFormPlaceholders.PHONE_NUMBER_FIELD_KEY],
66
126
  enable2FA: values?.[UserFormPlaceholders.ENABLE_2FA] || false,
67
127
  password: values?.[UserFormPlaceholders.PASSWORD_FIELD_KEY],
68
128
  roles: [...new Set(selectedPermissions)],
69
- notification: null,
129
+ notification: emailNotification,
70
130
  }
71
131
 
72
132
  try {
@@ -99,10 +159,30 @@ export const useCreateDataWithPdfActions = ({
99
159
 
100
160
  setDataLoadingType(FormLoadingModalTypeEnum.SavingChanges)
101
161
 
162
+ const templatesByLanguage = submissionTemplateReportConfig?.templatesByLanguage
163
+ const selectedLanguage = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.SelectedLanguage)
164
+ const defaultLanguage = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.DefaultLanguage)
165
+ const getTemplateForLanguage = (language?: string | null) =>
166
+ language && templatesByLanguage
167
+ ? templatesByLanguage[language as keyof typeof templatesByLanguage]
168
+ : undefined
169
+ const fallbackLanguageTemplate =
170
+ templatesByLanguage &&
171
+ Object.entries(templatesByLanguage)
172
+ .filter(([lang, blob]) => !!blob && lang !== selectedLanguage && lang !== defaultLanguage)
173
+ .map(([, blob]) => blob)
174
+ .shift()
175
+
176
+ const submissionTemplateBlobName =
177
+ getTemplateForLanguage(selectedLanguage) ||
178
+ getTemplateForLanguage(defaultLanguage) ||
179
+ submissionTemplateReportConfig?.template ||
180
+ fallbackLanguageTemplate
181
+
102
182
  const shouldGenerateSubmissionPdf =
103
183
  isNewFormDataPage(formDataId) &&
104
184
  submissionTemplateReportConfig?.enabled &&
105
- submissionTemplateReportConfig.template &&
185
+ submissionTemplateBlobName &&
106
186
  formId &&
107
187
  (isPublic ? !!companyKey : true)
108
188
 
@@ -110,7 +190,7 @@ export const useCreateDataWithPdfActions = ({
110
190
  const templateReportsForForm = formTemplateReports?.[formId]
111
191
  const templateInfo = Array.isArray(templateReportsForForm?.templates)
112
192
  ? (templateReportsForForm?.templates.find(
113
- (report: IFormTemplateReport) => report.fileBlobName === submissionTemplateReportConfig.template,
193
+ (report: IFormTemplateReport) => report.fileBlobName === submissionTemplateBlobName,
114
194
  ) as IFormTemplateReport | undefined)
115
195
  : undefined
116
196
 
@@ -180,6 +260,7 @@ export const useCreateDataWithPdfActions = ({
180
260
  submissionTemplateReportConfig,
181
261
  isPublic,
182
262
  isUserForm,
263
+ userFormNotificationId,
183
264
  baseServerUrl,
184
265
  companyKey,
185
266
  navigate,
@@ -190,6 +271,7 @@ export const useCreateDataWithPdfActions = ({
190
271
  onCreateFinal,
191
272
  t,
192
273
  warning,
274
+ formNotifications,
193
275
  ],
194
276
  )
195
277