Grid ve Tree state değişiklikleri

This commit is contained in:
Sedat Öztürk 2026-02-08 19:31:18 +03:00
parent 5d7db888ec
commit cd67bd2b06
4 changed files with 188 additions and 149 deletions

View file

@ -3210,6 +3210,12 @@
"en": "Reset Grid State", "en": "Reset Grid State",
"tr": "Tablo Yapısını Sıfırla" "tr": "Tablo Yapısını Sıfırla"
}, },
{
"resourceName": "Platform",
"key": "ListForms.ListForm.GridStateReset",
"en": "Grid State Reset",
"tr": "Tablo Yapısı Sıfırlandı"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListForm.SaveFilter", "key": "ListForms.ListForm.SaveFilter",

View file

@ -131,10 +131,53 @@ const Grid = (props: GridProps) => {
} }
}, [searchParams]) }, [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({ const { filterToolbarData, ...filterData } = useFilters({
gridDto, gridDto,
gridRef, gridRef,
listFormCode, listFormCode,
saveGridState: customSaveState,
}) })
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
@ -415,8 +458,12 @@ const Grid = (props: GridProps) => {
gridDto.columnFormats.forEach((col) => { gridDto.columnFormats.forEach((col) => {
if (col.lookupDto?.cascadeParentFields && col.fieldName) { if (col.lookupDto?.cascadeParentFields && col.fieldName) {
const parentFields = col.lookupDto.cascadeParentFields.split(',').map((f: string) => f.trim()) const parentFields = col.lookupDto.cascadeParentFields
const childFields = col.lookupDto.cascadeEmptyFields?.split(',').map((f: string) => f.trim()) .split(',')
.map((f: string) => f.trim())
const childFields = col.lookupDto.cascadeEmptyFields
?.split(',')
.map((f: string) => f.trim())
cascadeMap.set(col.fieldName, { cascadeMap.set(col.fieldName, {
parentFields, parentFields,
@ -557,28 +604,6 @@ const Grid = (props: GridProps) => {
[gridDto, cascadeFieldsMap], [gridDto, cascadeFieldsMap],
) )
// 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)
})
},
[listFormCode, storageKey],
)
const customLoadState = useCallback(() => { const customLoadState = useCallback(() => {
return getListFormCustomization( return getListFormCustomization(
listFormCode, listFormCode,
@ -810,13 +835,15 @@ const Grid = (props: GridProps) => {
const instance = gridRef?.current?.instance() const instance = gridRef?.current?.instance()
if (instance) { if (instance) {
customLoadState().then((state) => { customLoadState()
if (state) { .then((state) => {
instance.state(state) if (state) {
} instance.state(state)
}).catch((err) => { }
console.error('State load error:', err) })
}) .catch((err) => {
console.error('State load error:', err)
})
} }
}, [gridDto, gridDataSource, columnData]) }, [gridDto, gridDataSource, columnData])
@ -1021,10 +1048,7 @@ const Grid = (props: GridProps) => {
}) })
const buffer = await workbook.csv.writeBuffer() const buffer = await workbook.csv.writeBuffer()
saveAs( saveAs(new Blob([buffer], { type: 'text/csv' }), `${listFormCode}_export.csv`)
new Blob([buffer], { type: 'text/csv' }),
`${listFormCode}_export.csv`,
)
} else if (e.format === 'pdf') { } else if (e.format === 'pdf') {
// jspdf + devextreme pdf exporter => ihtiyaç anında yükle // jspdf + devextreme pdf exporter => ihtiyaç anında yükle
const [jspdfMod, { exportDataGrid: exportDataPdf }] = await Promise.all([ const [jspdfMod, { exportDataGrid: exportDataPdf }] = await Promise.all([

View file

@ -24,7 +24,6 @@ import TreeListDx, {
FilterPanel, FilterPanel,
FilterRow, FilterRow,
HeaderFilter, HeaderFilter,
IStateStoringProps,
LoadPanel, LoadPanel,
Pager, Pager,
Paging, Paging,
@ -126,10 +125,68 @@ const Tree = (props: TreeProps) => {
} }
}, [searchParams]) }, [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: `tree-${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 customLoadState = useCallback(() => {
return getListFormCustomization(
listFormCode,
ListFormCustomizationTypeEnum.GridState,
`tree-${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])
const { filterToolbarData, ...filterData } = useFilters({ const { filterToolbarData, ...filterData } = useFilters({
gridDto, gridDto,
gridRef, gridRef,
listFormCode, listFormCode,
saveGridState: customSaveState,
}) })
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
@ -543,52 +600,6 @@ const Tree = (props: TreeProps) => {
} }
} }
// 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: `tree-${storageKey}`,
customizationData: JSON.stringify(state),
}).then(() => {
setGridPanelColor(statedGridPanelColor)
})
},
[listFormCode, storageKey],
)
const customLoadState = useCallback(() => {
return getListFormCustomization(
listFormCode,
ListFormCustomizationTypeEnum.GridState,
`tree-${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])
// StateStoring fonksiyonlarını ref'e kaydet - Grid'deki gibi
const customSaveStateRef = useRef(customSaveState)
const customLoadStateRef = useRef(customLoadState)
useEffect(() => {
customSaveStateRef.current = customSaveState
customLoadStateRef.current = customLoadState
}, [customSaveState, customLoadState])
useEffect(() => { useEffect(() => {
if (gridRef?.current) { if (gridRef?.current) {
gridRef?.current?.instance().option('columns', undefined) gridRef?.current?.instance().option('columns', undefined)
@ -727,36 +738,58 @@ const Tree = (props: TreeProps) => {
gridRef.current?.instance().refresh() gridRef.current?.instance().refresh()
}, [extraFilters]) }, [extraFilters])
useEffect(() => {
if (!columnData || !gridRef?.current) return
const instance = gridRef?.current?.instance()
if (instance) {
instance.option('columns', columnData as any)
}
}, [columnData])
useEffect(() => {
if (!treeListDataSource || !gridRef?.current) return
const instance = gridRef?.current?.instance()
if (instance) {
instance.option('dataSource', treeListDataSource)
}
}, [treeListDataSource])
useEffect(() => { useEffect(() => {
refListFormCode.current = listFormCode refListFormCode.current = listFormCode
if (!gridRef?.current) { }, [listFormCode])
return
}
gridRef?.current?.instance().option('columns', columnData as any) // Component mount olduğunda state'i bir kez yükle
gridRef?.current?.instance().option('dataSource', treeListDataSource) useEffect(() => {
if (!gridDto || !gridRef?.current || !treeListDataSource || !columnData) return
const stateStoring: IStateStoringProps = { const instance = gridRef?.current?.instance()
enabled: gridDto?.gridOptions.stateStoringDto?.enabled, if (instance) {
type: gridDto?.gridOptions.stateStoringDto?.type, customLoadState()
savingTimeout: gridDto?.gridOptions.stateStoringDto?.savingTimeout, .then((state) => {
storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey, if (state) {
instance.state(state)
}
})
.catch((err) => {
console.error('State load error:', err)
})
} }
if ( }, [gridDto, treeListDataSource, columnData])
gridDto?.gridOptions.stateStoringDto?.enabled &&
gridDto?.gridOptions.stateStoringDto?.type === 'custom'
) {
// Ref pattern kullan - Grid'deki gibi
stateStoring.customSave = (state: any) => customSaveStateRef.current(state)
stateStoring.customLoad = () => customLoadStateRef.current()
}
gridRef?.current?.instance().option('stateStoring', stateStoring)
// State'i yükle - dataSource ve columns hazır olduğunda // StateStoring'i devre dışı bırak - manuel kaydetme kullanılacak
if (treeListDataSource && columnData) { useEffect(() => {
gridRef?.current?.instance().state(undefined) // undefined = reload from storage if (!gridDto || !gridRef?.current) return
const instance = gridRef?.current?.instance()
if (instance) {
// Otomatik state kaydetme/yükleme kapalı
instance.option('stateStoring', {
enabled: false,
})
} }
}, [columnData, treeListDataSource, gridDto]) // dataSource ve columns hazır olduğunda state yükle }, [gridDto])
return ( return (
<> <>

View file

@ -4,7 +4,7 @@ import {
ListFormCustomizationForUserDto, ListFormCustomizationForUserDto,
ListFormCustomizationTypeEnum, ListFormCustomizationTypeEnum,
} from '@/proxy/form/models' } from '@/proxy/form/models'
import { getListFormCustomization, postListFormCustomization } from '@/services/list-form-customization.service' import { getListFormCustomization } from '@/services/list-form-customization.service'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { DataGridRef } from 'devextreme-react/data-grid' import { DataGridRef } from 'devextreme-react/data-grid'
import { PivotGridRef } from 'devextreme-react/pivot-grid' import { PivotGridRef } from 'devextreme-react/pivot-grid'
@ -13,6 +13,7 @@ import { ToolbarItem } from 'devextreme/ui/data_grid_types'
import dxDataGrid from 'devextreme/ui/data_grid' import dxDataGrid from 'devextreme/ui/data_grid'
import dxPivotGrid from 'devextreme/ui/pivot_grid' import dxPivotGrid from 'devextreme/ui/pivot_grid'
import dxTreeList from 'devextreme/ui/tree_list' import dxTreeList from 'devextreme/ui/tree_list'
import dxGantt from 'devextreme/ui/gantt'
import { Dispatch, MutableRefObject, SetStateAction, useEffect, useState } from 'react' import { Dispatch, MutableRefObject, SetStateAction, useEffect, useState } from 'react'
import { setGridPanelColor } from './Utils' import { setGridPanelColor } from './Utils'
import { usePermission } from '@/utils/hooks/usePermission' import { usePermission } from '@/utils/hooks/usePermission'
@ -26,7 +27,7 @@ export interface ISelectBoxData {
label?: string label?: string
} }
type GridInstance = dxDataGrid | dxPivotGrid | dxTreeList type GridInstance = dxDataGrid | dxPivotGrid | dxTreeList | dxGantt
// Grid tipini kontrol eden yardımcı fonksiyonlar // Grid tipini kontrol eden yardımcı fonksiyonlar
const isDataGrid = (grid: GridInstance): grid is dxDataGrid => { const isDataGrid = (grid: GridInstance): grid is dxDataGrid => {
@ -37,15 +38,12 @@ const isTreeList = (grid: GridInstance): grid is dxTreeList => {
return 'getRootNode' in grid return 'getRootNode' in grid
} }
const getToolbarItems = (grid: GridInstance): any[] => { const isGantt = (grid: GridInstance): grid is dxGantt => {
if (isDataGrid(grid)) { return 'getTaskData' in grid
const toolbarOptions = grid.option('toolbar') }
return toolbarOptions?.items || []
} else if (isTreeList(grid)) { const supportsState = (grid: GridInstance): grid is dxDataGrid | dxTreeList => {
const toolbarOptions = grid.option('toolbar') return 'state' in grid && typeof (grid as any).state === 'function'
return toolbarOptions?.items || []
}
return []
} }
const setToolbarItemValue = (grid: GridInstance, itemName: string, value: any) => { const setToolbarItemValue = (grid: GridInstance, itemName: string, value: any) => {
@ -140,6 +138,7 @@ const useFilters = ({
gridDto, gridDto,
gridRef, gridRef,
listFormCode, listFormCode,
saveGridState,
}: { }: {
gridDto?: GridDto gridDto?: GridDto
gridRef: gridRef:
@ -148,6 +147,7 @@ const useFilters = ({
| MutableRefObject<TreeListRef<any, any> | undefined> | MutableRefObject<TreeListRef<any, any> | undefined>
| MutableRefObject<GanttRef | undefined> | MutableRefObject<GanttRef | undefined>
listFormCode: string listFormCode: string
saveGridState?: (state: any) => Promise<void>
}): { }): {
filterToolbarData: ToolbarItem[] filterToolbarData: ToolbarItem[]
isCreateUpdateModalOpen: boolean isCreateUpdateModalOpen: boolean
@ -261,7 +261,7 @@ const useFilters = ({
}) })
} }
if (checkPermission("App.Listforms.Listform.Update")) { if (checkPermission('App.Listforms.Listform.Update')) {
menus.push({ menus.push({
text: translate('::ListForms.ListForm.Manage'), text: translate('::ListForms.ListForm.Manage'),
id: 'openManage', id: 'openManage',
@ -324,36 +324,12 @@ const useFilters = ({
return return
} }
const currentState = grid.state() if (saveGridState && supportsState(grid)) {
const storageKey = gridDto?.gridOptions.stateStoringDto?.storageKey ?? '' const currentState = grid.state()
saveGridState(currentState).catch(() => {
postListFormCustomization({ // Error handling already done in customSaveState
listFormCode: listFormCode,
customizationType: ListFormCustomizationTypeEnum.GridState,
filterName: `list-${storageKey}`,
customizationData: JSON.stringify(currentState),
})
.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',
},
)
}) })
}
} else if (itemData.id === 'resetGridState') { } else if (itemData.id === 'resetGridState') {
// state ye kaydedilen grid ayarlarini siler ve gridi resetler // state ye kaydedilen grid ayarlarini siler ve gridi resetler
const grid = gridRef.current?.instance() const grid = gridRef.current?.instance()