form-craft-package 1.4.0 → 1.4.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.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/src/components/common/color-field.tsx +4 -1
  3. package/src/components/common/currency-field.tsx +68 -0
  4. package/src/components/common/custom-hooks/use-find-dynamic-form.hook.ts +2 -1
  5. package/src/components/common/loading-skeletons/table.tsx +1 -1
  6. package/src/components/form/1-list/index.tsx +3 -1
  7. package/src/components/form/1-list/{header.tsx → table-header.tsx} +14 -7
  8. package/src/components/form/1-list/table.tsx +15 -13
  9. package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +19 -7
  10. package/src/components/form/layout-renderer/3-element/2-field-element.tsx +18 -6
  11. package/src/components/form/layout-renderer/3-element/3-read-only.tsx +3 -2
  12. package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +28 -11
  13. package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +52 -18
  14. package/src/components/form/layout-renderer/3-element/index.tsx +5 -7
  15. package/src/constants.ts +2 -2
  16. package/src/enums.ts +13 -2
  17. package/src/functions/create-form-rules.ts +22 -22
  18. package/src/functions/data-render-functions.tsx +121 -43
  19. package/src/functions/index.ts +4 -1
  20. package/src/types/data-list/filter-config.ts +1 -0
  21. package/src/types/data-list/index.ts +12 -1
  22. package/src/types/index.ts +8 -10
  23. package/src/types/layout-elements/data-render-config.ts +5 -5
  24. package/src/types/layout-elements/index.ts +21 -3
  25. package/src/types/layout-elements/sanitization.ts +12 -0
  26. package/src/types/layout-elements/validation.ts +1 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-craft-package",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -15,7 +15,10 @@ export default function CustomColorField({ formRef, name, isPreview, disabled }:
15
15
  className={`flex items-center gap-2 border bg-white rounded-lg w-full p-1 ${
16
16
  isPreview || disabled ? 'cursor-not-allowed' : 'cursor-pointer'
17
17
  }`}
18
- onClick={() => colorInputRef.current?.click()}
18
+ onClick={() => {
19
+ colorInputRef.current?.click()
20
+ colorInputRef.current?.focus()
21
+ }}
19
22
  >
20
23
  <input
21
24
  type="color"
@@ -0,0 +1,68 @@
1
+ import { InputNumber } from 'antd'
2
+ import { useMemo, useRef } from 'react'
3
+ import { CountryEnum } from '../../enums'
4
+ import { INTERNATIONALIZATION_DATA } from '../../constants'
5
+
6
+ export default function CurrencyField({
7
+ country = CountryEnum.US,
8
+ decimalPoint = 0,
9
+ ...restProps
10
+ }: ICurrencyField) {
11
+ const inputRef = useRef<HTMLInputElement>(null)
12
+
13
+ const currencySymbol = useMemo(() => {
14
+ const { locale, currency } = INTERNATIONALIZATION_DATA[country]
15
+
16
+ const formatter = new Intl.NumberFormat(locale, {
17
+ style: 'currency',
18
+ currency: currency,
19
+ minimumFractionDigits: 0,
20
+ maximumFractionDigits: 0,
21
+ })
22
+ const parts = formatter.formatToParts(0)
23
+ const currencyPart = parts.find((part) => part.type === 'currency')
24
+ return currencyPart?.value ?? '$'
25
+ }, [country])
26
+
27
+ const handleFocus = () => {
28
+ const input = inputRef.current
29
+ if (input) {
30
+ const decimal = input.value ? parseInt(input.value.replace(/\p{Sc}\s?|(,*)/gu, '')) : 0
31
+
32
+ setTimeout(() => {
33
+ const decimalPos = input.value.indexOf('.')
34
+ if (decimalPos !== -1) {
35
+ input.setSelectionRange(decimal === 0 ? decimalPos - 1 : decimalPos, decimalPos) // if the whole number is 0, then it highlights the 0 -> so that it can replace right away
36
+ }
37
+ }, 10)
38
+ }
39
+ }
40
+
41
+ return (
42
+ <InputNumber
43
+ {...restProps}
44
+ ref={inputRef}
45
+ onFocus={handleFocus}
46
+ precision={decimalPoint}
47
+ formatter={(value) => {
48
+ if (!value) return `${currencySymbol}${parseInt('0').toFixed(decimalPoint)}`
49
+
50
+ return typeof value === 'string'
51
+ ? currencySymbol +
52
+ parseFloat(value)
53
+ .toFixed(decimalPoint)
54
+ .replace(/\B(?=(\d{3})+(?!\d))/g, ',')
55
+ : `${currencySymbol}${parseInt('0').toFixed(decimalPoint)}`
56
+ }}
57
+ parser={(value) => (value ? parseFloat(value.replace(/\p{Sc}\s?|(,*)/gu, '')) : 0)}
58
+ className="w-full right-align"
59
+ />
60
+ )
61
+ }
62
+
63
+ interface ICurrencyField {
64
+ country?: CountryEnum
65
+ decimalPoint?: number
66
+ placeholder?: string
67
+ disabled?: boolean
68
+ }
@@ -15,6 +15,7 @@ export const useFindDynamiForm = () => {
15
15
  if (storedData) {
16
16
  const parsedData: IDynamicForm[] = JSON.parse(storedData)
17
17
  const item = parsedData.find((entry) => entry.name.split(' ').join('-').toLocaleLowerCase() === formName)
18
+
18
19
  setFoundItem(item ?? null)
19
20
  }
20
21
  } catch (error) {
@@ -22,5 +23,5 @@ export const useFindDynamiForm = () => {
22
23
  }
23
24
  }, [formName])
24
25
 
25
- return foundItem
26
+ return foundItem?.name?.split(' ').join('-').toLowerCase() === formName ? foundItem : undefined
26
27
  }
@@ -3,7 +3,7 @@ import SkeletonBlock from '.'
3
3
  export default function FormDataListSkeleton_Table({ small = false }: { small?: boolean }) {
4
4
  return (
5
5
  <>
6
- <div className={`bg-white flex items-center justify-between rounded-md ${small ? 'p-2' : 'mb-2 p-3'}`}>
6
+ <div className={`bg-white flex items-center justify-between rounded-md mb-2 ${small ? 'p-2' : 'p-3'}`}>
7
7
  <SkeletonBlock width="200px" height={small ? 20 : undefined} />
8
8
  <div className="flex items-center gap-2">
9
9
  <SkeletonBlock width="150px" height={small ? 20 : undefined} />
@@ -7,6 +7,7 @@ import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-butt
7
7
  import FormDataListTableComponent, { IReqDataConfig } from './table'
8
8
 
9
9
  function FormDataListComponent({
10
+ formName,
10
11
  formId,
11
12
  userId,
12
13
  companyKey,
@@ -24,7 +25,7 @@ function FormDataListComponent({
24
25
  >()
25
26
 
26
27
  const attachmentBaseUrl = useMemo(() => `${baseServerUrl}/api/attachment/${companyKey}`, [baseServerUrl, companyKey])
27
- const constantValues = { formId, userId, attachmentBaseUrl }
28
+ const constantValues = { formId, userId, attachmentBaseUrl, formName }
28
29
 
29
30
  useEffect(() => {
30
31
  if (formId) {
@@ -84,6 +85,7 @@ function FormDataListComponent({
84
85
  export { FormDataListComponent }
85
86
 
86
87
  type IFormDataListComponent = {
88
+ formName?: string
87
89
  baseServerUrl?: string
88
90
  companyKey?: string
89
91
  formId?: number
@@ -30,7 +30,7 @@ export default function FormDataListHeaderComponent({
30
30
  }: IFormDataListHeaderComponent) {
31
31
  const [dataListHeaderFormRef] = Form.useForm()
32
32
  const [filterConfigs, setFilterConfigs] = useState<IFilterNested>({})
33
- const { userId, formId, formDataId } = constantValues
33
+ const { userId, formId, formDataId, formName, parentInfo } = constantValues
34
34
 
35
35
  const filterValues = Form.useWatch([], dataListHeaderFormRef)
36
36
 
@@ -60,7 +60,7 @@ export default function FormDataListHeaderComponent({
60
60
  if ('type' in config) {
61
61
  if (config.type === FilterConfigTypeEnum.ByLinkedForm) startLoading()
62
62
 
63
- const filterData = await handleFilterValues(config as IFilterConfig, value, userId)
63
+ const filterData = await handleFilterValues(config as IFilterConfig, value, { userId })
64
64
  filtersToApply.push(filterData)
65
65
  } else {
66
66
  // cases like radio buttons, where each has its own filter, but only 1 needs to apply
@@ -69,7 +69,7 @@ export default function FormDataListHeaderComponent({
69
69
 
70
70
  if (eachConfig.type === FilterConfigTypeEnum.ByLinkedForm) startLoading()
71
71
 
72
- const filterData = await handleFilterValues(eachConfig as IFilterConfig, value, userId)
72
+ const filterData = await handleFilterValues(eachConfig as IFilterConfig, value, { userId })
73
73
  filtersToApply.push(filterData)
74
74
  }
75
75
  }
@@ -97,6 +97,7 @@ export default function FormDataListHeaderComponent({
97
97
 
98
98
  return {
99
99
  ...c,
100
+ sort: defaultFilter.sort,
100
101
  pagination: { ...defaultFilter.pagination, current: 1 },
101
102
  customFilter,
102
103
  dynamicFilter: JSON.stringify(dynamicFilter),
@@ -104,7 +105,7 @@ export default function FormDataListHeaderComponent({
104
105
  })
105
106
  } else updateReqData(defaultFilter)
106
107
  },
107
- [filterConfigs],
108
+ [filterConfigs, defaultFilter],
108
109
  )
109
110
 
110
111
  useEffect(() => {
@@ -125,6 +126,8 @@ export default function FormDataListHeaderComponent({
125
126
  conditions={conditions}
126
127
  formRef={dataListHeaderFormRef}
127
128
  onCustomFunctionCall={onCustomFunctionCall}
129
+ formName={formName}
130
+ stateToPass={{ parentInfo }}
128
131
  />
129
132
  )}
130
133
  />
@@ -162,11 +165,15 @@ const getLinkedFormFilter = async (
162
165
  } else return { dataIds: parentDataIds, foreignKey: lastRel.foreignKey }
163
166
  }
164
167
 
165
- const handleFilterValues = async (config: IFilterConfig, value: any, userId?: string | number) => {
168
+ const handleFilterValues = async (
169
+ config: IFilterConfig,
170
+ value: any,
171
+ contextValues: { userId?: string | number; isDateFilter?: boolean },
172
+ ) => {
166
173
  if (config.type === FilterConfigTypeEnum.NoFilter) return DEFAULT_NO_FILTER
174
+ const { userId } = contextValues
167
175
 
168
- const isDate = dayjs(value as Date).isValid()
169
- if (isDate) value = dayjs(value as Date).toISOString()
176
+ if ((config as IFilterCustom).isDateFilter) value = dayjs(value as Date).toISOString()
170
177
 
171
178
  let customFilters = {}
172
179
  if (config.type === FilterConfigTypeEnum.ByAuthUser) customFilters = { createdById: userId }
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react'
2
- import { IFormData, IFormDataListConfig, IFormLayoutRow, ITableColumn } from '../../../types'
3
- import FormDataListHeaderComponent from './header'
2
+ import { IDataListParentInfo, IFormData, IFormDataListConfig, IFormLayoutRow, ITableColumn } from '../../../types'
3
+ import FormDataListHeaderComponent from './table-header'
4
4
  import useDebounced from '../../common/custom-hooks/use-debounce.hook'
5
5
  import { Table } from 'antd'
6
6
  import { FilterConfigTypeEnum, FormDataListViewTypeEnum, MongoDbSortOrderEnum } from '../../../enums'
@@ -26,7 +26,9 @@ export default function FormDataListTableComponent({
26
26
  undefined,
27
27
  250,
28
28
  )
29
- const [otherConfigs, setOtherConfigs] = useState<Partial<IFormDataListConfig> | undefined>(undefined)
29
+ const [otherConfigs, setOtherConfigs] = useState<
30
+ (Partial<IFormDataListConfig> & { hasNoPagination: boolean }) | undefined
31
+ >(undefined)
30
32
  const [headerLayout, setHeaderLayout] = useState<IFormLayoutRow[]>([])
31
33
  const [isHandlingSchema, setIsHandlingSchema] = useState(true)
32
34
  const isFinishedHandlingRef = useRef(false)
@@ -37,16 +39,14 @@ export default function FormDataListTableComponent({
37
39
  pagination: DEFAULT_PAGINATION,
38
40
  })
39
41
 
40
- const { attachmentBaseUrl, userId, formId } = constantValues
42
+ const { attachmentBaseUrl, userId, formId, formName } = constantValues
41
43
 
42
44
  useEffect(() => {
43
45
  setIsHandlingSchema(true)
44
46
  }, [location.pathname])
45
47
 
46
48
  useEffect(() => {
47
- if (debouncedFilterReqData) {
48
- updateDataList(debouncedFilterReqData)
49
- }
49
+ if (debouncedFilterReqData) updateDataList(debouncedFilterReqData)
50
50
  }, [debouncedFilterReqData])
51
51
 
52
52
  const handleDataListConfig = useCallback(
@@ -68,22 +68,22 @@ export default function FormDataListTableComponent({
68
68
  if (elementsWithOptions_key.length > 0)
69
69
  optionKeyValuePair = await extractElementsOptions(layout, elementsWithOptions_key)
70
70
 
71
- setTableColumns(generateTableColumns(elements, optionKeyValuePair, attachmentBaseUrl))
71
+ setTableColumns(generateTableColumns(elements, optionKeyValuePair, { attachmentBaseUrl, formName }))
72
72
  setHeaderLayout(header.layout)
73
- setOtherConfigs(restConfigs)
73
+ setOtherConfigs({ ...restConfigs, hasNoPagination: pagination?.hasNoPagination ?? false })
74
74
 
75
75
  if (defaultFilter && defaultFilter.type !== FilterConfigTypeEnum.NoFilter)
76
76
  defaultFilterRef.current = {
77
77
  ...defaultFilterRef.current,
78
78
  customFilter: defaultFilter.type === FilterConfigTypeEnum.ByAuthUser ? { createdById: userId } : {},
79
79
  dynamicFilter: defaultFilter.config
80
- ? // The operators are stored using @, and here it replaces @ with $. Otherwise, MongoDB's JSON-to-BSON conversion driver may detect certain operators, especially $regex, and automatically modify them. To prevent data manipulation by the MongoDB driver, @ is used instead of $.
80
+ ? // The operators are stored using @, and here it replaces @ with $. Otherwise, MongoDB's JSON-to-BSON conversion driver was detecting certain operators, especially $regex, and automatically modified them. To prevent data manipulation by the MongoDB driver, @ is used instead of $.
81
81
  JSON.stringify(defaultFilter.config).replaceAll('@', '$')
82
82
  : JSON.stringify({}),
83
- sort: defaultSorter ? { [defaultSorter.field]: defaultSorter.order } : {},
84
83
  }
85
84
  defaultFilterRef.current = {
86
85
  ...defaultFilterRef.current,
86
+ sort: defaultSorter ? { [defaultSorter.field]: defaultSorter.order } : {},
87
87
  pagination: {
88
88
  current: 1,
89
89
  pageSize: pagination?.defaultPageSize ?? 10,
@@ -95,7 +95,7 @@ export default function FormDataListTableComponent({
95
95
  setIsHandlingSchema(false)
96
96
  isFinishedHandlingRef.current = true
97
97
  },
98
- [attachmentBaseUrl, userId, formId],
98
+ [attachmentBaseUrl, formName, userId, formId],
99
99
  )
100
100
 
101
101
  useEffect(() => {
@@ -131,7 +131,7 @@ export default function FormDataListTableComponent({
131
131
  columns={tableColumns}
132
132
  rowKey={(record) => record.id}
133
133
  pagination={
134
- !otherConfigs?.pagination || otherConfigs.pagination.hasNoPagination
134
+ otherConfigs?.hasNoPagination
135
135
  ? false
136
136
  : { ...defaultFilterRef.current.pagination, ...filterReqData?.pagination, total: dataList.total }
137
137
  }
@@ -205,4 +205,6 @@ export interface IConstantValues {
205
205
  formId?: number
206
206
  attachmentBaseUrl?: string
207
207
  formDataId?: string
208
+ formName?: string
209
+ parentInfo?: IDataListParentInfo
208
210
  }
@@ -1,10 +1,10 @@
1
- import { useLocation, useNavigate } from 'react-router-dom'
1
+ import { useNavigate } from 'react-router-dom'
2
2
  import { useNotification } from '../../../../common/custom-hooks'
3
3
  import { useCheckElementConditions } from '../../../../common/custom-hooks/use-check-element-conditions.hook'
4
4
  import { memo, useCallback, useEffect, useMemo, useState } from 'react'
5
5
  import { ButtonActionCategoryEnum, FormLoadingModalTypeEnum, FormPreservedItemKeys } from '../../../../../enums'
6
6
  import { useFormPreservedItemValues } from '../../../../common/custom-hooks/use-preserved-form-items.hook'
7
- import { extractDynamicFormHref } from '../../../../../functions'
7
+ import { constructDynamicFormHref } from '../../../../../functions'
8
8
  import { useDuplicateDataAction } from './use-duplicate-data.hook'
9
9
  import { useDeleteDataAction } from './use-delete-data.hook'
10
10
  import { usePublishDataAction } from './use-publish-data.hook'
@@ -16,21 +16,30 @@ import { NEW_FORM_DATA_IDENTIFIER } from '../../../../../constants'
16
16
  import { Button_FillerPortal } from '../../../../common/button'
17
17
  import { getButtonRenderProps } from '../../../../../functions/get-element-props'
18
18
  import WarningIcon from '../../../../common/warning-icon'
19
- import { IDataRender_ButtonProps, IFormLayoutElementConditions } from '../../../../../types'
19
+ import { IButtonNavigateState, IDataRender_ButtonProps, IFormLayoutElementConditions } from '../../../../../types'
20
20
  import { FormInstance } from 'antd'
21
21
  import FormDataLoadingIndicatorModal from '../../../../modals/form-data-loading.modal'
22
22
 
23
23
  export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
24
- const { btnProps, conditions, formDataId, formRef, defaulDisabled = false, onCustomFunctionCall = () => {} } = props
24
+ const {
25
+ btnProps,
26
+ conditions,
27
+ formDataId,
28
+ formRef,
29
+ defaulDisabled = false,
30
+ onCustomFunctionCall = () => {},
31
+ formName = '',
32
+ stateToPass = {},
33
+ } = props
25
34
 
26
35
  const navigate = useNavigate()
27
- const location = useLocation()
28
36
  const { success, warning, error, confirmModal } = useNotification()
29
37
  const { isElementDisabled, isElementHidden } = useCheckElementConditions(formRef, conditions, defaulDisabled)
30
38
  const [loading, setLoading] = useState(false)
31
39
  const [dataLoadingType, setDataLoadingType] = useState<FormLoadingModalTypeEnum | undefined>()
32
40
  const { inPreviewMode, isPublic = false } = useFormPreservedItemValues(formRef)
33
- const baseDynamicUrl = useMemo(() => extractDynamicFormHref(location.pathname), [location.pathname])
41
+
42
+ const baseDynamicUrl = useMemo(() => constructDynamicFormHref(formName), [formName])
34
43
 
35
44
  useEffect(() => {
36
45
  formRef?.setFieldValue(FormPreservedItemKeys.FormDataId, formDataId)
@@ -102,7 +111,8 @@ export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
102
111
  const handleButtonClick = useCallback(async () => {
103
112
  switch (btnProps.category) {
104
113
  case ButtonActionCategoryEnum.CreateNewData:
105
- navigate(`${baseDynamicUrl}/${NEW_FORM_DATA_IDENTIFIER}`)
114
+ // window.location.href = `${baseDynamicUrl}/${NEW_FORM_DATA_IDENTIFIER}`
115
+ navigate(`${baseDynamicUrl}/${NEW_FORM_DATA_IDENTIFIER}`, { state: stateToPass })
106
116
  setLoading(false)
107
117
  break
108
118
 
@@ -246,6 +256,8 @@ type IDynamicButton = {
246
256
  formDataId?: string
247
257
  formRef?: FormInstance
248
258
  defaulDisabled?: boolean
259
+ formName?: string
260
+ stateToPass?: IButtonNavigateState
249
261
  } & ICustomFunctionCall
250
262
 
251
263
  export interface ICustomFunctionCall {
@@ -2,9 +2,12 @@ import { Key, ReactNode, Fragment } from 'react'
2
2
  import { FaCaretDown } from 'react-icons/fa'
3
3
  import dayjs, { Dayjs } from 'dayjs'
4
4
  import { FaUpload } from 'react-icons/fa6'
5
- import { CountryEnum, ElementTypeEnum } from '../../../../enums'
6
- import { IValidationRule, mapToFormItemRules, renderPhoneData } from '../../../../functions'
5
+ import { CountryEnum, DataSanitizationTypeEnum, ElementTypeEnum } from '../../../../enums'
6
+ import { formatByPattern, IValidationRule, mapToFormItemRules } from '../../../../functions'
7
7
  import { DisabledFieldIndicator } from '../../../common/disabled-field-indicator'
8
+ import CustomColorField from '../../../common/color-field'
9
+ import { IDataSanitization } from '../../../../types'
10
+ import CurrencyField from '../../../common/currency-field'
8
11
  import {
9
12
  Checkbox,
10
13
  Form,
@@ -18,7 +21,6 @@ import {
18
21
  Switch,
19
22
  FormInstance,
20
23
  } from 'antd'
21
- import CustomColorField from '../../../common/color-field'
22
24
 
23
25
  export const LayoutRenderer_FieldElement = ({
24
26
  fields,
@@ -45,9 +47,11 @@ export const LayoutRenderer_FieldElement = ({
45
47
  }
46
48
  rules={mapToFormItemRules(field.validations ?? [], field.name as string)}
47
49
  normalize={(value) => {
48
- if (field.phoneFormatCountry) return renderPhoneData(value, field.phoneFormatCountry)
50
+ const sanitizationRule = field.sanitization
51
+ if (!sanitizationRule || sanitizationRule.type === DataSanitizationTypeEnum.Default) return value
49
52
 
50
- return value
53
+ if (sanitizationRule.type === DataSanitizationTypeEnum.Custom)
54
+ return formatByPattern(value, sanitizationRule.pattern)
51
55
  }}
52
56
  >
53
57
  {field.type === ElementTypeEnum.ColorPicker ? (
@@ -74,6 +78,8 @@ const getField = ({
74
78
  numberFieldMin,
75
79
  numberFieldMax,
76
80
  isCustom,
81
+ country = CountryEnum.US,
82
+ decimalPoint = 0,
77
83
  disabledDate,
78
84
  }: Partial<IMapperFieldObj>): JSX.Element => {
79
85
  placeholder = placeholder && placeholder.length > 0 ? placeholder : typeof label === 'string' ? label : ''
@@ -146,6 +152,10 @@ const getField = ({
146
152
  // parser={(value) => (value ? parseFloat(value.replace(/\D/g, '')) : undefined)}
147
153
  />
148
154
  )
155
+ case ElementTypeEnum.CurrencyInput:
156
+ return (
157
+ <CurrencyField placeholder={placeholder} disabled={disabled} country={country} decimalPoint={decimalPoint} />
158
+ )
149
159
  case ElementTypeEnum.Password:
150
160
  return <Input.Password placeholder={placeholder} disabled={disabled} />
151
161
  case ElementTypeEnum.DatePicker:
@@ -204,7 +214,9 @@ interface IMapperFieldObj {
204
214
  numberFieldMin?: number
205
215
  numberFieldMax?: number
206
216
  isCustom?: boolean
207
- phoneFormatCountry?: CountryEnum
217
+ sanitization?: IDataSanitization
218
+ country?: CountryEnum
219
+ decimalPoint?: number
208
220
  disabledDate?: (date: Dayjs) => boolean
209
221
  }
210
222
  interface ISelectOption {
@@ -6,7 +6,7 @@ import { useMemo } from 'react'
6
6
  import { DataRenderTypeEnum } from '../../../../enums'
7
7
  import { IFormItemPath } from '.'
8
8
 
9
- export default function LayoutRenderer_ReadOnly({ formRef, elementProps }: ILayoutRenderer_ReadOnly) {
9
+ export default function LayoutRenderer_ReadOnly({ formRef, elementProps, style = {} }: ILayoutRenderer_ReadOnly) {
10
10
  const fieldValue = elementProps.isHardCoded
11
11
  ? elementProps.value
12
12
  : Form.useWatch(elementProps.value, { form: formRef, preserve: true })
@@ -22,7 +22,7 @@ export default function LayoutRenderer_ReadOnly({ formRef, elementProps }: ILayo
22
22
  return (
23
23
  <div className="flex items-center gap-2">
24
24
  {elementProps.label && <span>{elementProps.label}: </span>}
25
- <span className="font-bold">
25
+ <span style={style}>
26
26
  {renderData(
27
27
  elementProps.renderConfig?.type === DataRenderTypeEnum.Image
28
28
  ? attachmentUrl
@@ -45,4 +45,5 @@ type ILayoutRenderer_ReadOnly = {
45
45
  value?: string
46
46
  renderConfig?: IDataRenderConfig
47
47
  }
48
+ style?: { [key: string]: any }
48
49
  } & IFormItemPath
@@ -1,5 +1,5 @@
1
1
  import { FormInstance } from 'antd'
2
- import { useCallback, useEffect, useState } from 'react'
2
+ import { useCallback, useEffect, useMemo, useState } from 'react'
3
3
  import { IFilterByLinkedFormRelPath, IFormLayoutFieldOption, IRadioElement, ISelectElement } from '../../../../types'
4
4
  import { FieldElementOptionSourceEnum } from '../../../../enums'
5
5
  import { IFormItemPath } from '.'
@@ -8,6 +8,7 @@ import { fetchFormDataAsLookup } from '../../../../functions'
8
8
  import { getElementGeneralizedProps } from '../../../../functions/get-element-props'
9
9
  import { useFormPreservedItemValues } from '../../../common/custom-hooks/use-preserved-form-items.hook'
10
10
  import { IReqDataConfig } from '../../1-list/table'
11
+ import { useLocation } from 'react-router-dom'
11
12
 
12
13
  export default function LayoutRenderer_FieldsWithOptions({
13
14
  formRef,
@@ -15,23 +16,39 @@ export default function LayoutRenderer_FieldsWithOptions({
15
16
  elementData,
16
17
  isElementDisabled,
17
18
  }: ILayoutRenderer_FieldsWithOptions) {
19
+ const { state: locationState } = useLocation()
18
20
  const [options, setOptions] = useState<{ value: string; label: string }[]>([])
19
21
  const props = getElementGeneralizedProps(elementData.props)
20
22
 
21
23
  const { inPreviewMode, formDataId } = useFormPreservedItemValues(formRef)
22
24
 
23
- const fetchFormData = useCallback(async (formId: number, field: string, filter?: Partial<IReqDataConfig>) => {
24
- const formDataResData = await fetchFormDataAsLookup(formId, filter)
25
- setOptions(
26
- formDataResData.map((formData) => ({
27
- value: formData.id,
28
- label: formData[field],
29
- })),
30
- )
31
- }, [])
25
+ const potentialParentDataIds = useMemo(() => {
26
+ if (typeof formItemName === 'string' && locationState?.parentInfo?.foreignKey === formItemName)
27
+ return locationState.parentInfo.dataIds
28
+ return []
29
+ }, [locationState, formItemName])
30
+
31
+ const fetchFormData = useCallback(
32
+ async (formId: number, field: string, filter?: Partial<IReqDataConfig>) => {
33
+ const formDataResData = await fetchFormDataAsLookup(formId, filter)
34
+ let apiOptions = formDataResData
35
+ if (potentialParentDataIds.length) {
36
+ apiOptions = apiOptions.filter((formData) => potentialParentDataIds.includes(formData.id))
37
+ if (potentialParentDataIds.length === 1) formRef.setFieldValue(formItemName, potentialParentDataIds[0])
38
+ }
39
+
40
+ setOptions(
41
+ apiOptions.map((formData) => ({
42
+ value: formData.id,
43
+ label: formData[field],
44
+ })),
45
+ )
46
+ },
47
+ [potentialParentDataIds],
48
+ )
32
49
 
33
50
  const fetchLinkedFormData = useCallback(
34
- async (relationshipPath: IFilterByLinkedFormRelPath[] = []) => {
51
+ (relationshipPath: IFilterByLinkedFormRelPath[] = []) => {
35
52
  const lastPath = relationshipPath[relationshipPath.length - 1]
36
53
 
37
54
  if (lastPath && lastPath.field)