2026-07-08 06:14:40 +00:00
|
|
|
|
import { DX_CLASSNAMES, MULTIVALUE_DELIMITER } from '@/constants/app.constant'
|
2026-07-11 20:35:32 +00:00
|
|
|
|
import '@/utils/registerDevExtremeEditors'
|
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,
|
2026-07-13 08:39:13 +00:00
|
|
|
|
type FormRef,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
GroupItem as GroupItemDx,
|
|
|
|
|
|
SimpleItem as SimpleItemDx,
|
|
|
|
|
|
} from 'devextreme-react/form'
|
2026-07-13 08:39:13 +00:00
|
|
|
|
import type { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form'
|
2026-08-06 22:54:50 +00:00
|
|
|
|
import { Dispatch, ReactNode, RefObject, useCallback, 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-06-10 14:56:00 +00:00
|
|
|
|
import { ImageViewerEditorComponent } from './editors/ImageViewerEditorComponent'
|
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-08-06 22:54:50 +00:00
|
|
|
|
import {
|
|
|
|
|
|
getValueByField,
|
|
|
|
|
|
setFormEditorReadOnly,
|
|
|
|
|
|
shouldRunEditorScriptOnContentReady,
|
2026-10-01 08:50:16 +00:00
|
|
|
|
translateCaption,
|
2026-08-06 22:54:50 +00:00
|
|
|
|
} from '../list/shared/editingForm'
|
|
|
|
|
|
import { isTouchLikeDevice } from '../list/shared/editPopup'
|
|
|
|
|
|
import { flattenFormGroups, getCascadeParentFields } from './shared/formItems'
|
|
|
|
|
|
|
|
|
|
|
|
/** Kendi render'ını yapan platform editörleri. */
|
|
|
|
|
|
const CUSTOM_EDITOR_TYPES = [
|
|
|
|
|
|
PlatformEditorTypes.dxTagBox,
|
|
|
|
|
|
PlatformEditorTypes.dxGridBox,
|
|
|
|
|
|
PlatformEditorTypes.dxImageUpload,
|
|
|
|
|
|
PlatformEditorTypes.dxImageViewer,
|
|
|
|
|
|
] as const
|
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
|
2026-08-13 08:41:48 +00:00
|
|
|
|
refForm: RefObject<FormRef | null>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
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-08-13 08:41:48 +00:00
|
|
|
|
const formInstanceRef = useRef<any | undefined>(undefined)
|
|
|
|
|
|
const lastContentReadyScriptKeyRef = useRef<string | undefined>(undefined)
|
2026-06-08 10:15:22 +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-08-06 22:54:50 +00:00
|
|
|
|
formDataRef.current = formData
|
|
|
|
|
|
formItemsRef.current = formItems
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
runtimeReadOnlyFieldsRef.current = runtimeReadOnlyFields
|
|
|
|
|
|
}, [runtimeReadOnlyFields])
|
|
|
|
|
|
|
2026-06-08 10:15:22 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
didAutoFocusRef.current = false
|
|
|
|
|
|
}, [listFormCode, mode])
|
|
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const getFieldKey = (formItem: SimpleItemWithColData) =>
|
|
|
|
|
|
String(formItem.dataField || formItem.name || '').toLowerCase()
|
|
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
const setRuntimeEditorReadOnly = (field: string, readOnly: boolean) => {
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const key = String(field || '').toLowerCase()
|
2026-06-01 20:51:59 +00:00
|
|
|
|
if (!key || runtimeReadOnlyFieldsRef.current[key] === readOnly) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
runtimeReadOnlyFieldsRef.current = { ...runtimeReadOnlyFieldsRef.current, [key]: readOnly }
|
2026-06-01 20:51:59 +00:00
|
|
|
|
setRuntimeReadOnlyFields(runtimeReadOnlyFieldsRef.current)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getRuntimeEditorReadOnly = (formItem: SimpleItemWithColData) => {
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const key = getFieldKey(formItem)
|
2026-06-01 20:51:59 +00:00
|
|
|
|
return Object.prototype.hasOwnProperty.call(runtimeReadOnlyFields, key)
|
|
|
|
|
|
? runtimeReadOnlyFields[key]
|
|
|
|
|
|
: undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const applyEditorReadOnly = (form: any, field: string, readOnly: boolean) => {
|
|
|
|
|
|
setRuntimeEditorReadOnly(field, readOnly)
|
2026-08-06 22:54:50 +00:00
|
|
|
|
setFormEditorReadOnly(formInstanceRef.current ?? form, field, readOnly)
|
2026-06-01 20:51:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
/** Editör script'i / özel editör değişikliklerini forma ve state'e yazar. */
|
|
|
|
|
|
const commitFormData = useCallback(
|
|
|
|
|
|
(patch: Record<string, any>, form?: any) => {
|
|
|
|
|
|
const nextFormData = { ...(formDataRef.current || {}), ...patch }
|
|
|
|
|
|
formDataRef.current = nextFormData
|
2026-06-08 10:15:22 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const targetForm = form ?? formInstanceRef.current
|
|
|
|
|
|
targetForm?.option?.('formData', nextFormData)
|
|
|
|
|
|
Object.keys(patch).forEach((field) => {
|
|
|
|
|
|
targetForm?.getEditor?.(field)?.option?.('value', patch[field])
|
|
|
|
|
|
})
|
2026-06-08 10:15:22 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
setFormData(nextFormData)
|
|
|
|
|
|
return nextFormData
|
|
|
|
|
|
},
|
|
|
|
|
|
[setFormData],
|
|
|
|
|
|
)
|
2026-06-08 10:15:22 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const runEditorScript = (formItem: SimpleItemWithColData, eventValue: any) => {
|
|
|
|
|
|
if (!formItem?.editorScript) return
|
2026-06-01 20:51:59 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const form = formInstanceRef.current
|
2026-06-01 20:51:59 +00:00
|
|
|
|
const dataField = formItem.dataField
|
|
|
|
|
|
const nextFormData = {
|
|
|
|
|
|
...(formDataRef.current || {}),
|
|
|
|
|
|
...(form?.option?.('formData') || {}),
|
|
|
|
|
|
...(dataField ? { [dataField]: eventValue } : {}),
|
|
|
|
|
|
}
|
|
|
|
|
|
formDataRef.current = nextFormData
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
executeEditorScript(formItem.editorScript, {
|
2026-08-06 22:54:50 +00:00
|
|
|
|
formData: nextFormData,
|
|
|
|
|
|
e: { component: form, dataField, scriptEvent: 'valueChanged', value: eventValue },
|
|
|
|
|
|
editor: { dataField, component: form },
|
|
|
|
|
|
runtimeSetEditorReadOnly: (field: string, readOnly: boolean) =>
|
|
|
|
|
|
applyEditorReadOnly(form, field, readOnly),
|
|
|
|
|
|
setFormData: (newData: any) => commitFormData(newData || {}, form),
|
2026-06-01 20:51:59 +00:00
|
|
|
|
})
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Script execution failed for', formItem.name, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
/** Özel editörlerin ortak değer değişimi akışı. */
|
|
|
|
|
|
const handleCustomEditorChange = (formItem: SimpleItemWithColData, value: any) => {
|
|
|
|
|
|
if (!formItem.dataField) return
|
|
|
|
|
|
formDataRef.current = { ...formDataRef.current, [formItem.dataField]: value }
|
|
|
|
|
|
setFormData(formDataRef.current)
|
|
|
|
|
|
runEditorScript(formItem, value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
const getEditorOptions = (formItem: SimpleItemWithColData, index?: number) => {
|
|
|
|
|
|
const runtimeReadOnly = getRuntimeEditorReadOnly(formItem)
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const previousOnValueChanged = formItem.editorOptions?.onValueChanged
|
|
|
|
|
|
const configuredButtons = formItem.editorOptions?.buttons
|
2026-06-01 20:51:59 +00:00
|
|
|
|
|
|
|
|
|
|
return {
|
2026-06-08 10:15:22 +00:00
|
|
|
|
...(index !== undefined && mode !== 'view' && !isTouchLikeDevice()
|
|
|
|
|
|
? { autoFocus: index === 1 }
|
|
|
|
|
|
: {}),
|
2026-06-01 20:51:59 +00:00
|
|
|
|
...(formItem.editorType === 'dxDateBox'
|
2026-08-06 22:54:50 +00:00
|
|
|
|
? { useMaskBehavior: true, openOnFieldClick: true, showClearButton: true }
|
2026-06-01 20:51:59 +00:00
|
|
|
|
: {}),
|
|
|
|
|
|
...(formItem.colData?.placeHolder
|
|
|
|
|
|
? { placeholder: translate('::' + formItem.colData.placeHolder) }
|
|
|
|
|
|
: {}),
|
|
|
|
|
|
...formItem.editorOptions,
|
|
|
|
|
|
...(runtimeReadOnly !== undefined ? { readOnly: runtimeReadOnly } : {}),
|
|
|
|
|
|
...(mode === 'view' ? { readOnly: true } : {}),
|
|
|
|
|
|
...(formItem.editorScript
|
|
|
|
|
|
? {
|
|
|
|
|
|
onValueChanged: (e: any) => {
|
2026-08-06 22:54:50 +00:00
|
|
|
|
previousOnValueChanged?.(e)
|
2026-06-01 20:51:59 +00:00
|
|
|
|
if (formItem.dataField) {
|
2026-08-06 22:54:50 +00:00
|
|
|
|
commitFormData({ [formItem.dataField]: e?.value })
|
2026-06-01 20:51:59 +00:00
|
|
|
|
}
|
2026-08-06 22:54:50 +00:00
|
|
|
|
runEditorScript(formItem, e?.value)
|
2026-06-01 20:51:59 +00:00
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
: {}),
|
2026-08-06 22:54:50 +00:00
|
|
|
|
...(configuredButtons?.length
|
|
|
|
|
|
? {
|
|
|
|
|
|
buttons: configuredButtons.map((button: any) => {
|
|
|
|
|
|
// Yapılandırmadan string olarak gelen onClick bir kez fonksiyona çevrilir.
|
|
|
|
|
|
if (typeof button?.options?.onClick !== 'string') return button
|
|
|
|
|
|
|
|
|
|
|
|
const originalClick = eval(`(${button.options.onClick})`)
|
|
|
|
|
|
return {
|
|
|
|
|
|
...button,
|
|
|
|
|
|
options: {
|
|
|
|
|
|
...button.options,
|
|
|
|
|
|
onClick: (e: any) =>
|
|
|
|
|
|
originalClick({
|
|
|
|
|
|
...e,
|
|
|
|
|
|
formData: formDataRef.current,
|
|
|
|
|
|
fieldName: formItem.dataField,
|
|
|
|
|
|
mode,
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
2026-06-01 20:51:59 +00:00
|
|
|
|
}
|
2026-08-06 22:54:50 +00:00
|
|
|
|
: {}),
|
2026-06-01 20:51:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const getFormItemKey = (formItem: SimpleItemWithColData, index: number) =>
|
|
|
|
|
|
`formItem-${formItem.dataField || formItem.name || index}-${String(
|
|
|
|
|
|
getRuntimeEditorReadOnly(formItem),
|
|
|
|
|
|
)}`
|
2026-06-01 20:51:59 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
// Cascading alanlarda formItems yenilendiğinde editör dataSource'larını güncelle.
|
2026-02-24 20:44:16 +00:00
|
|
|
|
useEffect(() => {
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const form = refForm.current?.instance()
|
|
|
|
|
|
if (!form) return
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
flattenFormGroups(formItems).forEach((item) => {
|
|
|
|
|
|
if (!item.colData?.lookupDto?.dataSourceType || !item.editorOptions?.dataSource) return
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
try {
|
|
|
|
|
|
form.getEditor(item.dataField!)?.option('dataSource', item.editorOptions.dataSource)
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
// Editör henüz oluşmamış olabilir.
|
|
|
|
|
|
console.debug('Editor update skipped for', item.dataField, err)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-08-06 22:54:50 +00:00
|
|
|
|
}, [formItems, refForm])
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
/** Parent alanı boş olan cascade alanlarını devre dışı bırakır. */
|
|
|
|
|
|
const updateCascadeDisabledStates = useCallback(() => {
|
|
|
|
|
|
const form = refForm.current?.instance()
|
|
|
|
|
|
if (!form || mode === 'view') return
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
flattenFormGroups(formItemsRef.current).forEach((item) => {
|
|
|
|
|
|
const parentFields = getCascadeParentFields(item)
|
|
|
|
|
|
if (!parentFields.length) return
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
try {
|
|
|
|
|
|
const editor = form.getEditor(item.dataField!)
|
|
|
|
|
|
if (!editor) return
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
editor.option(
|
|
|
|
|
|
'disabled',
|
|
|
|
|
|
item.editorOptions?.disabled === true ||
|
|
|
|
|
|
parentFields.some((parentField) => !formDataRef.current?.[parentField]),
|
|
|
|
|
|
)
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.debug('Cascade disabled update skipped for', item.dataField, err)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-08-06 22:54:50 +00:00
|
|
|
|
}, [mode, refForm])
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
updateCascadeDisabledStates()
|
2026-08-06 22:54:50 +00:00
|
|
|
|
}, [formData, updateCascadeDisabledStates])
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-08 23:06:14 +00:00
|
|
|
|
// contentReady sırasında yapılan yazmalar yeni bir contentReady doğurabilir.
|
|
|
|
|
|
const contentReadyRunningRef = useRef(false)
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Form açılışında çalışması gereken script'leri yürütür: salt-okunur
|
|
|
|
|
|
* kararları ve `@runOnOpen` işaretli varsayılan değer kuralları.
|
|
|
|
|
|
*/
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const runReadOnlyScripts = useCallback(
|
|
|
|
|
|
(form: any) => {
|
2026-08-08 23:06:14 +00:00
|
|
|
|
if (!form || contentReadyRunningRef.current) return
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const currentFormData = {
|
|
|
|
|
|
...(formDataRef.current || {}),
|
|
|
|
|
|
...(form?.option?.('formData') || {}),
|
|
|
|
|
|
}
|
|
|
|
|
|
formDataRef.current = currentFormData
|
|
|
|
|
|
|
2026-08-08 23:06:14 +00:00
|
|
|
|
contentReadyRunningRef.current = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
flattenFormGroups(formItemsRef.current)
|
|
|
|
|
|
.filter((formItem) => shouldRunEditorScriptOnContentReady(formItem.editorScript))
|
|
|
|
|
|
.forEach((formItem) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
executeEditorScript(formItem.editorScript!, {
|
|
|
|
|
|
formData: currentFormData,
|
|
|
|
|
|
e: {
|
|
|
|
|
|
component: form,
|
|
|
|
|
|
dataField: formItem.dataField,
|
|
|
|
|
|
scriptEvent: 'contentReady',
|
|
|
|
|
|
value: getValueByField(currentFormData, formItem.dataField),
|
|
|
|
|
|
},
|
|
|
|
|
|
editor: { dataField: formItem.dataField, component: form },
|
|
|
|
|
|
runtimeSetEditorReadOnly: (field: string, readOnly: boolean) =>
|
|
|
|
|
|
applyEditorReadOnly(form, field, readOnly),
|
|
|
|
|
|
// Açılışta yazma artık destekleniyor; varsayılan değer üreten
|
|
|
|
|
|
// script'ler bu sayede çalışabiliyor.
|
|
|
|
|
|
setFormData: (newData: any) => commitFormData(newData || {}, form),
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Script execution failed on contentReady for', formItem.name, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
contentReadyRunningRef.current = false
|
|
|
|
|
|
}
|
2026-08-06 22:54:50 +00:00
|
|
|
|
},
|
|
|
|
|
|
// applyEditorReadOnly ref tabanlıdır; her render'da yeniden oluşması sorun değil.
|
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
|
[],
|
|
|
|
|
|
)
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const form = formInstanceRef.current
|
2026-08-06 22:54:50 +00:00
|
|
|
|
if (!form || mode === 'view' || !formData || !formItems?.length) return
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
const scriptFields = flattenFormGroups(formItemsRef.current)
|
2026-06-01 14:19:49 +00:00
|
|
|
|
.filter((formItem) => shouldRunEditorScriptOnContentReady(formItem.editorScript))
|
|
|
|
|
|
.map((formItem) => formItem.dataField)
|
2026-07-08 06:14:40 +00:00
|
|
|
|
.join(MULTIVALUE_DELIMITER)
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
if (!scriptFields) return
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
|
|
|
|
|
const scriptKey = `${mode}|${scriptFields}|${JSON.stringify(formData)}`
|
2026-08-06 22:54:50 +00:00
|
|
|
|
if (lastContentReadyScriptKeyRef.current === scriptKey) return
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
|
|
|
|
|
lastContentReadyScriptKeyRef.current = scriptKey
|
|
|
|
|
|
setTimeout(() => runReadOnlyScripts(form), 0)
|
2026-08-06 22:54:50 +00:00
|
|
|
|
}, [formData, formItems, mode, runReadOnlyScripts])
|
|
|
|
|
|
|
|
|
|
|
|
/** Platform editörünün render fonksiyonunu döner. */
|
|
|
|
|
|
const renderCustomEditor = (formItem: SimpleItemWithColData): ReactNode => {
|
|
|
|
|
|
const value = formData[formItem.dataField!]
|
|
|
|
|
|
const editorOptions = getEditorOptions(formItem)
|
|
|
|
|
|
const onChange = (next: any) => handleCustomEditorChange(formItem, next)
|
|
|
|
|
|
|
|
|
|
|
|
switch (formItem.editorType2) {
|
|
|
|
|
|
case PlatformEditorTypes.dxTagBox:
|
|
|
|
|
|
return (
|
|
|
|
|
|
<TagBoxEditorComponent
|
|
|
|
|
|
value={value || []}
|
|
|
|
|
|
setDefaultValue={false}
|
|
|
|
|
|
values={formData}
|
|
|
|
|
|
options={formItem.tagBoxOptions}
|
|
|
|
|
|
col={formItem.colData}
|
|
|
|
|
|
onValueChanged={onChange}
|
|
|
|
|
|
editorOptions={editorOptions}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)
|
|
|
|
|
|
case PlatformEditorTypes.dxGridBox:
|
|
|
|
|
|
return (
|
|
|
|
|
|
<GridBoxEditorComponent
|
|
|
|
|
|
value={value || []}
|
|
|
|
|
|
values={formData}
|
|
|
|
|
|
options={formItem.gridBoxOptions}
|
|
|
|
|
|
col={formItem.colData}
|
|
|
|
|
|
onValueChanged={onChange}
|
|
|
|
|
|
editorOptions={editorOptions}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)
|
|
|
|
|
|
case PlatformEditorTypes.dxImageUpload:
|
|
|
|
|
|
return (
|
|
|
|
|
|
<ImageUploadEditorComponent
|
|
|
|
|
|
value={value}
|
|
|
|
|
|
options={formItem.imageUploadOptions}
|
|
|
|
|
|
onValueChanged={onChange}
|
|
|
|
|
|
editorOptions={editorOptions}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)
|
|
|
|
|
|
case PlatformEditorTypes.dxImageViewer:
|
|
|
|
|
|
return (
|
|
|
|
|
|
<ImageViewerEditorComponent
|
|
|
|
|
|
value={value}
|
|
|
|
|
|
options={formItem.imageUploadOptions}
|
|
|
|
|
|
editorOptions={editorOptions}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)
|
|
|
|
|
|
default:
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const renderFormItem = (formItem: SimpleItemWithColData, index: number) => {
|
|
|
|
|
|
const key = getFormItemKey(formItem, index)
|
|
|
|
|
|
const label = {
|
|
|
|
|
|
text: translate('::' + formItem.colData?.captionName),
|
|
|
|
|
|
className: 'font-semibold',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!CUSTOM_EDITOR_TYPES.includes(formItem.editorType2 as any)) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SimpleItemDx
|
|
|
|
|
|
cssClass="font-semibold"
|
|
|
|
|
|
key={key}
|
|
|
|
|
|
{...formItem}
|
|
|
|
|
|
editorOptions={getEditorOptions(formItem, index)}
|
|
|
|
|
|
label={{ text: label.text }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Görsel editörleri DevExtreme'in bilmediği alan tiplerini yaymamak için
|
|
|
|
|
|
// yalnızca gerekli alanlarla oluşturulur.
|
|
|
|
|
|
const isImageEditor =
|
|
|
|
|
|
formItem.editorType2 === PlatformEditorTypes.dxImageUpload ||
|
|
|
|
|
|
formItem.editorType2 === PlatformEditorTypes.dxImageViewer
|
|
|
|
|
|
const itemProps = isImageEditor
|
|
|
|
|
|
? {
|
|
|
|
|
|
dataField: formItem.dataField,
|
|
|
|
|
|
name: formItem.name,
|
|
|
|
|
|
colSpan: formItem.colSpan,
|
|
|
|
|
|
isRequired: formItem.isRequired,
|
|
|
|
|
|
}
|
|
|
|
|
|
: formItem
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SimpleItemDx
|
|
|
|
|
|
cssClass="font-semibold"
|
|
|
|
|
|
key={key}
|
|
|
|
|
|
{...itemProps}
|
|
|
|
|
|
render={() => renderCustomEditor(formItem)}
|
|
|
|
|
|
label={label}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<FormDx
|
|
|
|
|
|
ref={refForm}
|
|
|
|
|
|
className={`${DX_CLASSNAMES} ${!isSubForm ? 'px-2' : ''} pb-2`}
|
|
|
|
|
|
formData={formData}
|
2026-08-06 22:54:50 +00:00
|
|
|
|
onFieldDataChanged={(e: FieldDataChangedEvent) => {
|
|
|
|
|
|
if (!e.dataField) return
|
2026-06-01 14:19:49 +00:00
|
|
|
|
|
2026-10-04 19:17:16 +00:00
|
|
|
|
// DevExtreme değeri formData nesnesine yerinde yazar; o durumda hasChanges false kalır ve
|
|
|
|
|
|
// state yenilenmez. Script'ler formDataRef'ten çalıştığı için ref her değişimde güncellenir,
|
|
|
|
|
|
// yoksa sonraki commit script'siz alanların değerini (ör. lookup seçimi) eski kopyayla ezerdi.
|
|
|
|
|
|
formDataRef.current = { ...formDataRef.current, [e.dataField]: e.value }
|
|
|
|
|
|
|
2026-06-01 14:19:49 +00:00
|
|
|
|
const newFormData = { ...formData, [e.dataField]: e.value }
|
|
|
|
|
|
let hasChanges = !Object.is(formData?.[e.dataField], e.value)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
// Parent alan değiştiğinde bağımlı (cascade) alanları temizle.
|
|
|
|
|
|
flattenFormGroups(formItemsRef.current)
|
|
|
|
|
|
.filter((item) => getCascadeParentFields(item).includes(e.dataField!))
|
|
|
|
|
|
.forEach((child) => {
|
|
|
|
|
|
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
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
// Editör güncellemesi tamamlandıktan sonra devre dışı durumlarını yenile.
|
|
|
|
|
|
setTimeout(updateCascadeDisabledStates, 0)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}}
|
|
|
|
|
|
onContentReady={(e) => {
|
2026-06-01 13:47:38 +00:00
|
|
|
|
formInstanceRef.current = e.component
|
2026-08-06 22:54:50 +00:00
|
|
|
|
runReadOnlyScripts(e.component)
|
|
|
|
|
|
|
|
|
|
|
|
const firstItem = (e.component.option('items') as any[])?.[0]?.items?.[0]
|
|
|
|
|
|
if (
|
|
|
|
|
|
!didAutoFocusRef.current &&
|
|
|
|
|
|
firstItem?.dataField &&
|
|
|
|
|
|
mode !== 'view' &&
|
|
|
|
|
|
!isTouchLikeDevice()
|
|
|
|
|
|
) {
|
2026-06-08 10:15:22 +00:00
|
|
|
|
didAutoFocusRef.current = true
|
2026-08-06 22:54:50 +00:00
|
|
|
|
e.component.getEditor(firstItem.dataField)?.focus()
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-08-06 22:54:50 +00:00
|
|
|
|
{formItems.map((formGroupItem, groupIndex) => (
|
|
|
|
|
|
<GroupItemDx
|
|
|
|
|
|
key={'formGroupItem-' + groupIndex}
|
|
|
|
|
|
colCount={formGroupItem.colCount}
|
|
|
|
|
|
colSpan={formGroupItem.colSpan}
|
2026-10-01 08:50:16 +00:00
|
|
|
|
caption={translateCaption(translate, formGroupItem.caption)}
|
2026-08-06 22:54:50 +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(renderFormItem)}
|
|
|
|
|
|
</GroupItemDx>
|
|
|
|
|
|
))}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</FormDx>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default FormDevExpress
|