Performanslı komponentler oluşturuldu

This commit is contained in:
Sedat Öztürk 2026-02-01 21:53:14 +03:00
parent 58d3e3c940
commit b43fb07e5b
6 changed files with 899 additions and 718 deletions

View file

@ -67,8 +67,8 @@ const EntityEditor: React.FC = () => {
if (response.data && Array.isArray(response.data)) { if (response.data && Array.isArray(response.data)) {
const options = response.data.map((menuItem: any) => ({ const options = response.data.map((menuItem: any) => ({
value: menuItem.shortName || menuItem.code, value: menuItem.shortName,
label: menuItem.displayName || menuItem.code, label: translate('::' + menuItem.displayName),
})) }))
setMenuOptions(options) setMenuOptions(options)
} }

View file

@ -4,7 +4,7 @@ import { Container } from '@/components/shared'
import { DX_CLASSNAMES } from '@/constants/app.constant' import { DX_CLASSNAMES } from '@/constants/app.constant'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import DxChart from 'devextreme-react/chart' import DxChart from 'devextreme-react/chart'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useParams, useSearchParams } from 'react-router-dom' import { useParams, useSearchParams } from 'react-router-dom'
import { GridDto } from '@/proxy/form/models' import { GridDto } from '@/proxy/form/models'
@ -98,11 +98,9 @@ const Chart = (props: ChartProps) => {
} }
}, [gridDto, userName]) }, [gridDto, userName])
useEffect(() => { const memoizedChartOptions = useMemo(() => {
if (!gridDto) return if (!gridDto || !initialized) return undefined
if (!initialized) return
// Chart her zaman currentSeries'e göre render edilir
const seriesDto = currentSeries const seriesDto = currentSeries
const gridOptions = { const gridOptions = {
@ -117,7 +115,7 @@ const Chart = (props: ChartProps) => {
layoutTypes.chart, layoutTypes.chart,
) )
const options = { return {
dataSource: dataSource, dataSource: dataSource,
adjustOnZoom: gridDto.gridOptions.commonDto?.adjustOnZoom ?? true, adjustOnZoom: gridDto.gridOptions.commonDto?.adjustOnZoom ?? true,
@ -126,7 +124,6 @@ const Chart = (props: ChartProps) => {
disabled: gridDto.gridOptions.commonDto?.disabled ?? false, disabled: gridDto.gridOptions.commonDto?.disabled ?? false,
palette: gridDto.gridOptions.commonDto?.palette ?? 'Material', palette: gridDto.gridOptions.commonDto?.palette ?? 'Material',
paletteExtensionMode: gridDto.gridOptions.commonDto?.paletteExtensionMode ?? 'blend', paletteExtensionMode: gridDto.gridOptions.commonDto?.paletteExtensionMode ?? 'blend',
//theme: s(chartDto.commonDto?.theme, 'generic.light'),
title: gridDto.gridOptions.titleDto, title: gridDto.gridOptions.titleDto,
size: gridDto.gridOptions.sizeDto?.useSize size: gridDto.gridOptions.sizeDto?.useSize
@ -170,9 +167,13 @@ const Chart = (props: ChartProps) => {
enabled: true, enabled: true,
}, },
} }
}, [gridDto, currentSeries, initialized, createSelectDataSource, listFormCode, urlSearchParams, openDrawer])
setChartOptions(options) useEffect(() => {
}, [gridDto, currentSeries, initialized, searchParams, urlSearchParams, openDrawer]) if (memoizedChartOptions) {
setChartOptions(memoizedChartOptions)
}
}, [memoizedChartOptions])
const onFilter = useCallback( const onFilter = useCallback(
(value?: string) => { (value?: string) => {
@ -220,7 +221,7 @@ const Chart = (props: ChartProps) => {
[gridDto, urlSearchParams, searchText], [gridDto, urlSearchParams, searchText],
) )
const getFields = async () => { const getFields = useCallback(async () => {
if (!props.listFormCode) return if (!props.listFormCode) return
try { try {
const resp = await getListFormFields({ const resp = await getListFormFields({
@ -246,24 +247,24 @@ const Chart = (props: ChartProps) => {
{ placement: 'top-end' }, { placement: 'top-end' },
) )
} }
} }, [props.listFormCode, translate])
useEffect(() => { useEffect(() => {
if (props.listFormCode) getFields() if (props.listFormCode) getFields()
}, [props.listFormCode, config]) }, [props.listFormCode, config])
const handlePreviewChange = (series: ChartSeriesDto[]) => { const handlePreviewChange = useCallback((series: ChartSeriesDto[]) => {
// Preview değişikliklerini anında chart'a yansıt // Preview değişikliklerini anında chart'a yansıt
setCurrentSeries(series) setCurrentSeries(series)
} }, [])
const handleDrawerClose = () => { const handleDrawerClose = useCallback(() => {
setOpenDrawer(false) setOpenDrawer(false)
// İptal - kaydedilmiş serilere geri dön // İptal - kaydedilmiş serilere geri dön
setCurrentSeries(savedSeries) setCurrentSeries(savedSeries)
} }, [savedSeries])
const onSave = async (newSeries: ChartSeriesDto[]) => { const onSave = useCallback(async (newSeries: ChartSeriesDto[]) => {
// 1. Silinecek serileri bul (savedSeries var ama newSeries yok) // 1. Silinecek serileri bul (savedSeries var ama newSeries yok)
const toDelete = savedSeries.filter((old: ChartSeriesDto) => !newSeries.some((s) => s.index === old.index)) const toDelete = savedSeries.filter((old: ChartSeriesDto) => !newSeries.some((s) => s.index === old.index))
@ -299,7 +300,7 @@ const Chart = (props: ChartProps) => {
setSavedSeries(newSeries) setSavedSeries(newSeries)
setCurrentSeries(newSeries) setCurrentSeries(newSeries)
} }
} }, [savedSeries, id, props])
return ( return (
<Container className={DX_CLASSNAMES}> <Container className={DX_CLASSNAMES}>

File diff suppressed because it is too large Load diff

View file

@ -13,9 +13,11 @@ import Chart, {
Tooltip, Tooltip,
} from 'devextreme-react/chart' } from 'devextreme-react/chart'
import PivotGrid, { import PivotGrid, {
Export,
FieldChooser, FieldChooser,
FieldPanel, FieldPanel,
HeaderFilter, HeaderFilter,
LoadPanel,
PivotGridRef, PivotGridRef,
PivotGridTypes, PivotGridTypes,
Scrolling, Scrolling,
@ -24,7 +26,7 @@ import PivotGrid, {
} from 'devextreme-react/pivot-grid' } from 'devextreme-react/pivot-grid'
import CustomStore from 'devextreme/data/custom_store' import CustomStore from 'devextreme/data/custom_store'
import PivotGridDataSource, { Field } from 'devextreme/ui/pivot_grid/data_source' import PivotGridDataSource, { Field } from 'devextreme/ui/pivot_grid/data_source'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { GridColumnData } from './GridColumnData' import { GridColumnData } from './GridColumnData'
import { import {
@ -36,7 +38,7 @@ import {
} from './Utils' } from './Utils'
import { useFilters } from './useFilters' import { useFilters } from './useFilters'
import WidgetGroup from '@/components/common/WidgetGroup' import WidgetGroup from '@/components/common/WidgetGroup'
import { Button } from '@/components/ui' import { Button, Notification, toast } from '@/components/ui'
import { FaCog, FaTimes, FaUndo } from 'react-icons/fa' import { FaCog, FaTimes, FaUndo } from 'react-icons/fa'
import { usePermission } from '@/utils/hooks/usePermission' import { usePermission } from '@/utils/hooks/usePermission'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
@ -86,7 +88,7 @@ const Pivot = (props: PivotProps) => {
gridRef, gridRef,
}) })
function onCellPrepared(e: any) { const onCellPrepared = useCallback((e: any) => {
const columnFormats = gridDto?.columnFormats const columnFormats = gridDto?.columnFormats
if (!columnFormats) { if (!columnFormats) {
return return
@ -118,9 +120,9 @@ const Pivot = (props: PivotProps) => {
} }
} }
} }
} }, [gridDto])
const clearPivotFilters = () => { const clearPivotFilters = useCallback(() => {
const grid = gridRef.current?.instance() const grid = gridRef.current?.instance()
if (!grid) return if (!grid) return
@ -133,9 +135,9 @@ const Pivot = (props: PivotProps) => {
}) })
ds.reload() ds.reload()
} }
} }, [])
const moveAllFieldsToFilterArea = () => { const moveAllFieldsToFilterArea = useCallback(() => {
const grid = gridRef.current?.instance() const grid = gridRef.current?.instance()
if (!grid) return if (!grid) return
@ -152,9 +154,9 @@ const Pivot = (props: PivotProps) => {
ds.fields(fields) ds.fields(fields)
ds.reload() // PivotGridi yeniden yükle ds.reload() // PivotGridi yeniden yükle
grid.repaint() // UI güncelle grid.repaint() // UI güncelle
} }, [])
const resetPivotGridState = async () => { const resetPivotGridState = useCallback(async () => {
const grid = gridRef.current?.instance() const grid = gridRef.current?.instance()
if (grid) { if (grid) {
// kullaniciya ait kayitli grid state i sil customizationData boşalt silinsin. // kullaniciya ait kayitli grid state i sil customizationData boşalt silinsin.
@ -169,8 +171,47 @@ const Pivot = (props: PivotProps) => {
clearPivotFilters() clearPivotFilters()
moveAllFieldsToFilterArea() moveAllFieldsToFilterArea()
} }
} }, [listFormCode, props, clearPivotFilters, moveAllFieldsToFilterArea])
const onExporting = useCallback(async (e: PivotGridTypes.ExportingEvent) => {
e.cancel = true
const pivot = gridRef?.current?.instance()
if (!pivot) return
try {
// PivotGrid sadece Excel export destekliyor
const [{ Workbook }, { saveAs }, { exportPivotGrid }] = await Promise.all([
import('exceljs'),
import('file-saver'),
import('devextreme/excel_exporter'),
])
const workbook = new Workbook()
const worksheet = workbook.addWorksheet(`${listFormCode}_pivot`)
await exportPivotGrid({
component: pivot as any,
worksheet,
})
const buffer = await workbook.xlsx.writeBuffer()
saveAs(
new Blob([buffer], { type: 'application/octet-stream' }),
`${listFormCode}_pivot_export.xlsx`,
)
} catch (err) {
console.error('Pivot 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' },
)
}
}, [listFormCode, translate])
// StateStoring fonksiyonlarını ref'e kaydet
const customSaveState = useCallback( const customSaveState = useCallback(
(state: any) => { (state: any) => {
return postListFormCustomization({ return postListFormCustomization({
@ -201,15 +242,16 @@ const Pivot = (props: PivotProps) => {
[listFormCode], [listFormCode],
) )
const customSaveStateRef = useRef(customSaveState)
const customLoadStateRef = useRef(customLoadState)
useEffect(() => { useEffect(() => {
if (gridRef?.current) { customSaveStateRef.current = customSaveState
const instance = gridRef?.current?.instance() customLoadStateRef.current = customLoadState
if (instance) { }, [customSaveState, customLoadState])
instance.option('remoteOperations', false)
instance.option('dataSource', undefined) useEffect(() => {
instance.option('stateStoring', undefined) refListFormCode.current = listFormCode
}
}
}, [listFormCode]) }, [listFormCode])
useEffect(() => { useEffect(() => {
@ -231,34 +273,67 @@ const Pivot = (props: PivotProps) => {
} }
}, [gridDto]) }, [gridDto])
useEffect(() => { // Kolonları memoize et
if (!gridDto) { const memoizedColumns = useMemo(() => {
return if (!gridDto || !config) return undefined
}
// Set columns
const cols = getBandedColumns() const cols = getBandedColumns()
setColumnData(cols?.filter((a) => a.colData?.pivotSettingsDto.isPivot)) return cols?.filter((a) => a.colData?.pivotSettingsDto.isPivot)
}, [gridDto, config])
// Set data source // DataSource'u memoize et
const dataSource: CustomStore<any, any> = createSelectDataSource( const memoizedDataSource = useMemo(() => {
if (!gridDto) return undefined
const cols = getBandedColumns()
return createSelectDataSource(
gridDto.gridOptions, gridDto.gridOptions,
listFormCode, listFormCode,
searchParams, searchParams,
layoutTypes.pivot, layoutTypes.pivot,
cols, cols,
) )
}, [gridDto, listFormCode, createSelectDataSource])
setGridDataSource(dataSource)
}, [gridDto, config, searchParams])
useEffect(() => { useEffect(() => {
refListFormCode.current = listFormCode if (memoizedColumns) {
if (!gridRef?.current) { setColumnData(memoizedColumns)
}
}, [memoizedColumns])
useEffect(() => {
if (memoizedDataSource) {
setGridDataSource(memoizedDataSource)
}
}, [memoizedDataSource])
// Pivot dataSource ve fields'i set et + StateStoring ayarla
useEffect(() => {
if (!columnData || !gridDataSource || !gridRef?.current || !gridDto) {
return return
} }
const fields: any = columnData?.map((b) => { 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
const defaultFields: any = columnData?.map((b) => {
return { return {
dataField: b.dataField, dataField: b.dataField,
caption: b.caption, caption: b.caption,
@ -275,31 +350,37 @@ const Pivot = (props: PivotProps) => {
} as Field } as Field
}) })
PivotGridDataSource // 3. DataSource'u ayarla - fields olmadan
const dataSource: PivotGridTypes.Properties['dataSource'] = { const dataSource: PivotGridTypes.Properties['dataSource'] = {
remoteOperations: true, remoteOperations: true,
store: gridDataSource, store: gridDataSource,
fields, fields: defaultFields,
} }
const instance = gridRef?.current?.instance() instance.option('dataSource', dataSource)
if (instance) {
instance.option('dataSource', dataSource) // 4. DataSource set edildikten SONRA state'i yükle - state fields'i override edecek
instance.option('state', null) setTimeout(() => {
} const ds = instance.getDataSource()
if (ds && typeof ds.state === 'function') {
//chart Integration ds.state(null) // Bu saved state'teki fields'i kullanacak
if (gridRef?.current && chartRef?.current) {
const pivotInstance = gridRef?.current?.instance()
const chartInstance = chartRef?.current?.instance()
if (pivotInstance && chartInstance) {
pivotInstance.bindChart(chartInstance, {
dataFieldsDisplayMode: 'splitPanes',
alternateDataFields: false,
})
} }
}, 50)
}, [columnData, gridDataSource, gridDto])
// Chart binding - sadece bir kez
useEffect(() => {
if (!gridRef?.current || !chartRef?.current) return
const pivotInstance = gridRef?.current?.instance()
const chartInstance = chartRef?.current?.instance()
if (pivotInstance && chartInstance) {
pivotInstance.bindChart(chartInstance, {
dataFieldsDisplayMode: 'splitPanes',
alternateDataFields: false,
})
} }
}, [columnData]) }, [gridDto])
return ( return (
<> <>
@ -383,7 +464,9 @@ const Pivot = (props: PivotProps) => {
rtlEnabled={gridDto.gridOptions.columnOptionDto?.rtlEnabled} rtlEnabled={gridDto.gridOptions.columnOptionDto?.rtlEnabled}
hoverStateEnabled={gridDto.gridOptions.columnOptionDto?.hoverStateEnabled} hoverStateEnabled={gridDto.gridOptions.columnOptionDto?.hoverStateEnabled}
onCellPrepared={onCellPrepared} onCellPrepared={onCellPrepared}
onExporting={onExporting}
> >
<Export enabled={gridDto.gridOptions.exportDto?.enabled} />
<HeaderFilter <HeaderFilter
allowSelectAll={gridDto.gridOptions.selectionDto.allowSelectAll} allowSelectAll={gridDto.gridOptions.selectionDto.allowSelectAll}
width={gridDto.gridOptions.headerFilterDto.width} width={gridDto.gridOptions.headerFilterDto.width}
@ -406,23 +489,9 @@ const Pivot = (props: PivotProps) => {
enabled={gridDto.gridOptions.pivotOptionDto.columnChooserEnabled} enabled={gridDto.gridOptions.pivotOptionDto.columnChooserEnabled}
height={500} height={500}
/> />
<StateStoring <LoadPanel
enabled={gridDto?.gridOptions.stateStoringDto?.enabled} enabled={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled as boolean | undefined}
type={gridDto?.gridOptions.stateStoringDto?.type} text={gridDto.gridOptions.pagerOptionDto?.loadPanelText}
savingTimeout={gridDto?.gridOptions.stateStoringDto?.savingTimeout}
storageKey={gridDto?.gridOptions.stateStoringDto?.storageKey}
customSave={
gridDto?.gridOptions.stateStoringDto?.enabled &&
gridDto?.gridOptions.stateStoringDto?.type === 'custom'
? customSaveState
: undefined
}
customLoad={
gridDto?.gridOptions.stateStoringDto?.enabled &&
gridDto?.gridOptions.stateStoringDto?.type === 'custom'
? customLoadState
: undefined
}
/> />
<Scrolling mode={gridDto.gridOptions.pagerOptionDto.scrollingMode} /> <Scrolling mode={gridDto.gridOptions.pagerOptionDto.scrollingMode} />
</PivotGrid> </PivotGrid>

View file

@ -38,7 +38,7 @@ import TreeListDx, {
} from 'devextreme-react/tree-list' } from 'devextreme-react/tree-list'
import { Item } from 'devextreme-react/toolbar' import { Item } from 'devextreme-react/toolbar'
import CustomStore from 'devextreme/data/custom_store' import CustomStore from 'devextreme/data/custom_store'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { RowMode, SimpleItemWithColData } from '../form/types' import { RowMode, SimpleItemWithColData } from '../form/types'
import { GridColumnData } from './GridColumnData' import { GridColumnData } from './GridColumnData'
@ -132,19 +132,6 @@ const Tree = (props: TreeProps) => {
} }
}, [searchParams]) }, [searchParams])
const layout = layoutTypes.tree
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
gridDto,
listFormCode,
getSelectedRowKeys,
getSelectedRowsData,
refreshData,
expandAll,
collapseAll,
getFilter,
layout,
})
const { filterToolbarData, ...filterData } = useFilters({ const { filterToolbarData, ...filterData } = useFilters({
gridDto, gridDto,
gridRef, gridRef,
@ -159,7 +146,7 @@ const Tree = (props: TreeProps) => {
gridRef, gridRef,
}) })
function extractSearchParamsFields(filter: any): [string, string, any][] { const extractSearchParamsFields = useCallback((filter: any): [string, string, any][] => {
if (!Array.isArray(filter)) return [] if (!Array.isArray(filter)) return []
if (filter.length === 3 && typeof filter[0] === 'string') { if (filter.length === 3 && typeof filter[0] === 'string') {
@ -167,27 +154,27 @@ const Tree = (props: TreeProps) => {
} }
return filter.flatMap((f) => extractSearchParamsFields(f)) return filter.flatMap((f) => extractSearchParamsFields(f))
} }, [])
async function getSelectedRowKeys() { const getSelectedRowKeys = useCallback(async () => {
const tree = gridRef.current?.instance() const tree = gridRef.current?.instance()
if (!tree) { if (!tree) {
return [] return []
} }
return await tree.getSelectedRowKeys() return await tree.getSelectedRowKeys()
} }, [])
function getSelectedRowsData() { const getSelectedRowsData = useCallback(() => {
const tree = gridRef.current?.instance() const tree = gridRef.current?.instance()
if (!tree) { if (!tree) {
return [] return []
} }
return tree.getSelectedRowsData() return tree.getSelectedRowsData()
} }, [])
function expandAll() { const expandAll = useCallback(() => {
const tree = gridRef.current?.instance() const tree = gridRef.current?.instance()
if (!tree) return if (!tree) return
tree.forEachNode((node: any) => { tree.forEachNode((node: any) => {
@ -195,9 +182,9 @@ const Tree = (props: TreeProps) => {
tree.expandRow(node.key) tree.expandRow(node.key)
} }
}) })
} }, [])
function collapseAll() { const collapseAll = useCallback(() => {
const tree = gridRef.current?.instance() const tree = gridRef.current?.instance()
if (!tree) return if (!tree) return
tree.forEachNode((node: any) => { tree.forEachNode((node: any) => {
@ -205,81 +192,103 @@ const Tree = (props: TreeProps) => {
tree.collapseRow(node.key) tree.collapseRow(node.key)
} }
}) })
} }, [])
function refreshData() { const refreshData = useCallback(() => {
gridRef.current?.instance().refresh() gridRef.current?.instance().refresh()
} }, [])
function getFilter() { const getFilter = useCallback(() => {
const tree = gridRef.current?.instance() const tree = gridRef.current?.instance()
if (!tree) { if (!tree) {
return return
} }
return tree.getCombinedFilter() return tree.getCombinedFilter()
} }, [])
function onSelectionChanged(data: any) { const layout = layoutTypes.tree
const treeOpt = gridDto?.gridOptions const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
const tree = gridRef.current?.instance() gridDto,
if (!treeOpt || !tree) { listFormCode,
return getSelectedRowKeys,
} getSelectedRowsData,
refreshData,
expandAll,
collapseAll,
getFilter,
layout,
})
if (treeOpt.editingOptionDto?.allowDeleting) { const onSelectionChanged = useCallback(
try { (data: any) => {
const opt = tree.option('toolbar') const treeOpt = gridDto?.gridOptions
if (opt && opt.items && Array.isArray(opt.items)) { const tree = gridRef.current?.instance()
const deleteSelectedRecordsIndex = opt.items if (!treeOpt || !tree) {
.map((e: any) => e.name) return
.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) { if (treeOpt.editingOptionDto?.allowDeleting) {
setFormData(data.selectedRowsData[0]) 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')
function onCellPrepared(e: any) { if (deleteSelectedRecordsIndex >= 0) {
const columnFormats = gridDto?.columnFormats tree.option(
if (!columnFormats) { `toolbar.items[${deleteSelectedRecordsIndex}].options.visible`,
return data.selectedRowsData.length > 1,
} )
}
}
} catch (error) {
console.error('Error updating toolbar items:', error)
}
}
for (let indxCol = 0; indxCol < columnFormats.length; indxCol++) { if (data.selectedRowsData.length) {
const colFormat = columnFormats[indxCol] setFormData(data.selectedRowsData[0])
for (let indxStyl = 0; indxStyl < colFormat.columnStylingDto.length; indxStyl++) { }
const colStyle = colFormat.columnStylingDto[indxStyl] },
if (e.rowType == colStyle.rowType) { [gridDto],
if (colStyle.useRow || e.column.dataField == colFormat.fieldName) { )
if (
!colStyle.conditionValue || const onCellPrepared = useCallback(
controlStyleCondition(e.data, colFormat.fieldName, colStyle) (e: any) => {
) { const columnFormats = gridDto?.columnFormats
if (colStyle.cssClassName) { if (!columnFormats) {
e.cellElement.addClass(colStyle.cssClassName) return
} }
if (colStyle.cssStyles) {
e.cellElement.attr('style', e.cellElement.attr('style') + ';' + colStyle.cssStyles) 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,
)
}
} }
} }
} }
} }
} }
} },
} [gridDto],
)
function onInitNewRow(e: any) { function onInitNewRow(e: any) {
if (!gridDto?.columnFormats) { if (!gridDto?.columnFormats) {
@ -349,55 +358,61 @@ const Tree = (props: TreeProps) => {
} }
} }
function onRowInserting(e: TreeListTypes.RowInsertingEvent) { const onRowInserting = useCallback((e: TreeListTypes.RowInsertingEvent) => {
e.data = setFormEditingExtraItemValues(e.data) e.data = setFormEditingExtraItemValues(e.data)
} }, [])
function onRowUpdating(e: TreeListTypes.RowUpdatingEvent) { const onRowUpdating = useCallback(
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) { (e: TreeListTypes.RowUpdatingEvent) => {
if (Object.keys(e.newData).some((a) => a.includes(':'))) { if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
Object.keys(e.oldData).forEach((col) => { if (Object.keys(e.newData).some((a) => a.includes(':'))) {
if (col.includes(':')) { Object.keys(e.oldData).forEach((col) => {
e.newData[col] = e.newData[col] ?? e.oldData[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
} }
e.newData = setFormEditingExtraItemValues(e.newData)
} else { if (gridDto?.gridOptions.keyFieldName) {
let newData = { ...e.oldData, ...e.newData } delete e.newData[gridDto?.gridOptions.keyFieldName]
newData = setFormEditingExtraItemValues(newData) }
Object.keys(newData).forEach((key) => { },
if (key.includes(':')) { [gridDto],
delete newData[key] )
const onEditingStart = useCallback(
(e: TreeListTypes.EditingStartEvent) => {
isEditingRef.current = true
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]]
}) })
e.newData = newData },
} [gridDto],
)
if (gridDto?.gridOptions.keyFieldName) { const onDataErrorOccurred = useCallback((e: TreeListTypes.DataErrorOccurredEvent) => {
delete e.newData[gridDto?.gridOptions.keyFieldName]
}
}
function onEditingStart(e: TreeListTypes.EditingStartEvent) {
isEditingRef.current = true
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( toast.push(
<Notification type="danger" duration={2000}> <Notification type="danger" duration={2000}>
{e.error?.message} {e.error?.message}
@ -406,7 +421,7 @@ const Tree = (props: TreeProps) => {
placement: 'top-end', placement: 'top-end',
}, },
) )
} }, [])
function onEditorPreparing(editor: TreeListTypes.EditorPreparingEvent) { function onEditorPreparing(editor: TreeListTypes.EditorPreparingEvent) {
if (editor.parentType === 'dataRow' && editor.dataField && gridDto) { if (editor.parentType === 'dataRow' && editor.dataField && gridDto) {
@ -566,6 +581,15 @@ const Tree = (props: TreeProps) => {
[listFormCode], [listFormCode],
) )
// 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)
@ -723,8 +747,9 @@ const Tree = (props: TreeProps) => {
gridDto?.gridOptions.stateStoringDto?.enabled && gridDto?.gridOptions.stateStoringDto?.enabled &&
gridDto?.gridOptions.stateStoringDto?.type === 'custom' gridDto?.gridOptions.stateStoringDto?.type === 'custom'
) { ) {
stateStoring.customSave = customSaveState // Ref pattern kullan - Grid'deki gibi
stateStoring.customLoad = customLoadState stateStoring.customSave = (state: any) => customSaveStateRef.current(state)
stateStoring.customLoad = () => customLoadStateRef.current()
} }
gridRef?.current?.instance().option('stateStoring', stateStoring) gridRef?.current?.instance().option('stateStoring', stateStoring)
}, [columnData]) }, [columnData])

View file

@ -2,7 +2,7 @@ import { DataGridTypes } from 'devextreme-react/data-grid'
import { DataType, HorizontalEdge, SortOrder, ValidationRule } from 'devextreme/common' import { DataType, HorizontalEdge, SortOrder, ValidationRule } from 'devextreme/common'
import CustomStore from 'devextreme/data/custom_store' import CustomStore from 'devextreme/data/custom_store'
import { SelectedFilterOperation } from 'devextreme/ui/data_grid' import { SelectedFilterOperation } from 'devextreme/ui/data_grid'
import { useEffect } from 'react' import { useCallback, useEffect } from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { usePermission } from '@/utils/hooks/usePermission' import { usePermission } from '@/utils/hooks/usePermission'
import { usePWA } from '@/utils/hooks/usePWA' import { usePWA } from '@/utils/hooks/usePWA'
@ -105,11 +105,27 @@ function calculateFilterExpressionMultiValue(
} }
} }
// lookup cache (module scope) // lookup cache (module scope) - cache süresini ve boyutunu yönet
const __lookupCache = new Map<string, Promise<any[]>>() const __lookupCache = new Map<string, { promise: Promise<any[]>; timestamp: number }>()
const CACHE_DURATION = 5 * 60 * 1000 // 5 dakika
const MAX_CACHE_SIZE = 100 // Maksimum cache entry sayısı
const cachedLoader = (key: string, loader: () => Promise<any[]>) => { const cachedLoader = (key: string, loader: () => Promise<any[]>) => {
if (__lookupCache.has(key)) return __lookupCache.get(key)! const now = Date.now()
const cached = __lookupCache.get(key)
// Cache'de var ve süresi dolmamışsa kullan
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
return cached.promise
}
// Cache boyutu limitini aşarsa en eskiyi temizle
if (__lookupCache.size >= MAX_CACHE_SIZE) {
const oldestKey = Array.from(__lookupCache.entries())
.sort((a, b) => a[1].timestamp - b[1].timestamp)[0][0]
__lookupCache.delete(oldestKey)
}
const p = Promise.resolve() const p = Promise.resolve()
.then(() => loader()) .then(() => loader())
.then((res) => res ?? []) .then((res) => res ?? [])
@ -117,7 +133,8 @@ const cachedLoader = (key: string, loader: () => Promise<any[]>) => {
__lookupCache.delete(key) // hata olursa tekrar denenebilsin __lookupCache.delete(key) // hata olursa tekrar denenebilsin
throw err throw err
}) })
__lookupCache.set(key, p)
__lookupCache.set(key, { promise: p, timestamp: now })
return p return p
} }
@ -142,7 +159,7 @@ const useListFormColumns = ({
__lookupCache.clear() __lookupCache.clear()
}, [listFormCode]) }, [listFormCode])
const lookupDataSource = (options: any, colData: any, listFormCode: string) => { const lookupDataSource = useCallback((options: any, colData: any, listFormCode: string) => {
const { lookupDto } = colData const { lookupDto } = colData
const filters = [] const filters = []
if (lookupDto.cascadeParentFields) { if (lookupDto.cascadeParentFields) {
@ -184,7 +201,7 @@ const useListFormColumns = ({
store: [], store: [],
} }
} }
} }, [listFormCode])
const createLookupStaticDataSource = ( const createLookupStaticDataSource = (
load: () => any, load: () => any,
@ -289,7 +306,7 @@ const useListFormColumns = ({
}) })
} }
const getCommandColumn = (): GridColumnData | undefined => { const getCommandColumn = useCallback((): GridColumnData | undefined => {
if (!gridDto) { if (!gridDto) {
return return
} }
@ -399,9 +416,9 @@ const useListFormColumns = ({
} }
return column as GridColumnData return column as GridColumnData
} }, [gridDto, checkPermission, translate, listFormCode, isPwaMode, dialog, gridRef])
const getColumns = (columnFormats: ColumnFormatDto[]) => { const getColumns = useCallback((columnFormats: ColumnFormatDto[]) => {
const columns: GridColumnData[] = [] const columns: GridColumnData[] = []
if (!gridDto || !columnFormats) { if (!gridDto || !columnFormats) {
@ -550,9 +567,9 @@ const useListFormColumns = ({
}) })
return columns return columns
} }, [gridDto, lookupDataSource, translate, checkPermission, dialog, isPwaMode, listFormCode])
const getBandedColumns = () => { const getBandedColumns = useCallback(() => {
if (!gridDto) { if (!gridDto) {
return return
} }
@ -635,7 +652,7 @@ const useListFormColumns = ({
} }
return columns return columns
} }, [gridDto, getColumns, getCommandColumn])
return { return {
getBandedColumns, getBandedColumns,