From 809bfb7129604fd001b421e46f137d26e381769f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:03:17 +0300 Subject: [PATCH] State sistemi manuel olarak tetiklenecek hale getirildi. --- .../ListFormCustomizationAppService.cs | 1 - .../Seeds/ListFormSeeder_Hr.cs | 33 ++-- ui/src/views/list/Grid.tsx | 139 +++++++++-------- ui/src/views/list/Pivot.tsx | 143 ++++++++++++------ ui/src/views/list/useFilters.tsx | 46 +++++- 5 files changed, 231 insertions(+), 131 deletions(-) diff --git a/api/src/Erp.Platform.Application/ListForms/ListFormCustomizationAppService.cs b/api/src/Erp.Platform.Application/ListForms/ListFormCustomizationAppService.cs index 53ee1e05..ab0339a8 100644 --- a/api/src/Erp.Platform.Application/ListForms/ListFormCustomizationAppService.cs +++ b/api/src/Erp.Platform.Application/ListForms/ListFormCustomizationAppService.cs @@ -7,7 +7,6 @@ using Erp.Platform.Enums; using Erp.Platform.ListForms.Customization; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; using Volo.Abp; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; diff --git a/api/src/Erp.Platform.DbMigrator/Seeds/ListFormSeeder_Hr.cs b/api/src/Erp.Platform.DbMigrator/Seeds/ListFormSeeder_Hr.cs index 0e69ae1d..028f35b3 100644 --- a/api/src/Erp.Platform.DbMigrator/Seeds/ListFormSeeder_Hr.cs +++ b/api/src/Erp.Platform.DbMigrator/Seeds/ListFormSeeder_Hr.cs @@ -764,22 +764,23 @@ public class ListFormSeeder_Hr : IDataSeedContributor, ITransientDependency DataSourceType = UiLookupDataSourceTypeEnum.StaticData, DisplayExpr = "name", ValueExpr = "key", - LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { - new () { Key= "⭐", Name= "⭐ Yıldız" }, - new () { Key= "🏆", Name= "🏆 Kupa" }, - new () { Key= "🥇", Name= "🥇 Altın Madalya" }, - new () { Key= "🥈", Name= "🥈 Gümüş Madalya" }, - new () { Key= "🥉", Name= "🥉 Bronz Madalya" }, - new () { Key= "👑", Name= "👑 Taç" }, - new () { Key= "💎", Name= "💎 Elmas" }, - new () { Key= "💡", Name= "💡 Ampul" }, - new () { Key= "🔥", Name= "🔥 Ateş" }, - new () { Key= "âš¡", Name= "âš¡ Şimşek" }, - new () { Key= "🎯", Name= "🎯 Hedef" }, - new () { Key= "📈", Name= "📈 Grafik" }, - new () { Key= "🚀", Name= "🚀 Roket" }, - new () { Key= "💪", Name= "💪 Güç" }, - new () { Key= "❤️", Name= "❤️ Kalp" } + LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] + { + new() { Key = "⭐", Name = "⭐ Yıldız" }, + new() { Key = "🏆", Name = "🏆 Kupa" }, + new() { Key = "🥇", Name = "🥇 Altın Madalya" }, + new() { Key = "🥈", Name = "🥈 Gümüş Madalya" }, + new() { Key = "🥉", Name = "🥉 Bronz Madalya" }, + new() { Key = "👑", Name = "👑 Taç" }, + new() { Key = "💎", Name = "💎 Elmas" }, + new() { Key = "💡", Name = "💡 Ampul" }, + new() { Key = "🔥", Name = "🔥 Ateş" }, + new() { Key = "⚡", Name = "⚡ Şimşek" }, + new() { Key = "🎯", Name = "🎯 Hedef" }, + new() { Key = "📈", Name = "📈 Grafik" }, + new() { Key = "🚀", Name = "🚀 Roket" }, + new() { Key = "💪", Name = "💪 Güç" }, + new() { Key = "❤️", Name = "❤️ Kalp" } }), }), ColumnCustomizationJson = DefaultColumnCustomizationJson, diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx index 346e90a5..d012a347 100644 --- a/ui/src/views/list/Grid.tsx +++ b/ui/src/views/list/Grid.tsx @@ -409,10 +409,11 @@ const Grid = (props: GridProps) => { }, []) // Cascade parent fields mapping'i memoize et - popup açılışını hızlandırır - const cascadeFieldsMap = useMemo(() => { - if (!gridDto) return new Map() + // 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 map = new Map< + const cascadeMap = new Map< string, { parentFields: string[] @@ -420,16 +421,31 @@ const Grid = (props: GridProps) => { } >() + const parentChildMap = new Map>() + gridDto.columnFormats.forEach((col) => { if (col.lookupDto?.cascadeParentFields && col.fieldName) { - map.set(col.fieldName, { - parentFields: col.lookupDto.cascadeParentFields.split(',').map((f: string) => f.trim()), - 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, + }) + + // 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 map + return { cascadeFieldsMap: cascadeMap, parentToChildrenMap: parentChildMap } }, [gridDto]) const onEditorPreparing = useCallback( @@ -439,50 +455,48 @@ const Grid = (props: GridProps) => { .flatMap((group) => group.items || []) .find((i) => i.dataField === editor.dataField) - // Cascade disabled mantığı + // 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) => { - if (prevHandler) prevHandler(e) + prevHandler?.(e) - // Parent field değiştiğinde tüm cascade childları kontrol et const grid = editor.component const rowKey = grid.option('editing.editRowKey') const rowIndex = grid.getRowIndexByKey(rowKey) - const rowData = grid.getVisibleRows().find((r) => r.key === rowKey)?.data || {} - // Bu field bir parent ise, child fieldleri temizle - if (cascadeInfo.childFields) { - cascadeInfo.childFields.forEach((childField: string) => { - if (rowIndex >= 0) { - grid.cellValue(rowIndex, childField, null) - } + // Parent field değiştiğinde child fieldleri temizle + if (childFields && rowIndex >= 0) { + childFields.forEach((childField: string) => { + grid.cellValue(rowIndex, childField, null) }) } - // Tüm cascade fieldlerin disabled durumlarını güncelle - sadece değişen field için - const popup = grid.option('editing.popup') - if (popup) { + // Sadece bu parent'tan etkilenen childların disabled durumunu güncelle + if (affectedChildren?.size) { const formInstance = grid.option('editing.form') as any - if (formInstance && formInstance.getEditor) { - // Sadece bu field'den etkilenen childları güncelle - cascadeFieldsMap.forEach((info, fieldName) => { - if (info.parentFields.includes(editor.dataField!)) { - const shouldDisable = info.parentFields.some((pf: string) => !rowData[pf]) - try { - const editorInstance = formInstance.getEditor(fieldName) - if (editorInstance) { - editorInstance.option('disabled', shouldDisable) - } - } catch (err) { - console.debug('Cascade disabled update skipped for', fieldName, err) + 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 { + formInstance.getEditor(childFieldName)?.option('disabled', shouldDisable) + } catch {} } - } - }) + }) + } } } } @@ -490,10 +504,10 @@ const Grid = (props: GridProps) => { // İlk açılışta disabled durumunu kontrol et const grid = editor.component const rowKey = grid.option('editing.editRowKey') - const rowData = grid.getVisibleRows().find((r) => r.key === rowKey)?.data || {} - const shouldDisable = parentFields.some((pf: string) => !rowData[pf]) + const visibleRows = grid.getVisibleRows() + const rowData = visibleRows.find((r) => r.key === rowKey)?.data - if (shouldDisable) { + if (rowData && parentFields.some((pf: string) => !rowData[pf])) { editor.editorOptions.disabled = true } } @@ -800,43 +814,34 @@ const Grid = (props: GridProps) => { refListFormCode.current = listFormCode }, [listFormCode]) - // StateStoring fonksiyonlarını ref'e kaydet - her render'da yeniden oluşturulmasın - const customSaveStateRef = useRef(customSaveState) - const customLoadStateRef = useRef(customLoadState) - + // Component mount olduğunda state'i bir kez yükle useEffect(() => { - customSaveStateRef.current = customSaveState - customLoadStateRef.current = customLoadState - }, [customSaveState, customLoadState]) + if (!gridDto || !gridRef?.current || !gridDataSource || !columnData) return - // StateStoring'i sadece gridDto değiştiğinde ayarla + const instance = gridRef?.current?.instance() + if (instance) { + customLoadState().then((state) => { + if (state) { + instance.state(state) + } + }).catch((err) => { + console.error('State load error:', err) + }) + } + }, [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) { - 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' - ) { - // Ref'i kullan, direkt fonksiyon yerine - stateStoring.customSave = (state: any) => customSaveStateRef.current(state) - stateStoring.customLoad = () => customLoadStateRef.current() - } - instance.option('stateStoring', stateStoring) - - // State'i yükle - dataSource ve columns hazır olduğunda - if (gridDataSource && columnData) { - instance.state(undefined) // undefined = reload from storage - } + // Otomatik state kaydetme/yükleme kapalı + instance.option('stateStoring', { + enabled: false, + }) } - }, [gridDto, gridDataSource, columnData]) // dataSource ve columns hazır olduğunda state yükle + }, [gridDto]) useEffect(() => { refListFormCode.current = listFormCode diff --git a/ui/src/views/list/Pivot.tsx b/ui/src/views/list/Pivot.tsx index adbba732..73cf4f30 100644 --- a/ui/src/views/list/Pivot.tsx +++ b/ui/src/views/list/Pivot.tsx @@ -39,7 +39,7 @@ import { import { useFilters } from './useFilters' import WidgetGroup from '@/components/common/WidgetGroup' import { Button, Notification, toast } from '@/components/ui' -import { FaCog, FaTimes, FaUndo } from 'react-icons/fa' +import { FaCog, FaSave, FaTimes, FaUndo } from 'react-icons/fa' import { usePermission } from '@/utils/hooks/usePermission' import { ROUTES_ENUM } from '@/routes/route.constant' import { usePWA } from '@/utils/hooks/usePWA' @@ -74,6 +74,11 @@ const Pivot = (props: PivotProps) => { const [gridDataSource, setGridDataSource] = useState>() const [columnData, setColumnData] = useState() + // StateStoring için storageKey'i memoize et + const storageKey = useMemo(() => { + return gridDto?.gridOptions.stateStoringDto?.storageKey ?? '' + }, [gridDto?.gridOptions.stateStoringDto?.storageKey]) + const { filterToolbarData, ...filterData } = useFilters({ gridDto, gridRef, @@ -159,19 +164,32 @@ const Pivot = (props: PivotProps) => { const resetPivotGridState = useCallback(async () => { const grid = gridRef.current?.instance() if (grid) { - // kullaniciya ait kayitli grid state i sil customizationData boşalt silinsin. + // State'i veritabanından sil await postListFormCustomization({ listFormCode: listFormCode, customizationType: ListFormCustomizationTypeEnum.GridState, - filterName: `pivot-${gridRef.current?.instance()?.option('stateStoring')?.storageKey ?? ''}`, + filterName: `pivot-${storageKey}`, customizationData: '', }) - await props.refreshGridDto() + // DataSource'u resetle + const ds = grid.getDataSource() + if (ds && typeof ds.state === 'function') { + ds.state(null) // State'i temizle + } + + // Filtreleri temizle clearPivotFilters() - moveAllFieldsToFilterArea() + setGridPanelColor('transparent') + + toast.push( + + {translate('::ListForms.ListForm.GridStateReset')} + , + { placement: 'top-end' }, + ) } - }, [listFormCode, props, clearPivotFilters, moveAllFieldsToFilterArea]) + }, [listFormCode, storageKey, clearPivotFilters, translate]) const onExporting = useCallback(async (e: PivotGridTypes.ExportingEvent) => { e.cancel = true @@ -217,13 +235,13 @@ const Pivot = (props: PivotProps) => { return postListFormCustomization({ listFormCode: listFormCode, customizationType: ListFormCustomizationTypeEnum.GridState, - filterName: `pivot-${gridRef.current?.instance()?.option('stateStoring')?.storageKey ?? ''}`, + filterName: `pivot-${storageKey}`, customizationData: JSON.stringify(state), }).then(() => { setGridPanelColor(statedGridPanelColor) }) }, - [listFormCode], + [listFormCode, storageKey], ) const customLoadState = useCallback( @@ -231,25 +249,18 @@ const Pivot = (props: PivotProps) => { return getListFormCustomization( listFormCode, ListFormCustomizationTypeEnum.GridState, - `pivot-${gridRef.current?.instance()?.option('stateStoring')?.storageKey ?? ''}`, + `pivot-${storageKey}`, ).then((response: any) => { - setGridPanelColor(statedGridPanelColor) if (response.data?.length > 0) { + setGridPanelColor(statedGridPanelColor) return JSON.parse(response.data[0].customizationData) } + return null }) }, - [listFormCode], + [listFormCode, storageKey], ) - const customSaveStateRef = useRef(customSaveState) - const customLoadStateRef = useRef(customLoadState) - - useEffect(() => { - customSaveStateRef.current = customSaveState - customLoadStateRef.current = customLoadState - }, [customSaveState, customLoadState]) - useEffect(() => { refListFormCode.current = listFormCode }, [listFormCode]) @@ -307,7 +318,19 @@ const Pivot = (props: PivotProps) => { } }, [memoizedDataSource]) - // Pivot dataSource ve fields'i set et + StateStoring ayarla + // StateStoring'i devre dışı bırak - manuel kaydetme kullanılacak + useEffect(() => { + if (!gridDto || !gridRef?.current) return + + const instance = gridRef?.current?.instance() + if (instance) { + instance.option('stateStoring', { + enabled: false, + }) + } + }, [gridDto]) + + // Pivot dataSource ve fields'i set et useEffect(() => { if (!columnData || !gridDataSource || !gridRef?.current || !gridDto) { return @@ -316,23 +339,7 @@ const Pivot = (props: PivotProps) => { const instance = gridRef?.current?.instance() if (!instance) return - // 1. StateStoring'i ÖNCE ayarla - const stateStoring: any = { - 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 = (state: any) => customSaveStateRef.current(state) - stateStoring.customLoad = () => customLoadStateRef.current() - } - instance.option('stateStoring', stateStoring) - - // 2. Default fields'i hazırla + // Default fields'i hazırla const defaultFields: any = columnData?.map((b) => { return { dataField: b.dataField, @@ -350,7 +357,7 @@ const Pivot = (props: PivotProps) => { } as Field }) - // 3. DataSource'u ayarla - fields olmadan + // DataSource'u ayarla const dataSource: PivotGridTypes.Properties['dataSource'] = { remoteOperations: true, store: gridDataSource, @@ -358,16 +365,27 @@ const Pivot = (props: PivotProps) => { } instance.option('dataSource', dataSource) - - // 4. DataSource set edildikten SONRA state'i yükle - state fields'i override edecek - setTimeout(() => { - const ds = instance.getDataSource() - if (ds && typeof ds.state === 'function') { - ds.state(null) // Bu saved state'teki fields'i kullanacak - } - }, 50) }, [columnData, gridDataSource, gridDto]) + // Component mount olduğunda state'i bir kez yükle + useEffect(() => { + if (!gridDto || !gridRef?.current || !columnData || !gridDataSource) return + + const instance = gridRef?.current?.instance() + if (instance) { + customLoadState().then((state) => { + if (state) { + const ds = instance.getDataSource() + if (ds && typeof ds.state === 'function') { + ds.state(state) + } + } + }).catch((err) => { + console.error('Pivot state load error:', err) + }) + } + }, [gridDto, columnData, gridDataSource, customLoadState]) + // Chart binding - sadece bir kez useEffect(() => { if (!gridRef?.current || !chartRef?.current) return @@ -409,6 +427,39 @@ const Pivot = (props: PivotProps) => { {translate('::ListForms.ListForm.RemoveFilter')} + +