2026-02-24 20:44:16 +00:00
|
|
|
|
import Container from '@/components/shared/Container'
|
|
|
|
|
|
import { Dialog, Notification, toast } from '@/components/ui'
|
|
|
|
|
|
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
|
|
|
|
|
|
import {
|
|
|
|
|
|
DbTypeEnum,
|
|
|
|
|
|
EditingFormItemDto,
|
2026-05-17 18:20:44 +00:00
|
|
|
|
FieldCustomValueTypeEnum,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
GridDto,
|
|
|
|
|
|
ListFormCustomizationTypeEnum,
|
|
|
|
|
|
PlatformEditorTypes,
|
|
|
|
|
|
SubFormTabTypeEnum,
|
|
|
|
|
|
} from '@/proxy/form/models'
|
|
|
|
|
|
import {
|
|
|
|
|
|
getListFormCustomization,
|
|
|
|
|
|
postListFormCustomization,
|
|
|
|
|
|
} from '@/services/list-form-customization.service'
|
|
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
|
|
|
|
import useResponsive from '@/utils/hooks/useResponsive'
|
2026-06-01 20:51:59 +00:00
|
|
|
|
import { executeEditorScript } from '@/utils/editorScriptRuntime'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import { Template } from 'devextreme-react/core/template'
|
|
|
|
|
|
import DataGrid, {
|
|
|
|
|
|
ColumnChooser,
|
|
|
|
|
|
ColumnFixing,
|
|
|
|
|
|
DataGridRef,
|
|
|
|
|
|
DataGridTypes,
|
|
|
|
|
|
Editing,
|
|
|
|
|
|
Export,
|
|
|
|
|
|
FilterPanel,
|
|
|
|
|
|
FilterRow,
|
|
|
|
|
|
Grouping,
|
|
|
|
|
|
GroupItem as GroupItemDx,
|
|
|
|
|
|
GroupPanel,
|
|
|
|
|
|
HeaderFilter,
|
|
|
|
|
|
LoadPanel,
|
|
|
|
|
|
Pager,
|
|
|
|
|
|
Paging,
|
|
|
|
|
|
Scrolling,
|
|
|
|
|
|
SearchPanel,
|
|
|
|
|
|
Selection,
|
|
|
|
|
|
Sorting,
|
|
|
|
|
|
Summary,
|
|
|
|
|
|
Toolbar,
|
|
|
|
|
|
TotalItem,
|
|
|
|
|
|
} from 'devextreme-react/data-grid'
|
|
|
|
|
|
import { Item } from 'devextreme-react/toolbar'
|
|
|
|
|
|
import { DataType } from 'devextreme/common'
|
|
|
|
|
|
import { captionize } from 'devextreme/core/utils/inflector'
|
|
|
|
|
|
import CustomStore from 'devextreme/data/custom_store'
|
|
|
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
|
|
|
import { Helmet } from 'react-helmet'
|
|
|
|
|
|
import SubForms from '../form/SubForms'
|
|
|
|
|
|
import { RowMode, SimpleItemWithColData } from '../form/types'
|
|
|
|
|
|
import { GridColumnData } from './GridColumnData'
|
|
|
|
|
|
import GridFilterDialogs from './GridFilterDialogs'
|
|
|
|
|
|
import {
|
|
|
|
|
|
addCss,
|
|
|
|
|
|
addJs,
|
|
|
|
|
|
autoNumber,
|
|
|
|
|
|
controlStyleCondition,
|
|
|
|
|
|
extractSearchParamsFields,
|
|
|
|
|
|
GridExtraFilterState,
|
|
|
|
|
|
setFormEditingExtraItemValues,
|
|
|
|
|
|
setGridPanelColor,
|
|
|
|
|
|
} from './Utils'
|
|
|
|
|
|
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 { useFilters } from './useFilters'
|
2026-05-23 21:12:01 +00:00
|
|
|
|
import { updateWorkflowApprovalToolbarItems, useToolbar } from './useToolbar'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
|
|
|
|
|
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
|
|
|
|
|
|
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
|
2026-05-17 18:20:44 +00:00
|
|
|
|
import { getList, getNextSequenceValue } from '@/services/form.service'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import { layoutTypes } from '../admin/listForm/edit/types'
|
|
|
|
|
|
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
|
|
|
|
|
|
import { useListFormColumns } from './useListFormColumns'
|
|
|
|
|
|
import { Loading } from '@/components/shared'
|
|
|
|
|
|
import { useStoreState } from '@/store'
|
2026-05-23 21:12:01 +00:00
|
|
|
|
import { workflowService } from '@/services/workflow.service'
|
2026-06-06 22:22:35 +00:00
|
|
|
|
import { NotePanel } from '../form/notes/NotePanel'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
interface GridProps {
|
|
|
|
|
|
listFormCode: string
|
|
|
|
|
|
searchParams?: URLSearchParams
|
|
|
|
|
|
isSubForm?: boolean
|
|
|
|
|
|
level?: number
|
|
|
|
|
|
refreshData?: () => Promise<void>
|
|
|
|
|
|
gridDto?: GridDto
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)' // kullanici tanimli gridState ile islem gormus gridin paneline ait renk
|
|
|
|
|
|
|
2026-05-23 21:12:01 +00:00
|
|
|
|
const isTemporaryDxKey = (key: unknown) => typeof key === 'string' && key.startsWith('_DX_KEY_')
|
|
|
|
|
|
|
|
|
|
|
|
const getPersistedInsertedKey = (
|
|
|
|
|
|
e: DataGridTypes.RowInsertedEvent<any, any>,
|
|
|
|
|
|
keyFieldName?: string,
|
|
|
|
|
|
) => {
|
|
|
|
|
|
const dataKey = keyFieldName ? e.data?.[keyFieldName] : undefined
|
|
|
|
|
|
if (dataKey !== undefined && dataKey !== null && !isTemporaryDxKey(dataKey)) {
|
|
|
|
|
|
return dataKey
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (e.key !== undefined && e.key !== null && !isTemporaryDxKey(e.key)) {
|
|
|
|
|
|
return e.key
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 13:47:38 +00:00
|
|
|
|
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()
|
2026-06-01 20:51:59 +00:00
|
|
|
|
setTimeout(apply, 0)
|
2026-06-01 13:47:38 +00:00
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getActiveEditingForm = (component: any) => {
|
|
|
|
|
|
const editForm = component?.getView?.('editingView')?._editForm
|
|
|
|
|
|
return editForm?.getEditor || editForm?.itemOption ? editForm : undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const flattenEditingFormItems = (items: any[] = []): EditingFormItemDto[] =>
|
|
|
|
|
|
items.flatMap((item) => [
|
|
|
|
|
|
...(item?.dataField ? [item] : []),
|
|
|
|
|
|
...flattenEditingFormItems(item?.items || []),
|
|
|
|
|
|
...(item?.tabs || []).flatMap((tab: any) => flattenEditingFormItems(tab?.items || [])),
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
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) =>
|
|
|
|
|
|
Boolean(script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')))
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const Grid = (props: GridProps) => {
|
|
|
|
|
|
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
|
|
|
|
|
const { translate } = useLocalization()
|
|
|
|
|
|
const { smaller } = useResponsive()
|
2026-06-06 18:31:03 +00:00
|
|
|
|
const currentUser = useStoreState((state) => state.auth.user)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
const gridRef = useRef<DataGridRef>()
|
|
|
|
|
|
const refListFormCode = useRef('')
|
|
|
|
|
|
const widgetGroupRef = useRef<HTMLDivElement>(null)
|
2026-05-31 09:36:51 +00:00
|
|
|
|
const editingFormDataRef = useRef<Record<string, any>>({})
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const editingFormInstanceRef = useRef<any>()
|
2026-02-24 20:44:16 +00:00
|
|
|
|
// Edit popup state kaydetmeyi engellemek için flag
|
|
|
|
|
|
const isEditingRef = useRef(false)
|
|
|
|
|
|
|
|
|
|
|
|
const [gridDataSource, setGridDataSource] = useState<CustomStore<any, any>>()
|
|
|
|
|
|
const [columnData, setColumnData] = useState<GridColumnData[]>()
|
|
|
|
|
|
const [formData, setFormData] = useState<any>()
|
|
|
|
|
|
const [mode, setMode] = useState<RowMode>('view')
|
|
|
|
|
|
const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([])
|
|
|
|
|
|
const [gridDto, setGridDto] = useState<GridDto>()
|
|
|
|
|
|
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
|
|
|
|
|
|
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
|
2026-06-06 22:22:35 +00:00
|
|
|
|
const [notePanelTarget, setNotePanelTarget] = useState<{
|
|
|
|
|
|
entityName: string
|
|
|
|
|
|
entityId: string
|
|
|
|
|
|
} | null>(null)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
type EditorOptionsWithButtons = {
|
|
|
|
|
|
buttons?: any[]
|
|
|
|
|
|
} & Record<string, any>
|
|
|
|
|
|
|
|
|
|
|
|
const defaultSearchParams = useRef<string | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const initializeGrid = async () => {
|
|
|
|
|
|
const response = await getList({ listFormCode })
|
|
|
|
|
|
setGridDto(response.data)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (extGridDto === undefined) {
|
|
|
|
|
|
initializeGrid()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setGridDto(extGridDto)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [listFormCode, extGridDto])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!defaultSearchParams.current) {
|
|
|
|
|
|
defaultSearchParams.current = searchParams?.get('filter') ?? null
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [searchParams])
|
|
|
|
|
|
|
|
|
|
|
|
// StateStoring için storageKey'i memoize et
|
|
|
|
|
|
const storageKey = useMemo(() => {
|
|
|
|
|
|
return gridDto?.gridOptions.stateStoringDto?.storageKey ?? ''
|
|
|
|
|
|
}, [gridDto?.gridOptions.stateStoringDto?.storageKey])
|
|
|
|
|
|
|
|
|
|
|
|
const customSaveState = useCallback(
|
|
|
|
|
|
(state: any) => {
|
|
|
|
|
|
if (isEditingRef.current) {
|
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
|
}
|
|
|
|
|
|
return postListFormCustomization({
|
|
|
|
|
|
listFormCode: listFormCode,
|
|
|
|
|
|
customizationType: ListFormCustomizationTypeEnum.GridState,
|
|
|
|
|
|
filterName: `list-${storageKey}`,
|
|
|
|
|
|
customizationData: JSON.stringify(state),
|
|
|
|
|
|
})
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
setGridPanelColor(statedGridPanelColor)
|
|
|
|
|
|
toast.push(
|
|
|
|
|
|
<Notification type="success" duration={2000}>
|
|
|
|
|
|
{translate('::ListForms.ListForm.GridStateSaved')}
|
|
|
|
|
|
</Notification>,
|
|
|
|
|
|
{
|
|
|
|
|
|
placement: 'top-end',
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
|
toast.push(
|
|
|
|
|
|
<Notification type="danger" duration={2500}>
|
|
|
|
|
|
{translate('::ListForms.ListForm.GridStateSaveError')}
|
|
|
|
|
|
</Notification>,
|
|
|
|
|
|
{
|
|
|
|
|
|
placement: 'top-end',
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
throw err
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
[listFormCode, storageKey, translate],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const { filterToolbarData, ...filterData } = useFilters({
|
|
|
|
|
|
gridDto,
|
|
|
|
|
|
gridRef,
|
|
|
|
|
|
listFormCode,
|
|
|
|
|
|
saveGridState: customSaveState,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
|
2026-06-06 22:22:35 +00:00
|
|
|
|
|
|
|
|
|
|
const openNotePanel = useCallback(
|
|
|
|
|
|
(rowData: Record<string, any>) => {
|
|
|
|
|
|
const keyFieldName = gridDto?.gridOptions.keyFieldName
|
|
|
|
|
|
const entityId = getValueByField(rowData, keyFieldName)
|
|
|
|
|
|
if (entityId === undefined || entityId === null || entityId === '') {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setNotePanelTarget({
|
|
|
|
|
|
entityName: gridDto?.gridOptions.listFormCode ?? listFormCode,
|
|
|
|
|
|
entityId: String(entityId),
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
[gridDto, listFormCode],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const { getBandedColumns } = useListFormColumns({
|
|
|
|
|
|
gridDto,
|
|
|
|
|
|
listFormCode,
|
|
|
|
|
|
isSubForm,
|
|
|
|
|
|
gridRef,
|
2026-06-06 22:22:35 +00:00
|
|
|
|
onShowNote: openNotePanel,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const getSelectedRowKeys = useCallback(async () => {
|
|
|
|
|
|
const grd = gridRef.current?.instance()
|
|
|
|
|
|
if (!grd) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await grd.getSelectedRowKeys()
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
const getSelectedRowsData = useCallback(() => {
|
|
|
|
|
|
const grd = gridRef.current?.instance()
|
|
|
|
|
|
if (!grd) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return grd.getSelectedRowsData()
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
2026-05-23 21:12:01 +00:00
|
|
|
|
const updateWorkflowApprovalButtons = useCallback(
|
|
|
|
|
|
(component?: any, selectedRowsData?: Record<string, unknown>[]) => {
|
|
|
|
|
|
const grd = component ?? gridRef.current?.instance()
|
|
|
|
|
|
if (!grd) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateWorkflowApprovalToolbarItems(
|
|
|
|
|
|
grd,
|
|
|
|
|
|
gridDto?.gridOptions.workflowDto,
|
|
|
|
|
|
selectedRowsData ?? grd.getSelectedRowsData(),
|
2026-06-06 18:31:03 +00:00
|
|
|
|
currentUser,
|
2026-05-23 21:12:01 +00:00
|
|
|
|
)
|
|
|
|
|
|
},
|
2026-06-06 18:31:03 +00:00
|
|
|
|
[currentUser, gridDto],
|
2026-05-23 21:12:01 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const refreshData = useCallback(() => {
|
2026-05-23 21:12:01 +00:00
|
|
|
|
const grd = gridRef.current?.instance()
|
|
|
|
|
|
const refreshResult = grd?.refresh()
|
|
|
|
|
|
Promise.resolve(refreshResult).finally(() => updateWorkflowApprovalButtons(grd))
|
|
|
|
|
|
}, [updateWorkflowApprovalButtons])
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
const getFilter = useCallback(() => {
|
|
|
|
|
|
const grd = gridRef.current?.instance()
|
|
|
|
|
|
if (!grd) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return grd.getCombinedFilter()
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
const layout = layoutTypes.grid
|
|
|
|
|
|
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
|
|
|
|
|
gridDto,
|
|
|
|
|
|
listFormCode,
|
|
|
|
|
|
getSelectedRowKeys,
|
|
|
|
|
|
getSelectedRowsData,
|
|
|
|
|
|
refreshData,
|
|
|
|
|
|
getFilter,
|
|
|
|
|
|
layout,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const onSelectionChanged = useCallback(
|
|
|
|
|
|
(data: any) => {
|
|
|
|
|
|
const grdOpt = gridDto?.gridOptions
|
|
|
|
|
|
const grd = gridRef.current?.instance()
|
|
|
|
|
|
if (!grdOpt || !grd) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// kullanicinin yetkisi varsa ve birden fazla kayit secili ise coklu silme gorunsun
|
|
|
|
|
|
if (grdOpt.editingOptionDto?.allowDeleting) {
|
|
|
|
|
|
// && abp.auth.isGranted(grdOpt.permissionDto?.d)
|
|
|
|
|
|
// kullanicinin silme yetkisi var ise
|
|
|
|
|
|
const opt = grd.option('toolbar')
|
|
|
|
|
|
const deleteSelectedRecordsIndex = opt?.items
|
|
|
|
|
|
?.map((e: any) => e.name)
|
|
|
|
|
|
.indexOf('deleteSelectedRecords')
|
|
|
|
|
|
// deleteSelectedRecords ismindeki custom butonun index degerini bul
|
|
|
|
|
|
|
|
|
|
|
|
grd.option(
|
|
|
|
|
|
`toolbar.items[${deleteSelectedRecordsIndex}].options.visible`,
|
|
|
|
|
|
data.selectedRowsData.length > 1,
|
|
|
|
|
|
) // birden fazla kayit secilmis ise gorunsun
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SubForm'ları gösterebilmek için secili satiri formData'ya at
|
2026-05-23 21:12:01 +00:00
|
|
|
|
updateWorkflowApprovalButtons(grd, data.selectedRowsData)
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (data.selectedRowsData.length) {
|
|
|
|
|
|
setFormData(data.selectedRowsData[0])
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
[gridDto],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const onCellPrepared = useCallback(
|
|
|
|
|
|
(e: any) => {
|
|
|
|
|
|
const columnFormats = gridDto?.columnFormats
|
|
|
|
|
|
if (!columnFormats) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// satir, hucre yada header vb. kisimlara conditional style uygulamak icin
|
|
|
|
|
|
for (let indxCol = 0; indxCol < columnFormats.length; indxCol++) {
|
|
|
|
|
|
const colFormat = columnFormats[indxCol]
|
|
|
|
|
|
for (let indxStyl = 0; indxStyl < colFormat.columnStylingDto.length; indxStyl++) {
|
|
|
|
|
|
const colStyle = colFormat.columnStylingDto[indxStyl] // uygulanacak style
|
|
|
|
|
|
if (e.rowType == colStyle.rowType) {
|
|
|
|
|
|
// header, filter, data, group, summaries ..her birisine style uygulanabilir
|
|
|
|
|
|
// style bütün satıra uygulansın olarak seçili ise yada sadece ilgili field üzerinde ise
|
|
|
|
|
|
if (colStyle.useRow || e.column.dataField == colFormat.fieldName) {
|
|
|
|
|
|
if (
|
|
|
|
|
|
!colStyle.conditionValue ||
|
|
|
|
|
|
controlStyleCondition(e.data, colFormat.fieldName, colStyle)
|
|
|
|
|
|
) {
|
|
|
|
|
|
// css sınıf ismi var ise uygula
|
|
|
|
|
|
if (colStyle.cssClassName) {
|
|
|
|
|
|
e.cellElement.addClass(colStyle.cssClassName)
|
|
|
|
|
|
}
|
|
|
|
|
|
// css inline style var ise uygula
|
|
|
|
|
|
if (colStyle.cssStyles) {
|
|
|
|
|
|
e.cellElement.attr(
|
|
|
|
|
|
'style',
|
|
|
|
|
|
e.cellElement.attr('style') + ';' + colStyle.cssStyles,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
[gridDto],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const onInitNewRow = useCallback(
|
|
|
|
|
|
(e: any) => {
|
|
|
|
|
|
if (!gridDto?.columnFormats) {
|
2026-05-31 09:36:51 +00:00
|
|
|
|
editingFormDataRef.current = { ...(e.data || {}) }
|
2026-02-24 20:44:16 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setMode('new')
|
|
|
|
|
|
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
|
|
|
|
|
|
2026-05-17 18:20:44 +00:00
|
|
|
|
e.promise = (async () => {
|
|
|
|
|
|
const rawFilter = searchParams?.get('filter')
|
|
|
|
|
|
let filters: any[] = []
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-05-17 18:20:44 +00:00
|
|
|
|
if (rawFilter) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(rawFilter)
|
|
|
|
|
|
filters = extractSearchParamsFields(parsed)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Filter parse edilemedi:', error)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 18:20:44 +00:00
|
|
|
|
for (const colFormat of gridDto.columnFormats) {
|
|
|
|
|
|
const fieldName = colFormat.fieldName
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-05-17 18:20:44 +00:00
|
|
|
|
if (!fieldName) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-05-17 18:20:44 +00:00
|
|
|
|
// ExtraFilters içerisinde ilgili Field varsa, default değerleri set etme
|
|
|
|
|
|
if (extraFilters.some((f) => f.fieldName === fieldName)) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1) Önce grid default value set et
|
|
|
|
|
|
if (colFormat.defaultValue !== null && colFormat.defaultValue !== undefined) {
|
|
|
|
|
|
if (
|
|
|
|
|
|
typeof colFormat.defaultValue === 'string' &&
|
|
|
|
|
|
colFormat.defaultValue === '@AUTONUMBER'
|
|
|
|
|
|
) {
|
|
|
|
|
|
e.data[fieldName] = autoNumber()
|
|
|
|
|
|
} else if (colFormat.defaultValueType === FieldCustomValueTypeEnum.Sequence) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getNextSequenceValue(String(colFormat.defaultValue))
|
|
|
|
|
|
e.data[fieldName] = response.data
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Sequence default value alınamadı:', {
|
|
|
|
|
|
fieldName,
|
|
|
|
|
|
defaultValue: colFormat.defaultValue,
|
|
|
|
|
|
error,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
e.data[fieldName] = colFormat.defaultValue
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2) Sonra URL / prop filter varsa üzerine yaz
|
|
|
|
|
|
const fieldMatch = filters.find(([field]) => field === fieldName)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
if (fieldMatch) {
|
2026-05-17 18:20:44 +00:00
|
|
|
|
const val = fieldMatch[2]
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const dType = colFormat.dataType as DataType
|
|
|
|
|
|
|
|
|
|
|
|
switch (dType) {
|
|
|
|
|
|
case 'date':
|
|
|
|
|
|
case 'datetime':
|
2026-05-17 18:20:44 +00:00
|
|
|
|
e.data[fieldName] = new Date(val)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
break
|
2026-05-17 18:20:44 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
case 'number':
|
2026-05-17 18:20:44 +00:00
|
|
|
|
e.data[fieldName] = Number(val)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
break
|
2026-05-17 18:20:44 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
case 'boolean':
|
2026-05-17 18:20:44 +00:00
|
|
|
|
e.data[fieldName] = val === true || val === 'true'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
break
|
2026-05-17 18:20:44 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
case 'object':
|
|
|
|
|
|
try {
|
2026-05-17 18:20:44 +00:00
|
|
|
|
e.data[fieldName] = JSON.parse(val)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
e.data[fieldName] = val
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
break
|
2026-05-17 18:20:44 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
default:
|
2026-05-17 18:20:44 +00:00
|
|
|
|
e.data[fieldName] = val
|
2026-02-24 20:44:16 +00:00
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-31 09:36:51 +00:00
|
|
|
|
|
|
|
|
|
|
editingFormDataRef.current = { ...(e.data || {}) }
|
2026-05-17 18:20:44 +00:00
|
|
|
|
})()
|
2026-02-24 20:44:16 +00:00
|
|
|
|
},
|
2026-05-17 18:20:44 +00:00
|
|
|
|
[gridDto, searchParams, extraFilters, getNextSequenceValue],
|
2026-02-24 20:44:16 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-23 21:12:01 +00:00
|
|
|
|
const onRowInserting = useCallback(
|
|
|
|
|
|
(e: DataGridTypes.RowInsertingEvent<any, any>) => {
|
|
|
|
|
|
if (!gridDto?.columnFormats) {
|
|
|
|
|
|
e.data = setFormEditingExtraItemValues(e.data)
|
|
|
|
|
|
return
|
2026-05-17 18:20:44 +00:00
|
|
|
|
}
|
2026-05-23 21:12:01 +00:00
|
|
|
|
const allowedFields = gridDto.columnFormats
|
|
|
|
|
|
.filter((f) => f.allowAdding)
|
|
|
|
|
|
.map((f) => f.fieldName)
|
|
|
|
|
|
const filteredData: any = {}
|
|
|
|
|
|
for (const key of allowedFields) {
|
|
|
|
|
|
if (e.data.hasOwnProperty(key) && key) {
|
|
|
|
|
|
filteredData[key] = e.data[key]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
e.data = setFormEditingExtraItemValues(filteredData)
|
|
|
|
|
|
},
|
|
|
|
|
|
[gridDto],
|
|
|
|
|
|
)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
const onRowUpdating = useCallback(
|
|
|
|
|
|
(e: DataGridTypes.RowUpdatingEvent<any, any>) => {
|
2026-05-17 18:20:44 +00:00
|
|
|
|
if (!gridDto?.columnFormats) return
|
2026-05-23 21:12:01 +00:00
|
|
|
|
const allowedFields = gridDto.columnFormats
|
|
|
|
|
|
.filter((f) => f.allowEditing)
|
|
|
|
|
|
.map((f) => f.fieldName)
|
2026-05-17 18:20:44 +00:00
|
|
|
|
let newData = { ...e.oldData, ...e.newData }
|
|
|
|
|
|
// Remove keys not allowed
|
|
|
|
|
|
Object.keys(newData).forEach((key) => {
|
|
|
|
|
|
if (!allowedFields.includes(key) || key.includes(':')) {
|
|
|
|
|
|
delete newData[key]
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
2026-05-17 18:20:44 +00:00
|
|
|
|
})
|
|
|
|
|
|
newData = setFormEditingExtraItemValues(newData)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (gridDto?.gridOptions.keyFieldName) {
|
2026-05-17 18:20:44 +00:00
|
|
|
|
delete newData[gridDto?.gridOptions.keyFieldName]
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
2026-05-17 18:20:44 +00:00
|
|
|
|
e.newData = newData
|
2026-02-24 20:44:16 +00:00
|
|
|
|
},
|
|
|
|
|
|
[gridDto],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const onEditingStart = useCallback(
|
|
|
|
|
|
(e: DataGridTypes.EditingStartEvent<any, any>) => {
|
|
|
|
|
|
isEditingRef.current = true
|
2026-05-31 09:36:51 +00:00
|
|
|
|
editingFormDataRef.current = { ...(e.data || {}) }
|
2026-02-24 20:44:16 +00:00
|
|
|
|
setMode('edit')
|
|
|
|
|
|
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
|
|
|
|
|
const columns = e.component.option('columns') as GridColumnData[]
|
2026-05-17 18:20:44 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
// FormEditingExtraItem field ise datayı doldur
|
|
|
|
|
|
columns?.forEach((col) => {
|
|
|
|
|
|
if (!col.dataField?.includes(':')) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const field = col.dataField.split(':')
|
|
|
|
|
|
if (!e.data[field[0]]) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-23 21:12:01 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const json = JSON.parse(e.data[field[0]])
|
|
|
|
|
|
e.data[col.dataField] = json[field[1]]
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
[gridDto, mode],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const onDataErrorOccurred = useCallback((e: DataGridTypes.DataErrorOccurredEvent<any, any>) => {
|
|
|
|
|
|
toast.push(
|
|
|
|
|
|
<Notification type="danger" duration={2000}>
|
|
|
|
|
|
{e.error?.message}
|
|
|
|
|
|
</Notification>,
|
|
|
|
|
|
{
|
|
|
|
|
|
placement: 'top-end',
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
// Cascade parent fields mapping'i memoize et - popup açılışını hızlandırır
|
|
|
|
|
|
// Ayrıca parent -> child ilişkisi için reverse map oluştur (performans için)
|
|
|
|
|
|
const { cascadeFieldsMap, parentToChildrenMap } = useMemo(() => {
|
|
|
|
|
|
if (!gridDto) return { cascadeFieldsMap: new Map(), parentToChildrenMap: new Map() }
|
|
|
|
|
|
|
|
|
|
|
|
const cascadeMap = new Map<
|
|
|
|
|
|
string,
|
|
|
|
|
|
{
|
|
|
|
|
|
parentFields: string[]
|
|
|
|
|
|
childFields?: string[]
|
|
|
|
|
|
}
|
|
|
|
|
|
>()
|
|
|
|
|
|
|
|
|
|
|
|
const parentChildMap = new Map<string, Set<string>>()
|
|
|
|
|
|
|
|
|
|
|
|
gridDto.columnFormats.forEach((col) => {
|
|
|
|
|
|
if (col.lookupDto?.cascadeParentFields && col.fieldName) {
|
|
|
|
|
|
const parentFields = col.lookupDto.cascadeParentFields
|
|
|
|
|
|
.split(',')
|
|
|
|
|
|
.map((f: string) => f.trim())
|
|
|
|
|
|
const childFields = col.lookupDto.cascadeEmptyFields
|
|
|
|
|
|
?.split(',')
|
|
|
|
|
|
.map((f: string) => f.trim())
|
|
|
|
|
|
|
|
|
|
|
|
cascadeMap.set(col.fieldName, {
|
|
|
|
|
|
parentFields,
|
|
|
|
|
|
childFields,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// Her parent field için, hangi childları etkilediğini kaydet
|
|
|
|
|
|
parentFields.forEach((parentField) => {
|
|
|
|
|
|
if (!parentChildMap.has(parentField)) {
|
|
|
|
|
|
parentChildMap.set(parentField, new Set())
|
|
|
|
|
|
}
|
|
|
|
|
|
if (col.fieldName) {
|
|
|
|
|
|
parentChildMap.get(parentField)!.add(col.fieldName)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return { cascadeFieldsMap: cascadeMap, parentToChildrenMap: parentChildMap }
|
|
|
|
|
|
}, [gridDto])
|
|
|
|
|
|
|
|
|
|
|
|
const onEditorPreparing = useCallback(
|
|
|
|
|
|
(editor: DataGridTypes.EditorPreparingEvent<any, any>) => {
|
|
|
|
|
|
if (editor.parentType === 'dataRow' && editor.dataField && gridDto) {
|
|
|
|
|
|
const formItem = gridDto.gridOptions.editingFormDto
|
2026-06-01 13:47:38 +00:00
|
|
|
|
.flatMap((group) => flattenEditingFormItems([group]))
|
2026-02-24 20:44:16 +00:00
|
|
|
|
.find((i) => i.dataField === editor.dataField)
|
2026-05-23 21:12:01 +00:00
|
|
|
|
const fieldName = editor.dataField.split(':')[0]
|
|
|
|
|
|
const columnFormat = gridDto.columnFormats.find((column) => column.fieldName === fieldName)
|
|
|
|
|
|
const isNewRow = Boolean((editor as any).row?.isNewRow) || mode === 'new'
|
|
|
|
|
|
|
2026-05-31 18:16:41 +00:00
|
|
|
|
const hasReadOnlyEditorOption = editor.editorOptions?.readOnly === true
|
|
|
|
|
|
const hasDisabledEditorOption = editor.editorOptions?.disabled === true
|
|
|
|
|
|
|
2026-05-23 21:12:01 +00:00
|
|
|
|
if (
|
|
|
|
|
|
(isNewRow && columnFormat?.allowAdding === false) ||
|
|
|
|
|
|
(!isNewRow && columnFormat?.allowEditing === false)
|
|
|
|
|
|
) {
|
|
|
|
|
|
editor.editorOptions.readOnly = true
|
|
|
|
|
|
} else if (isNewRow && columnFormat?.allowAdding === true) {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
if (!hasReadOnlyEditorOption) {
|
|
|
|
|
|
editor.editorOptions.readOnly = false
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!hasDisabledEditorOption) {
|
|
|
|
|
|
editor.editorOptions.disabled = false
|
|
|
|
|
|
}
|
2026-05-23 21:12:01 +00:00
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
// Cascade mantığı
|
|
|
|
|
|
const cascadeInfo = cascadeFieldsMap.get(editor.dataField)
|
|
|
|
|
|
if (cascadeInfo) {
|
|
|
|
|
|
const parentFields = cascadeInfo.parentFields
|
|
|
|
|
|
const childFields = cascadeInfo.childFields
|
|
|
|
|
|
const affectedChildren = parentToChildrenMap.get(editor.dataField!)
|
|
|
|
|
|
const prevHandler = editor.editorOptions.onValueChanged
|
|
|
|
|
|
|
|
|
|
|
|
editor.editorOptions.onValueChanged = (e: any) => {
|
|
|
|
|
|
prevHandler?.(e)
|
|
|
|
|
|
|
|
|
|
|
|
const grid = editor.component
|
|
|
|
|
|
const rowKey = grid.option('editing.editRowKey')
|
|
|
|
|
|
const rowIndex = grid.getRowIndexByKey(rowKey)
|
|
|
|
|
|
|
|
|
|
|
|
// Parent field değiştiğinde child fieldleri temizle
|
|
|
|
|
|
if (childFields && rowIndex >= 0) {
|
|
|
|
|
|
childFields.forEach((childField: string) => {
|
|
|
|
|
|
grid.cellValue(rowIndex, childField, null)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Sadece bu parent'tan etkilenen childların disabled durumunu güncelle
|
|
|
|
|
|
if (affectedChildren?.size) {
|
|
|
|
|
|
const formInstance = grid.option('editing.form') as any
|
|
|
|
|
|
if (formInstance?.getEditor) {
|
|
|
|
|
|
const visibleRows = grid.getVisibleRows()
|
|
|
|
|
|
const rowData = visibleRows.find((r) => r.key === rowKey)?.data
|
|
|
|
|
|
|
|
|
|
|
|
if (rowData) {
|
|
|
|
|
|
affectedChildren.forEach((childFieldName: string) => {
|
|
|
|
|
|
const childInfo = cascadeFieldsMap.get(childFieldName)
|
|
|
|
|
|
if (childInfo) {
|
|
|
|
|
|
const shouldDisable = childInfo.parentFields.some(
|
|
|
|
|
|
(pf: string) => !rowData[pf],
|
|
|
|
|
|
)
|
|
|
|
|
|
try {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
const childFormItem = gridDto.gridOptions.editingFormDto
|
2026-06-01 13:47:38 +00:00
|
|
|
|
.flatMap((group) => flattenEditingFormItems([group]))
|
2026-05-31 18:16:41 +00:00
|
|
|
|
.find((i) => i.dataField === childFieldName)
|
|
|
|
|
|
const childEditorOptions = childFormItem?.editorOptions
|
|
|
|
|
|
? JSON.parse(childFormItem.editorOptions)
|
|
|
|
|
|
: {}
|
|
|
|
|
|
const childEditor = formInstance.getEditor(childFieldName)
|
|
|
|
|
|
|
|
|
|
|
|
if (childEditorOptions?.disabled === true) {
|
|
|
|
|
|
childEditor?.option('disabled', true)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
childEditor?.option('disabled', shouldDisable)
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} catch {}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// İlk açılışta disabled durumunu kontrol et
|
|
|
|
|
|
const grid = editor.component
|
|
|
|
|
|
const rowKey = grid.option('editing.editRowKey')
|
|
|
|
|
|
const visibleRows = grid.getVisibleRows()
|
|
|
|
|
|
const rowData = visibleRows.find((r) => r.key === rowKey)?.data
|
|
|
|
|
|
|
|
|
|
|
|
if (rowData && parentFields.some((pf: string) => !rowData[pf])) {
|
|
|
|
|
|
editor.editorOptions.disabled = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (formItem?.editorScript) {
|
|
|
|
|
|
const prevHandler = editor.editorOptions.onValueChanged
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const editorDataField = editor.dataField
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
editor.editorOptions.onValueChanged = (e: any) => {
|
|
|
|
|
|
if (prevHandler) prevHandler(e)
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const grid = editor.component
|
|
|
|
|
|
const rowKey = grid.option('editing.editRowKey')
|
|
|
|
|
|
const rowIndex = grid.getRowIndexByKey(rowKey)
|
|
|
|
|
|
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const formData = {
|
|
|
|
|
|
...(grid.getVisibleRows().find((r) => r.key === rowKey)?.data || {}),
|
|
|
|
|
|
[editorDataField]: e.value,
|
|
|
|
|
|
}
|
|
|
|
|
|
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
|
|
|
|
|
|
setFormEditorReadOnly(
|
|
|
|
|
|
editingFormInstanceRef.current ?? getActiveEditingForm(grid),
|
|
|
|
|
|
field,
|
|
|
|
|
|
readOnly,
|
|
|
|
|
|
)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const setFormData = (newData: any) => {
|
|
|
|
|
|
if (rowIndex >= 0) {
|
|
|
|
|
|
Object.keys(newData).forEach((field) => {
|
|
|
|
|
|
grid.cellValue(rowIndex, field, newData[field])
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
executeEditorScript(formItem.editorScript!, {
|
|
|
|
|
|
formData,
|
|
|
|
|
|
e,
|
|
|
|
|
|
editor,
|
|
|
|
|
|
runtimeSetEditorReadOnly,
|
|
|
|
|
|
setFormData,
|
|
|
|
|
|
})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Script exec error', formItem.dataField, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (editor.editorOptions?.buttons) {
|
|
|
|
|
|
editor.editorOptions.buttons = editor.editorOptions.buttons.map((btn: any) => {
|
|
|
|
|
|
if (btn?.options?.onClick && typeof btn.options.onClick === 'function') {
|
|
|
|
|
|
const origClick = btn.options.onClick
|
|
|
|
|
|
btn.options.onClick = (e: any) => {
|
|
|
|
|
|
const grid = editor.component
|
|
|
|
|
|
const rowKey = grid.option('editing.editRowKey')
|
|
|
|
|
|
const rowIndex = grid.getRowIndexByKey(rowKey)
|
|
|
|
|
|
const formData = grid.getVisibleRows().find((r) => r.key === rowKey)?.data || {}
|
|
|
|
|
|
|
|
|
|
|
|
origClick({
|
|
|
|
|
|
...e,
|
|
|
|
|
|
formData,
|
|
|
|
|
|
fieldName: editor.dataField,
|
|
|
|
|
|
rowKey,
|
|
|
|
|
|
rowIndex,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return btn
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
[gridDto, cascadeFieldsMap],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const customLoadState = useCallback(() => {
|
|
|
|
|
|
return getListFormCustomization(
|
|
|
|
|
|
listFormCode,
|
|
|
|
|
|
ListFormCustomizationTypeEnum.GridState,
|
|
|
|
|
|
`list-${storageKey}`,
|
|
|
|
|
|
).then((response: any) => {
|
|
|
|
|
|
if (response.data?.length > 0) {
|
|
|
|
|
|
setGridPanelColor(statedGridPanelColor)
|
|
|
|
|
|
return JSON.parse(response.data[0].customizationData)
|
|
|
|
|
|
}
|
|
|
|
|
|
// Veri yoksa null dön (DevExtreme bunu default state olarak algılar)
|
|
|
|
|
|
return null
|
|
|
|
|
|
})
|
|
|
|
|
|
}, [listFormCode, storageKey])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-02-25 11:57:25 +00:00
|
|
|
|
// React state'i sıfırla - eski değerlerin customLoadState'i erken tetiklemesini önle
|
|
|
|
|
|
setGridDataSource(undefined)
|
|
|
|
|
|
setColumnData(undefined)
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (gridRef?.current) {
|
|
|
|
|
|
const instance = gridRef?.current?.instance()
|
|
|
|
|
|
if (instance) {
|
|
|
|
|
|
instance.option('columns', undefined)
|
|
|
|
|
|
instance.option('remoteOperations', false)
|
|
|
|
|
|
instance.option('dataSource', undefined)
|
|
|
|
|
|
instance.state(null)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (refListFormCode.current !== listFormCode) {
|
|
|
|
|
|
setExtraFilters([])
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [listFormCode])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!gridDto) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set js and css
|
|
|
|
|
|
const grdOpt = gridDto.gridOptions
|
|
|
|
|
|
if (grdOpt.customJsSources.length) {
|
|
|
|
|
|
for (const js of grdOpt.customJsSources) {
|
|
|
|
|
|
addJs(js)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (grdOpt.customStyleSources.length) {
|
|
|
|
|
|
for (const css of grdOpt.customStyleSources) {
|
|
|
|
|
|
addCss(css)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (gridDto?.gridOptions.extraFilterDto) {
|
|
|
|
|
|
setExtraFilters(
|
|
|
|
|
|
gridDto.gridOptions.extraFilterDto.map((f) => ({
|
|
|
|
|
|
fieldName: f.fieldName,
|
|
|
|
|
|
operator: f.operator,
|
|
|
|
|
|
controlType: f.controlType,
|
|
|
|
|
|
value: f.defaultValue ?? '',
|
|
|
|
|
|
})),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (gridDto.gridOptions.editingOptionDto?.popup) {
|
|
|
|
|
|
setIsPopupFullScreen(gridDto.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [gridDto])
|
|
|
|
|
|
|
|
|
|
|
|
// extraFilters değişikliklerini useMemo ile optimize et
|
|
|
|
|
|
const filterParams = useMemo(() => {
|
|
|
|
|
|
const activeFilters = extraFilters.filter((f) => f.value)
|
|
|
|
|
|
|
|
|
|
|
|
let base: any = null
|
|
|
|
|
|
if (defaultSearchParams.current) {
|
|
|
|
|
|
base = JSON.parse(defaultSearchParams.current)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Default filter tripletlerini çıkar
|
|
|
|
|
|
const baseTriplets = extractSearchParamsFields(base)
|
|
|
|
|
|
|
|
|
|
|
|
// Extra filter tripletleri hazırla
|
|
|
|
|
|
const extraTriplets = activeFilters.map(
|
|
|
|
|
|
(f) => [f.fieldName, f.operator, f.value] as [string, string, any],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Tripletleri birleştir, aynı field+op varsa sonuncuyu al
|
|
|
|
|
|
const merged = [...baseTriplets, ...extraTriplets].reduce(
|
|
|
|
|
|
(acc, cur) => {
|
|
|
|
|
|
const [field, op] = cur
|
|
|
|
|
|
const idx = acc.findIndex((a) => a[0] === field && a[1] === op)
|
|
|
|
|
|
if (idx >= 0) {
|
|
|
|
|
|
acc[idx] = cur // varsa üzerine yaz
|
|
|
|
|
|
} else {
|
|
|
|
|
|
acc.push(cur)
|
|
|
|
|
|
}
|
|
|
|
|
|
return acc
|
|
|
|
|
|
},
|
|
|
|
|
|
[] as [string, string, any][],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Tek filtre varsa direkt yaz
|
|
|
|
|
|
let filter: any = null
|
|
|
|
|
|
if (merged.length === 1) {
|
|
|
|
|
|
filter = merged[0]
|
|
|
|
|
|
} else if (merged.length > 1) {
|
|
|
|
|
|
filter = merged.reduce((acc, f, idx) => {
|
|
|
|
|
|
if (idx === 0) return f
|
|
|
|
|
|
return [acc, 'and', f]
|
|
|
|
|
|
}, null as any)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return filter
|
|
|
|
|
|
}, [extraFilters])
|
|
|
|
|
|
|
|
|
|
|
|
// Filter değiştiğinde searchParams'ı güncelle (side effect)
|
|
|
|
|
|
// NOT: searchParams'ı dependency'e koymuyoruz çünkü sonsuz döngü yaratır
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (filterParams) {
|
|
|
|
|
|
searchParams?.set('filter', JSON.stringify(filterParams))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
searchParams?.delete('filter')
|
|
|
|
|
|
}
|
|
|
|
|
|
// Grid'i yenile
|
|
|
|
|
|
gridRef.current?.instance()?.refresh()
|
|
|
|
|
|
}, [filterParams])
|
|
|
|
|
|
|
|
|
|
|
|
// Kolonları oluştur - dil değiştiğinde güncelle
|
|
|
|
|
|
const memoizedColumns = useMemo(() => {
|
2026-06-06 18:31:03 +00:00
|
|
|
|
if (!gridDto) return undefined
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
const cols = getBandedColumns()
|
|
|
|
|
|
|
|
|
|
|
|
cols?.forEach((col: any) => {
|
|
|
|
|
|
const eo = col.editorOptions
|
|
|
|
|
|
|
|
|
|
|
|
// Sadece phoneGlobal formatlı kolonlarda çalış
|
|
|
|
|
|
if (eo?.format === 'phoneGlobal') {
|
|
|
|
|
|
// DevExtreme bazen string tipinde formatter'ı çağırmaz
|
|
|
|
|
|
// Bu yüzden her durumda çalışması için customizeText ekleyeceğiz
|
|
|
|
|
|
col.dataType = 'string'
|
|
|
|
|
|
|
|
|
|
|
|
const formatter = (value: any) => {
|
|
|
|
|
|
if (!value) return ''
|
|
|
|
|
|
|
|
|
|
|
|
// string'e dönüştür ve sadece rakamları al
|
|
|
|
|
|
let digits = String(value).replace(/\D/g, '')
|
|
|
|
|
|
|
|
|
|
|
|
// +90, 0090, 0 gibi ülke kodu veya ön ekleri atla
|
|
|
|
|
|
if (digits.startsWith('90') && digits.length > 10) digits = digits.slice(-10)
|
|
|
|
|
|
if (digits.startsWith('0') && digits.length > 10) digits = digits.slice(-10)
|
|
|
|
|
|
if (digits.length > 10) digits = digits.slice(-10)
|
|
|
|
|
|
|
|
|
|
|
|
// 🔒 Eğer 10 haneli değilse geçersiz → boş göster
|
|
|
|
|
|
if (digits.length !== 10) return ''
|
|
|
|
|
|
|
|
|
|
|
|
// (XXX) XXX-XXXX formatında göster
|
|
|
|
|
|
const match = digits.match(/^(\d{3})(\d{3})(\d{4})$/)
|
|
|
|
|
|
return match ? `(${match[1]}) ${match[2]}-${match[3]}` : digits
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1️⃣ Normal format nesnesi
|
|
|
|
|
|
col.format = { formatter }
|
|
|
|
|
|
|
|
|
|
|
|
// 2️⃣ CustomizeText fallback — bazı durumlarda zorunlu
|
|
|
|
|
|
col.customizeText = (cellInfo: any) => formatter(cellInfo?.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return cols
|
2026-06-06 18:31:03 +00:00
|
|
|
|
}, [gridDto])
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-02-25 11:57:25 +00:00
|
|
|
|
setColumnData(memoizedColumns)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}, [memoizedColumns])
|
|
|
|
|
|
|
|
|
|
|
|
// DataSource oluştur - sadece gridDto değiştiğinde
|
|
|
|
|
|
const memoizedDataSource = useMemo(() => {
|
|
|
|
|
|
if (!gridDto) return undefined
|
|
|
|
|
|
|
|
|
|
|
|
return createSelectDataSource(
|
|
|
|
|
|
gridDto.gridOptions,
|
|
|
|
|
|
listFormCode,
|
|
|
|
|
|
searchParams,
|
|
|
|
|
|
layoutTypes.grid,
|
|
|
|
|
|
undefined, // columnData yerine undefined - datasource oluşturulurken columns henüz set edilmeyebilir
|
|
|
|
|
|
)
|
|
|
|
|
|
}, [gridDto, listFormCode, createSelectDataSource])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-02-25 11:57:25 +00:00
|
|
|
|
setGridDataSource(memoizedDataSource)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}, [memoizedDataSource])
|
|
|
|
|
|
|
|
|
|
|
|
// Grid columns'ı set et - sadece columnData değiştiğinde
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!columnData || !gridRef?.current) return
|
|
|
|
|
|
|
|
|
|
|
|
const instance = gridRef?.current?.instance()
|
|
|
|
|
|
if (instance) {
|
|
|
|
|
|
instance.option('columns', columnData)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [columnData])
|
|
|
|
|
|
|
|
|
|
|
|
// listFormCode'u ref'e kaydet
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
refListFormCode.current = listFormCode
|
|
|
|
|
|
}, [listFormCode])
|
|
|
|
|
|
|
2026-02-25 11:57:25 +00:00
|
|
|
|
// State yükle + DataSource'u tek seferde set et → tek bir veri çekimi
|
|
|
|
|
|
// customLoadState önce çalışır, state grid'e uygulandıktan SONRA dataSource set edilir
|
2026-02-24 20:44:16 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!gridDto || !gridRef?.current || !gridDataSource || !columnData) return
|
|
|
|
|
|
|
|
|
|
|
|
const instance = gridRef?.current?.instance()
|
2026-02-25 11:57:25 +00:00
|
|
|
|
if (!instance) return
|
|
|
|
|
|
|
|
|
|
|
|
const remoteOps = {
|
|
|
|
|
|
paging: true,
|
|
|
|
|
|
filtering: true,
|
|
|
|
|
|
sorting: true,
|
|
|
|
|
|
grouping: false,
|
|
|
|
|
|
summary: false,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
2026-02-25 11:57:25 +00:00
|
|
|
|
|
|
|
|
|
|
customLoadState()
|
|
|
|
|
|
.then((state) => {
|
|
|
|
|
|
instance.option('remoteOperations', remoteOps)
|
|
|
|
|
|
// State varsa dataSource set edilmeden ÖNCE uygula → tek veri çekimi
|
|
|
|
|
|
if (state) {
|
|
|
|
|
|
instance.state(state)
|
|
|
|
|
|
}
|
|
|
|
|
|
instance.option('dataSource', gridDataSource)
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
|
console.error('State load error:', err)
|
|
|
|
|
|
instance.option('remoteOperations', remoteOps)
|
|
|
|
|
|
instance.option('dataSource', gridDataSource)
|
|
|
|
|
|
})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}, [gridDto, gridDataSource, columnData])
|
|
|
|
|
|
|
|
|
|
|
|
// StateStoring'i devre dışı bırak - manuel kaydetme kullanılacak
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!gridDto || !gridRef?.current) return
|
|
|
|
|
|
|
|
|
|
|
|
const instance = gridRef?.current?.instance()
|
|
|
|
|
|
if (instance) {
|
|
|
|
|
|
// Otomatik state kaydetme/yükleme kapalı
|
|
|
|
|
|
instance.option('stateStoring', {
|
|
|
|
|
|
enabled: false,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [gridDto])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
refListFormCode.current = listFormCode
|
|
|
|
|
|
}, [listFormCode])
|
|
|
|
|
|
|
|
|
|
|
|
// Form items mapper'ı memoize et - popup açılışını hızlandırır
|
|
|
|
|
|
const mapFormItem = useCallback(
|
|
|
|
|
|
(i: EditingFormItemDto) => {
|
|
|
|
|
|
let editorOptions: EditorOptionsWithButtons = {}
|
2026-05-31 18:16:41 +00:00
|
|
|
|
let parsedEditorOptions: EditorOptionsWithButtons = {}
|
|
|
|
|
|
const forcedEditorOptions: EditorOptionsWithButtons = {}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
try {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
parsedEditorOptions = i.editorOptions ? JSON.parse(i.editorOptions) : {}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
const rawFilter = searchParams?.get('filter')
|
|
|
|
|
|
if (rawFilter) {
|
|
|
|
|
|
const parsed = JSON.parse(rawFilter)
|
|
|
|
|
|
const filters = extractSearchParamsFields(parsed)
|
|
|
|
|
|
|
|
|
|
|
|
const hasFilter = filters.some(([field, op, val]) => field === i.dataField)
|
|
|
|
|
|
|
|
|
|
|
|
if (hasFilter) {
|
|
|
|
|
|
const existsInExtra = extraFilters.some((f) => f.fieldName === i.dataField && !!f.value)
|
|
|
|
|
|
|
|
|
|
|
|
if (!existsInExtra) {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
forcedEditorOptions.readOnly = true
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
|
|
const fieldName = i.dataField.split(':')[0]
|
|
|
|
|
|
const listFormField = gridDto?.columnFormats.find((x: any) => x.fieldName === fieldName)
|
|
|
|
|
|
|
2026-05-31 18:16:41 +00:00
|
|
|
|
const defaultEditorOptions: EditorOptionsWithButtons = {}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (listFormField?.sourceDbType === DbTypeEnum.Date) {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
Object.assign(defaultEditorOptions, {
|
2026-02-24 20:44:16 +00:00
|
|
|
|
type: 'date',
|
|
|
|
|
|
dateSerializationFormat: 'yyyy-MM-dd',
|
|
|
|
|
|
displayFormat: 'shortDate',
|
2026-05-31 18:16:41 +00:00
|
|
|
|
})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} else if (
|
|
|
|
|
|
listFormField?.sourceDbType === DbTypeEnum.DateTime ||
|
|
|
|
|
|
listFormField?.sourceDbType === DbTypeEnum.DateTime2 ||
|
|
|
|
|
|
listFormField?.sourceDbType === DbTypeEnum.DateTimeOffset
|
|
|
|
|
|
) {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
Object.assign(defaultEditorOptions, {
|
2026-02-24 20:44:16 +00:00
|
|
|
|
type: 'datetime',
|
|
|
|
|
|
dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss',
|
|
|
|
|
|
displayFormat: 'shortDateShortTime',
|
2026-05-31 18:16:41 +00:00
|
|
|
|
})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-30 12:05:25 +00:00
|
|
|
|
// Her item'a placeholder olarak captionName ekle
|
|
|
|
|
|
if (listFormField?.placeHolder) {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
defaultEditorOptions.placeholder = translate('::' + listFormField.placeHolder)
|
2026-03-30 12:05:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
// Set defaultValue for @AUTONUMBER fields
|
|
|
|
|
|
if (
|
|
|
|
|
|
typeof listFormField?.defaultValue === 'string' &&
|
|
|
|
|
|
listFormField?.defaultValue === '@AUTONUMBER' &&
|
|
|
|
|
|
mode === 'new'
|
|
|
|
|
|
) {
|
2026-05-31 18:16:41 +00:00
|
|
|
|
defaultEditorOptions.value = autoNumber()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
editorOptions = {
|
|
|
|
|
|
...defaultEditorOptions,
|
|
|
|
|
|
...parsedEditorOptions,
|
|
|
|
|
|
...forcedEditorOptions,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (editorOptions?.buttons) {
|
|
|
|
|
|
editorOptions.buttons = (editorOptions?.buttons || []).map((btn: any) => {
|
|
|
|
|
|
if (btn?.options?.onClick && typeof btn.options.onClick === 'string') {
|
|
|
|
|
|
btn.options.onClick = eval(`(${btn.options.onClick})`)
|
|
|
|
|
|
}
|
|
|
|
|
|
return btn
|
|
|
|
|
|
})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const item: SimpleItemWithColData = {
|
|
|
|
|
|
canRead: listFormField?.canRead ?? false,
|
|
|
|
|
|
canUpdate: listFormField?.canUpdate ?? false,
|
|
|
|
|
|
canCreate: listFormField?.canCreate ?? false,
|
|
|
|
|
|
canExport: listFormField?.canExport ?? false,
|
2026-04-25 18:12:31 +00:00
|
|
|
|
allowEditing: listFormField?.allowEditing ?? true,
|
|
|
|
|
|
allowAdding: listFormField?.allowAdding ?? true,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
dataField: i.dataField,
|
|
|
|
|
|
name: i.dataField,
|
|
|
|
|
|
editorType2: i.editorType2,
|
|
|
|
|
|
editorType:
|
2026-05-06 10:55:34 +00:00
|
|
|
|
i.editorType2 == PlatformEditorTypes.dxGridBox
|
|
|
|
|
|
? 'dxDropDownBox'
|
|
|
|
|
|
: i.editorType2 == PlatformEditorTypes.dxImageUpload
|
|
|
|
|
|
? undefined
|
|
|
|
|
|
: i.editorType2,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
colSpan: i.colSpan,
|
|
|
|
|
|
isRequired: i.isRequired,
|
|
|
|
|
|
editorOptions,
|
|
|
|
|
|
editorScript: i.editorScript,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (i.dataField.indexOf(':') >= 0) {
|
|
|
|
|
|
item.label = { text: captionize(i.dataField.split(':')[1]) }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ((mode == 'edit' && !item.canUpdate) || (mode == 'new' && !item.canCreate)) {
|
|
|
|
|
|
item.editorOptions = {
|
|
|
|
|
|
...item.editorOptions,
|
|
|
|
|
|
readOnly: true,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return item
|
|
|
|
|
|
},
|
|
|
|
|
|
[gridDto, mode, searchParams, extraFilters],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// WidgetGroup yüksekliğini hesapla
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const calculateWidgetHeight = () => {
|
|
|
|
|
|
if (widgetGroupRef.current) {
|
|
|
|
|
|
const height = widgetGroupRef.current.offsetHeight
|
|
|
|
|
|
setWidgetGroupHeight(height)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// İlk render'da hesapla
|
|
|
|
|
|
calculateWidgetHeight()
|
|
|
|
|
|
|
|
|
|
|
|
// Resize durumunda tekrar hesapla
|
|
|
|
|
|
const resizeObserver = new ResizeObserver(calculateWidgetHeight)
|
|
|
|
|
|
if (widgetGroupRef.current) {
|
|
|
|
|
|
resizeObserver.observe(widgetGroupRef.current)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
resizeObserver.disconnect()
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [gridDto?.widgets])
|
|
|
|
|
|
|
|
|
|
|
|
const onExporting = async (e: DataGridTypes.ExportingEvent) => {
|
|
|
|
|
|
// DevExtreme’in varsayılan export davranışını iptal ediyoruz; kendi akışımızı çalıştıracağız
|
|
|
|
|
|
e.cancel = true
|
|
|
|
|
|
|
|
|
|
|
|
const grid = gridRef?.current?.instance()
|
|
|
|
|
|
if (!grid) return
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (e.format === 'xlsx') {
|
|
|
|
|
|
// Sadece Excel için gerekli kütüphaneleri yükle
|
|
|
|
|
|
const [{ Workbook }, { saveAs }, { exportDataGrid }] = await Promise.all([
|
|
|
|
|
|
import('exceljs'),
|
|
|
|
|
|
import('file-saver'),
|
|
|
|
|
|
import('devextreme/excel_exporter'),
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
const workbook = new Workbook()
|
|
|
|
|
|
const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`)
|
|
|
|
|
|
|
|
|
|
|
|
await exportDataGrid({
|
|
|
|
|
|
component: grid as any,
|
|
|
|
|
|
worksheet,
|
|
|
|
|
|
autoFilterEnabled: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const buffer = await workbook.xlsx.writeBuffer()
|
|
|
|
|
|
saveAs(
|
|
|
|
|
|
new Blob([buffer], { type: 'application/octet-stream' }),
|
|
|
|
|
|
`${listFormCode}_export.xlsx`,
|
|
|
|
|
|
)
|
|
|
|
|
|
} else if (e.format === 'csv') {
|
|
|
|
|
|
// Sadece CSV için gerekli kütüphaneleri yükle (exceljs CSV desteği için)
|
|
|
|
|
|
const [{ Workbook }, { saveAs }] = await Promise.all([
|
|
|
|
|
|
import('exceljs'),
|
|
|
|
|
|
import('file-saver'),
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
const workbook = new Workbook()
|
|
|
|
|
|
const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`)
|
|
|
|
|
|
|
|
|
|
|
|
// CSV için basit data export
|
|
|
|
|
|
const dataSource = grid.getDataSource()
|
|
|
|
|
|
const items = dataSource?.items() || []
|
|
|
|
|
|
const columns = grid.getVisibleColumns().filter((c: any) => c.dataField)
|
|
|
|
|
|
|
|
|
|
|
|
// Header ekle
|
|
|
|
|
|
worksheet.addRow(columns.map((c: any) => c.caption || c.dataField))
|
|
|
|
|
|
|
|
|
|
|
|
// Data ekle
|
|
|
|
|
|
items.forEach((item: any) => {
|
|
|
|
|
|
worksheet.addRow(columns.map((c: any) => item[c.dataField!]))
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const buffer = await workbook.csv.writeBuffer()
|
|
|
|
|
|
saveAs(new Blob([buffer], { type: 'text/csv' }), `${listFormCode}_export.csv`)
|
|
|
|
|
|
} else if (e.format === 'pdf') {
|
|
|
|
|
|
// jspdf + devextreme pdf exporter => ihtiyaç anında yükle
|
|
|
|
|
|
const [jspdfMod, { exportDataGrid: exportDataPdf }] = await Promise.all([
|
|
|
|
|
|
import('jspdf'),
|
|
|
|
|
|
import('devextreme/pdf_exporter'),
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
// jsPDF bazı paketlemelerde default, bazılarında named export olarak gelir
|
|
|
|
|
|
const JsPDFCtor = (jspdfMod as any).default ?? (jspdfMod as any).jsPDF
|
|
|
|
|
|
const doc = new JsPDFCtor({})
|
|
|
|
|
|
|
|
|
|
|
|
await exportDataPdf({
|
|
|
|
|
|
jsPDFDocument: doc,
|
|
|
|
|
|
component: grid as any,
|
|
|
|
|
|
indent: 5,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
doc.save(`${listFormCode}_export.pdf`)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Export error:', err)
|
|
|
|
|
|
toast.push(
|
|
|
|
|
|
<Notification type="danger" duration={2500}>
|
|
|
|
|
|
{translate('::App.Common.ExportError') ?? 'Dışa aktarma sırasında hata oluştu.'}
|
|
|
|
|
|
</Notification>,
|
|
|
|
|
|
{ placement: 'top-end' },
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
2026-05-12 06:57:23 +00:00
|
|
|
|
<div ref={widgetGroupRef} className="mt-2">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Container className={DX_CLASSNAMES}>
|
|
|
|
|
|
{!isSubForm && (
|
|
|
|
|
|
<Helmet
|
|
|
|
|
|
titleTemplate={`%s | ${APP_NAME}`}
|
|
|
|
|
|
title={translate('::' + gridDto?.gridOptions.title)}
|
2026-05-17 18:20:44 +00:00
|
|
|
|
defaultTitle={APP_NAME}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
></Helmet>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{!gridDto && (
|
|
|
|
|
|
<div className="p-4">
|
|
|
|
|
|
<Loading loading>Loading grid configuration...</Loading>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{gridDto && !columnData && (
|
|
|
|
|
|
<div className="p-4">
|
|
|
|
|
|
<Loading loading>Loading columns...</Loading>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{gridDto && columnData && !gridDataSource && (
|
|
|
|
|
|
<div className="p-4">
|
|
|
|
|
|
<Loading loading>Loading data source...</Loading>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{gridDto &&
|
|
|
|
|
|
columnData &&
|
|
|
|
|
|
gridDataSource &&
|
|
|
|
|
|
(() => {
|
|
|
|
|
|
return true
|
|
|
|
|
|
})() && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="p-1">
|
|
|
|
|
|
<DataGrid
|
|
|
|
|
|
ref={gridRef as any}
|
|
|
|
|
|
key={'Grid-' + listFormCode}
|
|
|
|
|
|
id={'Grid-' + listFormCode}
|
|
|
|
|
|
height={
|
|
|
|
|
|
gridDto.gridOptions.height > 0
|
|
|
|
|
|
? gridDto.gridOptions.height
|
|
|
|
|
|
: gridDto.gridOptions.fullHeight
|
|
|
|
|
|
? `calc(100vh - ${170 + widgetGroupHeight}px)`
|
|
|
|
|
|
: undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
width={gridDto.gridOptions.width || '100%'}
|
|
|
|
|
|
allowColumnResizing={gridDto.gridOptions.columnOptionDto?.allowColumnResizing}
|
|
|
|
|
|
allowColumnReordering={gridDto.gridOptions.columnOptionDto?.allowColumnReordering}
|
|
|
|
|
|
showBorders={gridDto.gridOptions.columnOptionDto?.showBorders}
|
|
|
|
|
|
showRowLines={gridDto.gridOptions.columnOptionDto?.showRowLines}
|
|
|
|
|
|
showColumnLines={gridDto.gridOptions.columnOptionDto?.showColumnLines}
|
|
|
|
|
|
columnResizingMode={gridDto.gridOptions.columnOptionDto?.columnResizingMode}
|
|
|
|
|
|
columnAutoWidth={gridDto.gridOptions.columnOptionDto?.columnAutoWidth}
|
|
|
|
|
|
rtlEnabled={gridDto.gridOptions.columnOptionDto?.rtlEnabled}
|
|
|
|
|
|
rowAlternationEnabled={gridDto.gridOptions.columnOptionDto?.rowAlternationEnabled}
|
|
|
|
|
|
onRowPrepared={(e) => {
|
|
|
|
|
|
//header, filter, data, group, summaries
|
|
|
|
|
|
if (e.rowType === 'data') {
|
|
|
|
|
|
e.rowElement.style.height = gridDto.gridOptions?.rowDto.rowHeight
|
|
|
|
|
|
e.rowElement.style.whiteSpace = gridDto.gridOptions?.rowDto.whiteSpace
|
|
|
|
|
|
e.rowElement.style.overflowWrap = gridDto.gridOptions?.rowDto.overflowWrap
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
hoverStateEnabled={gridDto.gridOptions.columnOptionDto?.hoverStateEnabled}
|
|
|
|
|
|
columnHidingEnabled={gridDto.gridOptions.columnOptionDto?.columnHidingEnabled}
|
|
|
|
|
|
focusedRowEnabled={gridDto.gridOptions.columnOptionDto?.focusedRowEnabled}
|
|
|
|
|
|
showColumnHeaders={gridDto.gridOptions.columnOptionDto?.showColumnHeaders}
|
|
|
|
|
|
filterSyncEnabled={true}
|
|
|
|
|
|
onSelectionChanged={onSelectionChanged}
|
2026-05-23 21:12:01 +00:00
|
|
|
|
onContentReady={(e) => updateWorkflowApprovalButtons(e.component)}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
onInitNewRow={onInitNewRow}
|
|
|
|
|
|
onCellPrepared={onCellPrepared}
|
|
|
|
|
|
onRowInserting={onRowInserting}
|
|
|
|
|
|
onRowUpdating={onRowUpdating}
|
|
|
|
|
|
onEditingStart={onEditingStart}
|
|
|
|
|
|
onDataErrorOccurred={onDataErrorOccurred}
|
|
|
|
|
|
onExporting={onExporting}
|
|
|
|
|
|
onEditCanceling={() => {
|
|
|
|
|
|
setMode('view')
|
|
|
|
|
|
}}
|
|
|
|
|
|
onEditCanceled={() => {
|
|
|
|
|
|
isEditingRef.current = false
|
|
|
|
|
|
setMode('view')
|
|
|
|
|
|
setIsPopupFullScreen(false)
|
|
|
|
|
|
}}
|
|
|
|
|
|
onSaved={() => {
|
|
|
|
|
|
isEditingRef.current = false
|
|
|
|
|
|
setMode('view')
|
|
|
|
|
|
setIsPopupFullScreen(false)
|
|
|
|
|
|
}}
|
2026-05-23 21:12:01 +00:00
|
|
|
|
onRowInserted={(e) => {
|
|
|
|
|
|
const insertedKey = getPersistedInsertedKey(e, gridDto.gridOptions.keyFieldName)
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
gridDto.gridOptions.workflowDto?.approvalStatusFieldName &&
|
|
|
|
|
|
insertedKey !== undefined
|
|
|
|
|
|
) {
|
|
|
|
|
|
workflowService
|
|
|
|
|
|
.startWorkflow(listFormCode, [insertedKey])
|
2026-06-07 19:42:02 +00:00
|
|
|
|
.then((result) => {
|
|
|
|
|
|
const messages = result.toastMessages ?? []
|
|
|
|
|
|
if (messages.length > 0) {
|
|
|
|
|
|
toast.push(
|
|
|
|
|
|
<Notification type="info" duration={7000}>
|
|
|
|
|
|
{messages.map((message, messageIndex) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={messageIndex}
|
|
|
|
|
|
className={messageIndex > 0 ? 'mt-2 border-t pt-2' : undefined}
|
|
|
|
|
|
>
|
|
|
|
|
|
{message.split('\n').map((line, lineIndex) => (
|
|
|
|
|
|
<div key={lineIndex}>{line}</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Notification>,
|
|
|
|
|
|
{ placement: 'top-end' },
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
gridRef.current?.instance()?.refresh()
|
|
|
|
|
|
})
|
2026-05-23 21:12:01 +00:00
|
|
|
|
.catch(console.error)
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
props.refreshData?.()
|
|
|
|
|
|
}}
|
|
|
|
|
|
onRowUpdated={() => {
|
|
|
|
|
|
props.refreshData?.()
|
|
|
|
|
|
}}
|
|
|
|
|
|
onRowRemoved={() => {
|
|
|
|
|
|
props.refreshData?.()
|
|
|
|
|
|
}}
|
|
|
|
|
|
onEditorPreparing={onEditorPreparing}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Export
|
|
|
|
|
|
enabled={gridDto.gridOptions.exportDto?.enabled}
|
|
|
|
|
|
allowExportSelectedData={gridDto.gridOptions.exportDto?.allowExportSelectedData}
|
|
|
|
|
|
formats={['pdf', 'xlsx']}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Editing
|
|
|
|
|
|
refreshMode={gridDto.gridOptions.editingOptionDto?.refreshMode}
|
|
|
|
|
|
mode={smaller.md ? 'form' : gridDto.gridOptions.editingOptionDto?.mode}
|
|
|
|
|
|
allowDeleting={gridDto.gridOptions.editingOptionDto?.allowDeleting}
|
2026-04-25 18:12:31 +00:00
|
|
|
|
allowUpdating={gridDto.gridOptions.editingOptionDto?.allowUpdating}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
allowAdding={gridDto.gridOptions.editingOptionDto?.allowAdding}
|
|
|
|
|
|
useIcons={gridDto.gridOptions.editingOptionDto?.useIcons}
|
|
|
|
|
|
confirmDelete={gridDto.gridOptions.editingOptionDto?.confirmDelete}
|
|
|
|
|
|
newRowPosition={gridDto.gridOptions.editingOptionDto?.newRowPosition}
|
|
|
|
|
|
selectTextOnEditStart={
|
|
|
|
|
|
gridDto.gridOptions.editingOptionDto?.selectTextOnEditStart
|
|
|
|
|
|
}
|
|
|
|
|
|
startEditAction={gridDto.gridOptions.editingOptionDto?.startEditAction}
|
|
|
|
|
|
popup={{
|
|
|
|
|
|
deferRendering: true,
|
|
|
|
|
|
animation: {},
|
|
|
|
|
|
title:
|
|
|
|
|
|
(mode === 'new' ? '✚ ' : '🖊️ ') +
|
|
|
|
|
|
translate('::' + gridDto.gridOptions.editingOptionDto?.popup?.title),
|
|
|
|
|
|
showTitle: gridDto.gridOptions.editingOptionDto?.popup?.showTitle,
|
|
|
|
|
|
hideOnOutsideClick:
|
|
|
|
|
|
gridDto.gridOptions.editingOptionDto?.popup?.hideOnOutsideClick,
|
|
|
|
|
|
width: gridDto.gridOptions.editingOptionDto?.popup?.width,
|
|
|
|
|
|
height: gridDto.gridOptions.editingOptionDto?.popup?.height,
|
|
|
|
|
|
resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled,
|
|
|
|
|
|
fullScreen: isPopupFullScreen,
|
|
|
|
|
|
toolbarItems: [
|
|
|
|
|
|
{
|
|
|
|
|
|
widget: 'dxButton',
|
|
|
|
|
|
toolbar: 'bottom',
|
|
|
|
|
|
location: 'after',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
text: translate('::Save'),
|
|
|
|
|
|
type: 'default',
|
|
|
|
|
|
onClick: () => {
|
|
|
|
|
|
const grid = gridRef.current?.instance()
|
|
|
|
|
|
grid?.saveEditData()
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
widget: 'dxButton',
|
|
|
|
|
|
toolbar: 'bottom',
|
|
|
|
|
|
location: 'after',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
text: translate('::Cancel'),
|
|
|
|
|
|
onClick: () => {
|
|
|
|
|
|
const grid = gridRef.current?.instance()
|
|
|
|
|
|
grid?.cancelEditData()
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
widget: 'dxButton',
|
|
|
|
|
|
toolbar: 'top',
|
|
|
|
|
|
location: 'after',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
icon: isPopupFullScreen ? 'collapse' : 'fullscreen',
|
|
|
|
|
|
hint: isPopupFullScreen
|
|
|
|
|
|
? translate('::Normal Boyut')
|
|
|
|
|
|
: translate('::Tam Ekran'),
|
|
|
|
|
|
stylingMode: 'text',
|
|
|
|
|
|
onClick: () => setIsPopupFullScreen(!isPopupFullScreen),
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
}}
|
|
|
|
|
|
form={{
|
|
|
|
|
|
colCount: 1,
|
2026-06-01 13:47:38 +00:00
|
|
|
|
onContentReady: (e) => {
|
|
|
|
|
|
editingFormInstanceRef.current = e.component
|
|
|
|
|
|
|
|
|
|
|
|
const form = e.component
|
|
|
|
|
|
const grid = gridRef.current?.instance()
|
|
|
|
|
|
const rowKey = grid?.option('editing.editRowKey')
|
|
|
|
|
|
const rowIndex = rowKey !== undefined ? grid?.getRowIndexByKey(rowKey) : -1
|
|
|
|
|
|
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
|
|
|
|
|
|
setFormEditorReadOnly(form, field, readOnly)
|
|
|
|
|
|
const setFormData = (newData: any) => {
|
|
|
|
|
|
form?.option?.('formData', newData)
|
|
|
|
|
|
editingFormDataRef.current = { ...newData }
|
|
|
|
|
|
if (grid && rowIndex !== undefined && rowIndex >= 0) {
|
|
|
|
|
|
Object.keys(newData).forEach((field) => {
|
|
|
|
|
|
grid.cellValue(rowIndex, field, newData[field])
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const runReadOnlyScripts = () => {
|
2026-06-01 20:51:59 +00:00
|
|
|
|
const editorValues = gridDto.gridOptions.editingFormDto
|
|
|
|
|
|
.flatMap((group) => flattenEditingFormItems([group]))
|
|
|
|
|
|
.reduce<Record<string, any>>((values, formItem) => {
|
|
|
|
|
|
const editorInstance = form?.getEditor?.(formItem.dataField)
|
|
|
|
|
|
if (editorInstance?.option) {
|
|
|
|
|
|
values[formItem.dataField] = editorInstance.option('value')
|
|
|
|
|
|
}
|
|
|
|
|
|
return values
|
|
|
|
|
|
}, {})
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const formData = {
|
|
|
|
|
|
...editingFormDataRef.current,
|
|
|
|
|
|
...(form?.option?.('formData') || {}),
|
2026-06-01 20:51:59 +00:00
|
|
|
|
...editorValues,
|
2026-06-01 13:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
editingFormDataRef.current = { ...formData }
|
|
|
|
|
|
|
|
|
|
|
|
gridDto.gridOptions.editingFormDto
|
|
|
|
|
|
.flatMap((group) => flattenEditingFormItems([group]))
|
|
|
|
|
|
.filter((formItem) =>
|
|
|
|
|
|
shouldRunEditorScriptOnContentReady(formItem.editorScript),
|
|
|
|
|
|
)
|
|
|
|
|
|
.forEach((formItem) => {
|
|
|
|
|
|
try {
|
2026-06-01 20:51:59 +00:00
|
|
|
|
const editorInstance = form?.getEditor?.(formItem.dataField)
|
|
|
|
|
|
const editorValue =
|
|
|
|
|
|
editorInstance?.option?.('value') ??
|
|
|
|
|
|
getValueByField(formData, formItem.dataField)
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const editor = {
|
|
|
|
|
|
dataField: formItem.dataField,
|
|
|
|
|
|
component: grid,
|
|
|
|
|
|
}
|
|
|
|
|
|
const e = {
|
|
|
|
|
|
component: form,
|
|
|
|
|
|
dataField: formItem.dataField,
|
2026-06-01 20:51:59 +00:00
|
|
|
|
value: editorValue,
|
2026-06-01 13:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
executeEditorScript(formItem.editorScript!, {
|
|
|
|
|
|
formData,
|
|
|
|
|
|
e,
|
|
|
|
|
|
editor,
|
|
|
|
|
|
runtimeSetEditorReadOnly,
|
|
|
|
|
|
setFormData,
|
|
|
|
|
|
})
|
2026-06-01 13:47:38 +00:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Script exec error on contentReady', formItem.dataField, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
runReadOnlyScripts()
|
|
|
|
|
|
},
|
2026-02-24 20:44:16 +00:00
|
|
|
|
onFieldDataChanged: (e) => {
|
|
|
|
|
|
if (e.dataField) {
|
2026-05-31 09:36:51 +00:00
|
|
|
|
const previousValue = editingFormDataRef.current?.[e.dataField]
|
|
|
|
|
|
editingFormDataRef.current = {
|
|
|
|
|
|
...(e.component?.option?.('formData') || {}),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const formItem = gridDto.gridOptions.editingFormDto
|
2026-06-01 13:47:38 +00:00
|
|
|
|
.flatMap((group) => flattenEditingFormItems([group]))
|
|
|
|
|
|
.find(
|
|
|
|
|
|
(i) =>
|
|
|
|
|
|
String(i.dataField || '').toLowerCase() ===
|
|
|
|
|
|
String(e.dataField || '').toLowerCase(),
|
|
|
|
|
|
)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (formItem?.editorScript) {
|
|
|
|
|
|
try {
|
2026-05-31 09:36:51 +00:00
|
|
|
|
const grid = gridRef.current?.instance()
|
|
|
|
|
|
const rowKey = grid?.option('editing.editRowKey')
|
|
|
|
|
|
const rowIndex =
|
|
|
|
|
|
rowKey !== undefined ? grid?.getRowIndexByKey(rowKey) : -1
|
2026-06-01 13:47:38 +00:00
|
|
|
|
const formData = {
|
|
|
|
|
|
...(e.component?.option?.('formData') || {}),
|
|
|
|
|
|
[e.dataField]: e.value,
|
|
|
|
|
|
}
|
|
|
|
|
|
editingFormDataRef.current = { ...formData }
|
|
|
|
|
|
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
|
|
|
|
|
|
setFormEditorReadOnly(e.component, field, readOnly)
|
2026-05-31 09:36:51 +00:00
|
|
|
|
const setFormData = (newData: any) => {
|
|
|
|
|
|
e.component?.option?.('formData', newData)
|
|
|
|
|
|
editingFormDataRef.current = { ...newData }
|
|
|
|
|
|
if (grid && rowIndex !== undefined && rowIndex >= 0) {
|
|
|
|
|
|
Object.keys(newData).forEach((field) => {
|
|
|
|
|
|
grid.cellValue(rowIndex, field, newData[field])
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 20:51:59 +00:00
|
|
|
|
executeEditorScript(formItem.editorScript, {
|
|
|
|
|
|
formData,
|
|
|
|
|
|
e,
|
|
|
|
|
|
editor: {
|
|
|
|
|
|
dataField: e.dataField,
|
|
|
|
|
|
component: grid,
|
|
|
|
|
|
},
|
|
|
|
|
|
runtimeSetEditorReadOnly,
|
|
|
|
|
|
setFormData,
|
|
|
|
|
|
})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Script exec error', err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-01 13:47:38 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
items:
|
|
|
|
|
|
gridDto.gridOptions.editingFormDto?.length > 0
|
|
|
|
|
|
? (() => {
|
|
|
|
|
|
const sortedFormDto = gridDto.gridOptions.editingFormDto
|
|
|
|
|
|
.slice()
|
|
|
|
|
|
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
|
|
|
|
|
|
|
|
|
|
|
// Tabbed item'ları grupla
|
|
|
|
|
|
const tabbedItems = sortedFormDto.filter(
|
|
|
|
|
|
(e: any) => e.itemType === 'tabbed',
|
|
|
|
|
|
)
|
|
|
|
|
|
const result: any[] = []
|
|
|
|
|
|
|
|
|
|
|
|
sortedFormDto.forEach((e: any) => {
|
|
|
|
|
|
if (e.itemType !== 'tabbed') {
|
|
|
|
|
|
// Backend'den gelen colCount ve colSpan değerlerini kullan
|
|
|
|
|
|
const effectiveColCount = e.colCount || 1
|
|
|
|
|
|
const effectiveColSpan = e.colSpan || 1
|
|
|
|
|
|
|
|
|
|
|
|
result.push({
|
|
|
|
|
|
itemType: e.itemType,
|
|
|
|
|
|
colCount: effectiveColCount,
|
|
|
|
|
|
colSpan: effectiveColSpan,
|
|
|
|
|
|
caption: e.caption, // Group'larda caption olmalı
|
|
|
|
|
|
items: e.items
|
|
|
|
|
|
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
|
|
|
|
|
.map(mapFormItem)
|
|
|
|
|
|
.filter((a: any) => {
|
|
|
|
|
|
if (mode === 'view') {
|
|
|
|
|
|
return a.canRead
|
|
|
|
|
|
} else if (mode === 'new') {
|
2026-05-23 21:12:01 +00:00
|
|
|
|
return a.canCreate && a.allowAdding
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} else if (mode === 'edit') {
|
2026-05-23 21:12:01 +00:00
|
|
|
|
return a.canUpdate && a.allowEditing
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
|
|
|
|
|
})
|
|
|
|
|
|
} else if (tabbedItems.length > 0 && e === tabbedItems[0]) {
|
|
|
|
|
|
// Tabbed için caption OLMAMALI - sadece tabs array içinde title kullan
|
|
|
|
|
|
result.push({
|
|
|
|
|
|
itemType: 'tabbed',
|
|
|
|
|
|
colCount: 1,
|
|
|
|
|
|
colSpan: 1,
|
|
|
|
|
|
// caption kullanma! Tabs içindeki title'lar yeterli
|
|
|
|
|
|
tabs: tabbedItems.map((tabbedItem: any) => {
|
|
|
|
|
|
// Backend'den gelen colCount ve colSpan değerlerini kullan
|
|
|
|
|
|
const effectiveColCount = tabbedItem.colCount || 1
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
title: tabbedItem.caption, // Her tab'ın title'ı
|
|
|
|
|
|
colCount: effectiveColCount,
|
|
|
|
|
|
items: tabbedItem.items
|
|
|
|
|
|
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
|
|
|
|
|
.map(mapFormItem)
|
|
|
|
|
|
.filter((a: any) => {
|
|
|
|
|
|
if (mode === 'view') {
|
|
|
|
|
|
return a.canRead
|
|
|
|
|
|
} else if (mode === 'new') {
|
2026-05-23 21:12:01 +00:00
|
|
|
|
return a.canCreate && a.allowAdding
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} else if (mode === 'edit') {
|
2026-05-23 21:12:01 +00:00
|
|
|
|
return a.canUpdate && a.allowEditing
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
})()
|
|
|
|
|
|
: undefined,
|
|
|
|
|
|
}}
|
|
|
|
|
|
></Editing>
|
|
|
|
|
|
<Template name={'cellEditTagBox'} render={TagBoxEditorComponent} />
|
|
|
|
|
|
<Template name={'cellEditGridBox'} render={GridBoxEditorComponent} />
|
2026-05-06 10:55:34 +00:00
|
|
|
|
<Template name={'cellEditImageUpload'} render={ImageUploadEditorComponent} />
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<Template name="extraFilters">
|
|
|
|
|
|
<GridExtraFilterToolbar
|
|
|
|
|
|
filters={gridDto?.gridOptions.extraFilterDto ?? []}
|
|
|
|
|
|
extraFilters={extraFilters}
|
|
|
|
|
|
setExtraFilters={setExtraFilters}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Template>
|
|
|
|
|
|
<Toolbar visible={toolbarData.length > 0 || filterToolbarData.length > 0}>
|
|
|
|
|
|
{toolbarData.map((item) => (
|
|
|
|
|
|
<Item key={item.name} {...item}></Item>
|
|
|
|
|
|
))}
|
|
|
|
|
|
{filterToolbarData.map((item) => (
|
|
|
|
|
|
<Item key={item.name} {...item}></Item>
|
|
|
|
|
|
))}
|
|
|
|
|
|
{/* burada özel filtre alanını Template ile bağla */}
|
|
|
|
|
|
{gridDto?.gridOptions.extraFilterDto?.length ? (
|
|
|
|
|
|
<Item location="before" template="extraFilters" cssClass="no-default" />
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
</Toolbar>
|
|
|
|
|
|
<Sorting mode={gridDto.gridOptions?.sortMode}></Sorting>
|
|
|
|
|
|
<FilterRow
|
|
|
|
|
|
visible={gridDto.gridOptions.filterRowDto?.visible}
|
|
|
|
|
|
applyFilter={gridDto.gridOptions.filterRowDto?.applyFilter}
|
|
|
|
|
|
></FilterRow>
|
|
|
|
|
|
<FilterPanel visible={gridDto.gridOptions.filterPanelDto.visible}></FilterPanel>
|
|
|
|
|
|
<HeaderFilter
|
|
|
|
|
|
visible={gridDto.gridOptions.headerFilterDto.visible}
|
|
|
|
|
|
></HeaderFilter>
|
|
|
|
|
|
<SearchPanel
|
|
|
|
|
|
visible={gridDto.gridOptions.searchPanelDto.visible}
|
|
|
|
|
|
width={gridDto.gridOptions.searchPanelDto.width}
|
|
|
|
|
|
placeholder={translate('::App.Search')}
|
|
|
|
|
|
></SearchPanel>
|
|
|
|
|
|
<GroupPanel
|
|
|
|
|
|
visible={gridDto.gridOptions.groupPanelDto?.visible}
|
|
|
|
|
|
emptyPanelText={translate('::App.GroupPanel.EmptyPanelText')}
|
|
|
|
|
|
></GroupPanel>
|
|
|
|
|
|
<Grouping
|
|
|
|
|
|
autoExpandAll={gridDto.gridOptions.groupPanelDto?.autoExpandAll}
|
|
|
|
|
|
></Grouping>
|
|
|
|
|
|
<Selection
|
|
|
|
|
|
mode={gridDto.gridOptions.selectionDto?.mode}
|
|
|
|
|
|
allowSelectAll={gridDto.gridOptions.selectionDto?.allowSelectAll}
|
|
|
|
|
|
selectAllMode={gridDto.gridOptions.selectionDto?.selectAllMode}
|
|
|
|
|
|
showCheckBoxesMode={gridDto.gridOptions.selectionDto?.showCheckBoxesMode}
|
|
|
|
|
|
></Selection>
|
|
|
|
|
|
<Paging defaultPageSize={gridDto.gridOptions.pageSize ?? 20} />
|
|
|
|
|
|
<Pager
|
|
|
|
|
|
visible={gridDto.gridOptions.pagerOptionDto?.visible}
|
|
|
|
|
|
allowedPageSizes={gridDto.gridOptions.pagerOptionDto?.allowedPageSizes
|
|
|
|
|
|
?.split(',')
|
|
|
|
|
|
.map((a: any) => +a)}
|
|
|
|
|
|
showPageSizeSelector={gridDto.gridOptions.pagerOptionDto?.showPageSizeSelector}
|
|
|
|
|
|
showInfo={false}
|
|
|
|
|
|
showNavigationButtons={
|
|
|
|
|
|
gridDto.gridOptions.pagerOptionDto?.showNavigationButtons
|
|
|
|
|
|
}
|
|
|
|
|
|
displayMode={gridDto.gridOptions.pagerOptionDto?.displayMode}
|
|
|
|
|
|
></Pager>
|
|
|
|
|
|
<ColumnChooser
|
|
|
|
|
|
enabled={gridDto.gridOptions.columnOptionDto?.columnChooserEnabled}
|
|
|
|
|
|
mode={gridDto.gridOptions.columnOptionDto?.columnChooserMode}
|
|
|
|
|
|
></ColumnChooser>
|
|
|
|
|
|
<ColumnFixing
|
|
|
|
|
|
enabled={gridDto.gridOptions.columnOptionDto?.columnFixingEnabled}
|
|
|
|
|
|
></ColumnFixing>
|
|
|
|
|
|
<Scrolling mode={gridDto.gridOptions.pagerOptionDto?.scrollingMode}></Scrolling>
|
|
|
|
|
|
<LoadPanel
|
|
|
|
|
|
enabled={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled}
|
|
|
|
|
|
text={gridDto.gridOptions.pagerOptionDto?.loadPanelText}
|
|
|
|
|
|
></LoadPanel>
|
|
|
|
|
|
<Summary>
|
|
|
|
|
|
{gridDto.columnFormats
|
|
|
|
|
|
.filter((x: any) => !!x.columnTotalSummaryDto?.summaryType)
|
|
|
|
|
|
.map((x: any) => (
|
|
|
|
|
|
<TotalItem
|
|
|
|
|
|
key={`Total_${x.fieldName}`}
|
|
|
|
|
|
column={x.fieldName}
|
|
|
|
|
|
summaryType={x.columnTotalSummaryDto.summaryType as any}
|
|
|
|
|
|
showInColumn={x.columnTotalSummaryDto.showInColumn}
|
|
|
|
|
|
valueFormat={x.columnTotalSummaryDto.valueFormat}
|
|
|
|
|
|
displayFormat={x.columnTotalSummaryDto.displayFormat}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
{gridDto.columnFormats
|
|
|
|
|
|
.filter((x: any) => !!x.columnGroupSummaryDto?.summaryType)
|
|
|
|
|
|
.map((x: any) => (
|
|
|
|
|
|
<GroupItemDx
|
|
|
|
|
|
key={`Group_${x.fieldName}`}
|
|
|
|
|
|
column={x.fieldName}
|
|
|
|
|
|
summaryType={x.columnGroupSummaryDto.summaryType as any}
|
|
|
|
|
|
showInColumn={x.columnGroupSummaryDto.showInColumn}
|
|
|
|
|
|
valueFormat={x.columnGroupSummaryDto.valueFormat}
|
|
|
|
|
|
displayFormat={x.columnGroupSummaryDto.displayFormat}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Summary>
|
|
|
|
|
|
</DataGrid>
|
|
|
|
|
|
|
|
|
|
|
|
{gridDto?.gridOptions?.subFormsDto?.length > 0 &&
|
|
|
|
|
|
gridDto?.gridOptions?.subFormsListFormType === SubFormTabTypeEnum.List && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<hr className="my-2" />
|
|
|
|
|
|
<SubForms gridDto={gridDto!} formData={formData} level={level ?? 0} />
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<Dialog
|
|
|
|
|
|
width={smaller.md ? '100%' : 1000}
|
|
|
|
|
|
isOpen={filterData.isImportModalOpen || false}
|
|
|
|
|
|
onClose={() => filterData.setIsImportModalOpen(false)}
|
|
|
|
|
|
onRequestClose={() => filterData.setIsImportModalOpen(false)}
|
|
|
|
|
|
>
|
2026-05-13 11:31:15 +00:00
|
|
|
|
<Dialog.Body className="flex flex-col">
|
|
|
|
|
|
<ImportDashboard gridDto={gridDto} />
|
|
|
|
|
|
</Dialog.Body>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</Dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<Dialog
|
|
|
|
|
|
isOpen={toolbarModalData?.open || false}
|
|
|
|
|
|
onClose={() => setToolbarModalData(undefined)}
|
|
|
|
|
|
onRequestClose={() => setToolbarModalData(undefined)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{toolbarModalData?.content}
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
<GridFilterDialogs gridRef={gridRef} listFormCode={listFormCode} {...filterData} />
|
2026-06-06 22:22:35 +00:00
|
|
|
|
{notePanelTarget && (
|
|
|
|
|
|
<NotePanel
|
|
|
|
|
|
entityName={notePanelTarget.entityName}
|
|
|
|
|
|
entityId={notePanelTarget.entityId}
|
|
|
|
|
|
isVisible
|
|
|
|
|
|
onToggle={() => setNotePanelTarget(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</Container>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default Grid
|