From cd67bd2b06a312609741f5ab3608681ed5c2d1e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sun, 8 Feb 2026 19:31:18 +0300 Subject: [PATCH] =?UTF-8?q?Grid=20ve=20Tree=20state=20de=C4=9Fi=C5=9Fiklik?= =?UTF-8?q?leri?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Seeds/LanguagesData.json | 6 + ui/src/views/list/Grid.tsx | 98 ++++++---- ui/src/views/list/Tree.tsx | 175 +++++++++++------- ui/src/views/list/useFilters.tsx | 58 ++---- 4 files changed, 188 insertions(+), 149 deletions(-) diff --git a/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json index 4590e79d..f9997464 100644 --- a/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json @@ -3210,6 +3210,12 @@ "en": "Reset Grid State", "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", "key": "ListForms.ListForm.SaveFilter", diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx index d9efddc1..866145ed 100644 --- a/ui/src/views/list/Grid.tsx +++ b/ui/src/views/list/Grid.tsx @@ -131,10 +131,53 @@ const Grid = (props: GridProps) => { } }, [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( + + {translate('::ListForms.ListForm.GridStateSaved')} + , + { + placement: 'top-end', + }, + ) + }) + .catch((err) => { + toast.push( + + {translate('::ListForms.ListForm.GridStateSaveError')} + , + { + placement: 'top-end', + }, + ) + throw err + }) + }, + [listFormCode, storageKey, translate], + ) + const { filterToolbarData, ...filterData } = useFilters({ gridDto, gridRef, listFormCode, + saveGridState: customSaveState, }) const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) @@ -415,9 +458,13 @@ const Grid = (props: GridProps) => { 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()) - + 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, @@ -557,28 +604,6 @@ const Grid = (props: GridProps) => { [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(() => { return getListFormCustomization( listFormCode, @@ -810,13 +835,15 @@ const Grid = (props: GridProps) => { const instance = gridRef?.current?.instance() if (instance) { - customLoadState().then((state) => { - if (state) { - instance.state(state) - } - }).catch((err) => { - console.error('State load error:', err) - }) + customLoadState() + .then((state) => { + if (state) { + instance.state(state) + } + }) + .catch((err) => { + console.error('State load error:', err) + }) } }, [gridDto, gridDataSource, columnData]) @@ -1014,17 +1041,14 @@ const Grid = (props: GridProps) => { // 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`, - ) + 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([ diff --git a/ui/src/views/list/Tree.tsx b/ui/src/views/list/Tree.tsx index 77695853..c8b8f8a9 100644 --- a/ui/src/views/list/Tree.tsx +++ b/ui/src/views/list/Tree.tsx @@ -24,7 +24,6 @@ import TreeListDx, { FilterPanel, FilterRow, HeaderFilter, - IStateStoringProps, LoadPanel, Pager, Paging, @@ -126,10 +125,68 @@ const Tree = (props: TreeProps) => { } }, [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( + + {translate('::ListForms.ListForm.GridStateSaved')} + , + { + placement: 'top-end', + }, + ) + }) + .catch((err) => { + toast.push( + + {translate('::ListForms.ListForm.GridStateSaveError')} + , + { + 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({ gridDto, gridRef, listFormCode, + saveGridState: customSaveState, }) 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(() => { if (gridRef?.current) { gridRef?.current?.instance().option('columns', undefined) @@ -727,36 +738,58 @@ const Tree = (props: TreeProps) => { gridRef.current?.instance().refresh() }, [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(() => { refListFormCode.current = listFormCode - if (!gridRef?.current) { - return - } + }, [listFormCode]) - gridRef?.current?.instance().option('columns', columnData as any) - gridRef?.current?.instance().option('dataSource', treeListDataSource) + // Component mount olduğunda state'i bir kez yükle + useEffect(() => { + if (!gridDto || !gridRef?.current || !treeListDataSource || !columnData) return - const stateStoring: IStateStoringProps = { - enabled: gridDto?.gridOptions.stateStoringDto?.enabled, - type: gridDto?.gridOptions.stateStoringDto?.type, - savingTimeout: gridDto?.gridOptions.stateStoringDto?.savingTimeout, - storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey, + const instance = gridRef?.current?.instance() + if (instance) { + customLoadState() + .then((state) => { + if (state) { + instance.state(state) + } + }) + .catch((err) => { + console.error('State load error:', err) + }) } - if ( - 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() + }, [gridDto, treeListDataSource, 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, + }) } - gridRef?.current?.instance().option('stateStoring', stateStoring) - - // State'i yükle - dataSource ve columns hazır olduğunda - if (treeListDataSource && columnData) { - gridRef?.current?.instance().state(undefined) // undefined = reload from storage - } - }, [columnData, treeListDataSource, gridDto]) // dataSource ve columns hazır olduğunda state yükle + }, [gridDto]) return ( <> diff --git a/ui/src/views/list/useFilters.tsx b/ui/src/views/list/useFilters.tsx index 9cd5eeb5..ed58c659 100644 --- a/ui/src/views/list/useFilters.tsx +++ b/ui/src/views/list/useFilters.tsx @@ -4,7 +4,7 @@ import { ListFormCustomizationForUserDto, ListFormCustomizationTypeEnum, } 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 { DataGridRef } from 'devextreme-react/data-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 dxPivotGrid from 'devextreme/ui/pivot_grid' import dxTreeList from 'devextreme/ui/tree_list' +import dxGantt from 'devextreme/ui/gantt' import { Dispatch, MutableRefObject, SetStateAction, useEffect, useState } from 'react' import { setGridPanelColor } from './Utils' import { usePermission } from '@/utils/hooks/usePermission' @@ -26,7 +27,7 @@ export interface ISelectBoxData { label?: string } -type GridInstance = dxDataGrid | dxPivotGrid | dxTreeList +type GridInstance = dxDataGrid | dxPivotGrid | dxTreeList | dxGantt // Grid tipini kontrol eden yardımcı fonksiyonlar const isDataGrid = (grid: GridInstance): grid is dxDataGrid => { @@ -37,15 +38,12 @@ const isTreeList = (grid: GridInstance): grid is dxTreeList => { return 'getRootNode' in grid } -const getToolbarItems = (grid: GridInstance): any[] => { - if (isDataGrid(grid)) { - const toolbarOptions = grid.option('toolbar') - return toolbarOptions?.items || [] - } else if (isTreeList(grid)) { - const toolbarOptions = grid.option('toolbar') - return toolbarOptions?.items || [] - } - return [] +const isGantt = (grid: GridInstance): grid is dxGantt => { + return 'getTaskData' in grid +} + +const supportsState = (grid: GridInstance): grid is dxDataGrid | dxTreeList => { + return 'state' in grid && typeof (grid as any).state === 'function' } const setToolbarItemValue = (grid: GridInstance, itemName: string, value: any) => { @@ -140,6 +138,7 @@ const useFilters = ({ gridDto, gridRef, listFormCode, + saveGridState, }: { gridDto?: GridDto gridRef: @@ -148,6 +147,7 @@ const useFilters = ({ | MutableRefObject | undefined> | MutableRefObject listFormCode: string + saveGridState?: (state: any) => Promise }): { filterToolbarData: ToolbarItem[] isCreateUpdateModalOpen: boolean @@ -261,7 +261,7 @@ const useFilters = ({ }) } - if (checkPermission("App.Listforms.Listform.Update")) { + if (checkPermission('App.Listforms.Listform.Update')) { menus.push({ text: translate('::ListForms.ListForm.Manage'), id: 'openManage', @@ -324,36 +324,12 @@ const useFilters = ({ return } - const currentState = grid.state() - const storageKey = gridDto?.gridOptions.stateStoringDto?.storageKey ?? '' - - postListFormCustomization({ - listFormCode: listFormCode, - customizationType: ListFormCustomizationTypeEnum.GridState, - filterName: `list-${storageKey}`, - customizationData: JSON.stringify(currentState), - }) - .then(() => { - setGridPanelColor(statedGridPanelColor) - toast.push( - - {translate('::ListForms.ListForm.GridStateSaved')} - , - { - placement: 'top-end', - }, - ) - }) - .catch((err) => { - toast.push( - - {translate('::ListForms.ListForm.GridStateSaveError')} - , - { - placement: 'top-end', - }, - ) + if (saveGridState && supportsState(grid)) { + const currentState = grid.state() + saveGridState(currentState).catch(() => { + // Error handling already done in customSaveState }) + } } else if (itemData.id === 'resetGridState') { // state ye kaydedilen grid ayarlarini siler ve gridi resetler const grid = gridRef.current?.instance()