sozsoft-platform/ui/src/views/list/useListFormCustomDataSource.ts
2026-08-07 01:54:50 +03:00

418 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { DataGridRef } from 'devextreme-react/data-grid'
import type { PivotGridRef } from 'devextreme-react/pivot-grid'
import CustomStore from 'devextreme/data/custom_store'
import { MutableRefObject, useCallback } from 'react'
import { layoutTypes, ListViewLayoutType } from '@/views/admin/listForm/edit/types'
import { getLoadOptions, getServiceAddress, safeJsonParse, setGridPanelColor } from './Utils'
import { GridOptionsDto } from '@/proxy/form/models'
import { GridColumnData } from './GridColumnData'
import { dynamicFetch } from '@/services/form.service'
import { MULTIVALUE_DELIMITER } from '@/constants/app.constant'
import { FILTERED_GRID_PANEL_COLOR } from './shared/columns'
import type { TreeListRef } from 'devextreme-react/cjs/tree-list'
import type { GanttRef } from 'devextreme-react/cjs/gantt'
import type { SchedulerRef } from 'devextreme-react/cjs/scheduler'
import type { CardViewRef, CardViewTypes } from 'devextreme-react/cjs/card-view'
/** URL'den gelen varsayılan filtre ile grid filtresini `and` ile birleştirir. */
const mergeWithDefaultFilter = (gridFilter: any, searchParams?: URLSearchParams) => {
const defaultFilter = safeJsonParse(
searchParams?.get('filter'),
null,
'Search filter parse error:',
)
if (defaultFilter && gridFilter) return [defaultFilter, 'and', gridFilter]
return defaultFilter ?? gridFilter
}
/** Filtre doluysa serialize eder, boşsa parametreyi hiç göndermez. */
const applyCombinedFilter = (parameters: any, combinedFilter: any) => {
if (combinedFilter && combinedFilter.length > 0) {
parameters.filter = JSON.stringify(combinedFilter)
} else {
delete parameters.filter
}
}
const toInsertedRowData = (values: any, responseData: any, keyFieldName?: string | null) => {
if (!keyFieldName) {
return responseData ?? values
}
if (responseData && typeof responseData === 'object' && !Array.isArray(responseData)) {
if (responseData[keyFieldName] !== undefined && responseData[keyFieldName] !== null) {
return { ...values, ...responseData }
}
if (
responseData.data &&
typeof responseData.data === 'object' &&
!Array.isArray(responseData.data) &&
responseData.data[keyFieldName] !== undefined &&
responseData.data[keyFieldName] !== null
) {
return { ...values, ...responseData.data }
}
}
if (responseData !== undefined && responseData !== null) {
return { ...values, [keyFieldName]: responseData }
}
return values
}
const useListFormCustomDataSource = ({
gridRef,
}: {
gridRef:
| MutableRefObject<DataGridRef<any, any> | undefined>
| MutableRefObject<PivotGridRef | undefined>
| MutableRefObject<TreeListRef<any, any> | undefined>
| MutableRefObject<GanttRef | undefined>
| MutableRefObject<SchedulerRef | undefined>
| MutableRefObject<CardViewRef | undefined>
}) => {
const createSelectDataSource = useCallback(
(
gridOptions: GridOptionsDto,
listFormCode: string,
searchParams?: URLSearchParams,
layout?: ListViewLayoutType | string,
cols?: GridColumnData[] | CardViewTypes.Column[],
) => {
const store: any = new CustomStore({
key:
layout === layoutTypes.tree
? gridOptions.treeOptionDto?.keyExpr
: gridOptions.keyFieldName,
useDefaultSearch: true,
load: async (loadOptions) => {
// URL'den sort parametresini al ve loadOptions'a ekle
const urlSort = searchParams?.get('sort')
if (urlSort && !loadOptions.sort) {
loadOptions.sort = safeJsonParse(urlSort, undefined, 'Sort parse error:')
}
const parameters = getLoadOptions(loadOptions, {
listFormCode,
filter: '',
createDeleteQuery: searchParams?.get('createDeleteQuery'),
chart: layout === layoutTypes.chart,
})
// Chart için group ve groupSummary parametreleri ekle
if (layout === layoutTypes.chart && gridOptions.seriesDto && gridOptions.seriesDto.length > 0) {
// Tüm series'lerin unique argumentField'larını topla
const allArgumentFields = [...new Set(gridOptions.seriesDto.map(s => s.argumentField).filter(Boolean))] as string[]
// İlk argumentField üzerinden group yap (chart tek bir X ekseni kullanır)
if (allArgumentFields.length > 0) {
loadOptions.group = allArgumentFields.map(field => ({
selector: field as string,
isExpanded: false
}))
}
// Tüm series'lerin valueField'ları için summary hesapla
const groupSummaries: any[] = []
gridOptions.seriesDto.forEach(series => {
if (series.valueField && series.summaryType) {
groupSummaries.push({
selector: series.valueField,
summaryType: series.summaryType as any
})
}
})
if (groupSummaries.length > 0) {
loadOptions.groupSummary = groupSummaries
}
// Parametreleri tekrar oluştur
const chartParameters = getLoadOptions(loadOptions, {
listFormCode,
filter: '',
createDeleteQuery: searchParams?.get('createDeleteQuery'),
chart: layout === layoutTypes.chart,
})
Object.assign(parameters, chartParameters)
}
//editing asamasinda her bir field de yapilan degisiklik load istegi olarak buraya dusuyor.
//TODO: bu bug halen devam ediyor!!
//Bunu engellemek icin eklendi.
//if (!params.hasOwnProperty('requireTotalCount')) {
// return;
//}
try {
if (gridRef?.current) {
//TODO:
}
// Type guard to handle union type for gridRef
let columns = cols
if (gridRef?.current?.instance()) {
const instance = gridRef?.current?.instance() as any
const instanceColumns = instance.option('columns')
if (instanceColumns) {
columns = instanceColumns as GridColumnData[]
}
}
// URL'deki filtreyi grid'in mevcut filtrelerinin üzerine uygula
if (columns?.length && searchParams) {
const filters: (string[] | string)[] = []
for (const col of columns) {
if (col.dataField) {
const sValue = searchParams.get(col.dataField)
if (sValue) {
filters.push([col.dataField, '=', sValue])
filters.push('and')
}
}
}
if (filters.length) {
filters.pop() // son and'i sil
if (parameters.filter) {
parameters.filter = [...parameters.filter, 'and', ...filters]
} else {
parameters.filter = filters
}
}
}
// NOT: Birleştirme, yukarıdaki kolon bazlı URL filtreleri parameters.filter'a
// eklendikten SONRA yapılmalı; aksi halde bu filtreler sessizce kayboluyordu.
applyCombinedFilter(
parameters,
mergeWithDefaultFilter(parameters.filter, searchParams),
)
const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
// Chart için grouped data'yı chart formatına çevir
if (layout === layoutTypes.chart && Array.isArray(response.data.data)) {
const flattenGroupedData = (items: any[], parentKeys: any = {}): any[] => {
const result: any[] = []
items.forEach((item: any) => {
if (item.items && item.items.length > 0) {
// Alt grup var, recursive olarak işle
const currentKeys = { ...parentKeys }
// Bu level'daki key'i ilgili argumentField'a map'le
gridOptions.seriesDto?.forEach(series => {
if (series.argumentField && !currentKeys[series.argumentField]) {
currentKeys[series.argumentField] = item.key
}
})
result.push(...flattenGroupedData(item.items, currentKeys))
} else {
// Leaf node - gerçek data
const transformed: any = { ...parentKeys }
// Son level'daki key'i ekle
gridOptions.seriesDto?.forEach(series => {
if (series.argumentField && item.key !== undefined && !transformed[series.argumentField]) {
transformed[series.argumentField] = item.key
}
})
// Summary değerlerini valueField'lara map'le
if (Array.isArray(item.summary)) {
gridOptions.seriesDto?.forEach((series, index) => {
if (series.valueField && item.summary[index] !== undefined) {
transformed[series.valueField] = item.summary[index]
}
})
}
result.push(transformed)
}
})
return result
}
response.data.data = flattenGroupedData(response.data.data)
}
// Column format multiValue ise, gelen stringi array yapmaliyiz
if (columns) {
columns.forEach((col: any) => {
// Column multiValue mu?
if (col.extras?.multiValue) {
//console.log('MultiValue:', col.dataField, col.extras?.multiValue)
// Multivalue column icin header filter acildiginda gonderilen group querysi sonuclari
// "key" field ile geliyor, bunu array yapmamiz lazim, bu group query'nin,
// bu field icin olup olmadigini anlayabiliriz
const isGroupQuery = parameters?.group?.includes(col.dataField)
response.data.data?.forEach((row: any) => {
try {
if (row[col.dataField]) {
// value'yu arraye cevir
row[col.dataField] = row[col.dataField]?.split(MULTIVALUE_DELIMITER)
}
if (isGroupQuery && row.key) {
// group query sonucunda gelen key'i arraye cevir
row.key = row.key.split(MULTIVALUE_DELIMITER)
}
} catch (e) {
// toast.push(
// <Notification type="danger" duration={2000}>
// {'multiValue Error'}
// </Notification>,
// {
// placement: 'bottom-end',
// },
// )
console.log('multiValue Error', e)
// JSON parse sirasinde hata olursa data gosterimi devam etsin
}
})
}
})
//TODO: Bu neden var? Eğer gerekli değilse kaldırılabilir.
searchParams?.set('createDeleteQuery', 'false')
}
// gride filtre uygulanmis ise renklendir
setGridPanelColor(
response.data.queryInfos?.isAppliedGridFilter
? FILTERED_GRID_PANEL_COLOR
: 'transparent',
)
const retValue = {
data: Array.isArray(response.data?.data) ? response.data.data : [],
totalCount: Number(response.data?.totalCount) || 0,
summary: response.data.summary,
groupCount: response.data.groupCount,
}
return retValue
} catch (error) {
console.error('List form data could not be loaded:', error)
// toast.push(
// <Notification type="danger" duration={2000}>
// Select error
// {error.toString()}
// </Notification>,
// {
// placement: 'bottom-end',
// },
// )
return {
data: [],
totalCount: 0,
summary: [],
groupCount: 0,
}
}
},
totalCount: async (loadOptions) => {
const parameters = getLoadOptions(loadOptions, {
listFormCode,
filter: '',
createDeleteQuery: searchParams?.get('createDeleteQuery'),
group: '',
})
const combinedFilter = mergeWithDefaultFilter(parameters.filter, searchParams)
applyCombinedFilter(parameters, combinedFilter)
try {
const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
return response.data.totalCount
} catch (error) {
console.error('List form total count could not be loaded:', error)
// toast.push(
// <Notification type="danger" duration={2000}>
// TotalCount error
// {error.toString()}
// </Notification>,
// {
// placement: 'bottom-end',
// },
// )
return 0
}
},
byKey: async (key) => {
const parameters = getLoadOptions(
{ key },
{ listFormCode, filter: '', createDeleteQuery: '' },
)
parameters.filter = JSON.stringify(parameters.filter)
try {
const response = await dynamicFetch('list-form-select/select', 'GET', parameters)
return response.data.data[0]
} catch {
// toast.push(
// <Notification type="danger" duration={2000}>
// ByKey error
// {error.toString()}
// </Notification>,
// {
// placement: 'bottom-end',
// },
// )
return null
}
},
remove: function (key) {
if (!gridOptions.deleteServiceAddress) {
return
}
const deleteUrl = getServiceAddress(gridOptions.deleteServiceAddress)
return dynamicFetch(deleteUrl, 'POST', searchParams, {
keys: [key],
listFormCode,
})
},
update: function (key, values) {
if (!gridOptions.updateServiceAddress) {
return
}
const updateUrl = getServiceAddress(gridOptions.updateServiceAddress)
return dynamicFetch(updateUrl, 'POST', searchParams, {
keys: [key],
data: values,
listFormCode,
})
},
insert: function (values) {
if (!gridOptions.insertServiceAddress) {
return
}
const insertUrl = getServiceAddress(gridOptions.insertServiceAddress)
return dynamicFetch(insertUrl, 'POST', searchParams, { data: values, listFormCode }).then(
(response: any) =>
toInsertedRowData(values, response?.data, gridOptions.keyFieldName),
)
},
errorHandler: (error: any) => {
console.log(error.message)
},
})
return store
},
[],
)
return {
createSelectDataSource,
}
}
export { useListFormCustomDataSource }