form-craft-package 1.9.0 → 1.9.1

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.9.0",
3
+ "version": "1.9.1",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -4,7 +4,7 @@ import { FormPreservedItemKeys } from '../../../enums'
4
4
  export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues => {
5
5
  const baseServerUrl = Form.useWatch(FormPreservedItemKeys.BaseServerUrl, { form: formRef, preserve: true })
6
6
  const isGeneratingPdf = Form.useWatch(FormPreservedItemKeys.IsGeneratingPDF, { form: formRef, preserve: true })
7
- const hasSignature = Form.useWatch(FormPreservedItemKeys.HasSignature, { form: formRef, preserve: true })
7
+ const signatureFields = Form.useWatch(FormPreservedItemKeys.SignatureFields, { form: formRef, preserve: true })
8
8
  const isPublic = Form.useWatch(FormPreservedItemKeys.IsPublic, { form: formRef, preserve: true })
9
9
  const submissionPdfConfig = Form.useWatch(FormPreservedItemKeys.SubmissionPdfConfig, {
10
10
  form: formRef,
@@ -15,7 +15,7 @@ export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues
15
15
  [FormPreservedItemKeys.BaseServerUrl]: baseServerUrl,
16
16
  [FormPreservedItemKeys.SubmissionPdfConfig]: submissionPdfConfig,
17
17
  [FormPreservedItemKeys.IsGeneratingPDF]: isGeneratingPdf,
18
- [FormPreservedItemKeys.HasSignature]: hasSignature,
18
+ [FormPreservedItemKeys.SignatureFields]: signatureFields,
19
19
  [FormPreservedItemKeys.IsPublic]: isPublic,
20
20
  }
21
21
  }
@@ -17,6 +17,8 @@ export function useTranslation(formId?: number) {
17
17
 
18
18
  const t = useCallback(
19
19
  (params: ITranslateFuncParam | ITranslateFuncParam[]) => {
20
+ if (!formId) return ''
21
+
20
22
  if (Array.isArray(params)) return params.map((p) => (p.key ? translationStore.translate(p, formId) : ''))
21
23
 
22
24
  return params.key ? translationStore.translate(params, formId) : ''
@@ -12,6 +12,8 @@ import { useManyToManyConnector } from '../../common/custom-hooks/use-many-to-ma
12
12
  import { PageViewTypEnum, DeviceBreakpointEnum, FormPreservedItemKeys, LOCAL_STORAGE_KEYS_ENUM } from '../../../enums'
13
13
  import { UserAuth } from '../../../api/user'
14
14
  import { useCacheFormLayoutConfig } from '../../common/custom-hooks/use-cache-form-layout-config.hook'
15
+ import { translationStore } from '../../common/custom-hooks/use-translation.hook/store'
16
+ import { ELEMENTS_DEFAULT_CLASS } from '../../../constants'
15
17
  import {
16
18
  handleHiddenSet,
17
19
  useSetHiddenNodes,
@@ -20,7 +22,6 @@ import {
20
22
  handleDisabledSet,
21
23
  useSetDisabledElements,
22
24
  } from '../../common/custom-hooks/use-node-condition.hook/use-disabled-elements.hook'
23
- import { ELEMENTS_DEFAULT_CLASS } from '../../../constants'
24
25
  import {
25
26
  DynamicFormButtonRender,
26
27
  ICustomFunctionCall,
@@ -33,7 +34,6 @@ import {
33
34
  isValidMongoDbId,
34
35
  queryParamsToObject,
35
36
  } from '../../../functions/forms'
36
- import { translationStore } from '../../common/custom-hooks/use-translation.hook/store'
37
37
 
38
38
  export default function FormDataDetailsComponent({
39
39
  isPublic,
@@ -41,7 +41,6 @@ export default function FormDataDetailsComponent({
41
41
  formKey,
42
42
  formDataId,
43
43
  formName,
44
- companyKey,
45
44
  baseServerUrl,
46
45
  initialValues,
47
46
  onCustomFunctionCall,
@@ -117,6 +116,7 @@ export default function FormDataDetailsComponent({
117
116
  if (res.status === 200) {
118
117
  const { data: jsonData, ...restFormData } = res.data
119
118
  let parsedFormData = JSON.parse(jsonData)
119
+
120
120
  if (parsedFormData) {
121
121
  const fieldsValue: { [key: string]: any } = fromMongoDbExtendedJSON(
122
122
  parsedFormData,
@@ -165,17 +165,17 @@ export default function FormDataDetailsComponent({
165
165
  return deviceLayout
166
166
  }, [cachedConfig?.detailsConfig, currentBreakpoint])
167
167
 
168
- const formContext = useMemo(
169
- () => ({
170
- detailPageFormId: formId,
171
- formId,
172
- formKey,
173
- formDataId,
174
- formRef: formDataRef,
175
- companyKey: UserAuth.getCompanyKey(),
176
- }),
177
- [formDataId, formDataRef, formId, formKey, companyKey],
178
- )
168
+ // const formContext = useMemo(
169
+ // () => ({
170
+ // detailPageFormId: formId,
171
+ // formId,
172
+ // formKey,
173
+ // formDataId,
174
+ // formRef: formDataRef,
175
+ // companyKey: UserAuth.getCompanyKey(),
176
+ // }),
177
+ // [formId, formKey, formDataId, formDataRef, companyKey],
178
+ // )
179
179
 
180
180
  if (isConfigLoading || !cachedConfig) return <FormDataListSkeleton_Details />
181
181
 
@@ -197,12 +197,26 @@ export default function FormDataDetailsComponent({
197
197
  <LayoutRendererRow
198
198
  key={rowIdx}
199
199
  rowData={row}
200
- formContext={formContext}
200
+ formContext={{
201
+ detailPageFormId: formId,
202
+ formId,
203
+ formKey,
204
+ formDataId,
205
+ formRef: formDataRef,
206
+ companyKey: UserAuth.getCompanyKey(),
207
+ }}
201
208
  elements={cachedConfig.detailsConfig.elements}
202
209
  renderButton={(btnProps) => (
203
210
  <DynamicFormButtonRender
204
211
  displayStateProps={btnProps as IDynamicButton_DisplayStateProps}
205
- formContext={formContext}
212
+ formContext={{
213
+ detailPageFormId: formId,
214
+ formId,
215
+ formKey,
216
+ formDataId,
217
+ formRef: formDataRef,
218
+ companyKey: UserAuth.getCompanyKey(),
219
+ }}
206
220
  onCustomFunctionCall={onCustomFunctionCall}
207
221
  />
208
222
  )}
@@ -1,21 +1,17 @@
1
- import { useMemo } from 'react'
2
1
  import { IFormDndLayoutRowHeader } from '../../../../types'
3
- import { kebabCaseToCamelCase } from '../../../../functions/forms'
4
2
  import { FaCaretDown, FaCaretUp } from 'react-icons/fa6'
5
3
 
6
4
  export default function RowHeader({
7
5
  name,
8
6
  isCollapsible,
9
- style,
7
+ style = {},
10
8
  isCollapsed = false,
11
9
  setIsCollapsed = () => {},
12
10
  }: IRowHeader) {
13
- const styleConfig = useMemo(() => (style ? kebabCaseToCamelCase(style) : {}), [style])
14
-
15
11
  return (
16
12
  <div
17
13
  className={`flex items-center justify-between ${isCollapsible ? 'cursor-pointer' : ''}`}
18
- style={styleConfig}
14
+ style={style}
19
15
  onClick={() => {
20
16
  if (isCollapsible) setIsCollapsed()
21
17
  }}
@@ -1,7 +1,7 @@
1
1
  import { FormInstance } from 'antd'
2
- import { getGridContainerStyle, getColumnStyle, kebabCaseToCamelCase } from '../../../../functions/forms'
2
+ import { getGridContainerStyle, getColumnStyle } from '../../../../functions/forms'
3
3
  import LayoutRendererCol from '../2-col'
4
- import { memo, ReactElement, useMemo } from 'react'
4
+ import { memo, ReactElement } from 'react'
5
5
  import { LayoutRowConditionalHeaderRenderer } from './header-render'
6
6
  import { LayoutRowRepeatableRenderer } from './repeatable-render'
7
7
  import { IDndLayoutElement, IDndLayoutRow, IFormJoin } from '../../../../types'
@@ -16,10 +16,6 @@ export const LayoutRendererRow = memo(
16
16
  ({ basePath = [], rowData, dataCount, formContext, elements, renderButton }: ILayoutRendererRow) => {
17
17
  const isHidden = useIsNodeHidden(rowData.id)
18
18
 
19
- const styleConfig = useMemo(() => {
20
- if (!rowData.style) return {}
21
- return kebabCaseToCamelCase(rowData.style)
22
- }, [rowData.style])
23
19
 
24
20
  return (
25
21
  <div
@@ -40,7 +36,10 @@ export const LayoutRendererRow = memo(
40
36
  repeatingSection={rowData.props?.repeatingSection}
41
37
  >
42
38
  {(formListItemProps) => {
43
- const style: { [key: string]: any } = { ...styleConfig, ...getGridContainerStyle(rowData.display) }
39
+ const style: { [key: string]: any } = {
40
+ ...(rowData.style || {}),
41
+ ...getGridContainerStyle(rowData.display),
42
+ }
44
43
  const hiddenColsIndices = rowData.children.reduce(
45
44
  (curr: number[], col, colIdx) => (checkIsNodeHidden(col.id) ? [...curr, colIdx] : curr),
46
45
  [],
@@ -13,12 +13,6 @@ import { getButtonRenderProps } from '../../../../../functions/forms/get-element
13
13
  import WarningIcon from '../../../../common/warning-icon'
14
14
  import FormDataLoadingIndicatorModal from '../../../../modals/form-data-loading.modal'
15
15
  import { IFormContext } from '../../1-row'
16
- import {
17
- ButtonActionCategoryEnum,
18
- FormLoadingModalTypeEnum,
19
- TranslationTextSubTypeEnum,
20
- TranslationTextTypeEnum,
21
- } from '../../../../../enums'
22
16
  import { useButtonNavigateAction } from './use-button-navigate.hook'
23
17
  import { constructDynamicFormHref, isNewFormDataPage } from '../../../../../functions'
24
18
  import { useCustomFunctionCallAction } from './use-custom-function-call.hook'
@@ -29,6 +23,12 @@ import {
29
23
  useIsNodeDisabled,
30
24
  useIsNodeHidden,
31
25
  } from '../../../../common/custom-hooks/use-node-condition.hook/use-node-condition.hook'
26
+ import {
27
+ ButtonActionCategoryEnum,
28
+ FormLoadingModalTypeEnum,
29
+ TranslationTextSubTypeEnum,
30
+ TranslationTextTypeEnum,
31
+ } from '../../../../../enums'
32
32
 
33
33
  export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
34
34
  const { displayStateProps = {}, formContext = {}, onCustomFunctionCall = () => {} } = props
@@ -15,15 +15,26 @@ export const useSaveSignatureAction = ({ formRef, companyKey }: IFormContext) =>
15
15
  if (!formRef) return
16
16
 
17
17
  try {
18
- const signatureField = formRef.getFieldValue(FormPreservedItemKeys.HasSignature)
19
- if (!signatureField) return
18
+ const signatureFieldContainer = formRef.getFieldValue(FormPreservedItemKeys.SignatureFields)
19
+ if (!signatureFieldContainer) return
20
20
 
21
- const signatureBase64 = formRef.getFieldValue(signatureField)
21
+ const signatureFields = Object.entries(signatureFieldContainer) || []
22
22
 
23
- const blob = base64ToBlob(signatureBase64)
24
- const blobName = await saveFile(blob, 'signature', isPublic ? companyKey : '')
23
+ const blobsToSave = signatureFields.reduce((acc: [string, Blob][], [fieldName, signatureBase64]) => {
24
+ if (signatureBase64) return [...acc, [fieldName, base64ToBlob(signatureBase64 as string)] as [string, Blob]]
25
25
 
26
- if (blobName) formRef.setFieldValue(signatureField, blobName)
26
+ return acc
27
+ }, [])
28
+
29
+ const blobNames = await Promise.all(
30
+ blobsToSave.map(([fieldName, blob]) => saveFile(blob, `${fieldName}_signature`, isPublic ? companyKey : '')),
31
+ )
32
+
33
+ const fieldValuesToSet: Record<string, string> = {}
34
+ blobsToSave.forEach(([fieldName], blobToSaveIdx) => {
35
+ if (blobNames[blobToSaveIdx]) fieldValuesToSet[fieldName as string] = blobNames[blobToSaveIdx]
36
+ })
37
+ formRef.setFieldsValue(fieldValuesToSet)
27
38
  } catch (err) {
28
39
  console.error('Error saving signature:', err)
29
40
  }
@@ -14,7 +14,8 @@ function LayoutRenderer_PickerField({ formContext, formItem, elementData, isDisa
14
14
  const { t } = useTranslation(formContext.formId)
15
15
  const props = getElementGeneralizedProps(elementData.props)
16
16
 
17
- const [placeholder1, placeholder2] = t([
17
+ const [label, placeholder1, placeholder2] = t([
18
+ { key: elementData.key, type: TranslationTextTypeEnum.Label },
18
19
  { key: elementData.key, type: TranslationTextTypeEnum.Placeholder },
19
20
  { key: elementData.key, type: TranslationTextTypeEnum.Placeholder, subType: TranslationTextSubTypeEnum.Secondary },
20
21
  ])
@@ -27,6 +28,7 @@ function LayoutRenderer_PickerField({ formContext, formItem, elementData, isDisa
27
28
  <LayoutRenderer_FieldElement formRef={formContext.formRef} elementKey={elementData.key} formId={formContext.formId}>
28
29
  {() => ({
29
30
  ...props,
31
+ label,
30
32
  placeholder,
31
33
  type: elementData.elementType,
32
34
  name: formItem.name ?? '',
@@ -0,0 +1,11 @@
1
+ .ant-select.fc-language-selector {
2
+ .ant-select-selector {
3
+ @apply bg-transparent border-none outline-none shadow-none;
4
+ }
5
+ &.ant-select-focused.ant-select-outlined:not(.ant-select-disabled):not(.ant-select-customize-input):not(
6
+ .ant-pagination-size-changer
7
+ )
8
+ .ant-select-selector {
9
+ @apply shadow-none outline-none;
10
+ }
11
+ }
@@ -1,11 +1,14 @@
1
- import { IElementBaseProps } from '.'
2
- import { CountryEnum, LOCAL_STORAGE_KEYS_ENUM, TranslationTextTypeEnum } from '../../../../enums'
1
+ import { IElementBaseProps } from '../'
2
+ import { CountryEnum, LOCAL_STORAGE_KEYS_ENUM, TranslationTextTypeEnum } from '../../../../../enums'
3
3
  import { memo, useEffect, useState } from 'react'
4
- import { ELEMENTS_DEFAULT_CLASS } from '../../../../constants'
5
- import { useTranslation } from '../../../common/custom-hooks/use-translation.hook/hook'
6
- import { translationStore } from '../../../common/custom-hooks/use-translation.hook/store'
4
+ import { ELEMENTS_DEFAULT_CLASS } from '../../../../../constants'
5
+ import { useTranslation } from '../../../../common/custom-hooks/use-translation.hook/hook'
6
+ import { translationStore } from '../../../../common/custom-hooks/use-translation.hook/store'
7
7
  import { Select } from 'antd'
8
8
  import { FaCaretDown } from 'react-icons/fa6'
9
+ import { MdOutlineLanguage } from 'react-icons/md'
10
+
11
+ import './index.scss'
9
12
 
10
13
  function LayoutRenderer_LanguageSelector({ formId, isDisabled, elementKey }: ILayoutRenderer_LanguageSelector) {
11
14
  const { t } = useTranslation(formId)
@@ -24,13 +27,14 @@ function LayoutRenderer_LanguageSelector({ formId, isDisabled, elementKey }: ILa
24
27
  <div className="flex flex-col min-w-max">
25
28
  {label && <span>{label}</span>}
26
29
  <Select
30
+ prefix={<MdOutlineLanguage size={16} className="mr-2 text-primary" />}
27
31
  placeholder={placeholder}
28
32
  suffixIcon={<FaCaretDown />}
29
33
  options={languages}
30
34
  defaultValue={localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.SelectedLanguage)}
31
35
  disabled={isDisabled}
32
36
  allowClear={false}
33
- className={`${ELEMENTS_DEFAULT_CLASS.Select} min-w-max`}
37
+ className={`${ELEMENTS_DEFAULT_CLASS.LanguageSelector} min-w-max`}
34
38
  onChange={(lang) => translationStore.setLanguage(lang as CountryEnum)}
35
39
  />
36
40
  </div>
@@ -43,16 +43,18 @@ function LayoutRenderer_Signature({
43
43
  !elementProps.hasNoLabel && (
44
44
  <div className="flex items-center gap-1">
45
45
  <span>{t({ key: elementKey, type: TranslationTextTypeEnum.Label })}</span>
46
- <Button_FillerPortal
47
- link
48
- onClick={() => {
49
- sigCanvasRef.current?.clear()
50
- if (fieldKeyJoined)
51
- formRef?.setFieldsValue({ [fieldKeyJoined]: null, [FormPreservedItemKeys.HasSignature]: null })
52
- }}
53
- >
54
- Clear
55
- </Button_FillerPortal>
46
+ {isNewFormDataPage(formDataId) && (
47
+ <Button_FillerPortal
48
+ link
49
+ onClick={() => {
50
+ sigCanvasRef.current?.clear()
51
+ if (fieldKeyJoined)
52
+ formRef?.setFieldsValue({ [FormPreservedItemKeys.SignatureFields]: { [fieldKeyJoined]: null } })
53
+ }}
54
+ >
55
+ Clear
56
+ </Button_FillerPortal>
57
+ )}
56
58
  </div>
57
59
  )
58
60
  }
@@ -69,15 +71,18 @@ function LayoutRenderer_Signature({
69
71
  onEnd={() => {
70
72
  const sigBase64 = sigCanvasRef.current?.toDataURL()
71
73
  if (sigBase64 && fieldKeyJoined)
72
- formRef?.setFieldsValue({
73
- [fieldKeyJoined]: sigBase64,
74
- [FormPreservedItemKeys.HasSignature]: fieldKeyJoined,
75
- })
74
+ formRef?.setFieldsValue({ [FormPreservedItemKeys.SignatureFields]: { [fieldKeyJoined]: sigBase64 } })
76
75
  }}
77
76
  />
78
77
  ) : (
79
- <div className={`${ELEMENTS_DEFAULT_CLASS.SignatureCanvas} h-[300px] rounded-md border-2 border-[#f0f0f0]`}>
80
- <img alt="signature" src={`${baseServerUrl}/api/attachment/${companyKey}/${savedSignatureBlobName}`} />
78
+ <div
79
+ className={`${ELEMENTS_DEFAULT_CLASS.SignatureCanvas} h-[300px] rounded-md border-2 border-[#f0f0f0] text-center bg-gray-100 flex justify-center p-2`}
80
+ >
81
+ <img
82
+ alt="signature"
83
+ src={`${baseServerUrl}/api/attachment/${companyKey}/${savedSignatureBlobName}`}
84
+ className="bg-white rounded-md"
85
+ />
81
86
  </div>
82
87
  )}
83
88
  </Form.Item>
@@ -3,7 +3,7 @@ import { lazy, memo, ReactElement, useMemo } from 'react'
3
3
  import { getElementGeneralizedProps } from '../../../../functions/forms/get-element-props'
4
4
  import { ElementTypeEnum, TextElementTypeEnum, TranslationTextTypeEnum } from '../../../../enums'
5
5
  import useGetCurrentBreakpoint from '../../../common/custom-hooks/use-window-width.hook'
6
- import { findMaxAndMinValues, kebabCaseToCamelCase } from '../../../../functions/forms'
6
+ import { findMaxAndMinValues } from '../../../../functions/forms'
7
7
  import { IFormContext } from '../1-row'
8
8
  import { LayoutRenderer_FieldElement } from './2-field-element'
9
9
  import { IDynamicButton_DisplayStateProps } from './1-dynamic-button'
@@ -166,7 +166,7 @@ function LayoutRendererElement({
166
166
  renderButton ? renderButton({ btnKey: key, btnProps: props as IButtonElementProps }) : <></>,
167
167
 
168
168
  [ElementTypeEnum.Text]: () => {
169
- const textStyle = kebabCaseToCamelCase((elementData as ITextElement).style?.[currentBreakpoint] ?? {})
169
+ const textStyle = (elementData as ITextElement).style?.[currentBreakpoint] || {}
170
170
  if (props.type === TextElementTypeEnum.Plain)
171
171
  return (
172
172
  <div style={textStyle}>
@@ -245,6 +245,7 @@ function LayoutRendererElement({
245
245
  }
246
246
 
247
247
  const renderer = renderers[elementType]
248
+
248
249
  return renderer ? (
249
250
  renderer()
250
251
  ) : (
@@ -255,6 +256,7 @@ function LayoutRendererElement({
255
256
  placeholder,
256
257
  numberFieldMax: maxValue,
257
258
  numberFieldMin: minValue,
259
+ minRows: elementType === ElementTypeEnum.LongInput ? 2 : undefined,
258
260
  type: elementType,
259
261
  name: formItemName as string,
260
262
  nameFullPath: fullPath,
package/src/constants.ts CHANGED
@@ -211,6 +211,7 @@ export const ELEMENTS_DEFAULT_CLASS = {
211
211
  Currency: 'fc-currency',
212
212
  Breadcrumb: 'fc-breadcrumb',
213
213
  Select: 'fc-select',
214
+ LanguageSelector: 'fc-language-selector',
214
215
  Input: 'fc-input',
215
216
  CheckboxGroup: 'fc-checkbox-group',
216
217
  Checkbox: 'fc-checkbox',
@@ -136,7 +136,7 @@ export enum FormPreservedItemKeys {
136
136
  BaseServerUrl = 'baseServerUrl',
137
137
  SubmissionPdfConfig = 'submissionPdfConfig',
138
138
  IsGeneratingPDF = 'isGeneratingPdf',
139
- HasSignature = 'hasSignature',
139
+ SignatureFields = 'signatureFields',
140
140
  IsPublic = 'isPublic',
141
141
  }
142
142
  export enum DataCategoryEnum {
@@ -136,21 +136,6 @@ export const queryParamsToObject = (query: string): Record<string, string | unde
136
136
 
137
137
  /** --------------------------------------------------------------------------------------------------------- */
138
138
 
139
- export const kebabCaseToCamelCase = (cssObject: Record<string, string | number>): Record<string, string | number> => {
140
- const toCamelCase = (str: string): string => str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
141
-
142
- const camelCaseObject: Record<string, string | number> = {}
143
-
144
- for (const [key, value] of Object.entries(cssObject)) {
145
- const camelCaseKey = toCamelCase(key)
146
- camelCaseObject[camelCaseKey] = value
147
- }
148
-
149
- return camelCaseObject
150
- }
151
-
152
- /** --------------------------------------------------------------------------------------------------------- */
153
-
154
139
  export async function fetchFormDataAsLookup(
155
140
  formId: number,
156
141
  filter: Partial<IFormDataApiReqConfig> = {},