erp-platform/ui/src/views/list/Tree.tsx

1004 lines
37 KiB
TypeScript
Raw Normal View History

/* eslint-disable @typescript-eslint/no-explicit-any */
import Container from '@/components/shared/Container'
import { Dialog, Notification, toast } from '@/components/ui'
import { DX_CLASSNAMES } from '@/constants/app.constant'
2025-11-08 20:22:50 +00:00
import {
DbTypeEnum,
EditingFormItemDto,
GridDto,
ListFormCustomizationTypeEnum,
PlatformEditorTypes,
} from '@/proxy/form/models'
import {
getListFormCustomization,
postListFormCustomization,
} from '@/services/list-form-customization.service'
import { useListFormColumns } from '@/shared/useListFormColumns'
import { useListFormCustomDataSource } from '@/shared/useListFormCustomDataSource'
import { useLocalization } from '@/utils/hooks/useLocalization'
import useResponsive from '@/utils/hooks/useResponsive'
2025-11-08 07:49:38 +00:00
import { captionize } from 'devextreme/core/utils/inflector'
import { Template } from 'devextreme-react/core/template'
import TreeListDx, {
ColumnChooser,
ColumnFixing,
Editing,
FilterPanel,
FilterRow,
HeaderFilter,
IStateStoringProps,
LoadPanel,
Pager,
Paging,
RemoteOperations,
Scrolling,
SearchPanel,
Selection,
Sorting,
Toolbar,
TreeListTypes,
} from 'devextreme-react/tree-list'
import { Item } from 'devextreme-react/toolbar'
import CustomStore from 'devextreme/data/custom_store'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
2025-11-08 07:49:38 +00:00
import { RowMode, SimpleItemWithColData } from '../form/types'
import { GridColumnData } from './GridColumnData'
import GridFilterDialogs from './GridFilterDialogs'
import {
addCss,
addJs,
controlStyleCondition,
GridExtraFilterState,
setFormEditingExtraItemValues,
setGridPanelColor,
} from './Utils'
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
import { useFilters } from './useFilters'
import { useToolbar } from './useToolbar'
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
import { getList } from '@/services/form.service'
2025-11-08 20:22:50 +00:00
import { layoutTypes } from '../admin/listForm/edit/types'
interface TreeProps {
listFormCode: string
searchParams?: URLSearchParams
isSubForm?: boolean
level?: number
refreshData?: () => Promise<void>
gridDto?: GridDto
}
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)'
const Tree = (props: TreeProps) => {
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
const { translate } = useLocalization()
const { smaller } = useResponsive()
const gridRef = useRef<TreeListDx>()
const refListFormCode = useRef('')
const [treeListDataSource, setTreeListDataSource] = 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 [expandedRowKeys, setExpandedRowKeys] = useState<any[]>([])
2025-11-08 07:49:38 +00:00
const preloadExportLibs = () => {
import('exceljs')
import('file-saver')
import('devextreme/excel_exporter')
import('jspdf')
import('devextreme/pdf_exporter')
}
type EditorOptionsWithButtons = {
buttons?: any[]
} & Record<string, any>
const defaultSearchParams = useRef<string | null>(null)
useEffect(() => {
const initializeTreeList = async () => {
const response = await getList({ listFormCode })
setGridDto(response.data)
}
if (extGridDto === undefined) {
initializeTreeList()
} else {
setGridDto(extGridDto)
}
}, [listFormCode, extGridDto])
useEffect(() => {
if (!defaultSearchParams.current) {
defaultSearchParams.current = searchParams?.get('filter') ?? null
}
}, [searchParams])
2025-11-08 20:22:50 +00:00
const layout = layoutTypes.tree
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
gridDto,
listFormCode,
getSelectedRowKeys,
getSelectedRowsData,
refreshData,
expandAll,
collapseAll,
getFilter,
2025-11-08 20:22:50 +00:00
layout,
})
const { filterToolbarData, ...filterData } = useFilters({
gridDto,
gridRef,
listFormCode,
})
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
const { getBandedColumns } = useListFormColumns({
gridDto,
listFormCode,
isSubForm,
gridRef,
})
function extractSearchParamsFields(filter: any): [string, string, any][] {
if (!Array.isArray(filter)) return []
if (filter.length === 3 && typeof filter[0] === 'string') {
return [filter as [string, string, any]]
}
return filter.flatMap((f) => extractSearchParamsFields(f))
}
async function getSelectedRowKeys() {
const tree = gridRef.current?.instance
if (!tree) {
return []
}
return await tree.getSelectedRowKeys()
}
function getSelectedRowsData() {
const tree = gridRef.current?.instance
if (!tree) {
return []
}
return tree.getSelectedRowsData()
}
function expandAll() {
const tree = gridRef.current?.instance
if (!tree) return
tree.forEachNode((node: any) => {
if (node.hasChildren) {
tree.expandRow(node.key)
}
})
}
function collapseAll() {
const tree = gridRef.current?.instance
if (!tree) return
tree.forEachNode((node: any) => {
if (node.hasChildren) {
tree.collapseRow(node.key)
}
})
}
function refreshData() {
gridRef.current?.instance.refresh()
}
function getFilter() {
const tree = gridRef.current?.instance
if (!tree) {
return
}
return tree.getCombinedFilter()
}
function onSelectionChanged(data: any) {
const treeOpt = gridDto?.gridOptions
const tree = gridRef.current?.instance
if (!treeOpt || !tree) {
return
}
if (treeOpt.editingOptionDto?.allowDeleting) {
try {
const opt = tree.option('toolbar')
if (opt && opt.items && Array.isArray(opt.items)) {
const deleteSelectedRecordsIndex = opt.items
.map((e: any) => e.name)
.indexOf('deleteSelectedRecords')
if (deleteSelectedRecordsIndex >= 0) {
tree.option(
`toolbar.items[${deleteSelectedRecordsIndex}].options.visible`,
data.selectedRowsData.length > 1,
)
}
}
} catch (error) {
console.error('Error updating toolbar items:', error)
}
}
if (data.selectedRowsData.length) {
setFormData(data.selectedRowsData[0])
}
}
function onCellPrepared(e: any) {
const columnFormats = gridDto?.columnFormats
if (!columnFormats) {
return
}
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]
if (e.rowType == colStyle.rowType) {
if (colStyle.useRow || e.column.dataField == colFormat.fieldName) {
if (
!colStyle.conditionValue ||
controlStyleCondition(e.data, colFormat.fieldName, colStyle)
) {
if (colStyle.cssClassName) {
e.cellElement.addClass(colStyle.cssClassName)
}
if (colStyle.cssStyles) {
e.cellElement.attr('style', e.cellElement.attr('style') + ';' + colStyle.cssStyles)
}
}
}
}
}
}
}
function onInitNewRow(e: any) {
if (!gridDto?.columnFormats) {
return
}
setMode('new')
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
for (const colFormat of gridDto?.columnFormats) {
if (!colFormat.fieldName) {
continue
}
if (colFormat.defaultValue != null) {
e.data[colFormat.fieldName] = colFormat.defaultValue
}
if (extraFilters.some((f) => f.fieldName === colFormat.fieldName)) {
continue
}
if (!searchParams) {
continue
}
const rawFilter = searchParams?.get('filter')
if (rawFilter) {
const parsed = JSON.parse(rawFilter)
const filters = extractSearchParamsFields(parsed)
const hasFilter = filters.some(([field, op, val]) => field === colFormat.fieldName)
if (hasFilter) {
const fieldMatch = filters.find(([field, op, val]) => field === colFormat.fieldName)
if (fieldMatch) {
const [, , val] = fieldMatch
const dType = colFormat.dataType as any
switch (dType) {
case 'date':
case 'datetime':
e.data[colFormat.fieldName] = new Date(val)
break
case 'number':
e.data[colFormat.fieldName] = Number(val)
break
case 'boolean':
e.data[colFormat.fieldName] = val === true || val === 'true'
break
case 'object':
try {
e.data[colFormat.fieldName] = JSON.parse(val)
} catch {}
break
default:
e.data[colFormat.fieldName] = val
break
}
}
}
}
}
}
function onRowInserting(e: TreeListTypes.RowInsertingEvent) {
e.data = setFormEditingExtraItemValues(e.data)
}
function onRowUpdating(e: TreeListTypes.RowUpdatingEvent) {
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
if (Object.keys(e.newData).some((a) => a.includes(':'))) {
Object.keys(e.oldData).forEach((col) => {
if (col.includes(':')) {
e.newData[col] = e.newData[col] ?? e.oldData[col]
}
})
}
e.newData = setFormEditingExtraItemValues(e.newData)
} else {
let newData = { ...e.oldData, ...e.newData }
newData = setFormEditingExtraItemValues(newData)
Object.keys(newData).forEach((key) => {
if (key.includes(':')) {
delete newData[key]
}
})
e.newData = newData
}
if (gridDto?.gridOptions.keyFieldName) {
delete e.newData[gridDto?.gridOptions.keyFieldName]
}
}
function onEditingStart(e: TreeListTypes.EditingStartEvent) {
setMode('edit')
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
const columns = e.component.option('columns') as GridColumnData[]
columns?.forEach((col) => {
if (!col.dataField?.includes(':')) {
return
}
const field = col.dataField.split(':')
if (!e.data[field[0]]) {
return
}
const json = JSON.parse(e.data[field[0]])
e.data[col.dataField] = json[field[1]]
})
}
function onDataErrorOccurred(e: TreeListTypes.DataErrorOccurredEvent) {
toast.push(
<Notification type="danger" duration={2000}>
{e.error?.message}
</Notification>,
{
placement: 'top-end',
},
)
}
const customSaveState = useCallback(
(state: any) =>
postListFormCustomization({
listFormCode: listFormCode,
customizationType: ListFormCustomizationTypeEnum.GridState,
filterName: `tree-${gridRef.current?.instance.option('stateStoring')?.storageKey ?? ''}`,
customizationData: JSON.stringify(state),
}).then(() => {
setGridPanelColor(statedGridPanelColor)
}),
[listFormCode],
)
const customLoadState = useCallback(
() =>
getListFormCustomization(
listFormCode,
ListFormCustomizationTypeEnum.GridState,
`tree-${gridRef.current?.instance.option('stateStoring')?.storageKey ?? ''}`,
).then((response: any) => {
setGridPanelColor(statedGridPanelColor)
if (response.data?.length > 0) {
return JSON.parse(response.data[0].customizationData)
}
}),
[listFormCode],
)
useEffect(() => {
if (gridRef?.current) {
gridRef.current.instance.option('columns', undefined)
gridRef.current.instance.option('dataSource', undefined)
gridRef.current.instance.state(null)
}
if (refListFormCode.current !== listFormCode) {
setExtraFilters([])
}
}, [listFormCode])
useEffect(() => {
if (!gridDto) {
return
}
const treeOpt = gridDto.gridOptions
if (treeOpt.customJsSources.length) {
for (const js of treeOpt.customJsSources) {
addJs(js)
}
}
if (treeOpt.customStyleSources.length) {
for (const css of treeOpt.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)
}
// Set initial expanded row keys
if (gridDto.gridOptions.treeOptionDto?.expandedRowKeys) {
setExpandedRowKeys(gridDto.gridOptions.treeOptionDto.expandedRowKeys)
} else if (gridDto.gridOptions.treeOptionDto?.autoExpandAll) {
setExpandedRowKeys([])
}
}, [gridDto])
useEffect(() => {
if (!gridDto) return
const cols = getBandedColumns()
setColumnData(cols)
2025-11-08 20:22:50 +00:00
const dataSource = createSelectDataSource(
gridDto.gridOptions,
listFormCode,
searchParams,
cols,
layoutTypes.tree,
)
setTreeListDataSource(dataSource)
}, [gridDto, searchParams])
useEffect(() => {
const activeFilters = extraFilters.filter((f) => f.value)
let base: any = null
if (defaultSearchParams.current) {
base = JSON.parse(defaultSearchParams.current)
}
const baseTriplets = extractSearchParamsFields(base)
const extraTriplets = activeFilters.map(
(f) => [f.fieldName, f.operator, f.value] as [string, string, any],
)
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
} else {
acc.push(cur)
}
return acc
},
[] as [string, string, any][],
)
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)
}
if (filter) {
searchParams?.set('filter', JSON.stringify(filter))
} else {
searchParams?.delete('filter')
}
gridRef.current?.instance.refresh()
}, [extraFilters])
useEffect(() => {
refListFormCode.current = listFormCode
if (!gridRef?.current) {
return
}
gridRef.current.instance.option('columns', columnData as any)
gridRef.current.instance.option('dataSource', treeListDataSource)
const stateStoring: IStateStoringProps = {
enabled: gridDto?.gridOptions.stateStoringDto?.enabled,
type: gridDto?.gridOptions.stateStoringDto?.type,
savingTimeout: gridDto?.gridOptions.stateStoringDto?.savingTimeout,
storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey,
}
if (
gridDto?.gridOptions.stateStoringDto?.enabled &&
gridDto?.gridOptions.stateStoringDto?.type === 'custom'
) {
stateStoring.customSave = customSaveState
stateStoring.customLoad = customLoadState
}
gridRef.current.instance.option('stateStoring', stateStoring)
}, [columnData])
return (
<>
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
<Container className={DX_CLASSNAMES}>
{!isSubForm && (
<Helmet
titleTemplate="%s | Erp Platform"
title={translate('::' + gridDto?.gridOptions.title)}
defaultTitle="Erp Platform"
></Helmet>
)}
{gridDto && columnData && (
<div className="p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 ">
<TreeListDx
ref={gridRef as any}
id={'TreeList-' + listFormCode}
height={gridDto.gridOptions.height || 'calc(100vh - 150px)'}
width={gridDto.gridOptions.width || '100%'}
dataStructure="plain"
2025-11-08 20:22:50 +00:00
keyExpr={gridDto.gridOptions.treeOptionDto?.keyExpr}
parentIdExpr={gridDto.gridOptions.treeOptionDto?.parentIdExpr}
hasItemsExpr={gridDto.gridOptions.treeOptionDto?.hasItemsExpr}
rootValue={
gridDto.gridOptions.treeOptionDto?.rootValue === '' ||
gridDto.gridOptions.treeOptionDto?.rootValue === undefined
? null
: gridDto.gridOptions.treeOptionDto?.rootValue
}
{...(!gridDto.gridOptions.treeOptionDto?.autoExpandAll && {
expandedRowKeys: expandedRowKeys,
onRowExpanding: (e: any) => {
setExpandedRowKeys((prev) => [...prev, e.key])
},
onRowCollapsing: (e: any) => {
setExpandedRowKeys((prev) => prev.filter((k) => k !== e.key))
},
})}
autoExpandAll={gridDto.gridOptions.treeOptionDto?.autoExpandAll || false}
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}
hoverStateEnabled={gridDto.gridOptions.columnOptionDto?.hoverStateEnabled}
columnHidingEnabled={gridDto.gridOptions.columnOptionDto?.columnHidingEnabled}
focusedRowEnabled={gridDto.gridOptions.columnOptionDto?.focusedRowEnabled}
showColumnHeaders={gridDto.gridOptions.columnOptionDto?.showColumnHeaders}
filterSyncEnabled={true}
onSelectionChanged={onSelectionChanged}
onInitNewRow={onInitNewRow}
onCellPrepared={onCellPrepared}
onRowInserting={onRowInserting}
onRowUpdating={onRowUpdating}
onEditingStart={onEditingStart}
onDataErrorOccurred={onDataErrorOccurred}
onEditCanceled={() => {
setMode('view')
setIsPopupFullScreen(false)
}}
onSaved={() => {
setMode('view')
setIsPopupFullScreen(false)
}}
onRowInserted={() => {
props.refreshData?.()
}}
onRowUpdated={() => {
props.refreshData?.()
}}
onRowRemoved={() => {
props.refreshData?.()
}}
onContentReady={(e) => {
// Restore expanded keys after data refresh (only if autoExpandAll is false)
if (
!gridDto.gridOptions.treeOptionDto?.autoExpandAll &&
expandedRowKeys.length > 0
) {
e.component.option('expandedRowKeys', expandedRowKeys)
}
}}
>
<RemoteOperations filtering={true} sorting={true} grouping={false} />
<Editing
refreshMode={gridDto.gridOptions.editingOptionDto?.refreshMode}
mode={smaller.md ? 'form' : gridDto.gridOptions.editingOptionDto?.mode}
allowDeleting={gridDto.gridOptions.editingOptionDto?.allowDeleting}
allowUpdating={gridDto.gridOptions.editingOptionDto?.allowUpdating}
allowAdding={gridDto.gridOptions.editingOptionDto?.allowAdding}
useIcons={gridDto.gridOptions.editingOptionDto?.useIcons}
2025-11-08 07:49:38 +00:00
confirmDelete={gridDto.gridOptions.editingOptionDto?.confirmDelete}
selectTextOnEditStart={gridDto.gridOptions.editingOptionDto?.selectTextOnEditStart}
startEditAction={gridDto.gridOptions.editingOptionDto?.startEditAction}
popup={{
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 tree = gridRef.current?.instance
tree?.saveEditData()
},
},
},
{
widget: 'dxButton',
toolbar: 'bottom',
location: 'after',
options: {
text: translate('::Cancel'),
onClick: () => {
const tree = gridRef.current?.instance
tree?.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),
},
},
],
}}
2025-11-08 07:49:38 +00:00
form={{
colCount: 1,
onFieldDataChanged: (e) => {
if (e.dataField) {
const formItem = gridDto.gridOptions.editingFormDto
.flatMap((group) => group.items || [])
.find((i) => i.dataField === e.dataField)
if (formItem?.editorScript) {
try {
eval(formItem.editorScript)
} catch (err) {
console.error('Script exec error', err)
}
}
}
},
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[] = []
// Helper function: item mapper
const mapFormItem = (i: EditingFormItemDto) => {
let editorOptions: EditorOptionsWithButtons = {}
try {
editorOptions = i.editorOptions && JSON.parse(i.editorOptions)
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
},
)
}
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) {
editorOptions = {
...editorOptions,
readOnly: true,
}
}
}
}
} catch {}
const fieldName = i.dataField.split(':')[0]
const listFormField = gridDto.columnFormats.find(
(x: any) => x.fieldName === fieldName,
)
if (listFormField?.sourceDbType === DbTypeEnum.Date) {
editorOptions = {
...{
type: 'date',
dateSerializationFormat: 'yyyy-MM-dd',
displayFormat: 'shortDate',
},
...editorOptions,
}
} else if (
listFormField?.sourceDbType === DbTypeEnum.DateTime ||
listFormField?.sourceDbType === DbTypeEnum.DateTime2 ||
listFormField?.sourceDbType === DbTypeEnum.DateTimeOffset
) {
editorOptions = {
...{
type: 'datetime',
dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ssxxx',
displayFormat: 'shortDateShortTime',
},
...editorOptions,
}
}
const item: SimpleItemWithColData = {
canRead: listFormField?.canRead ?? false,
canUpdate: listFormField?.canUpdate ?? false,
canCreate: listFormField?.canCreate ?? false,
canExport: listFormField?.canExport ?? false,
dataField: i.dataField,
name: i.dataField,
editorType2: i.editorType2,
editorType:
i.editorType2 == PlatformEditorTypes.dxGridBox
? 'dxDropDownBox'
: i.editorType2,
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
}
sortedFormDto.forEach((e: any) => {
if (e.itemType !== 'tabbed') {
result.push({
itemType: e.itemType,
colCount: e.colCount,
2025-11-08 20:22:50 +00:00
colSpan: e.colSpan,
2025-11-08 07:49:38 +00:00
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') {
return a.canCreate || a.canRead
} else if (mode === 'edit') {
return a.canUpdate || a.canRead
} 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') {
return a.canCreate || a.canRead
} else if (mode === 'edit') {
return a.canUpdate || a.canRead
} else {
return false
}
}),
}
}),
})
}
})
return result
})()
: undefined,
}}
></Editing>
<Template name={'cellEditTagBox'} render={TagBoxEditorComponent} />
<Template name={'cellEditGridBox'} render={GridBoxEditorComponent} />
<Template name="extraFilters">
<GridExtraFilterToolbar
filters={gridDto?.gridOptions.extraFilterDto ?? []}
extraFilters={extraFilters}
setExtraFilters={setExtraFilters}
/>
</Template>
<Toolbar
visible={(toolbarData?.length ?? 0) > 0 || (filterToolbarData?.length ?? 0) > 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}
></SearchPanel>
<Selection
mode={gridDto.gridOptions.selectionDto?.mode}
recursive={gridDto.gridOptions.treeOptionDto?.recursiveSelection || false}
></Selection>
<Paging enabled={true} defaultPageSize={gridDto.gridOptions.pageSize ?? 10} />
<Pager
visible={gridDto.gridOptions.pagerOptionDto?.visible ?? true}
allowedPageSizes={
gridDto.gridOptions.pagerOptionDto?.allowedPageSizes
?.split(',')
.map((a: any) => +a) ?? [10, 20, 50, 100]
}
showPageSizeSelector={
gridDto.gridOptions.pagerOptionDto?.showPageSizeSelector ?? true
}
showInfo={gridDto.gridOptions.pagerOptionDto?.showInfo ?? true}
showNavigationButtons={
gridDto.gridOptions.pagerOptionDto?.showNavigationButtons ?? true
}
infoText={gridDto.gridOptions.pagerOptionDto?.infoText}
displayMode={gridDto.gridOptions.pagerOptionDto?.displayMode ?? 'full'}
></Pager>
<ColumnChooser
enabled={gridDto.gridOptions.columnOptionDto?.columnChooserEnabled}
mode={gridDto.gridOptions.columnOptionDto?.columnChooserMode}
></ColumnChooser>
<ColumnFixing
enabled={gridDto.gridOptions.columnOptionDto?.columnFixingEnabled}
></ColumnFixing>
<Scrolling
mode="virtual"
rowRenderingMode="virtual"
showScrollbar="always"
></Scrolling>
<LoadPanel
enabled={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled}
text={gridDto.gridOptions.pagerOptionDto?.loadPanelText}
></LoadPanel>
</TreeListDx>
</div>
)}
<Dialog
isOpen={toolbarModalData?.open || false}
onClose={() => setToolbarModalData(undefined)}
onRequestClose={() => setToolbarModalData(undefined)}
>
{toolbarModalData?.content}
</Dialog>
<GridFilterDialogs gridRef={gridRef as any} listFormCode={listFormCode} {...filterData} />
</Container>
</>
)
}
export default Tree