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)) {
|
||||
const options = response.data.map((menuItem: any) => ({
|
||||
value: menuItem.shortName || menuItem.code,
|
||||
label: menuItem.displayName || menuItem.code,
|
||||
value: menuItem.shortName,
|
||||
label: translate('::' + menuItem.displayName),
|
||||
}))
|
||||
setMenuOptions(options)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Container } from '@/components/shared'
|
|||
import { DX_CLASSNAMES } from '@/constants/app.constant'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
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 { useParams, useSearchParams } from 'react-router-dom'
|
||||
import { GridDto } from '@/proxy/form/models'
|
||||
|
|
@ -98,11 +98,9 @@ const Chart = (props: ChartProps) => {
|
|||
}
|
||||
}, [gridDto, userName])
|
||||
|
||||
useEffect(() => {
|
||||
if (!gridDto) return
|
||||
if (!initialized) return
|
||||
const memoizedChartOptions = useMemo(() => {
|
||||
if (!gridDto || !initialized) return undefined
|
||||
|
||||
// Chart her zaman currentSeries'e göre render edilir
|
||||
const seriesDto = currentSeries
|
||||
|
||||
const gridOptions = {
|
||||
|
|
@ -117,7 +115,7 @@ const Chart = (props: ChartProps) => {
|
|||
layoutTypes.chart,
|
||||
)
|
||||
|
||||
const options = {
|
||||
return {
|
||||
dataSource: dataSource,
|
||||
|
||||
adjustOnZoom: gridDto.gridOptions.commonDto?.adjustOnZoom ?? true,
|
||||
|
|
@ -126,7 +124,6 @@ const Chart = (props: ChartProps) => {
|
|||
disabled: gridDto.gridOptions.commonDto?.disabled ?? false,
|
||||
palette: gridDto.gridOptions.commonDto?.palette ?? 'Material',
|
||||
paletteExtensionMode: gridDto.gridOptions.commonDto?.paletteExtensionMode ?? 'blend',
|
||||
//theme: s(chartDto.commonDto?.theme, 'generic.light'),
|
||||
|
||||
title: gridDto.gridOptions.titleDto,
|
||||
size: gridDto.gridOptions.sizeDto?.useSize
|
||||
|
|
@ -170,9 +167,13 @@ const Chart = (props: ChartProps) => {
|
|||
enabled: true,
|
||||
},
|
||||
}
|
||||
}, [gridDto, currentSeries, initialized, createSelectDataSource, listFormCode, urlSearchParams, openDrawer])
|
||||
|
||||
setChartOptions(options)
|
||||
}, [gridDto, currentSeries, initialized, searchParams, urlSearchParams, openDrawer])
|
||||
useEffect(() => {
|
||||
if (memoizedChartOptions) {
|
||||
setChartOptions(memoizedChartOptions)
|
||||
}
|
||||
}, [memoizedChartOptions])
|
||||
|
||||
const onFilter = useCallback(
|
||||
(value?: string) => {
|
||||
|
|
@ -220,7 +221,7 @@ const Chart = (props: ChartProps) => {
|
|||
[gridDto, urlSearchParams, searchText],
|
||||
)
|
||||
|
||||
const getFields = async () => {
|
||||
const getFields = useCallback(async () => {
|
||||
if (!props.listFormCode) return
|
||||
try {
|
||||
const resp = await getListFormFields({
|
||||
|
|
@ -246,24 +247,24 @@ const Chart = (props: ChartProps) => {
|
|||
{ placement: 'top-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [props.listFormCode, translate])
|
||||
|
||||
useEffect(() => {
|
||||
if (props.listFormCode) getFields()
|
||||
}, [props.listFormCode, config])
|
||||
|
||||
const handlePreviewChange = (series: ChartSeriesDto[]) => {
|
||||
const handlePreviewChange = useCallback((series: ChartSeriesDto[]) => {
|
||||
// Preview değişikliklerini anında chart'a yansıt
|
||||
setCurrentSeries(series)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleDrawerClose = () => {
|
||||
const handleDrawerClose = useCallback(() => {
|
||||
setOpenDrawer(false)
|
||||
// İptal - kaydedilmiş serilere geri dön
|
||||
setCurrentSeries(savedSeries)
|
||||
}
|
||||
}, [savedSeries])
|
||||
|
||||
const onSave = async (newSeries: ChartSeriesDto[]) => {
|
||||
const onSave = useCallback(async (newSeries: ChartSeriesDto[]) => {
|
||||
// 1. Silinecek serileri bul (savedSeries var ama newSeries yok)
|
||||
const toDelete = savedSeries.filter((old: ChartSeriesDto) => !newSeries.some((s) => s.index === old.index))
|
||||
|
||||
|
|
@ -299,7 +300,7 @@ const Chart = (props: ChartProps) => {
|
|||
setSavedSeries(newSeries)
|
||||
setCurrentSeries(newSeries)
|
||||
}
|
||||
}
|
||||
}, [savedSeries, id, props])
|
||||
|
||||
return (
|
||||
<Container className={DX_CLASSNAMES}>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ import { Item } from 'devextreme-react/toolbar'
|
|||
import { DataType } from 'devextreme/common'
|
||||
import { captionize } from 'devextreme/core/utils/inflector'
|
||||
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 SubForms from '../form/SubForms'
|
||||
import { RowMode, SimpleItemWithColData } from '../form/types'
|
||||
|
|
@ -140,17 +140,6 @@ const Grid = (props: GridProps) => {
|
|||
}
|
||||
}, [searchParams])
|
||||
|
||||
const layout = layoutTypes.grid
|
||||
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
||||
gridDto,
|
||||
listFormCode,
|
||||
getSelectedRowKeys,
|
||||
getSelectedRowsData,
|
||||
refreshData,
|
||||
getFilter,
|
||||
layout,
|
||||
})
|
||||
|
||||
const { filterToolbarData, ...filterData } = useFilters({
|
||||
gridDto,
|
||||
gridRef,
|
||||
|
|
@ -165,38 +154,50 @@ const Grid = (props: GridProps) => {
|
|||
gridRef,
|
||||
})
|
||||
|
||||
async function getSelectedRowKeys() {
|
||||
const getSelectedRowKeys = useCallback(async () => {
|
||||
const grd = gridRef.current?.instance()
|
||||
if (!grd) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await grd.getSelectedRowKeys()
|
||||
}
|
||||
}, [])
|
||||
|
||||
function getSelectedRowsData() {
|
||||
const getSelectedRowsData = useCallback(() => {
|
||||
const grd = gridRef.current?.instance()
|
||||
if (!grd) {
|
||||
return []
|
||||
}
|
||||
|
||||
return grd.getSelectedRowsData()
|
||||
}
|
||||
}, [])
|
||||
|
||||
function refreshData() {
|
||||
const refreshData = useCallback(() => {
|
||||
gridRef.current?.instance()?.refresh()
|
||||
}
|
||||
}, [])
|
||||
|
||||
function getFilter() {
|
||||
const getFilter = useCallback(() => {
|
||||
const grd = gridRef.current?.instance()
|
||||
if (!grd) {
|
||||
return
|
||||
}
|
||||
|
||||
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 grd = gridRef.current?.instance()
|
||||
if (!grdOpt || !grd) {
|
||||
|
|
@ -223,9 +224,12 @@ const Grid = (props: GridProps) => {
|
|||
if (data.selectedRowsData.length) {
|
||||
setFormData(data.selectedRowsData[0])
|
||||
}
|
||||
}
|
||||
},
|
||||
[gridDto],
|
||||
)
|
||||
|
||||
function onCellPrepared(e: any) {
|
||||
const onCellPrepared = useCallback(
|
||||
(e: any) => {
|
||||
const columnFormats = gridDto?.columnFormats
|
||||
if (!columnFormats) {
|
||||
return
|
||||
|
|
@ -250,16 +254,22 @@ const Grid = (props: GridProps) => {
|
|||
}
|
||||
// css inline style var ise uygula
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
}, [])
|
||||
|
||||
function onRowUpdating(e: DataGridTypes.RowUpdatingEvent<any, any>) {
|
||||
const onRowUpdating = useCallback(
|
||||
(e: DataGridTypes.RowUpdatingEvent<any, any>) => {
|
||||
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
|
||||
if (Object.keys(e.newData).some((a) => a.includes(':'))) {
|
||||
Object.keys(e.oldData).forEach((col) => {
|
||||
|
|
@ -357,9 +370,12 @@ const Grid = (props: GridProps) => {
|
|||
if (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
|
||||
setMode('edit')
|
||||
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
||||
|
|
@ -376,9 +392,11 @@ const Grid = (props: GridProps) => {
|
|||
const json = JSON.parse(e.data[field[0]])
|
||||
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(
|
||||
<Notification type="danger" duration={2000}>
|
||||
{e.error?.message}
|
||||
|
|
@ -387,20 +405,43 @@ const Grid = (props: GridProps) => {
|
|||
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) {
|
||||
const formItem = gridDto.gridOptions.editingFormDto
|
||||
.flatMap((group) => group.items || [])
|
||||
.find((i) => i.dataField === editor.dataField)
|
||||
|
||||
// Cascade disabled mantığı
|
||||
const colFormat = gridDto.columnFormats.find((c) => c.fieldName === editor.dataField)
|
||||
if (colFormat?.lookupDto?.cascadeParentFields) {
|
||||
const parentFields = colFormat.lookupDto.cascadeParentFields
|
||||
.split(',')
|
||||
.map((f: string) => f.trim())
|
||||
const cascadeInfo = cascadeFieldsMap.get(editor.dataField)
|
||||
if (cascadeInfo) {
|
||||
const parentFields = cascadeInfo.parentFields
|
||||
|
||||
const prevHandler = editor.editorOptions.onValueChanged
|
||||
|
||||
|
|
@ -414,36 +455,30 @@ const Grid = (props: GridProps) => {
|
|||
const rowData = grid.getVisibleRows().find((r) => r.key === rowKey)?.data || {}
|
||||
|
||||
// Bu field bir parent ise, child fieldleri temizle
|
||||
if (colFormat.lookupDto?.cascadeEmptyFields) {
|
||||
const childFields = colFormat.lookupDto.cascadeEmptyFields
|
||||
.split(',')
|
||||
.map((f: string) => f.trim())
|
||||
childFields.forEach((childField: string) => {
|
||||
if (cascadeInfo.childFields) {
|
||||
cascadeInfo.childFields.forEach((childField: string) => {
|
||||
if (rowIndex >= 0) {
|
||||
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')
|
||||
if (popup) {
|
||||
const formInstance = grid.option('editing.form') as any
|
||||
if (formInstance && formInstance.getEditor) {
|
||||
gridDto.columnFormats.forEach((col) => {
|
||||
if (col.lookupDto?.cascadeParentFields) {
|
||||
const colParentFields = col.lookupDto.cascadeParentFields
|
||||
.split(',')
|
||||
.map((f: string) => f.trim())
|
||||
const shouldDisable = colParentFields.some((pf: string) => !rowData[pf])
|
||||
|
||||
// Sadece bu field'den etkilenen childları güncelle
|
||||
cascadeFieldsMap.forEach((info, fieldName) => {
|
||||
if (info.parentFields.includes(editor.dataField!)) {
|
||||
const shouldDisable = info.parentFields.some((pf: string) => !rowData[pf])
|
||||
try {
|
||||
const editorInstance = formInstance.getEditor(col.fieldName!)
|
||||
const editorInstance = formInstance.getEditor(fieldName)
|
||||
if (editorInstance) {
|
||||
editorInstance.option('disabled', shouldDisable)
|
||||
}
|
||||
} 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(
|
||||
(state: any) => {
|
||||
|
|
@ -595,106 +632,8 @@ const Grid = (props: GridProps) => {
|
|||
}
|
||||
}, [gridDto])
|
||||
|
||||
// Kolonları oluştur - dil değiştiğinde güncelle
|
||||
useEffect(() => {
|
||||
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(() => {
|
||||
// extraFilters değişikliklerini useMemo ile optimize et
|
||||
const filterParams = useMemo(() => {
|
||||
const activeFilters = extraFilters.filter((f) => f.value)
|
||||
|
||||
let base: any = null
|
||||
|
|
@ -736,19 +675,269 @@ const Grid = (props: GridProps) => {
|
|||
}, null as any)
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
searchParams?.set('filter', JSON.stringify(filter))
|
||||
return 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 {
|
||||
searchParams?.delete('filter')
|
||||
}
|
||||
|
||||
// Grid'i yenile
|
||||
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(() => {
|
||||
refListFormCode.current = 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
|
||||
useEffect(() => {
|
||||
const calculateWidgetHeight = () => {
|
||||
|
|
@ -1053,126 +1242,6 @@ const Grid = (props: GridProps) => {
|
|||
)
|
||||
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) => {
|
||||
if (e.itemType !== 'tabbed') {
|
||||
// Backend'den gelen colCount ve colSpan değerlerini kullan
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ import Chart, {
|
|||
Tooltip,
|
||||
} from 'devextreme-react/chart'
|
||||
import PivotGrid, {
|
||||
Export,
|
||||
FieldChooser,
|
||||
FieldPanel,
|
||||
HeaderFilter,
|
||||
LoadPanel,
|
||||
PivotGridRef,
|
||||
PivotGridTypes,
|
||||
Scrolling,
|
||||
|
|
@ -24,7 +26,7 @@ import PivotGrid, {
|
|||
} from 'devextreme-react/pivot-grid'
|
||||
import CustomStore from 'devextreme/data/custom_store'
|
||||
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 { GridColumnData } from './GridColumnData'
|
||||
import {
|
||||
|
|
@ -36,7 +38,7 @@ import {
|
|||
} from './Utils'
|
||||
import { useFilters } from './useFilters'
|
||||
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 { usePermission } from '@/utils/hooks/usePermission'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
|
|
@ -86,7 +88,7 @@ const Pivot = (props: PivotProps) => {
|
|||
gridRef,
|
||||
})
|
||||
|
||||
function onCellPrepared(e: any) {
|
||||
const onCellPrepared = useCallback((e: any) => {
|
||||
const columnFormats = gridDto?.columnFormats
|
||||
if (!columnFormats) {
|
||||
return
|
||||
|
|
@ -118,9 +120,9 @@ const Pivot = (props: PivotProps) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [gridDto])
|
||||
|
||||
const clearPivotFilters = () => {
|
||||
const clearPivotFilters = useCallback(() => {
|
||||
const grid = gridRef.current?.instance()
|
||||
if (!grid) return
|
||||
|
||||
|
|
@ -133,9 +135,9 @@ const Pivot = (props: PivotProps) => {
|
|||
})
|
||||
ds.reload()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const moveAllFieldsToFilterArea = () => {
|
||||
const moveAllFieldsToFilterArea = useCallback(() => {
|
||||
const grid = gridRef.current?.instance()
|
||||
if (!grid) return
|
||||
|
||||
|
|
@ -152,9 +154,9 @@ const Pivot = (props: PivotProps) => {
|
|||
ds.fields(fields)
|
||||
ds.reload() // PivotGrid’i yeniden yükle
|
||||
grid.repaint() // UI güncelle
|
||||
}
|
||||
}, [])
|
||||
|
||||
const resetPivotGridState = async () => {
|
||||
const resetPivotGridState = useCallback(async () => {
|
||||
const grid = gridRef.current?.instance()
|
||||
if (grid) {
|
||||
// kullaniciya ait kayitli grid state i sil customizationData boşalt silinsin.
|
||||
|
|
@ -169,8 +171,47 @@ const Pivot = (props: PivotProps) => {
|
|||
clearPivotFilters()
|
||||
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(
|
||||
(state: any) => {
|
||||
return postListFormCustomization({
|
||||
|
|
@ -201,15 +242,16 @@ const Pivot = (props: PivotProps) => {
|
|||
[listFormCode],
|
||||
)
|
||||
|
||||
const customSaveStateRef = useRef(customSaveState)
|
||||
const customLoadStateRef = useRef(customLoadState)
|
||||
|
||||
useEffect(() => {
|
||||
if (gridRef?.current) {
|
||||
const instance = gridRef?.current?.instance()
|
||||
if (instance) {
|
||||
instance.option('remoteOperations', false)
|
||||
instance.option('dataSource', undefined)
|
||||
instance.option('stateStoring', undefined)
|
||||
}
|
||||
}
|
||||
customSaveStateRef.current = customSaveState
|
||||
customLoadStateRef.current = customLoadState
|
||||
}, [customSaveState, customLoadState])
|
||||
|
||||
useEffect(() => {
|
||||
refListFormCode.current = listFormCode
|
||||
}, [listFormCode])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -231,34 +273,67 @@ const Pivot = (props: PivotProps) => {
|
|||
}
|
||||
}, [gridDto])
|
||||
|
||||
useEffect(() => {
|
||||
if (!gridDto) {
|
||||
return
|
||||
}
|
||||
// Kolonları memoize et
|
||||
const memoizedColumns = useMemo(() => {
|
||||
if (!gridDto || !config) return undefined
|
||||
|
||||
// Set columns
|
||||
const cols = getBandedColumns()
|
||||
setColumnData(cols?.filter((a) => a.colData?.pivotSettingsDto.isPivot))
|
||||
return cols?.filter((a) => a.colData?.pivotSettingsDto.isPivot)
|
||||
}, [gridDto, config])
|
||||
|
||||
// Set data source
|
||||
const dataSource: CustomStore<any, any> = createSelectDataSource(
|
||||
// DataSource'u memoize et
|
||||
const memoizedDataSource = useMemo(() => {
|
||||
if (!gridDto) return undefined
|
||||
|
||||
const cols = getBandedColumns()
|
||||
return createSelectDataSource(
|
||||
gridDto.gridOptions,
|
||||
listFormCode,
|
||||
searchParams,
|
||||
layoutTypes.pivot,
|
||||
cols,
|
||||
)
|
||||
|
||||
setGridDataSource(dataSource)
|
||||
}, [gridDto, config, searchParams])
|
||||
}, [gridDto, listFormCode, createSelectDataSource])
|
||||
|
||||
useEffect(() => {
|
||||
refListFormCode.current = listFormCode
|
||||
if (!gridRef?.current) {
|
||||
if (memoizedColumns) {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
dataField: b.dataField,
|
||||
caption: b.caption,
|
||||
|
|
@ -275,21 +350,28 @@ const Pivot = (props: PivotProps) => {
|
|||
} as Field
|
||||
})
|
||||
|
||||
PivotGridDataSource
|
||||
// 3. DataSource'u ayarla - fields olmadan
|
||||
const dataSource: PivotGridTypes.Properties['dataSource'] = {
|
||||
remoteOperations: true,
|
||||
store: gridDataSource,
|
||||
fields,
|
||||
fields: defaultFields,
|
||||
}
|
||||
|
||||
const instance = gridRef?.current?.instance()
|
||||
if (instance) {
|
||||
instance.option('dataSource', dataSource)
|
||||
instance.option('state', null)
|
||||
}
|
||||
|
||||
//chart Integration
|
||||
if (gridRef?.current && chartRef?.current) {
|
||||
// 4. DataSource set edildikten SONRA state'i yükle - state fields'i override edecek
|
||||
setTimeout(() => {
|
||||
const ds = instance.getDataSource()
|
||||
if (ds && typeof ds.state === 'function') {
|
||||
ds.state(null) // Bu saved state'teki fields'i kullanacak
|
||||
}
|
||||
}, 50)
|
||||
}, [columnData, gridDataSource, gridDto])
|
||||
|
||||
// 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) {
|
||||
|
|
@ -298,8 +380,7 @@ const Pivot = (props: PivotProps) => {
|
|||
alternateDataFields: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [columnData])
|
||||
}, [gridDto])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -383,7 +464,9 @@ const Pivot = (props: PivotProps) => {
|
|||
rtlEnabled={gridDto.gridOptions.columnOptionDto?.rtlEnabled}
|
||||
hoverStateEnabled={gridDto.gridOptions.columnOptionDto?.hoverStateEnabled}
|
||||
onCellPrepared={onCellPrepared}
|
||||
onExporting={onExporting}
|
||||
>
|
||||
<Export enabled={gridDto.gridOptions.exportDto?.enabled} />
|
||||
<HeaderFilter
|
||||
allowSelectAll={gridDto.gridOptions.selectionDto.allowSelectAll}
|
||||
width={gridDto.gridOptions.headerFilterDto.width}
|
||||
|
|
@ -406,23 +489,9 @@ const Pivot = (props: PivotProps) => {
|
|||
enabled={gridDto.gridOptions.pivotOptionDto.columnChooserEnabled}
|
||||
height={500}
|
||||
/>
|
||||
<StateStoring
|
||||
enabled={gridDto?.gridOptions.stateStoringDto?.enabled}
|
||||
type={gridDto?.gridOptions.stateStoringDto?.type}
|
||||
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
|
||||
}
|
||||
<LoadPanel
|
||||
enabled={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled as boolean | undefined}
|
||||
text={gridDto.gridOptions.pagerOptionDto?.loadPanelText}
|
||||
/>
|
||||
<Scrolling mode={gridDto.gridOptions.pagerOptionDto.scrollingMode} />
|
||||
</PivotGrid>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import TreeListDx, {
|
|||
} from 'devextreme-react/tree-list'
|
||||
import { Item } from 'devextreme-react/toolbar'
|
||||
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 { RowMode, SimpleItemWithColData } from '../form/types'
|
||||
import { GridColumnData } from './GridColumnData'
|
||||
|
|
@ -132,19 +132,6 @@ const Tree = (props: TreeProps) => {
|
|||
}
|
||||
}, [searchParams])
|
||||
|
||||
const layout = layoutTypes.tree
|
||||
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
||||
gridDto,
|
||||
listFormCode,
|
||||
getSelectedRowKeys,
|
||||
getSelectedRowsData,
|
||||
refreshData,
|
||||
expandAll,
|
||||
collapseAll,
|
||||
getFilter,
|
||||
layout,
|
||||
})
|
||||
|
||||
const { filterToolbarData, ...filterData } = useFilters({
|
||||
gridDto,
|
||||
gridRef,
|
||||
|
|
@ -159,7 +146,7 @@ const Tree = (props: TreeProps) => {
|
|||
gridRef,
|
||||
})
|
||||
|
||||
function extractSearchParamsFields(filter: any): [string, string, any][] {
|
||||
const extractSearchParamsFields = useCallback((filter: any): [string, string, any][] => {
|
||||
if (!Array.isArray(filter)) return []
|
||||
|
||||
if (filter.length === 3 && typeof filter[0] === 'string') {
|
||||
|
|
@ -167,27 +154,27 @@ const Tree = (props: TreeProps) => {
|
|||
}
|
||||
|
||||
return filter.flatMap((f) => extractSearchParamsFields(f))
|
||||
}
|
||||
}, [])
|
||||
|
||||
async function getSelectedRowKeys() {
|
||||
const getSelectedRowKeys = useCallback(async () => {
|
||||
const tree = gridRef.current?.instance()
|
||||
if (!tree) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await tree.getSelectedRowKeys()
|
||||
}
|
||||
}, [])
|
||||
|
||||
function getSelectedRowsData() {
|
||||
const getSelectedRowsData = useCallback(() => {
|
||||
const tree = gridRef.current?.instance()
|
||||
if (!tree) {
|
||||
return []
|
||||
}
|
||||
|
||||
return tree.getSelectedRowsData()
|
||||
}
|
||||
}, [])
|
||||
|
||||
function expandAll() {
|
||||
const expandAll = useCallback(() => {
|
||||
const tree = gridRef.current?.instance()
|
||||
if (!tree) return
|
||||
tree.forEachNode((node: any) => {
|
||||
|
|
@ -195,9 +182,9 @@ const Tree = (props: TreeProps) => {
|
|||
tree.expandRow(node.key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
function collapseAll() {
|
||||
const collapseAll = useCallback(() => {
|
||||
const tree = gridRef.current?.instance()
|
||||
if (!tree) return
|
||||
tree.forEachNode((node: any) => {
|
||||
|
|
@ -205,22 +192,36 @@ const Tree = (props: TreeProps) => {
|
|||
tree.collapseRow(node.key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
function refreshData() {
|
||||
const refreshData = useCallback(() => {
|
||||
gridRef.current?.instance().refresh()
|
||||
}
|
||||
}, [])
|
||||
|
||||
function getFilter() {
|
||||
const getFilter = useCallback(() => {
|
||||
const tree = gridRef.current?.instance()
|
||||
if (!tree) {
|
||||
return
|
||||
}
|
||||
|
||||
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 tree = gridRef.current?.instance()
|
||||
if (!treeOpt || !tree) {
|
||||
|
|
@ -250,9 +251,12 @@ const Tree = (props: TreeProps) => {
|
|||
if (data.selectedRowsData.length) {
|
||||
setFormData(data.selectedRowsData[0])
|
||||
}
|
||||
}
|
||||
},
|
||||
[gridDto],
|
||||
)
|
||||
|
||||
function onCellPrepared(e: any) {
|
||||
const onCellPrepared = useCallback(
|
||||
(e: any) => {
|
||||
const columnFormats = gridDto?.columnFormats
|
||||
if (!columnFormats) {
|
||||
return
|
||||
|
|
@ -272,14 +276,19 @@ const Tree = (props: TreeProps) => {
|
|||
e.cellElement.addClass(colStyle.cssClassName)
|
||||
}
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
}, [])
|
||||
|
||||
function onRowUpdating(e: TreeListTypes.RowUpdatingEvent) {
|
||||
const onRowUpdating = useCallback(
|
||||
(e: TreeListTypes.RowUpdatingEvent) => {
|
||||
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
|
||||
if (Object.keys(e.newData).some((a) => a.includes(':'))) {
|
||||
Object.keys(e.oldData).forEach((col) => {
|
||||
|
|
@ -377,9 +387,12 @@ const Tree = (props: TreeProps) => {
|
|||
if (gridDto?.gridOptions.keyFieldName) {
|
||||
delete e.newData[gridDto?.gridOptions.keyFieldName]
|
||||
}
|
||||
}
|
||||
},
|
||||
[gridDto],
|
||||
)
|
||||
|
||||
function onEditingStart(e: TreeListTypes.EditingStartEvent) {
|
||||
const onEditingStart = useCallback(
|
||||
(e: TreeListTypes.EditingStartEvent) => {
|
||||
isEditingRef.current = true
|
||||
setMode('edit')
|
||||
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
||||
|
|
@ -395,9 +408,11 @@ const Tree = (props: TreeProps) => {
|
|||
const json = JSON.parse(e.data[field[0]])
|
||||
e.data[col.dataField] = json[field[1]]
|
||||
})
|
||||
}
|
||||
},
|
||||
[gridDto],
|
||||
)
|
||||
|
||||
function onDataErrorOccurred(e: TreeListTypes.DataErrorOccurredEvent) {
|
||||
const onDataErrorOccurred = useCallback((e: TreeListTypes.DataErrorOccurredEvent) => {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
{e.error?.message}
|
||||
|
|
@ -406,7 +421,7 @@ const Tree = (props: TreeProps) => {
|
|||
placement: 'top-end',
|
||||
},
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
function onEditorPreparing(editor: TreeListTypes.EditorPreparingEvent) {
|
||||
if (editor.parentType === 'dataRow' && editor.dataField && gridDto) {
|
||||
|
|
@ -566,6 +581,15 @@ const Tree = (props: TreeProps) => {
|
|||
[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(() => {
|
||||
if (gridRef?.current) {
|
||||
gridRef?.current?.instance().option('columns', undefined)
|
||||
|
|
@ -723,8 +747,9 @@ const Tree = (props: TreeProps) => {
|
|||
gridDto?.gridOptions.stateStoringDto?.enabled &&
|
||||
gridDto?.gridOptions.stateStoringDto?.type === 'custom'
|
||||
) {
|
||||
stateStoring.customSave = customSaveState
|
||||
stateStoring.customLoad = customLoadState
|
||||
// Ref pattern kullan - Grid'deki gibi
|
||||
stateStoring.customSave = (state: any) => customSaveStateRef.current(state)
|
||||
stateStoring.customLoad = () => customLoadStateRef.current()
|
||||
}
|
||||
gridRef?.current?.instance().option('stateStoring', stateStoring)
|
||||
}, [columnData])
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { DataGridTypes } from 'devextreme-react/data-grid'
|
|||
import { DataType, HorizontalEdge, SortOrder, ValidationRule } from 'devextreme/common'
|
||||
import CustomStore from 'devextreme/data/custom_store'
|
||||
import { SelectedFilterOperation } from 'devextreme/ui/data_grid'
|
||||
import { useEffect } from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { usePermission } from '@/utils/hooks/usePermission'
|
||||
import { usePWA } from '@/utils/hooks/usePWA'
|
||||
|
|
@ -105,11 +105,27 @@ function calculateFilterExpressionMultiValue(
|
|||
}
|
||||
}
|
||||
|
||||
// lookup cache (module scope)
|
||||
const __lookupCache = new Map<string, Promise<any[]>>()
|
||||
// lookup cache (module scope) - cache süresini ve boyutunu yönet
|
||||
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[]>) => {
|
||||
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()
|
||||
.then(() => loader())
|
||||
.then((res) => res ?? [])
|
||||
|
|
@ -117,7 +133,8 @@ const cachedLoader = (key: string, loader: () => Promise<any[]>) => {
|
|||
__lookupCache.delete(key) // hata olursa tekrar denenebilsin
|
||||
throw err
|
||||
})
|
||||
__lookupCache.set(key, p)
|
||||
|
||||
__lookupCache.set(key, { promise: p, timestamp: now })
|
||||
return p
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +159,7 @@ const useListFormColumns = ({
|
|||
__lookupCache.clear()
|
||||
}, [listFormCode])
|
||||
|
||||
const lookupDataSource = (options: any, colData: any, listFormCode: string) => {
|
||||
const lookupDataSource = useCallback((options: any, colData: any, listFormCode: string) => {
|
||||
const { lookupDto } = colData
|
||||
const filters = []
|
||||
if (lookupDto.cascadeParentFields) {
|
||||
|
|
@ -184,7 +201,7 @@ const useListFormColumns = ({
|
|||
store: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [listFormCode])
|
||||
|
||||
const createLookupStaticDataSource = (
|
||||
load: () => any,
|
||||
|
|
@ -289,7 +306,7 @@ const useListFormColumns = ({
|
|||
})
|
||||
}
|
||||
|
||||
const getCommandColumn = (): GridColumnData | undefined => {
|
||||
const getCommandColumn = useCallback((): GridColumnData | undefined => {
|
||||
if (!gridDto) {
|
||||
return
|
||||
}
|
||||
|
|
@ -399,9 +416,9 @@ const useListFormColumns = ({
|
|||
}
|
||||
|
||||
return column as GridColumnData
|
||||
}
|
||||
}, [gridDto, checkPermission, translate, listFormCode, isPwaMode, dialog, gridRef])
|
||||
|
||||
const getColumns = (columnFormats: ColumnFormatDto[]) => {
|
||||
const getColumns = useCallback((columnFormats: ColumnFormatDto[]) => {
|
||||
const columns: GridColumnData[] = []
|
||||
|
||||
if (!gridDto || !columnFormats) {
|
||||
|
|
@ -550,9 +567,9 @@ const useListFormColumns = ({
|
|||
})
|
||||
|
||||
return columns
|
||||
}
|
||||
}, [gridDto, lookupDataSource, translate, checkPermission, dialog, isPwaMode, listFormCode])
|
||||
|
||||
const getBandedColumns = () => {
|
||||
const getBandedColumns = useCallback(() => {
|
||||
if (!gridDto) {
|
||||
return
|
||||
}
|
||||
|
|
@ -635,7 +652,7 @@ const useListFormColumns = ({
|
|||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
}, [gridDto, getColumns, getCommandColumn])
|
||||
|
||||
return {
|
||||
getBandedColumns,
|
||||
|
|
|
|||
Loading…
Reference in a new issue