2026-02-24 20:44:16 +00:00
|
|
|
|
import { DX_CLASSNAMES } from '@/constants/app.constant'
|
2026-06-01 20:51:59 +00:00
|
|
|
|
import { executeEditorScript } from '@/utils/editorScriptRuntime'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import {
|
|
|
|
|
|
Form as FormDx,
|
|
|
|
|
|
FormRef,
|
|
|
|
|
|
GroupItem as GroupItemDx,
|
|
|
|
|
|
SimpleItem as SimpleItemDx,
|
|
|
|
|
|
} from 'devextreme-react/form'
|
|
|
|
|
|
import { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form'
|
2026-06-01 20:51:59 +00:00
|
|
|
|
import { Dispatch, RefObject, useEffect, useRef, useState } from 'react'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
|
2026-05-06 10:55:34 +00:00
|
|
|
|
import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
|
|
|
|
|
|
import { RowMode, SimpleItemWithColData } from './types'
|
|
|
|
|
|
import { PlatformEditorTypes } from '@/proxy/form/models'
|
|
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
|
|
|
|
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const flattenFormItems = (items: any[] = []): SimpleItemWithColData[] =>
|
|
|
|
|
|
items.flatMap((item) => [
|
|
|
|
|
|
...(item?.dataField ? [item] : []),
|
|
|
|
|
|
...flattenFormItems(item?.items || []),
|
|
|
|
|
|
...(item?.tabs || []).flatMap((tab: any) => flattenFormItems(tab?.items || [])),
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
const updateReadOnlyInFormItems = (items: any[] = [], field: string, readOnly: boolean) => {
|
|
|
|
|
|
let changed = false
|
|
|
|
|
|
const expected = String(field || '').toLowerCase()
|
|
|
|
|
|
|
|
|
|
|
|
const nextItems = items.map((item) => {
|
|
|
|
|
|
const key = item?.dataField || item?.name
|
|
|
|
|
|
let nextItem = item
|
|
|
|
|
|
|
|
|
|
|
|
if (key && String(key).toLowerCase() === expected) {
|
|
|
|
|
|
const editorOptions = nextItem.editorOptions || {}
|
|
|
|
|
|
if (editorOptions.readOnly !== readOnly) {
|
|
|
|
|
|
changed = true
|
|
|
|
|
|
nextItem = {
|
|
|
|
|
|
...nextItem,
|
|
|
|
|
|
editorOptions: {
|
|
|
|
|
|
...editorOptions,
|
|
|
|
|
|
readOnly,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nextItem?.items?.length) {
|
|
|
|
|
|
const childResult = updateReadOnlyInFormItems(nextItem.items, field, readOnly)
|
|
|
|
|
|
if (childResult.changed) {
|
|
|
|
|
|
changed = true
|
|
|
|
|
|
nextItem = { ...nextItem, items: childResult.items }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nextItem?.tabs?.length) {
|
|
|
|
|
|
const tabs = nextItem.tabs.map((tab: any) => {
|
|
|
|
|
|
const tabResult = updateReadOnlyInFormItems(tab.items, field, readOnly)
|
|
|
|
|
|
if (tabResult.changed) {
|
|
|
|
|
|
changed = true
|
|
|
|
|
|
return { ...tab, items: tabResult.items }
|
|
|
|
|
|
}
|
|
|
|
|
|
return tab
|
|
|
|
|
|
})
|
|
|
|
|
|
nextItem = tabs === nextItem.tabs ? nextItem : { ...nextItem, tabs }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nextItem
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return { items: nextItems, changed }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const findFormFieldKey = (items: any[] = [], field: string): string => {
|
|
|
|
|
|
const expected = String(field || '').toLowerCase()
|
|
|
|
|
|
|
|
|
|
|
|
for (const item of items || []) {
|
|
|
|
|
|
const key = item?.dataField || item?.name
|
|
|
|
|
|
if (key && String(key).toLowerCase() === expected) {
|
|
|
|
|
|
return key
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const childKey = findFormFieldKey(item?.items || [], field)
|
|
|
|
|
|
if (childKey) {
|
|
|
|
|
|
return childKey
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const tab of item?.tabs || []) {
|
|
|
|
|
|
const tabKey = findFormFieldKey(tab?.items || [], field)
|
|
|
|
|
|
if (tabKey) {
|
|
|
|
|
|
return tabKey
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return field
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const setFormEditorReadOnly = (form: any, field: string, readOnly: boolean) => {
|
|
|
|
|
|
if (!form?.option) return false
|
|
|
|
|
|
|
|
|
|
|
|
const apply = () => {
|
|
|
|
|
|
const formItems = form.option('items') || []
|
|
|
|
|
|
const resolvedField = findFormFieldKey(formItems, field)
|
|
|
|
|
|
const editor = form.getEditor?.(resolvedField) ?? form.getEditor?.(field)
|
|
|
|
|
|
const result = updateReadOnlyInFormItems(formItems, resolvedField, readOnly)
|
|
|
|
|
|
|
|
|
|
|
|
if (result.changed) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const item = form.itemOption?.(resolvedField) ?? form.itemOption?.(field)
|
|
|
|
|
|
if (item) {
|
|
|
|
|
|
form.itemOption?.(resolvedField, 'editorOptions', {
|
|
|
|
|
|
...(item.editorOptions || {}),
|
|
|
|
|
|
readOnly,
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
form.option('items', result.items)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
form.option('items', result.items)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const activeEditor = editor ?? form.getEditor?.(resolvedField) ?? form.getEditor?.(field)
|
|
|
|
|
|
if (activeEditor?.option?.('readOnly') !== readOnly) {
|
|
|
|
|
|
activeEditor?.option?.('readOnly', readOnly)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
apply()
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getValueByField = (data: Record<string, any> = {}, field?: string) => {
|
|
|
|
|
|
if (!field) return undefined
|
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(data, field)) return data[field]
|
|
|
|
|
|
const key = Object.keys(data).find(
|
|
|
|
|
|
(itemKey) => itemKey.toLowerCase() === String(field).toLowerCase(),
|
|
|
|
|
|
)
|
|
|
|
|
|
return key ? data[key] : undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const shouldRunEditorScriptOnContentReady = (script?: string) =>
|
2026-06-08 08:46:21 +00:00
|
|
|
|
Boolean(
|
|
|
|
|
|
script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')),
|
|
|
|
|
|
)
|
2026-06-01 13:47:38 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const FormDevExpress = (props: {
|
|
|
|
|
|
listFormCode: string
|
|
|
|
|
|
isSubForm?: boolean
|
|
|
|
|
|
mode: RowMode
|
|
|
|
|
|
refForm: RefObject<FormRef>
|
|
|
|
|
|
formData: any
|
|
|
|
|
|
formItems: GroupItem[]
|
|
|
|
|
|
setFormData: Dispatch<any>
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const { listFormCode, isSubForm, mode, refForm, formData, formItems, setFormData } = props
|
|
|
|
|
|
const { translate } = useLocalization()
|
|
|
|
|
|
|
|
|
|
|
|
const formDataRef = useRef(formData)
|
|
|
|
|
|
const formItemsRef = useRef(formItems)
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const formInstanceRef = useRef<any>()
|
2026-06-01 14:19:49 +00:00
|
|
|
|
const lastContentReadyScriptKeyRef = useRef<string>()
|
2026-06-08 08:46:21 +00:00
|
|
|
|
const didAutoFocusRef = useRef(false)
|
2026-06-01 20:51:59 +00:00
|
|
|
|
const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState<Record<string, boolean>>({})
|
|
|
|
|
|
const runtimeReadOnlyFieldsRef = useRef<Record<string, boolean>>({})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-06-08 08:46:21 +00:00
|
|
|
|
const isTouchLikeDevice = () =>
|
|
|
|
|
|
typeof window !== 'undefined' &&
|
|
|
|
|
|
(window.matchMedia?.('(pointer: coarse)').matches ||
|
|
|
|
|
|
window.matchMedia?.('(hover: none)').matches)
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
formDataRef.current = formData
|
|
|
|
|
|
}, [formData])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
formItemsRef.current = formItems
|
|
|
|
|
|
}, [formItems])
|
|
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
runtimeReadOnlyFieldsRef.current = runtimeReadOnlyFields
|
|
|
|
|
|
}, [runtimeReadOnlyFields])
|
|
|
|
|
|
|
2026-06-08 08:46:21 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
didAutoFocusRef.current = false
|
|
|
|
|
|
}, [listFormCode, mode])
|
|
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
const setRuntimeEditorReadOnly = (field: string, readOnly: boolean) => {
|
|
|
|
|
|
const resolvedField = findFormFieldKey(formItemsRef.current, field)
|
|
|
|
|
|
const key = String(resolvedField || field || '').toLowerCase()
|
|
|
|
|
|
if (!key || runtimeReadOnlyFieldsRef.current[key] === readOnly) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
runtimeReadOnlyFieldsRef.current = {
|
|
|
|
|
|
...runtimeReadOnlyFieldsRef.current,
|
|
|
|
|
|
[key]: readOnly,
|
|
|
|
|
|
}
|
|
|
|
|
|
setRuntimeReadOnlyFields(runtimeReadOnlyFieldsRef.current)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getRuntimeEditorReadOnly = (formItem: SimpleItemWithColData) => {
|
|
|
|
|
|
const field = formItem.dataField || formItem.name
|
|
|
|
|
|
const resolvedField = findFormFieldKey(formItemsRef.current, field || '')
|
|
|
|
|
|
const key = String(resolvedField || field || '').toLowerCase()
|
|
|
|
|
|
return Object.prototype.hasOwnProperty.call(runtimeReadOnlyFields, key)
|
|
|
|
|
|
? runtimeReadOnlyFields[key]
|
|
|
|
|
|
: undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const applyEditorReadOnly = (form: any, field: string, readOnly: boolean) => {
|
|
|
|
|
|
setRuntimeEditorReadOnly(field, readOnly)
|
|
|
|
|
|
setFormEditorReadOnly(form, field, readOnly)
|
|
|
|
|
|
setTimeout(() => setFormEditorReadOnly(formInstanceRef.current ?? form, field, readOnly), 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 08:46:21 +00:00
|
|
|
|
const runEditorScript = (formItem: SimpleItemWithColData, eventValue: any, component?: any) => {
|
2026-06-01 20:51:59 +00:00
|
|
|
|
if (!formItem?.editorScript) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const form = formInstanceRef.current ?? component
|
|
|
|
|
|
const dataField = formItem.dataField
|
|
|
|
|
|
const nextFormData = {
|
|
|
|
|
|
...(formDataRef.current || {}),
|
|
|
|
|
|
...(form?.option?.('formData') || {}),
|
|
|
|
|
|
...(dataField ? { [dataField]: eventValue } : {}),
|
|
|
|
|
|
}
|
|
|
|
|
|
formDataRef.current = nextFormData
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const editor = {
|
|
|
|
|
|
dataField,
|
|
|
|
|
|
component: form,
|
|
|
|
|
|
}
|
|
|
|
|
|
const formData = nextFormData
|
|
|
|
|
|
const e = {
|
|
|
|
|
|
component: form,
|
|
|
|
|
|
dataField,
|
|
|
|
|
|
value: eventValue,
|
|
|
|
|
|
}
|
|
|
|
|
|
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
|
|
|
|
|
|
applyEditorReadOnly(form, field, readOnly)
|
|
|
|
|
|
|
|
|
|
|
|
executeEditorScript(formItem.editorScript, {
|
|
|
|
|
|
formData,
|
|
|
|
|
|
e,
|
|
|
|
|
|
editor,
|
|
|
|
|
|
runtimeSetEditorReadOnly,
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Script execution failed for', formItem.name, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getEditorOptions = (formItem: SimpleItemWithColData, index?: number) => {
|
|
|
|
|
|
const runtimeReadOnly = getRuntimeEditorReadOnly(formItem)
|
|
|
|
|
|
const prevOnValueChanged = formItem.editorOptions?.onValueChanged
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
2026-06-08 08:46:21 +00:00
|
|
|
|
...(index !== undefined && mode !== 'view' && !isTouchLikeDevice()
|
|
|
|
|
|
? { autoFocus: index === 1 }
|
|
|
|
|
|
: {}),
|
2026-06-01 20:51:59 +00:00
|
|
|
|
...(formItem.editorType === 'dxDateBox'
|
|
|
|
|
|
? {
|
|
|
|
|
|
useMaskBehavior: true,
|
|
|
|
|
|
openOnFieldClick: true,
|
|
|
|
|
|
showClearButton: true,
|
|
|
|
|
|
}
|
|
|
|
|
|
: {}),
|
|
|
|
|
|
...(formItem.colData?.placeHolder
|
|
|
|
|
|
? { placeholder: translate('::' + formItem.colData.placeHolder) }
|
|
|
|
|
|
: {}),
|
|
|
|
|
|
...formItem.editorOptions,
|
|
|
|
|
|
...(runtimeReadOnly !== undefined ? { readOnly: runtimeReadOnly } : {}),
|
|
|
|
|
|
...(mode === 'view' ? { readOnly: true } : {}),
|
|
|
|
|
|
...(formItem.editorScript
|
|
|
|
|
|
? {
|
|
|
|
|
|
onValueChanged: (e: any) => {
|
|
|
|
|
|
if (typeof prevOnValueChanged === 'function') {
|
|
|
|
|
|
prevOnValueChanged(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (formItem.dataField) {
|
|
|
|
|
|
const nextFormData = {
|
|
|
|
|
|
...(formDataRef.current || {}),
|
|
|
|
|
|
...(formInstanceRef.current?.option?.('formData') || {}),
|
|
|
|
|
|
[formItem.dataField]: e?.value,
|
|
|
|
|
|
}
|
|
|
|
|
|
formDataRef.current = nextFormData
|
|
|
|
|
|
formInstanceRef.current?.option?.('formData', nextFormData)
|
|
|
|
|
|
setFormData(nextFormData)
|
|
|
|
|
|
}
|
|
|
|
|
|
runEditorScript(formItem, e?.value, formInstanceRef.current)
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
: {}),
|
|
|
|
|
|
buttons: (formItem.editorOptions?.buttons || []).map((btn: any) => {
|
|
|
|
|
|
if (btn?.options?.onClick && typeof btn.options.onClick === 'string') {
|
|
|
|
|
|
const origClick = eval(`(${btn.options.onClick})`)
|
|
|
|
|
|
btn.options.onClick = (e: any) => {
|
|
|
|
|
|
origClick({
|
|
|
|
|
|
...e,
|
|
|
|
|
|
formData: formDataRef.current,
|
|
|
|
|
|
fieldName: formItem.dataField,
|
|
|
|
|
|
mode,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return btn
|
|
|
|
|
|
}),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getFormItemKey = (formItem: SimpleItemWithColData, index: number) => {
|
|
|
|
|
|
const runtimeReadOnly = getRuntimeEditorReadOnly(formItem)
|
|
|
|
|
|
return `formItem-${formItem.dataField || formItem.name || index}-${String(runtimeReadOnly)}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
// formItems değiştiğinde (özellikle cascading alanlar için) editörlerin dataSource'larını güncelle
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!refForm.current?.instance()) return
|
|
|
|
|
|
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const allItems = formItems.flatMap((group) => flattenFormItems([group]))
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
allItems.forEach((item) => {
|
|
|
|
|
|
if (item.colData?.lookupDto?.dataSourceType && item.editorOptions?.dataSource) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const editor = refForm.current?.instance().getEditor(item.dataField!)
|
|
|
|
|
|
if (editor) {
|
|
|
|
|
|
editor.option('dataSource', item.editorOptions.dataSource)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
// Editor henüz oluşmamış olabilir, sessizce devam et
|
|
|
|
|
|
console.debug('Editor update skipped for', item.dataField, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}, [formItems])
|
|
|
|
|
|
|
|
|
|
|
|
// Cascade fieldlerin disabled durumunu güncelle
|
|
|
|
|
|
const updateCascadeDisabledStates = () => {
|
|
|
|
|
|
if (!refForm.current?.instance()) return
|
|
|
|
|
|
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const allItems = formItemsRef.current.flatMap((group) => flattenFormItems([group]))
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
allItems.forEach((item) => {
|
|
|
|
|
|
const cascadeParentFields = item.colData?.lookupDto?.cascadeParentFields
|
|
|
|
|
|
if (cascadeParentFields) {
|
|
|
|
|
|
const parentFields = cascadeParentFields.split(',').map((f: string) => f.trim())
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const editor = refForm.current?.instance().getEditor(item.dataField!)
|
|
|
|
|
|
if (editor && mode !== 'view') {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
if (item.editorOptions?.disabled === true) {
|
|
|
|
|
|
editor.option('disabled', true)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
// Parent fieldlerden en az biri boşsa disabled olmalı
|
|
|
|
|
|
const shouldDisable = parentFields.some((parentField: string) => {
|
|
|
|
|
|
return !formDataRef.current || !formDataRef.current[parentField]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
editor.option('disabled', shouldDisable)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.debug('Cascade disabled update skipped for', item.dataField, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// formData değiştiğinde cascade disabled durumlarını güncelle
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
updateCascadeDisabledStates()
|
|
|
|
|
|
}, [formData, mode])
|
|
|
|
|
|
|
2026-06-01 14:19:49 +00:00
|
|
|
|
const runReadOnlyScripts = (form: any) => {
|
|
|
|
|
|
if (!form) return
|
|
|
|
|
|
|
|
|
|
|
|
const currentFormData = {
|
|
|
|
|
|
...(formDataRef.current || {}),
|
|
|
|
|
|
...(form?.option?.('formData') || {}),
|
|
|
|
|
|
}
|
|
|
|
|
|
formDataRef.current = currentFormData
|
|
|
|
|
|
|
|
|
|
|
|
formItemsRef.current
|
|
|
|
|
|
.flatMap((group) => flattenFormItems([group]))
|
|
|
|
|
|
.filter((formItem) => shouldRunEditorScriptOnContentReady(formItem.editorScript))
|
|
|
|
|
|
.forEach((formItem) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const editor = {
|
|
|
|
|
|
dataField: formItem.dataField,
|
|
|
|
|
|
component: form,
|
|
|
|
|
|
}
|
|
|
|
|
|
const formData = currentFormData
|
|
|
|
|
|
const e = {
|
|
|
|
|
|
component: form,
|
|
|
|
|
|
dataField: formItem.dataField,
|
|
|
|
|
|
value: getValueByField(currentFormData, formItem.dataField),
|
|
|
|
|
|
}
|
|
|
|
|
|
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
|
2026-06-01 20:51:59 +00:00
|
|
|
|
applyEditorReadOnly(form, field, readOnly)
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
executeEditorScript(formItem.editorScript!, {
|
|
|
|
|
|
formData,
|
|
|
|
|
|
e,
|
|
|
|
|
|
editor,
|
|
|
|
|
|
runtimeSetEditorReadOnly,
|
|
|
|
|
|
})
|
2026-06-01 14:19:49 +00:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Script execution failed on contentReady for', formItem.name, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const form = formInstanceRef.current
|
|
|
|
|
|
if (!form || mode === 'view' || !formData || !formItems?.length) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const scriptFields = formItemsRef.current
|
|
|
|
|
|
.flatMap((group) => flattenFormItems([group]))
|
|
|
|
|
|
.filter((formItem) => shouldRunEditorScriptOnContentReady(formItem.editorScript))
|
|
|
|
|
|
.map((formItem) => formItem.dataField)
|
|
|
|
|
|
.join('|')
|
|
|
|
|
|
|
|
|
|
|
|
if (!scriptFields) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const scriptKey = `${mode}|${scriptFields}|${JSON.stringify(formData)}`
|
|
|
|
|
|
if (lastContentReadyScriptKeyRef.current === scriptKey) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
lastContentReadyScriptKeyRef.current = scriptKey
|
|
|
|
|
|
setTimeout(() => runReadOnlyScripts(form), 0)
|
|
|
|
|
|
}, [formData, formItems, mode])
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<FormDx
|
|
|
|
|
|
ref={refForm}
|
|
|
|
|
|
className={`${DX_CLASSNAMES} ${!isSubForm ? 'px-2' : ''} pb-2`}
|
|
|
|
|
|
formData={formData}
|
|
|
|
|
|
onFieldDataChanged={async (e: FieldDataChangedEvent) => {
|
2026-06-01 14:19:49 +00:00
|
|
|
|
if (!e.dataField) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const newFormData = { ...formData, [e.dataField]: e.value }
|
|
|
|
|
|
let hasChanges = !Object.is(formData?.[e.dataField], e.value)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
// Cascading child field'leri temizle (parent field değiştiğinde)
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const allItems = formItemsRef.current.flatMap((group) => flattenFormItems([group]))
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const cascadingChildren = allItems.filter((item) => {
|
|
|
|
|
|
const parentFields = item.colData?.lookupDto?.cascadeParentFields?.split(',') || []
|
|
|
|
|
|
return parentFields.some((field) => field.trim() === e.dataField)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// Parent field değiştiğinde child field'leri temizle
|
|
|
|
|
|
cascadingChildren.forEach((child) => {
|
2026-06-01 14:19:49 +00:00
|
|
|
|
if (!Object.is(newFormData[child.dataField!], null)) {
|
|
|
|
|
|
newFormData[child.dataField!] = null
|
|
|
|
|
|
hasChanges = true
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-06-01 14:19:49 +00:00
|
|
|
|
if (hasChanges) {
|
2026-06-01 20:51:59 +00:00
|
|
|
|
formDataRef.current = newFormData
|
2026-06-01 14:19:49 +00:00
|
|
|
|
setFormData(newFormData)
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
// Cascade disabled durumlarını güncelle (setTimeout ile editor güncellemesinden sonra çalışsın)
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
updateCascadeDisabledStates()
|
|
|
|
|
|
}, 0)
|
|
|
|
|
|
}}
|
|
|
|
|
|
onContentReady={(e) => {
|
2026-06-01 13:47:38 +00:00
|
|
|
|
formInstanceRef.current = e.component
|
|
|
|
|
|
|
|
|
|
|
|
const form = e.component
|
|
|
|
|
|
|
2026-06-01 14:19:49 +00:00
|
|
|
|
runReadOnlyScripts(form)
|
2026-06-01 13:47:38 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const groupItems = e.component.option('items') as any[]
|
|
|
|
|
|
const firstItem = groupItems?.[0]?.items?.[0]
|
|
|
|
|
|
|
2026-06-08 08:46:21 +00:00
|
|
|
|
if (!didAutoFocusRef.current && firstItem?.dataField && !isTouchLikeDevice()) {
|
|
|
|
|
|
didAutoFocusRef.current = true
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const editor = e.component.getEditor(firstItem.dataField)
|
|
|
|
|
|
mode !== 'view' && editor?.focus()
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{formItems.map((formGroupItem, i) => {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<GroupItemDx
|
|
|
|
|
|
key={'formGroupItem-' + i}
|
|
|
|
|
|
colCount={formGroupItem.colCount}
|
|
|
|
|
|
colSpan={formGroupItem.colSpan}
|
|
|
|
|
|
caption={formGroupItem.caption}
|
|
|
|
|
|
>
|
2026-06-08 08:46:21 +00:00
|
|
|
|
{(formGroupItem.items as SimpleItemWithColData[])
|
|
|
|
|
|
?.filter((formItem) => {
|
|
|
|
|
|
if (mode === 'edit') return formItem.allowEditing !== false
|
|
|
|
|
|
if (mode === 'new') return formItem.allowAdding !== false
|
|
|
|
|
|
return true
|
|
|
|
|
|
})
|
|
|
|
|
|
.map((formItem, i) => {
|
|
|
|
|
|
return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? (
|
|
|
|
|
|
<SimpleItemDx
|
|
|
|
|
|
cssClass="font-semibold"
|
|
|
|
|
|
key={getFormItemKey(formItem, i)}
|
|
|
|
|
|
{...formItem}
|
|
|
|
|
|
render={() => (
|
|
|
|
|
|
<TagBoxEditorComponent
|
|
|
|
|
|
value={formData[formItem.dataField!] || []}
|
|
|
|
|
|
setDefaultValue={false}
|
|
|
|
|
|
values={formData}
|
|
|
|
|
|
options={formItem.tagBoxOptions}
|
|
|
|
|
|
col={formItem.colData}
|
|
|
|
|
|
onValueChanged={(e: any) => {
|
|
|
|
|
|
const newData = { ...formDataRef.current, [formItem.dataField!]: e }
|
|
|
|
|
|
formDataRef.current = newData
|
|
|
|
|
|
setFormData(newData)
|
|
|
|
|
|
runEditorScript(formItem, e, formInstanceRef.current)
|
|
|
|
|
|
}}
|
|
|
|
|
|
editorOptions={getEditorOptions(formItem)}
|
|
|
|
|
|
></TagBoxEditorComponent>
|
|
|
|
|
|
)}
|
|
|
|
|
|
label={{
|
|
|
|
|
|
text: translate('::' + formItem.colData?.captionName),
|
|
|
|
|
|
className: 'font-semibold',
|
|
|
|
|
|
}}
|
|
|
|
|
|
></SimpleItemDx>
|
|
|
|
|
|
) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? (
|
|
|
|
|
|
<SimpleItemDx
|
|
|
|
|
|
cssClass="font-semibold"
|
|
|
|
|
|
key={getFormItemKey(formItem, i)}
|
|
|
|
|
|
{...formItem}
|
|
|
|
|
|
render={() => (
|
|
|
|
|
|
<GridBoxEditorComponent
|
|
|
|
|
|
value={formData[formItem.dataField!] || []}
|
|
|
|
|
|
values={formData}
|
|
|
|
|
|
options={formItem.gridBoxOptions}
|
|
|
|
|
|
col={formItem.colData}
|
|
|
|
|
|
onValueChanged={(e: any) => {
|
|
|
|
|
|
const newData = { ...formDataRef.current, [formItem.dataField!]: e }
|
|
|
|
|
|
formDataRef.current = newData
|
|
|
|
|
|
setFormData(newData)
|
|
|
|
|
|
runEditorScript(formItem, e, formInstanceRef.current)
|
|
|
|
|
|
}}
|
|
|
|
|
|
editorOptions={getEditorOptions(formItem)}
|
|
|
|
|
|
></GridBoxEditorComponent>
|
|
|
|
|
|
)}
|
|
|
|
|
|
label={{
|
|
|
|
|
|
text: translate('::' + formItem.colData?.captionName),
|
|
|
|
|
|
className: 'font-semibold',
|
|
|
|
|
|
}}
|
|
|
|
|
|
></SimpleItemDx>
|
|
|
|
|
|
) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? (
|
|
|
|
|
|
<SimpleItemDx
|
|
|
|
|
|
cssClass="font-semibold"
|
|
|
|
|
|
key={getFormItemKey(formItem, i)}
|
|
|
|
|
|
dataField={formItem.dataField}
|
|
|
|
|
|
name={formItem.name}
|
|
|
|
|
|
colSpan={formItem.colSpan}
|
|
|
|
|
|
isRequired={formItem.isRequired}
|
|
|
|
|
|
render={() => (
|
|
|
|
|
|
<ImageUploadEditorComponent
|
|
|
|
|
|
value={formData[formItem.dataField!]}
|
|
|
|
|
|
options={formItem.imageUploadOptions}
|
|
|
|
|
|
onValueChanged={(val: any) => {
|
|
|
|
|
|
const newData = { ...formDataRef.current, [formItem.dataField!]: val }
|
|
|
|
|
|
formDataRef.current = newData
|
|
|
|
|
|
setFormData(newData)
|
|
|
|
|
|
runEditorScript(formItem, val, formInstanceRef.current)
|
|
|
|
|
|
}}
|
|
|
|
|
|
editorOptions={getEditorOptions(formItem)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
label={{
|
|
|
|
|
|
text: translate('::' + formItem.colData?.captionName),
|
|
|
|
|
|
className: 'font-semibold',
|
|
|
|
|
|
}}
|
|
|
|
|
|
></SimpleItemDx>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<SimpleItemDx
|
|
|
|
|
|
cssClass="font-semibold"
|
|
|
|
|
|
key={getFormItemKey(formItem, i)}
|
|
|
|
|
|
{...formItem}
|
|
|
|
|
|
editorOptions={getEditorOptions(formItem, i)}
|
|
|
|
|
|
label={{ text: translate('::' + formItem.colData?.captionName) }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</GroupItemDx>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</FormDx>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default FormDevExpress
|