Performanslı komponentler oluşturuldu
This commit is contained in:
parent
58d3e3c940
commit
b43fb07e5b
6 changed files with 899 additions and 718 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ import { Item } from 'devextreme-react/toolbar'
|
||||||
import { DataType } from 'devextreme/common'
|
import { DataType } from 'devextreme/common'
|
||||||
import { captionize } from 'devextreme/core/utils/inflector'
|
import { captionize } from 'devextreme/core/utils/inflector'
|
||||||
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 SubForms from '../form/SubForms'
|
import SubForms from '../form/SubForms'
|
||||||
import { RowMode, SimpleItemWithColData } from '../form/types'
|
import { RowMode, SimpleItemWithColData } from '../form/types'
|
||||||
|
|
@ -140,17 +140,6 @@ const Grid = (props: GridProps) => {
|
||||||
}
|
}
|
||||||
}, [searchParams])
|
}, [searchParams])
|
||||||
|
|
||||||
const layout = layoutTypes.grid
|
|
||||||
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
|
||||||
gridDto,
|
|
||||||
listFormCode,
|
|
||||||
getSelectedRowKeys,
|
|
||||||
getSelectedRowsData,
|
|
||||||
refreshData,
|
|
||||||
getFilter,
|
|
||||||
layout,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { filterToolbarData, ...filterData } = useFilters({
|
const { filterToolbarData, ...filterData } = useFilters({
|
||||||
gridDto,
|
gridDto,
|
||||||
gridRef,
|
gridRef,
|
||||||
|
|
@ -165,38 +154,50 @@ const Grid = (props: GridProps) => {
|
||||||
gridRef,
|
gridRef,
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getSelectedRowKeys() {
|
const getSelectedRowKeys = useCallback(async () => {
|
||||||
const grd = gridRef.current?.instance()
|
const grd = gridRef.current?.instance()
|
||||||
if (!grd) {
|
if (!grd) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return await grd.getSelectedRowKeys()
|
return await grd.getSelectedRowKeys()
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
function getSelectedRowsData() {
|
const getSelectedRowsData = useCallback(() => {
|
||||||
const grd = gridRef.current?.instance()
|
const grd = gridRef.current?.instance()
|
||||||
if (!grd) {
|
if (!grd) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return grd.getSelectedRowsData()
|
return grd.getSelectedRowsData()
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
function refreshData() {
|
const refreshData = useCallback(() => {
|
||||||
gridRef.current?.instance()?.refresh()
|
gridRef.current?.instance()?.refresh()
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
function getFilter() {
|
const getFilter = useCallback(() => {
|
||||||
const grd = gridRef.current?.instance()
|
const grd = gridRef.current?.instance()
|
||||||
if (!grd) {
|
if (!grd) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return grd.getCombinedFilter()
|
return grd.getCombinedFilter()
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
function onSelectionChanged(data: any) {
|
const layout = layoutTypes.grid
|
||||||
|
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
||||||
|
gridDto,
|
||||||
|
listFormCode,
|
||||||
|
getSelectedRowKeys,
|
||||||
|
getSelectedRowsData,
|
||||||
|
refreshData,
|
||||||
|
getFilter,
|
||||||
|
layout,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSelectionChanged = useCallback(
|
||||||
|
(data: any) => {
|
||||||
const grdOpt = gridDto?.gridOptions
|
const grdOpt = gridDto?.gridOptions
|
||||||
const grd = gridRef.current?.instance()
|
const grd = gridRef.current?.instance()
|
||||||
if (!grdOpt || !grd) {
|
if (!grdOpt || !grd) {
|
||||||
|
|
@ -223,9 +224,12 @@ const Grid = (props: GridProps) => {
|
||||||
if (data.selectedRowsData.length) {
|
if (data.selectedRowsData.length) {
|
||||||
setFormData(data.selectedRowsData[0])
|
setFormData(data.selectedRowsData[0])
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
function onCellPrepared(e: any) {
|
const onCellPrepared = useCallback(
|
||||||
|
(e: any) => {
|
||||||
const columnFormats = gridDto?.columnFormats
|
const columnFormats = gridDto?.columnFormats
|
||||||
if (!columnFormats) {
|
if (!columnFormats) {
|
||||||
return
|
return
|
||||||
|
|
@ -250,16 +254,22 @@ const Grid = (props: GridProps) => {
|
||||||
}
|
}
|
||||||
// css inline style var ise uygula
|
// css inline style var ise uygula
|
||||||
if (colStyle.cssStyles) {
|
if (colStyle.cssStyles) {
|
||||||
e.cellElement.attr('style', e.cellElement.attr('style') + ';' + colStyle.cssStyles)
|
e.cellElement.attr(
|
||||||
}
|
'style',
|
||||||
|
e.cellElement.attr('style') + ';' + colStyle.cssStyles,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
function onInitNewRow(e: any) {
|
const onInitNewRow = useCallback(
|
||||||
|
(e: any) => {
|
||||||
if (!gridDto?.columnFormats) {
|
if (!gridDto?.columnFormats) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -327,13 +337,16 @@ const Grid = (props: GridProps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[gridDto, searchParams, extraFilters],
|
||||||
|
)
|
||||||
|
|
||||||
function onRowInserting(e: DataGridTypes.RowInsertingEvent<any, any>) {
|
const onRowInserting = useCallback((e: DataGridTypes.RowInsertingEvent<any, any>) => {
|
||||||
e.data = setFormEditingExtraItemValues(e.data)
|
e.data = setFormEditingExtraItemValues(e.data)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
function onRowUpdating(e: DataGridTypes.RowUpdatingEvent<any, any>) {
|
const onRowUpdating = useCallback(
|
||||||
|
(e: DataGridTypes.RowUpdatingEvent<any, any>) => {
|
||||||
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
|
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
|
||||||
if (Object.keys(e.newData).some((a) => a.includes(':'))) {
|
if (Object.keys(e.newData).some((a) => a.includes(':'))) {
|
||||||
Object.keys(e.oldData).forEach((col) => {
|
Object.keys(e.oldData).forEach((col) => {
|
||||||
|
|
@ -357,9 +370,12 @@ const Grid = (props: GridProps) => {
|
||||||
if (gridDto?.gridOptions.keyFieldName) {
|
if (gridDto?.gridOptions.keyFieldName) {
|
||||||
delete e.newData[gridDto?.gridOptions.keyFieldName]
|
delete e.newData[gridDto?.gridOptions.keyFieldName]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
function onEditingStart(e: DataGridTypes.EditingStartEvent<any, any>) {
|
const onEditingStart = useCallback(
|
||||||
|
(e: DataGridTypes.EditingStartEvent<any, any>) => {
|
||||||
isEditingRef.current = true
|
isEditingRef.current = true
|
||||||
setMode('edit')
|
setMode('edit')
|
||||||
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
||||||
|
|
@ -376,9 +392,11 @@ const Grid = (props: GridProps) => {
|
||||||
const json = JSON.parse(e.data[field[0]])
|
const json = JSON.parse(e.data[field[0]])
|
||||||
e.data[col.dataField] = json[field[1]]
|
e.data[col.dataField] = json[field[1]]
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
[gridDto, mode],
|
||||||
|
)
|
||||||
|
|
||||||
function onDataErrorOccurred(e: DataGridTypes.DataErrorOccurredEvent<any, any>) {
|
const onDataErrorOccurred = useCallback((e: DataGridTypes.DataErrorOccurredEvent<any, any>) => {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="danger" duration={2000}>
|
<Notification type="danger" duration={2000}>
|
||||||
{e.error?.message}
|
{e.error?.message}
|
||||||
|
|
@ -387,20 +405,43 @@ const Grid = (props: GridProps) => {
|
||||||
placement: 'top-end',
|
placement: 'top-end',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
function onEditorPreparing(editor: DataGridTypes.EditorPreparingEvent<any, any>) {
|
// Cascade parent fields mapping'i memoize et - popup açılışını hızlandırır
|
||||||
|
const cascadeFieldsMap = useMemo(() => {
|
||||||
|
if (!gridDto) return new Map()
|
||||||
|
|
||||||
|
const map = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
parentFields: string[]
|
||||||
|
childFields?: string[]
|
||||||
|
}
|
||||||
|
>()
|
||||||
|
|
||||||
|
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()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return map
|
||||||
|
}, [gridDto])
|
||||||
|
|
||||||
|
const onEditorPreparing = useCallback(
|
||||||
|
(editor: DataGridTypes.EditorPreparingEvent<any, any>) => {
|
||||||
if (editor.parentType === 'dataRow' && editor.dataField && gridDto) {
|
if (editor.parentType === 'dataRow' && editor.dataField && gridDto) {
|
||||||
const formItem = gridDto.gridOptions.editingFormDto
|
const formItem = gridDto.gridOptions.editingFormDto
|
||||||
.flatMap((group) => group.items || [])
|
.flatMap((group) => group.items || [])
|
||||||
.find((i) => i.dataField === editor.dataField)
|
.find((i) => i.dataField === editor.dataField)
|
||||||
|
|
||||||
// Cascade disabled mantığı
|
// Cascade disabled mantığı
|
||||||
const colFormat = gridDto.columnFormats.find((c) => c.fieldName === editor.dataField)
|
const cascadeInfo = cascadeFieldsMap.get(editor.dataField)
|
||||||
if (colFormat?.lookupDto?.cascadeParentFields) {
|
if (cascadeInfo) {
|
||||||
const parentFields = colFormat.lookupDto.cascadeParentFields
|
const parentFields = cascadeInfo.parentFields
|
||||||
.split(',')
|
|
||||||
.map((f: string) => f.trim())
|
|
||||||
|
|
||||||
const prevHandler = editor.editorOptions.onValueChanged
|
const prevHandler = editor.editorOptions.onValueChanged
|
||||||
|
|
||||||
|
|
@ -414,36 +455,30 @@ const Grid = (props: GridProps) => {
|
||||||
const rowData = grid.getVisibleRows().find((r) => r.key === rowKey)?.data || {}
|
const rowData = grid.getVisibleRows().find((r) => r.key === rowKey)?.data || {}
|
||||||
|
|
||||||
// Bu field bir parent ise, child fieldleri temizle
|
// Bu field bir parent ise, child fieldleri temizle
|
||||||
if (colFormat.lookupDto?.cascadeEmptyFields) {
|
if (cascadeInfo.childFields) {
|
||||||
const childFields = colFormat.lookupDto.cascadeEmptyFields
|
cascadeInfo.childFields.forEach((childField: string) => {
|
||||||
.split(',')
|
|
||||||
.map((f: string) => f.trim())
|
|
||||||
childFields.forEach((childField: string) => {
|
|
||||||
if (rowIndex >= 0) {
|
if (rowIndex >= 0) {
|
||||||
grid.cellValue(rowIndex, childField, null)
|
grid.cellValue(rowIndex, childField, null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tüm cascade fieldlerin disabled durumlarını güncelle
|
// Tüm cascade fieldlerin disabled durumlarını güncelle - sadece değişen field için
|
||||||
const popup = grid.option('editing.popup')
|
const popup = grid.option('editing.popup')
|
||||||
if (popup) {
|
if (popup) {
|
||||||
const formInstance = grid.option('editing.form') as any
|
const formInstance = grid.option('editing.form') as any
|
||||||
if (formInstance && formInstance.getEditor) {
|
if (formInstance && formInstance.getEditor) {
|
||||||
gridDto.columnFormats.forEach((col) => {
|
// Sadece bu field'den etkilenen childları güncelle
|
||||||
if (col.lookupDto?.cascadeParentFields) {
|
cascadeFieldsMap.forEach((info, fieldName) => {
|
||||||
const colParentFields = col.lookupDto.cascadeParentFields
|
if (info.parentFields.includes(editor.dataField!)) {
|
||||||
.split(',')
|
const shouldDisable = info.parentFields.some((pf: string) => !rowData[pf])
|
||||||
.map((f: string) => f.trim())
|
|
||||||
const shouldDisable = colParentFields.some((pf: string) => !rowData[pf])
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const editorInstance = formInstance.getEditor(col.fieldName!)
|
const editorInstance = formInstance.getEditor(fieldName)
|
||||||
if (editorInstance) {
|
if (editorInstance) {
|
||||||
editorInstance.option('disabled', shouldDisable)
|
editorInstance.option('disabled', shouldDisable)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.debug('Cascade disabled update skipped for', col.fieldName, err)
|
console.debug('Cascade disabled update skipped for', fieldName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -513,7 +548,9 @@ const Grid = (props: GridProps) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[gridDto, cascadeFieldsMap],
|
||||||
|
)
|
||||||
|
|
||||||
const customSaveState = useCallback(
|
const customSaveState = useCallback(
|
||||||
(state: any) => {
|
(state: any) => {
|
||||||
|
|
@ -595,106 +632,8 @@ const Grid = (props: GridProps) => {
|
||||||
}
|
}
|
||||||
}, [gridDto])
|
}, [gridDto])
|
||||||
|
|
||||||
// Kolonları oluştur - dil değiştiğinde güncelle
|
// extraFilters değişikliklerini useMemo ile optimize et
|
||||||
useEffect(() => {
|
const filterParams = useMemo(() => {
|
||||||
if (!gridDto || !config) return
|
|
||||||
|
|
||||||
const cols = getBandedColumns()
|
|
||||||
|
|
||||||
cols?.forEach((col) => {
|
|
||||||
const eo = col.editorOptions
|
|
||||||
|
|
||||||
// Sadece phoneGlobal formatlı kolonlarda çalış
|
|
||||||
if (eo?.format === 'phoneGlobal') {
|
|
||||||
// DevExtreme bazen string tipinde formatter'ı çağırmaz
|
|
||||||
// Bu yüzden her durumda çalışması için customizeText ekleyeceğiz
|
|
||||||
col.dataType = 'string'
|
|
||||||
|
|
||||||
const formatter = (value: any) => {
|
|
||||||
if (!value) return ''
|
|
||||||
|
|
||||||
// string'e dönüştür ve sadece rakamları al
|
|
||||||
let digits = String(value).replace(/\D/g, '')
|
|
||||||
|
|
||||||
// +90, 0090, 0 gibi ülke kodu veya ön ekleri atla
|
|
||||||
if (digits.startsWith('90') && digits.length > 10) digits = digits.slice(-10)
|
|
||||||
if (digits.startsWith('0') && digits.length > 10) digits = digits.slice(-10)
|
|
||||||
if (digits.length > 10) digits = digits.slice(-10)
|
|
||||||
|
|
||||||
// 🔒 Eğer 10 haneli değilse geçersiz → boş göster
|
|
||||||
if (digits.length !== 10) return ''
|
|
||||||
|
|
||||||
// (XXX) XXX-XXXX formatında göster
|
|
||||||
const match = digits.match(/^(\d{3})(\d{3})(\d{4})$/)
|
|
||||||
return match ? `(${match[1]}) ${match[2]}-${match[3]}` : digits
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1️⃣ Normal format nesnesi
|
|
||||||
col.format = { formatter }
|
|
||||||
|
|
||||||
// 2️⃣ CustomizeText fallback — bazı durumlarda zorunlu
|
|
||||||
col.customizeText = (cellInfo: any) => formatter(cellInfo?.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setColumnData(cols)
|
|
||||||
}, [gridDto, config])
|
|
||||||
|
|
||||||
// DataSource oluştur
|
|
||||||
useEffect(() => {
|
|
||||||
if (!gridDto) return
|
|
||||||
|
|
||||||
const dataSource = createSelectDataSource(
|
|
||||||
gridDto.gridOptions,
|
|
||||||
listFormCode,
|
|
||||||
searchParams,
|
|
||||||
layoutTypes.grid,
|
|
||||||
columnData,
|
|
||||||
)
|
|
||||||
|
|
||||||
setGridDataSource(dataSource)
|
|
||||||
}, [gridDto, searchParams])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!columnData) return
|
|
||||||
|
|
||||||
refListFormCode.current = listFormCode
|
|
||||||
if (!gridRef?.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = gridRef?.current?.instance()
|
|
||||||
if (instance) {
|
|
||||||
instance.option('columns', columnData)
|
|
||||||
instance.option('remoteOperations', {
|
|
||||||
groupPaging: true,
|
|
||||||
filtering: true,
|
|
||||||
sorting: true,
|
|
||||||
paging: true,
|
|
||||||
grouping: true,
|
|
||||||
summary: true,
|
|
||||||
})
|
|
||||||
instance.option('dataSource', gridDataSource)
|
|
||||||
instance.state(null)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
instance.option('stateStoring', stateStoring)
|
|
||||||
}
|
|
||||||
}, [columnData])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const activeFilters = extraFilters.filter((f) => f.value)
|
const activeFilters = extraFilters.filter((f) => f.value)
|
||||||
|
|
||||||
let base: any = null
|
let base: any = null
|
||||||
|
|
@ -736,19 +675,269 @@ const Grid = (props: GridProps) => {
|
||||||
}, null as any)
|
}, null as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter) {
|
return filter
|
||||||
searchParams?.set('filter', JSON.stringify(filter))
|
}, [extraFilters])
|
||||||
|
|
||||||
|
// Filter değiştiğinde searchParams'ı güncelle (side effect)
|
||||||
|
// NOT: searchParams'ı dependency'e koymuyoruz çünkü sonsuz döngü yaratır
|
||||||
|
useEffect(() => {
|
||||||
|
if (filterParams) {
|
||||||
|
searchParams?.set('filter', JSON.stringify(filterParams))
|
||||||
} else {
|
} else {
|
||||||
searchParams?.delete('filter')
|
searchParams?.delete('filter')
|
||||||
}
|
}
|
||||||
|
// Grid'i yenile
|
||||||
gridRef.current?.instance()?.refresh()
|
gridRef.current?.instance()?.refresh()
|
||||||
}, [extraFilters])
|
}, [filterParams])
|
||||||
|
|
||||||
|
// Kolonları oluştur - dil değiştiğinde güncelle
|
||||||
|
const memoizedColumns = useMemo(() => {
|
||||||
|
if (!gridDto || !config) return undefined
|
||||||
|
|
||||||
|
const cols = getBandedColumns()
|
||||||
|
|
||||||
|
cols?.forEach((col: any) => {
|
||||||
|
const eo = col.editorOptions
|
||||||
|
|
||||||
|
// Sadece phoneGlobal formatlı kolonlarda çalış
|
||||||
|
if (eo?.format === 'phoneGlobal') {
|
||||||
|
// DevExtreme bazen string tipinde formatter'ı çağırmaz
|
||||||
|
// Bu yüzden her durumda çalışması için customizeText ekleyeceğiz
|
||||||
|
col.dataType = 'string'
|
||||||
|
|
||||||
|
const formatter = (value: any) => {
|
||||||
|
if (!value) return ''
|
||||||
|
|
||||||
|
// string'e dönüştür ve sadece rakamları al
|
||||||
|
let digits = String(value).replace(/\D/g, '')
|
||||||
|
|
||||||
|
// +90, 0090, 0 gibi ülke kodu veya ön ekleri atla
|
||||||
|
if (digits.startsWith('90') && digits.length > 10) digits = digits.slice(-10)
|
||||||
|
if (digits.startsWith('0') && digits.length > 10) digits = digits.slice(-10)
|
||||||
|
if (digits.length > 10) digits = digits.slice(-10)
|
||||||
|
|
||||||
|
// 🔒 Eğer 10 haneli değilse geçersiz → boş göster
|
||||||
|
if (digits.length !== 10) return ''
|
||||||
|
|
||||||
|
// (XXX) XXX-XXXX formatında göster
|
||||||
|
const match = digits.match(/^(\d{3})(\d{3})(\d{4})$/)
|
||||||
|
return match ? `(${match[1]}) ${match[2]}-${match[3]}` : digits
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1️⃣ Normal format nesnesi
|
||||||
|
col.format = { formatter }
|
||||||
|
|
||||||
|
// 2️⃣ CustomizeText fallback — bazı durumlarda zorunlu
|
||||||
|
col.customizeText = (cellInfo: any) => formatter(cellInfo?.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return cols
|
||||||
|
}, [gridDto, config])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (memoizedColumns) {
|
||||||
|
setColumnData(memoizedColumns)
|
||||||
|
}
|
||||||
|
}, [memoizedColumns])
|
||||||
|
|
||||||
|
// DataSource oluştur - sadece gridDto değiştiğinde
|
||||||
|
const memoizedDataSource = useMemo(() => {
|
||||||
|
if (!gridDto) return undefined
|
||||||
|
|
||||||
|
return createSelectDataSource(
|
||||||
|
gridDto.gridOptions,
|
||||||
|
listFormCode,
|
||||||
|
searchParams,
|
||||||
|
layoutTypes.grid,
|
||||||
|
undefined, // columnData yerine undefined - datasource oluşturulurken columns henüz set edilmeyebilir
|
||||||
|
)
|
||||||
|
}, [gridDto, listFormCode, createSelectDataSource])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (memoizedDataSource) {
|
||||||
|
setGridDataSource(memoizedDataSource)
|
||||||
|
}
|
||||||
|
}, [memoizedDataSource])
|
||||||
|
|
||||||
|
// Grid columns'ı set et - sadece columnData değiştiğinde
|
||||||
|
useEffect(() => {
|
||||||
|
if (!columnData || !gridRef?.current) return
|
||||||
|
|
||||||
|
const instance = gridRef?.current?.instance()
|
||||||
|
if (instance) {
|
||||||
|
instance.option('columns', columnData)
|
||||||
|
}
|
||||||
|
}, [columnData])
|
||||||
|
|
||||||
|
// Grid dataSource'u set et - sadece gridDataSource değiştiğinde
|
||||||
|
useEffect(() => {
|
||||||
|
if (!gridDataSource || !gridRef?.current) return
|
||||||
|
|
||||||
|
const instance = gridRef?.current?.instance()
|
||||||
|
if (instance) {
|
||||||
|
instance.option('remoteOperations', {
|
||||||
|
groupPaging: true,
|
||||||
|
filtering: true,
|
||||||
|
sorting: true,
|
||||||
|
paging: true,
|
||||||
|
grouping: true,
|
||||||
|
summary: true,
|
||||||
|
})
|
||||||
|
instance.option('dataSource', gridDataSource)
|
||||||
|
}
|
||||||
|
}, [gridDataSource])
|
||||||
|
|
||||||
|
// listFormCode'u ref'e kaydet
|
||||||
|
useEffect(() => {
|
||||||
|
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)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
customSaveStateRef.current = customSaveState
|
||||||
|
customLoadStateRef.current = customLoadState
|
||||||
|
}, [customSaveState, customLoadState])
|
||||||
|
|
||||||
|
// StateStoring'i sadece gridDto değiştiğinde ayarla
|
||||||
|
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)
|
||||||
|
instance.state(null)
|
||||||
|
}
|
||||||
|
}, [gridDto]) // Sadece gridDto'ya bağlı
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refListFormCode.current = listFormCode
|
refListFormCode.current = listFormCode
|
||||||
}, [listFormCode])
|
}, [listFormCode])
|
||||||
|
|
||||||
|
// Form items mapper'ı memoize et - popup açılışını hızlandırır
|
||||||
|
const mapFormItem = useCallback(
|
||||||
|
(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:ss',
|
||||||
|
displayFormat: 'shortDateShortTime',
|
||||||
|
},
|
||||||
|
...editorOptions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaultValue for @AUTONUMBER fields
|
||||||
|
if (
|
||||||
|
typeof listFormField?.defaultValue === 'string' &&
|
||||||
|
listFormField?.defaultValue === '@AUTONUMBER' &&
|
||||||
|
mode === 'new'
|
||||||
|
) {
|
||||||
|
editorOptions = {
|
||||||
|
...editorOptions,
|
||||||
|
value: autoNumber(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
[gridDto, mode, searchParams, extraFilters],
|
||||||
|
)
|
||||||
|
|
||||||
// WidgetGroup yüksekliğini hesapla
|
// WidgetGroup yüksekliğini hesapla
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const calculateWidgetHeight = () => {
|
const calculateWidgetHeight = () => {
|
||||||
|
|
@ -1053,126 +1242,6 @@ const Grid = (props: GridProps) => {
|
||||||
)
|
)
|
||||||
const result: any[] = []
|
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:ss',
|
|
||||||
displayFormat: 'shortDateShortTime',
|
|
||||||
},
|
|
||||||
...editorOptions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set defaultValue for @AUTONUMBER fields
|
|
||||||
if (
|
|
||||||
typeof listFormField?.defaultValue === 'string' &&
|
|
||||||
listFormField?.defaultValue === '@AUTONUMBER' &&
|
|
||||||
mode === 'new'
|
|
||||||
) {
|
|
||||||
editorOptions = {
|
|
||||||
...editorOptions,
|
|
||||||
value: autoNumber(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
sortedFormDto.forEach((e: any) => {
|
||||||
if (e.itemType !== 'tabbed') {
|
if (e.itemType !== 'tabbed') {
|
||||||
// Backend'den gelen colCount ve colSpan değerlerini kullan
|
// Backend'den gelen colCount ve colSpan değerlerini kullan
|
||||||
|
|
|
||||||
|
|
@ -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() // PivotGrid’i yeniden yükle
|
ds.reload() // PivotGrid’i 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,21 +350,28 @@ 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()
|
|
||||||
if (instance) {
|
|
||||||
instance.option('dataSource', dataSource)
|
instance.option('dataSource', dataSource)
|
||||||
instance.option('state', null)
|
|
||||||
}
|
|
||||||
|
|
||||||
//chart Integration
|
// 4. DataSource set edildikten SONRA state'i yükle - state fields'i override edecek
|
||||||
if (gridRef?.current && chartRef?.current) {
|
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])
|
||||||
|
|
||||||
|
// Chart binding - sadece bir kez
|
||||||
|
useEffect(() => {
|
||||||
|
if (!gridRef?.current || !chartRef?.current) return
|
||||||
|
|
||||||
const pivotInstance = gridRef?.current?.instance()
|
const pivotInstance = gridRef?.current?.instance()
|
||||||
const chartInstance = chartRef?.current?.instance()
|
const chartInstance = chartRef?.current?.instance()
|
||||||
if (pivotInstance && chartInstance) {
|
if (pivotInstance && chartInstance) {
|
||||||
|
|
@ -298,8 +380,7 @@ const Pivot = (props: PivotProps) => {
|
||||||
alternateDataFields: false,
|
alternateDataFields: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}, [gridDto])
|
||||||
}, [columnData])
|
|
||||||
|
|
||||||
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>
|
||||||
|
|
|
||||||
|
|
@ -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,22 +192,36 @@ 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 { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
||||||
|
gridDto,
|
||||||
|
listFormCode,
|
||||||
|
getSelectedRowKeys,
|
||||||
|
getSelectedRowsData,
|
||||||
|
refreshData,
|
||||||
|
expandAll,
|
||||||
|
collapseAll,
|
||||||
|
getFilter,
|
||||||
|
layout,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSelectionChanged = useCallback(
|
||||||
|
(data: any) => {
|
||||||
const treeOpt = gridDto?.gridOptions
|
const treeOpt = gridDto?.gridOptions
|
||||||
const tree = gridRef.current?.instance()
|
const tree = gridRef.current?.instance()
|
||||||
if (!treeOpt || !tree) {
|
if (!treeOpt || !tree) {
|
||||||
|
|
@ -250,9 +251,12 @@ const Tree = (props: TreeProps) => {
|
||||||
if (data.selectedRowsData.length) {
|
if (data.selectedRowsData.length) {
|
||||||
setFormData(data.selectedRowsData[0])
|
setFormData(data.selectedRowsData[0])
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
function onCellPrepared(e: any) {
|
const onCellPrepared = useCallback(
|
||||||
|
(e: any) => {
|
||||||
const columnFormats = gridDto?.columnFormats
|
const columnFormats = gridDto?.columnFormats
|
||||||
if (!columnFormats) {
|
if (!columnFormats) {
|
||||||
return
|
return
|
||||||
|
|
@ -272,14 +276,19 @@ const Tree = (props: TreeProps) => {
|
||||||
e.cellElement.addClass(colStyle.cssClassName)
|
e.cellElement.addClass(colStyle.cssClassName)
|
||||||
}
|
}
|
||||||
if (colStyle.cssStyles) {
|
if (colStyle.cssStyles) {
|
||||||
e.cellElement.attr('style', e.cellElement.attr('style') + ';' + 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,11 +358,12 @@ 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(
|
||||||
|
(e: TreeListTypes.RowUpdatingEvent) => {
|
||||||
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
|
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
|
||||||
if (Object.keys(e.newData).some((a) => a.includes(':'))) {
|
if (Object.keys(e.newData).some((a) => a.includes(':'))) {
|
||||||
Object.keys(e.oldData).forEach((col) => {
|
Object.keys(e.oldData).forEach((col) => {
|
||||||
|
|
@ -377,9 +387,12 @@ const Tree = (props: TreeProps) => {
|
||||||
if (gridDto?.gridOptions.keyFieldName) {
|
if (gridDto?.gridOptions.keyFieldName) {
|
||||||
delete e.newData[gridDto?.gridOptions.keyFieldName]
|
delete e.newData[gridDto?.gridOptions.keyFieldName]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
function onEditingStart(e: TreeListTypes.EditingStartEvent) {
|
const onEditingStart = useCallback(
|
||||||
|
(e: TreeListTypes.EditingStartEvent) => {
|
||||||
isEditingRef.current = true
|
isEditingRef.current = true
|
||||||
setMode('edit')
|
setMode('edit')
|
||||||
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
||||||
|
|
@ -395,9 +408,11 @@ const Tree = (props: TreeProps) => {
|
||||||
const json = JSON.parse(e.data[field[0]])
|
const json = JSON.parse(e.data[field[0]])
|
||||||
e.data[col.dataField] = json[field[1]]
|
e.data[col.dataField] = json[field[1]]
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
[gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
function onDataErrorOccurred(e: TreeListTypes.DataErrorOccurredEvent) {
|
const onDataErrorOccurred = useCallback((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])
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue