1473 lines
51 KiB
TypeScript
1473 lines
51 KiB
TypeScript
|
|
import Container from '@/components/shared/Container'
|
|||
|
|
import { Notification, toast } from '@/components/ui'
|
|||
|
|
import { DX_CLASSNAMES } from '@/constants/app.constant'
|
|||
|
|
import {
|
|||
|
|
DbTypeEnum,
|
|||
|
|
GridDto,
|
|||
|
|
ListFormCustomizationTypeEnum,
|
|||
|
|
PlatformEditorTypes,
|
|||
|
|
UiLookupDataSourceTypeEnum,
|
|||
|
|
} from '@/proxy/form/models'
|
|||
|
|
import {
|
|||
|
|
getListFormCustomization,
|
|||
|
|
postListFormCustomization,
|
|||
|
|
} from '@/services/list-form-customization.service'
|
|||
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|||
|
|
import { usePermission } from '@/utils/hooks/usePermission'
|
|||
|
|
import { usePWA } from '@/utils/hooks/usePWA'
|
|||
|
|
import CardViewDx, {
|
|||
|
|
CardViewRef,
|
|||
|
|
Column,
|
|||
|
|
ColumnChooser,
|
|||
|
|
Editing,
|
|||
|
|
HeaderFilter,
|
|||
|
|
SearchPanel,
|
|||
|
|
Sorting,
|
|||
|
|
Paging,
|
|||
|
|
Pager,
|
|||
|
|
Toolbar,
|
|||
|
|
ToolbarItem,
|
|||
|
|
Selection,
|
|||
|
|
FilterPanel,
|
|||
|
|
} from 'devextreme-react/card-view'
|
|||
|
|
import type {
|
|||
|
|
CardInsertingEvent,
|
|||
|
|
CardUpdatingEvent,
|
|||
|
|
EditingStartEvent,
|
|||
|
|
InitNewCardEvent,
|
|||
|
|
SelectionChangedEvent,
|
|||
|
|
} from 'devextreme/ui/card_view'
|
|||
|
|
import type { DataErrorOccurredInfo } from 'devextreme/common/grids'
|
|||
|
|
import type { EventInfo } from 'devextreme/events'
|
|||
|
|
import { EditingFormItemDto } from '@/proxy/form/models'
|
|||
|
|
import { captionize } from 'devextreme/core/utils/inflector'
|
|||
|
|
import CustomStore from 'devextreme/data/custom_store'
|
|||
|
|
import DataSource from 'devextreme/data/data_source'
|
|||
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||
|
|
import { Helmet } from 'react-helmet'
|
|||
|
|
import { RowMode } from '../form/types'
|
|||
|
|
import { GridColumnData } from './GridColumnData'
|
|||
|
|
import {
|
|||
|
|
addCss,
|
|||
|
|
addJs,
|
|||
|
|
autoNumber,
|
|||
|
|
extractSearchParamsFields,
|
|||
|
|
GridExtraFilterState,
|
|||
|
|
setFormEditingExtraItemValues,
|
|||
|
|
setGridPanelColor,
|
|||
|
|
} from './Utils'
|
|||
|
|
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
|
|||
|
|
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
|
|||
|
|
import { getList } from '@/services/form.service'
|
|||
|
|
import { layoutTypes } from '../admin/listForm/edit/types'
|
|||
|
|
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
|
|||
|
|
import { useListFormColumns } from './useListFormColumns'
|
|||
|
|
import { Loading } from '@/components/shared'
|
|||
|
|
import { useStoreState } from '@/store'
|
|||
|
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
|||
|
|
import { dynamicFetch } from '@/services/form.service'
|
|||
|
|
|
|||
|
|
interface CardViewProps {
|
|||
|
|
listFormCode: string
|
|||
|
|
searchParams?: URLSearchParams
|
|||
|
|
isSubForm?: boolean
|
|||
|
|
level?: number
|
|||
|
|
refreshData?: () => Promise<void>
|
|||
|
|
gridDto?: GridDto
|
|||
|
|
refreshGridDto?: () => Promise<void>
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)'
|
|||
|
|
|
|||
|
|
// Lookup cache (module scope)
|
|||
|
|
const __lookupCache = new Map<string, Promise<any[]>>()
|
|||
|
|
|
|||
|
|
const cachedLoader = (key: string, loader: () => Promise<any[]>) => {
|
|||
|
|
if (__lookupCache.has(key)) return __lookupCache.get(key)!
|
|||
|
|
const p = Promise.resolve()
|
|||
|
|
.then(() => loader())
|
|||
|
|
.then((res) => res ?? [])
|
|||
|
|
.catch((err) => {
|
|||
|
|
__lookupCache.delete(key)
|
|||
|
|
throw err
|
|||
|
|
})
|
|||
|
|
__lookupCache.set(key, p)
|
|||
|
|
return p
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const CardView = (props: CardViewProps) => {
|
|||
|
|
const { listFormCode, searchParams, isSubForm, gridDto: extGridDto, refreshGridDto } = props
|
|||
|
|
const { translate } = useLocalization()
|
|||
|
|
const { checkPermission } = usePermission()
|
|||
|
|
const isPwaMode = usePWA()
|
|||
|
|
const config = useStoreState((state) => state.abpConfig.config)
|
|||
|
|
|
|||
|
|
const cardViewRef = useRef<CardViewRef<any, any>>(null)
|
|||
|
|
const refListFormCode = useRef('')
|
|||
|
|
const widgetGroupRef = useRef<HTMLDivElement>(null)
|
|||
|
|
|
|||
|
|
const [columnData, setColumnData] = useState<GridColumnData[]>()
|
|||
|
|
const [formData, setFormData] = useState<any>()
|
|||
|
|
const [mode, setMode] = useState<RowMode>('view')
|
|||
|
|
const [extraFilters, setExtraFilters] = useState<GridExtraFilterState[]>([])
|
|||
|
|
const [gridDto, setGridDto] = useState<GridDto>()
|
|||
|
|
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
|
|||
|
|
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
|
|||
|
|
const [cardsPerRow, setCardsPerRow] = useState<number>(0)
|
|||
|
|
const [pageSize, setPageSize] = useState<number>(20)
|
|||
|
|
const [lookupItemsCache, setLookupItemsCache] = useState<Map<string, any[]>>(new Map())
|
|||
|
|
|
|||
|
|
const defaultSearchParams = useRef<string | null>(null)
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const initializeCardView = async () => {
|
|||
|
|
const response = await getList({ listFormCode })
|
|||
|
|
setGridDto(response.data)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (extGridDto === undefined) {
|
|||
|
|
initializeCardView()
|
|||
|
|
} else {
|
|||
|
|
setGridDto(extGridDto)
|
|||
|
|
}
|
|||
|
|
}, [listFormCode, extGridDto])
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (!defaultSearchParams.current) {
|
|||
|
|
defaultSearchParams.current = searchParams?.get('filter') ?? null
|
|||
|
|
}
|
|||
|
|
}, [searchParams])
|
|||
|
|
|
|||
|
|
// Clear lookup cache when listFormCode changes
|
|||
|
|
useEffect(() => {
|
|||
|
|
__lookupCache.clear()
|
|||
|
|
}, [listFormCode])
|
|||
|
|
|
|||
|
|
const layout = layoutTypes.cardView
|
|||
|
|
|
|||
|
|
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: cardViewRef as any })
|
|||
|
|
const { getBandedColumns } = useListFormColumns({
|
|||
|
|
gridDto,
|
|||
|
|
listFormCode,
|
|||
|
|
isSubForm,
|
|||
|
|
gridRef: cardViewRef as any,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// Lookup data source helpers
|
|||
|
|
const createLookupStaticDataSource = useCallback(
|
|||
|
|
(load: () => any, filter: any = null, key: any = 'static', sort: any = 'name') =>
|
|||
|
|
new DataSource({
|
|||
|
|
store: new CustomStore({
|
|||
|
|
key,
|
|||
|
|
loadMode: 'raw',
|
|||
|
|
load: async () => {
|
|||
|
|
return cachedLoader(`static:${key}`, () => Promise.resolve(load()))
|
|||
|
|
},
|
|||
|
|
}),
|
|||
|
|
paginate: false,
|
|||
|
|
sort,
|
|||
|
|
filter,
|
|||
|
|
}),
|
|||
|
|
[],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const createLookupQueryDataSource = useCallback(
|
|||
|
|
(listFormCode?: string, listFormFieldName?: string, filters?: any[]) => {
|
|||
|
|
return new DataSource({
|
|||
|
|
store: new CustomStore({
|
|||
|
|
loadMode: 'raw',
|
|||
|
|
load: async () => {
|
|||
|
|
try {
|
|||
|
|
const cacheKey = `query:${listFormCode}:${listFormFieldName}:${JSON.stringify(filters ?? null)}`
|
|||
|
|
return cachedLoader(cacheKey, async () => {
|
|||
|
|
const response = await dynamicFetch('list-form-select/lookup', 'POST', null, {
|
|||
|
|
listFormCode,
|
|||
|
|
listFormFieldName,
|
|||
|
|
filters,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return (response.data ?? []).map((a: any) => ({
|
|||
|
|
key: a.Key,
|
|||
|
|
name: a.Name,
|
|||
|
|
group: a.Group,
|
|||
|
|
...a,
|
|||
|
|
}))
|
|||
|
|
})
|
|||
|
|
} catch (error: any) {
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
}),
|
|||
|
|
paginate: false,
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
[],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const createLookupApiDataSource = useCallback(
|
|||
|
|
(listFormCode?: string, lookupQuery?: string, filters?: any[], keyName?: string) => {
|
|||
|
|
return new DataSource({
|
|||
|
|
store: new CustomStore({
|
|||
|
|
key: keyName,
|
|||
|
|
loadMode: 'raw',
|
|||
|
|
load: async () => {
|
|||
|
|
if (!lookupQuery) return []
|
|||
|
|
|
|||
|
|
const [method, url, body, keySelector, nameSelector, groupSelector] =
|
|||
|
|
lookupQuery.split(';')
|
|||
|
|
|
|||
|
|
let resolvedBody = body
|
|||
|
|
if (filters?.length) {
|
|||
|
|
for (let i = 0; i < filters.length; i++) {
|
|||
|
|
resolvedBody = resolvedBody.replace(
|
|||
|
|
new RegExp(`@param${i}`, 'g'),
|
|||
|
|
String(filters[i]),
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const cacheKey = `api:${lookupQuery}:${JSON.stringify(filters ?? null)}`
|
|||
|
|
return cachedLoader(cacheKey, async () => {
|
|||
|
|
const response = await dynamicFetch(url, method, null, resolvedBody)
|
|||
|
|
let { data } = response
|
|||
|
|
if (!data) return []
|
|||
|
|
if (!Array.isArray(data)) data = [data]
|
|||
|
|
return data.map((a: any) => ({
|
|||
|
|
key: eval(keySelector),
|
|||
|
|
name: eval(nameSelector),
|
|||
|
|
group: eval(groupSelector),
|
|||
|
|
...a,
|
|||
|
|
}))
|
|||
|
|
})
|
|||
|
|
} catch {
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
}),
|
|||
|
|
paginate: false,
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
[],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const lookupDataSource = useCallback(
|
|||
|
|
(options: any, colData: any) => {
|
|||
|
|
const { lookupDto } = colData
|
|||
|
|
const filters: any[] = []
|
|||
|
|
|
|||
|
|
if (lookupDto.cascadeParentFields) {
|
|||
|
|
if (lookupDto.dataSourceType === UiLookupDataSourceTypeEnum.StaticData) {
|
|||
|
|
filters.push([
|
|||
|
|
lookupDto?.cascadeRelationField,
|
|||
|
|
lookupDto?.cascadeFilterOperator,
|
|||
|
|
options?.data?.[lookupDto?.cascadeParentField],
|
|||
|
|
])
|
|||
|
|
} else {
|
|||
|
|
const data = options?.data ?? options
|
|||
|
|
for (const cascadeParentField of lookupDto.cascadeParentFields.split(',')) {
|
|||
|
|
filters.push(data?.[cascadeParentField])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (lookupDto.dataSourceType === UiLookupDataSourceTypeEnum.StaticData) {
|
|||
|
|
return createLookupStaticDataSource(
|
|||
|
|
() => JSON.parse(lookupDto?.lookupQuery),
|
|||
|
|
filters.length ? filters : null,
|
|||
|
|
`static:${listFormCode}:${colData.fieldName}`,
|
|||
|
|
)
|
|||
|
|
} else if (lookupDto.dataSourceType === UiLookupDataSourceTypeEnum.Query) {
|
|||
|
|
return createLookupQueryDataSource(listFormCode, colData.fieldName, filters)
|
|||
|
|
} else if (lookupDto.dataSourceType === UiLookupDataSourceTypeEnum.WebService) {
|
|||
|
|
return createLookupApiDataSource(
|
|||
|
|
listFormCode,
|
|||
|
|
lookupDto?.lookupQuery,
|
|||
|
|
filters,
|
|||
|
|
colData.lookupDto?.valueExpr?.toLowerCase(),
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
return { store: [] }
|
|||
|
|
},
|
|||
|
|
[
|
|||
|
|
listFormCode,
|
|||
|
|
createLookupStaticDataSource,
|
|||
|
|
createLookupQueryDataSource,
|
|||
|
|
createLookupApiDataSource,
|
|||
|
|
],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
function refreshData() {
|
|||
|
|
// Cache'i temizle
|
|||
|
|
if (typeof (window as any).__clearCardViewCache === 'function') {
|
|||
|
|
;(window as any).__clearCardViewCache()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const instance = cardViewRef.current?.instance()
|
|||
|
|
if (instance) {
|
|||
|
|
instance.getDataSource()?.reload()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CardView specific events
|
|||
|
|
function onSelectionChanged(data: SelectionChangedEvent<any, any>) {
|
|||
|
|
const grdOpt = gridDto?.gridOptions
|
|||
|
|
const cardView = cardViewRef.current?.instance()
|
|||
|
|
if (!grdOpt || !cardView) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (data.selectedCardsData?.length) {
|
|||
|
|
setFormData(data.selectedCardsData[0])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onInitNewCard(e: InitNewCardEvent<any>) {
|
|||
|
|
if (!gridDto?.columnFormats) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setMode('new')
|
|||
|
|
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
|||
|
|
|
|||
|
|
for (const colFormat of gridDto?.columnFormats) {
|
|||
|
|
if (!colFormat.fieldName) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Grid'den gelen columnFormat'ları kullanarak default değerleri set et
|
|||
|
|
if (colFormat.defaultValue != null) {
|
|||
|
|
const defaultValStr = String(colFormat.defaultValue)
|
|||
|
|
if (defaultValStr === '@AUTONUMBER') {
|
|||
|
|
e.data[colFormat.fieldName] = autoNumber()
|
|||
|
|
} else {
|
|||
|
|
e.data[colFormat.fieldName] = colFormat.defaultValue
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ExtraFilters içerisinde ilgili Field varsa, default değerleri set etme
|
|||
|
|
if (extraFilters.some((f) => f.fieldName === colFormat.fieldName)) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// URL'den veya Component Prop'dan gelen parametreleri set et
|
|||
|
|
const defaultValue = searchParams?.get(colFormat.fieldName)
|
|||
|
|
if (defaultValue) {
|
|||
|
|
e.data[colFormat.fieldName] = defaultValue
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onCardInserting(e: CardInsertingEvent<any>) {
|
|||
|
|
e.data = setFormEditingExtraItemValues(e.data)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onCardUpdating(e: CardUpdatingEvent<any, any>) {
|
|||
|
|
if (gridDto?.gridOptions.editingOptionDto?.sendOnlyChangedFormValuesUpdate) {
|
|||
|
|
e.newData = {
|
|||
|
|
...e.newData,
|
|||
|
|
[gridDto?.gridOptions.keyFieldName!]: e.oldData[gridDto?.gridOptions.keyFieldName!],
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
e.newData = {
|
|||
|
|
...e.oldData,
|
|||
|
|
...e.newData,
|
|||
|
|
[gridDto?.gridOptions.keyFieldName!]: e.oldData[gridDto?.gridOptions.keyFieldName!],
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (gridDto?.gridOptions.keyFieldName) {
|
|||
|
|
e.newData = setFormEditingExtraItemValues(e.newData)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onEditingStart(e: EditingStartEvent<any, any>) {
|
|||
|
|
setMode('edit')
|
|||
|
|
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onDataErrorOccurred(e: EventInfo<any> & DataErrorOccurredInfo) {
|
|||
|
|
toast.push(
|
|||
|
|
<Notification type="danger" duration={5000}>
|
|||
|
|
{(e.error as any)?.message || 'An error occurred'}
|
|||
|
|
</Notification>,
|
|||
|
|
{
|
|||
|
|
placement: 'top-end',
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const customSaveState = useCallback(
|
|||
|
|
(state: any) => {
|
|||
|
|
return postListFormCustomization({
|
|||
|
|
listFormCode: listFormCode,
|
|||
|
|
customizationType: ListFormCustomizationTypeEnum.GridState,
|
|||
|
|
filterName: `cardview-state`,
|
|||
|
|
customizationData: JSON.stringify(state),
|
|||
|
|
}).then(() => {
|
|||
|
|
setGridPanelColor(statedGridPanelColor)
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
[listFormCode],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const customLoadState = useCallback(() => {
|
|||
|
|
return getListFormCustomization(
|
|||
|
|
listFormCode,
|
|||
|
|
ListFormCustomizationTypeEnum.GridState,
|
|||
|
|
`cardview-state`,
|
|||
|
|
).then((response: any) => {
|
|||
|
|
setGridPanelColor(statedGridPanelColor)
|
|||
|
|
if (response.data?.length > 0) {
|
|||
|
|
return JSON.parse(response.data[0].customizationData)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}, [listFormCode])
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (cardViewRef?.current) {
|
|||
|
|
const instance = cardViewRef?.current?.instance()
|
|||
|
|
if (instance) {
|
|||
|
|
instance.option('dataSource', undefined)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (refListFormCode.current !== listFormCode) {
|
|||
|
|
setColumnData(undefined)
|
|||
|
|
}
|
|||
|
|
}, [listFormCode])
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (!gridDto) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Set js and css
|
|||
|
|
const grdOpt = gridDto.gridOptions
|
|||
|
|
if (grdOpt.customJsSources.length) {
|
|||
|
|
for (const js of grdOpt.customJsSources) {
|
|||
|
|
addJs(js)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (grdOpt.customStyleSources.length) {
|
|||
|
|
for (const css of grdOpt.customStyleSources) {
|
|||
|
|
addCss(css)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (gridDto?.gridOptions.extraFilterDto) {
|
|||
|
|
const extras = gridDto.gridOptions.extraFilterDto.map((f) => ({
|
|||
|
|
fieldName: f.fieldName,
|
|||
|
|
caption: f.caption,
|
|||
|
|
operator: f.operator || '=',
|
|||
|
|
value: f.defaultValue || '',
|
|||
|
|
controlType: f.controlType,
|
|||
|
|
}))
|
|||
|
|
// Sadece ilk yüklemede extraFilters'ı set et, her gridDto değişiminde değil
|
|||
|
|
setExtraFilters((prev) => {
|
|||
|
|
if (prev.length === 0) return extras
|
|||
|
|
return prev
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (gridDto?.gridOptions.editingOptionDto?.popup) {
|
|||
|
|
setIsPopupFullScreen(gridDto.gridOptions.editingOptionDto.popup.fullScreen)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cardsPerRow başlangıç değeri - localStorage'dan oku
|
|||
|
|
const storageKey = `cardview-cardsPerRow-${listFormCode}`
|
|||
|
|
const savedCardsPerRow = localStorage.getItem(storageKey)
|
|||
|
|
|
|||
|
|
if (savedCardsPerRow !== null) {
|
|||
|
|
setCardsPerRow(parseInt(savedCardsPerRow, 10))
|
|||
|
|
} else if (gridDto?.gridOptions.layoutDto?.cardLayoutColumn) {
|
|||
|
|
setCardsPerRow(gridDto.gridOptions.layoutDto.cardLayoutColumn)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// pageSize başlangıç değeri
|
|||
|
|
if (gridDto?.gridOptions.pageSize) {
|
|||
|
|
setPageSize(gridDto.gridOptions.pageSize)
|
|||
|
|
}
|
|||
|
|
}, [gridDto, listFormCode])
|
|||
|
|
|
|||
|
|
// Lookup DataSource'ları bir kere oluştur ve cache'le
|
|||
|
|
const lookupDataSourcesRef = useRef<Map<string, any>>(new Map())
|
|||
|
|
|
|||
|
|
// gridDto değiştiğinde lookup cache'i temizle
|
|||
|
|
useEffect(() => {
|
|||
|
|
lookupDataSourcesRef.current.clear()
|
|||
|
|
}, [gridDto, listFormCode])
|
|||
|
|
|
|||
|
|
// Build columns with lookup support - sadece gridDto değiştiğinde
|
|||
|
|
const columnsWithLookup = useMemo(() => {
|
|||
|
|
if (!gridDto || !config) return []
|
|||
|
|
|
|||
|
|
const cols = getBandedColumns()
|
|||
|
|
if (!cols) return []
|
|||
|
|
|
|||
|
|
return cols.map((col) => {
|
|||
|
|
const colData = gridDto.columnFormats.find((c) => c.fieldName === col.dataField)
|
|||
|
|
if (!colData) return col
|
|||
|
|
|
|||
|
|
// Type assertion for extended column properties
|
|||
|
|
const extCol = col as any
|
|||
|
|
|
|||
|
|
// Form items için editorType ve editorOptions ayarla
|
|||
|
|
const gridFormItem = gridDto.gridOptions.editingFormDto
|
|||
|
|
?.flatMap((f) => f.items)
|
|||
|
|
.find((i) => i?.dataField === col.dataField)
|
|||
|
|
|
|||
|
|
// Lookup desteği ekle - dataSourceType > 0 olmalı (0 = None)
|
|||
|
|
if (colData.lookupDto?.dataSourceType && colData.lookupDto.dataSourceType > 0) {
|
|||
|
|
// Cache'den al veya oluştur
|
|||
|
|
const cacheKey = `${colData.fieldName}`
|
|||
|
|
let lookupDs = lookupDataSourcesRef.current.get(cacheKey)
|
|||
|
|
|
|||
|
|
if (!lookupDs) {
|
|||
|
|
lookupDs = lookupDataSource(null, colData)
|
|||
|
|
lookupDataSourcesRef.current.set(cacheKey, lookupDs)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NOT: Lookup için CardView'da editorType ve editorOptions KULLANMA
|
|||
|
|
// DevExpress bunları görünce otomatik lookup resolution yapıp çift görünmeye sebep oluyor
|
|||
|
|
// Sadece fieldValueRender ile lookup gösterimi yapılacak
|
|||
|
|
// Edit popup'ta form item için lookup ayarları yapılacak
|
|||
|
|
|
|||
|
|
// Lookup olduğunu işaretle (renderColumns'da kullanılacak)
|
|||
|
|
;(col as any).isLookup = true
|
|||
|
|
;(col as any).lookupInfo = {
|
|||
|
|
valueExpr: colData.lookupDto?.valueExpr?.toLowerCase() || 'key',
|
|||
|
|
displayExpr: colData.lookupDto?.displayExpr?.toLowerCase() || 'name',
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Form item ayarları
|
|||
|
|
if (gridFormItem) {
|
|||
|
|
const editorOptions: any = {}
|
|||
|
|
|
|||
|
|
// Parse editorOptions from JSON
|
|||
|
|
if (gridFormItem.editorOptions) {
|
|||
|
|
try {
|
|||
|
|
Object.assign(editorOptions, JSON.parse(gridFormItem.editorOptions))
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// EditorType belirleme (SchedulerView pattern'i)
|
|||
|
|
let editorType: any = gridFormItem.editorType2 || gridFormItem.editorType
|
|||
|
|
if (gridFormItem.editorType2 === PlatformEditorTypes.dxGridBox) {
|
|||
|
|
editorType = 'dxDropDownBox'
|
|||
|
|
} else if (gridFormItem.editorType2 === PlatformEditorTypes.dxTagBox) {
|
|||
|
|
editorType = 'dxTagBox'
|
|||
|
|
} else if (gridFormItem.editorType2) {
|
|||
|
|
editorType = gridFormItem.editorType2
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Lookup için dataSource ve valueExpr/displayExpr ekle - dataSourceType > 0 olmalı
|
|||
|
|
if (colData.lookupDto?.dataSourceType && colData.lookupDto.dataSourceType > 0) {
|
|||
|
|
// Cache'den al
|
|||
|
|
const cacheKey = `${colData.fieldName}`
|
|||
|
|
const lookupDs = lookupDataSourcesRef.current.get(cacheKey)
|
|||
|
|
|
|||
|
|
if (lookupDs) {
|
|||
|
|
editorOptions.dataSource = lookupDs
|
|||
|
|
editorOptions.valueExpr = colData.lookupDto?.valueExpr?.toLowerCase() || 'key'
|
|||
|
|
editorOptions.displayExpr = colData.lookupDto?.displayExpr?.toLowerCase() || 'name'
|
|||
|
|
editorOptions.searchEnabled = true
|
|||
|
|
editorOptions.showClearButton = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Lookup varsa SelectBox kullan (eğer başka bir tip belirtilmediyse)
|
|||
|
|
if (!editorType || editorType === 'dxTextBox') {
|
|||
|
|
editorType = 'dxSelectBox'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Date/DateTime alanları için özel ayarlar
|
|||
|
|
if (colData.sourceDbType === DbTypeEnum.Date) {
|
|||
|
|
editorType = 'dxDateBox'
|
|||
|
|
editorOptions.type = 'date'
|
|||
|
|
editorOptions.dateSerializationFormat = 'yyyy-MM-dd'
|
|||
|
|
editorOptions.displayFormat = 'shortDate'
|
|||
|
|
} else if (
|
|||
|
|
colData.sourceDbType === DbTypeEnum.DateTime ||
|
|||
|
|
colData.sourceDbType === DbTypeEnum.DateTime2 ||
|
|||
|
|
colData.sourceDbType === DbTypeEnum.DateTimeOffset
|
|||
|
|
) {
|
|||
|
|
editorType = 'dxDateBox'
|
|||
|
|
editorOptions.type = 'datetime'
|
|||
|
|
editorOptions.dateSerializationFormat = 'yyyy-MM-ddTHH:mm:ss'
|
|||
|
|
editorOptions.displayFormat = 'shortDateShortTime'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
col.formItem = {
|
|||
|
|
...col.formItem,
|
|||
|
|
colSpan: gridFormItem.colSpan,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (editorType) {
|
|||
|
|
extCol.editorType = editorType
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
col.editorOptions = {
|
|||
|
|
...col.editorOptions,
|
|||
|
|
...editorOptions,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return col
|
|||
|
|
})
|
|||
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|||
|
|
}, [gridDto, config]) // getBandedColumns ve lookupDataSource çıkarıldı - sonsuz döngüyü önlemek için
|
|||
|
|
|
|||
|
|
// DataSource oluştur - sadece gridDto ve listFormCode değiştiğinde (useMemo ile cache'le)
|
|||
|
|
const cardViewDataSource = useMemo(() => {
|
|||
|
|
if (!gridDto) return null
|
|||
|
|
|
|||
|
|
const cols = getBandedColumns()
|
|||
|
|
if (!cols || cols.length === 0) return null
|
|||
|
|
|
|||
|
|
const baseStore = createSelectDataSource(
|
|||
|
|
gridDto.gridOptions,
|
|||
|
|
listFormCode,
|
|||
|
|
searchParams,
|
|||
|
|
layoutTypes.cardView,
|
|||
|
|
cols,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// CardView için sadece 1 select çağrısı yapacak wrapper
|
|||
|
|
let cachedData: any[] | null = null
|
|||
|
|
let cachedTotalCount: number = 0
|
|||
|
|
let isLoading = false
|
|||
|
|
const keyExpr = gridDto.gridOptions.keyFieldName
|
|||
|
|
|
|||
|
|
const clearCache = () => {
|
|||
|
|
cachedData = null
|
|||
|
|
cachedTotalCount = 0
|
|||
|
|
isLoading = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Cache temizleme fonksiyonunu dışarıya expose et
|
|||
|
|
;(window as any).__clearCardViewCache = clearCache
|
|||
|
|
|
|||
|
|
const optimizedStore: any = new CustomStore({
|
|||
|
|
key: keyExpr,
|
|||
|
|
load: async (loadOptions: any) => {
|
|||
|
|
// Zaten yüklenmişse cache'den dön
|
|||
|
|
if (cachedData !== null && !isLoading) {
|
|||
|
|
return {
|
|||
|
|
data: cachedData,
|
|||
|
|
totalCount: cachedTotalCount,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Yükleme devam ediyorsa bekle
|
|||
|
|
if (isLoading) {
|
|||
|
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|||
|
|
if (cachedData !== null) {
|
|||
|
|
return {
|
|||
|
|
data: cachedData,
|
|||
|
|
totalCount: cachedTotalCount,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
isLoading = true
|
|||
|
|
try {
|
|||
|
|
const result = await baseStore.load(loadOptions)
|
|||
|
|
cachedData = result?.data || []
|
|||
|
|
cachedTotalCount = result?.totalCount || 0
|
|||
|
|
return result
|
|||
|
|
} finally {
|
|||
|
|
isLoading = false
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
byKey: async (key: any) => {
|
|||
|
|
// Cache'de ara
|
|||
|
|
if (cachedData && keyExpr) {
|
|||
|
|
const item = cachedData.find((row: any) => row?.[keyExpr] === key)
|
|||
|
|
if (item) return item
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Bulamazsa server'a git
|
|||
|
|
return baseStore.byKey(key)
|
|||
|
|
},
|
|||
|
|
insert: async (values: any) => {
|
|||
|
|
const result = await baseStore.insert(values)
|
|||
|
|
clearCache()
|
|||
|
|
return result
|
|||
|
|
},
|
|||
|
|
update: async (key: any, values: any) => {
|
|||
|
|
const result = await baseStore.update(key, values)
|
|||
|
|
clearCache()
|
|||
|
|
return result
|
|||
|
|
},
|
|||
|
|
remove: async (key: any) => {
|
|||
|
|
const result = await baseStore.remove(key)
|
|||
|
|
clearCache()
|
|||
|
|
return result
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// DataSource içine sar
|
|||
|
|
return new DataSource({
|
|||
|
|
store: optimizedStore,
|
|||
|
|
reshapeOnPush: true,
|
|||
|
|
paginate: true,
|
|||
|
|
pageSize: pageSize,
|
|||
|
|
})
|
|||
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|||
|
|
}, [gridDto, listFormCode])
|
|||
|
|
|
|||
|
|
// extraFilters değişikliğini izlemek için ref
|
|||
|
|
const extraFiltersInitialized = useRef(false)
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
// İlk yüklemede reload yapma, sadece kullanıcı filtre değiştirdiğinde
|
|||
|
|
if (!extraFiltersInitialized.current) {
|
|||
|
|
extraFiltersInitialized.current = true
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const instance = cardViewRef.current?.instance()
|
|||
|
|
if (!instance) return
|
|||
|
|
|
|||
|
|
const activeFilters = extraFilters.filter((f) => f.value)
|
|||
|
|
|
|||
|
|
let base: any = null
|
|||
|
|
if (defaultSearchParams.current) {
|
|||
|
|
base = JSON.parse(defaultSearchParams.current)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const baseTriplets = extractSearchParamsFields(base)
|
|||
|
|
const extraTriplets = activeFilters.map(
|
|||
|
|
(f) => [f.fieldName, f.operator, f.value] as [string, string, any],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const merged = [...baseTriplets, ...extraTriplets].reduce(
|
|||
|
|
(acc, cur) => {
|
|||
|
|
const idx = acc.findIndex((a) => a[0] === cur[0] && a[1] === cur[1])
|
|||
|
|
if (idx >= 0) {
|
|||
|
|
acc[idx] = cur
|
|||
|
|
} else {
|
|||
|
|
acc.push(cur)
|
|||
|
|
}
|
|||
|
|
return acc
|
|||
|
|
},
|
|||
|
|
[] as [string, string, any][],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
let filter: any = null
|
|||
|
|
if (merged.length === 1) {
|
|||
|
|
filter = merged[0]
|
|||
|
|
} else if (merged.length > 1) {
|
|||
|
|
filter = merged.reduce((acc, f, idx) => {
|
|||
|
|
if (idx === 0) return f
|
|||
|
|
return [acc, 'and', f]
|
|||
|
|
}, null as any)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filter) {
|
|||
|
|
instance?.option('filterValue', filter)
|
|||
|
|
} else {
|
|||
|
|
instance?.option('filterValue', undefined)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
instance?.getDataSource()?.reload()
|
|||
|
|
}, [extraFilters])
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
refListFormCode.current = listFormCode
|
|||
|
|
}, [listFormCode])
|
|||
|
|
|
|||
|
|
// WidgetGroup yüksekliğini hesapla
|
|||
|
|
useEffect(() => {
|
|||
|
|
const calculateWidgetHeight = () => {
|
|||
|
|
if (widgetGroupRef.current) {
|
|||
|
|
const height = widgetGroupRef.current.offsetHeight
|
|||
|
|
setWidgetGroupHeight(height)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
calculateWidgetHeight()
|
|||
|
|
|
|||
|
|
const resizeObserver = new ResizeObserver(calculateWidgetHeight)
|
|||
|
|
if (widgetGroupRef.current) {
|
|||
|
|
resizeObserver.observe(widgetGroupRef.current)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return () => {
|
|||
|
|
resizeObserver.disconnect()
|
|||
|
|
}
|
|||
|
|
}, [gridDto?.widgets])
|
|||
|
|
|
|||
|
|
const settingButtonClick = useCallback(() => {
|
|||
|
|
window.open(
|
|||
|
|
ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(':listFormCode', listFormCode),
|
|||
|
|
isPwaMode ? '_self' : '_blank',
|
|||
|
|
)
|
|||
|
|
}, [listFormCode, isPwaMode])
|
|||
|
|
|
|||
|
|
const customizeLookupText = useCallback(
|
|||
|
|
(fieldName: string) => {
|
|||
|
|
return (cellInfo: any) => {
|
|||
|
|
const value = cellInfo.value
|
|||
|
|
if (!value) return ''
|
|||
|
|
|
|||
|
|
const cacheKey = fieldName
|
|||
|
|
const items = lookupItemsCache.get(cacheKey)
|
|||
|
|
|
|||
|
|
if (!items || items.length === 0) {
|
|||
|
|
return value
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const colData = gridDto?.columnFormats.find((c) => c.fieldName === fieldName)
|
|||
|
|
const valueExpr = colData?.lookupDto?.valueExpr?.toLowerCase() || 'key'
|
|||
|
|
const displayExpr = colData?.lookupDto?.displayExpr?.toLowerCase() || 'name'
|
|||
|
|
|
|||
|
|
const item = items.find((i: any) => i[valueExpr] === value)
|
|||
|
|
|
|||
|
|
return item ? item[displayExpr] : value
|
|||
|
|
} catch {
|
|||
|
|
return value
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
[gridDto, lookupItemsCache],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// CardView column render from GridColumnData
|
|||
|
|
const renderColumns = () => {
|
|||
|
|
if (!columnsWithLookup || !Array.isArray(columnsWithLookup) || columnsWithLookup.length === 0) {
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return columnsWithLookup
|
|||
|
|
.filter((col) => col.type !== 'buttons' && col.visible !== false)
|
|||
|
|
.map((col) => {
|
|||
|
|
const extCol = col as any
|
|||
|
|
const colData = gridDto?.columnFormats.find((c) => c.fieldName === col.dataField)
|
|||
|
|
|
|||
|
|
// Column props
|
|||
|
|
const columnProps: any = {
|
|||
|
|
dataField: col.dataField,
|
|||
|
|
caption: col.caption ? translate('::' + col.caption) : captionize(col.dataField || ''),
|
|||
|
|
dataType: col.dataType,
|
|||
|
|
visible: col.visible,
|
|||
|
|
allowSorting: col.allowSorting,
|
|||
|
|
allowFiltering: col.allowFiltering,
|
|||
|
|
allowHeaderFiltering: col.allowHeaderFiltering,
|
|||
|
|
sortOrder: col.sortOrder,
|
|||
|
|
sortIndex: col.sortIndex,
|
|||
|
|
format: col.format,
|
|||
|
|
alignment: col.alignment,
|
|||
|
|
formItem: col.formItem,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Lookup varsa customizeText kullan - fieldValueRender çift görünmeye sebep oluyor
|
|||
|
|
if (extCol.isLookup) {
|
|||
|
|
columnProps.customizeText = customizeLookupText(col.dataField!)
|
|||
|
|
} else {
|
|||
|
|
// Lookup değilse editorType ve editorOptions ekle
|
|||
|
|
if (extCol.editorType) {
|
|||
|
|
columnProps.editorType = extCol.editorType
|
|||
|
|
}
|
|||
|
|
if (col.editorOptions) {
|
|||
|
|
columnProps.editorOptions = col.editorOptions
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// fieldValueRender - farklı veri tipleri için özel renderlar (lookup değilse)
|
|||
|
|
if (colData?.sourceDbType === DbTypeEnum.Date || col.dataType === 'date') {
|
|||
|
|
columnProps.customizeText = (cellInfo: any) => {
|
|||
|
|
if (!cellInfo.value) return '—'
|
|||
|
|
return new Date(cellInfo.value).toLocaleDateString()
|
|||
|
|
}
|
|||
|
|
} else if (
|
|||
|
|
colData?.sourceDbType === DbTypeEnum.DateTime ||
|
|||
|
|
colData?.sourceDbType === DbTypeEnum.DateTime2 ||
|
|||
|
|
colData?.sourceDbType === DbTypeEnum.DateTimeOffset ||
|
|||
|
|
col.dataType === 'datetime'
|
|||
|
|
) {
|
|||
|
|
columnProps.customizeText = (cellInfo: any) => {
|
|||
|
|
if (!cellInfo.value) return '—'
|
|||
|
|
return new Date(cellInfo.value).toLocaleString()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Boolean için customizeText kullan (fieldValueRender çift checkbox oluşturuyor)
|
|||
|
|
if (colData?.sourceDbType === DbTypeEnum.Boolean || col.dataType === 'boolean') {
|
|||
|
|
columnProps.customizeText = (cellInfo: any) => {
|
|||
|
|
return cellInfo.value
|
|||
|
|
? translate('::App.Listforms.ImportManager.Yes')
|
|||
|
|
: translate('::App.Listforms.ImportManager.No')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return <Column key={col.dataField} {...columnProps} />
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Kolon sayısı değiştiğinde
|
|||
|
|
const onCardsPerRowChanged = useCallback(
|
|||
|
|
(value: number) => {
|
|||
|
|
setCardsPerRow(value)
|
|||
|
|
// localStorage'a kaydet
|
|||
|
|
const storageKey = `cardview-cardsPerRow-${listFormCode}`
|
|||
|
|
localStorage.setItem(storageKey, value.toString())
|
|||
|
|
},
|
|||
|
|
[listFormCode],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Page size değiştiğinde
|
|||
|
|
const onPageSizeChanged = useCallback((newPageSize: number) => {
|
|||
|
|
setPageSize(newPageSize)
|
|||
|
|
const instance = cardViewRef.current?.instance()
|
|||
|
|
if (instance) {
|
|||
|
|
instance.pageSize(newPageSize)
|
|||
|
|
}
|
|||
|
|
}, [])
|
|||
|
|
|
|||
|
|
// Toolbar items
|
|||
|
|
const toolbarItems = useMemo(() => {
|
|||
|
|
if (!gridDto) return []
|
|||
|
|
|
|||
|
|
const items: any[] = [
|
|||
|
|
{ name: 'addCardButton' },
|
|||
|
|
{ name: 'searchPanel' },
|
|||
|
|
{
|
|||
|
|
location: 'after',
|
|||
|
|
widget: 'dxButtonGroup',
|
|||
|
|
options: {
|
|||
|
|
items: [
|
|||
|
|
{ text: 'Auto', value: 0, hint: 'Otomatik' },
|
|||
|
|
{ text: '1', value: 1, hint: '1 Kolon' },
|
|||
|
|
{ text: '2', value: 2, hint: '2 Kolon' },
|
|||
|
|
{ text: '3', value: 3, hint: '3 Kolon' },
|
|||
|
|
{ text: '4', value: 4, hint: '4 Kolon' },
|
|||
|
|
{ text: '5', value: 5, hint: '5 Kolon' },
|
|||
|
|
],
|
|||
|
|
keyExpr: 'value',
|
|||
|
|
selectedItemKeys: [cardsPerRow || 0],
|
|||
|
|
selectionMode: 'single',
|
|||
|
|
stylingMode: 'outlined',
|
|||
|
|
onItemClick: (e: any) => {
|
|||
|
|
if (e.itemData) {
|
|||
|
|
onCardsPerRowChanged(e.itemData.value)
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
location: 'after',
|
|||
|
|
widget: 'dxButton',
|
|||
|
|
options: {
|
|||
|
|
icon: 'refresh',
|
|||
|
|
hint: translate('::Refresh'),
|
|||
|
|
stylingMode: 'text',
|
|||
|
|
onClick: () => refreshData(),
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
// Column Chooser butonu için permission kontrolü
|
|||
|
|
if (checkPermission(gridDto?.gridOptions.permissionDto?.u)) {
|
|||
|
|
items.push({
|
|||
|
|
location: 'after',
|
|||
|
|
widget: 'dxButton',
|
|||
|
|
options: {
|
|||
|
|
icon: 'columnchooser',
|
|||
|
|
hint: translate('::ColumnChooser'),
|
|||
|
|
stylingMode: 'text',
|
|||
|
|
onClick: () => {
|
|||
|
|
const instance = cardViewRef.current?.instance()
|
|||
|
|
instance?.showColumnChooser()
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Settings butonu için permission kontrolü
|
|||
|
|
if (checkPermission(gridDto?.gridOptions.permissionDto?.u)) {
|
|||
|
|
items.push({
|
|||
|
|
location: 'after',
|
|||
|
|
widget: 'dxButton',
|
|||
|
|
options: {
|
|||
|
|
icon: 'preferences',
|
|||
|
|
hint: translate('::ListForms.ListForm.Manage'),
|
|||
|
|
stylingMode: 'text',
|
|||
|
|
onClick: settingButtonClick,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return items
|
|||
|
|
}, [
|
|||
|
|
gridDto,
|
|||
|
|
cardsPerRow
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
// Paging ayarları
|
|||
|
|
const pagingConfig = useMemo(
|
|||
|
|
() => ({
|
|||
|
|
enabled: gridDto?.gridOptions.pagerOptionDto?.visible !== false,
|
|||
|
|
pageSize: pageSize,
|
|||
|
|
}),
|
|||
|
|
[gridDto?.gridOptions.pagerOptionDto?.visible, pageSize],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Pager ayarları
|
|||
|
|
const pagerConfig = useMemo(() => {
|
|||
|
|
const allowedSizes = gridDto?.gridOptions.pagerOptionDto?.allowedPageSizes
|
|||
|
|
?.split(',')
|
|||
|
|
.map((s) => Number(s.trim()))
|
|||
|
|
.filter((n) => !isNaN(n) && n > 0) || [10, 20, 50, 100]
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
visible: gridDto?.gridOptions.pagerOptionDto?.visible !== false,
|
|||
|
|
showPageSizeSelector: gridDto?.gridOptions.pagerOptionDto?.showPageSizeSelector !== false,
|
|||
|
|
showInfo: gridDto?.gridOptions.pagerOptionDto?.showInfo !== false,
|
|||
|
|
showNavigationButtons: gridDto?.gridOptions.pagerOptionDto?.showNavigationButtons !== false,
|
|||
|
|
allowedPageSizes: allowedSizes,
|
|||
|
|
displayMode: gridDto?.gridOptions.pagerOptionDto?.displayMode || 'full',
|
|||
|
|
infoText: gridDto?.gridOptions.pagerOptionDto?.infoText,
|
|||
|
|
}
|
|||
|
|
}, [gridDto?.gridOptions.pagerOptionDto])
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<div ref={widgetGroupRef}>
|
|||
|
|
<WidgetGroup widgetGroups={gridDto?.widgets ?? []} />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{gridDto?.gridOptions.extraFilterDto && gridDto?.gridOptions.extraFilterDto.length > 0 && (
|
|||
|
|
<GridExtraFilterToolbar
|
|||
|
|
filters={gridDto.gridOptions.extraFilterDto}
|
|||
|
|
extraFilters={extraFilters}
|
|||
|
|
setExtraFilters={setExtraFilters}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<Container className={DX_CLASSNAMES}>
|
|||
|
|
{!isSubForm && (
|
|||
|
|
<Helmet
|
|||
|
|
titleTemplate="%s | Erp Platform"
|
|||
|
|
title={translate('::' + gridDto?.gridOptions.title)}
|
|||
|
|
defaultTitle="Erp Platform"
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{!gridDto && (
|
|||
|
|
<div className="p-4">
|
|||
|
|
<Loading loading>Loading CardView configuration...</Loading>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{gridDto && !cardViewDataSource && (
|
|||
|
|
<div className="p-4">
|
|||
|
|
<Loading loading>Loading data source...</Loading>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{gridDto && columnsWithLookup && Array.isArray(columnsWithLookup) && columnsWithLookup.length > 0 && cardViewDataSource && (
|
|||
|
|
<div className="p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700">
|
|||
|
|
<CardViewDx
|
|||
|
|
ref={cardViewRef as any}
|
|||
|
|
key={`CardView-${listFormCode}`}
|
|||
|
|
id={'CardView-' + listFormCode}
|
|||
|
|
dataSource={cardViewDataSource}
|
|||
|
|
keyExpr={gridDto.gridOptions.keyFieldName}
|
|||
|
|
cardsPerRow={cardsPerRow || 'auto'}
|
|||
|
|
wordWrapEnabled={true}
|
|||
|
|
hoverStateEnabled={gridDto.gridOptions.columnOptionDto?.hoverStateEnabled}
|
|||
|
|
height={
|
|||
|
|
gridDto.gridOptions.height > 0
|
|||
|
|
? gridDto.gridOptions.height
|
|||
|
|
: gridDto.gridOptions.fullHeight
|
|||
|
|
? `calc(100vh - ${170 + widgetGroupHeight}px)`
|
|||
|
|
: undefined
|
|||
|
|
}
|
|||
|
|
remoteOperations={false}
|
|||
|
|
onSelectionChanged={onSelectionChanged as any}
|
|||
|
|
onInitNewCard={onInitNewCard as any}
|
|||
|
|
onCardInserting={onCardInserting as any}
|
|||
|
|
onCardUpdating={onCardUpdating as any}
|
|||
|
|
onEditingStart={onEditingStart as any}
|
|||
|
|
onDataErrorOccurred={onDataErrorOccurred as any}
|
|||
|
|
onEditCanceled={() => {
|
|||
|
|
setMode('view')
|
|||
|
|
setIsPopupFullScreen(false)
|
|||
|
|
}}
|
|||
|
|
onCardInserted={() => {
|
|||
|
|
setMode('view')
|
|||
|
|
setIsPopupFullScreen(false)
|
|||
|
|
// Küçük bir gecikme ile reload - server transaction commit bekle
|
|||
|
|
setTimeout(() => {
|
|||
|
|
refreshData()
|
|||
|
|
props.refreshData?.()
|
|||
|
|
}, 100)
|
|||
|
|
}}
|
|||
|
|
onCardUpdated={() => {
|
|||
|
|
setMode('view')
|
|||
|
|
setIsPopupFullScreen(false)
|
|||
|
|
// Küçük bir gecikme ile reload - server transaction commit bekle
|
|||
|
|
setTimeout(() => {
|
|||
|
|
refreshData()
|
|||
|
|
props.refreshData?.()
|
|||
|
|
}, 100)
|
|||
|
|
}}
|
|||
|
|
onCardRemoved={() => {
|
|||
|
|
// Küçük bir gecikme ile reload - server transaction commit bekle
|
|||
|
|
setTimeout(() => {
|
|||
|
|
refreshData()
|
|||
|
|
props.refreshData?.()
|
|||
|
|
}, 100)
|
|||
|
|
}}
|
|||
|
|
onOptionChanged={(e: any) => {
|
|||
|
|
if (e.name === 'paging.pageSize' && e.value !== pageSize) {
|
|||
|
|
setPageSize(e.value)
|
|||
|
|
}
|
|||
|
|
}}
|
|||
|
|
onContentReady={() => {
|
|||
|
|
// Lookup DataSource'ları yükle ve state'e cache'le (sadece ilk yüklemede)
|
|||
|
|
if (lookupItemsCache.size === 0 && lookupDataSourcesRef.current.size > 0) {
|
|||
|
|
const lookupPromises: Array<Promise<{ key: string; items: any[] }>> = []
|
|||
|
|
|
|||
|
|
lookupDataSourcesRef.current.forEach((ds, key) => {
|
|||
|
|
if (ds && typeof ds.load === 'function') {
|
|||
|
|
lookupPromises.push(
|
|||
|
|
ds.load().then(() => ({
|
|||
|
|
key,
|
|||
|
|
items: ds.items() || [],
|
|||
|
|
})),
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (lookupPromises.length > 0) {
|
|||
|
|
Promise.all(lookupPromises)
|
|||
|
|
.then((results) => {
|
|||
|
|
const newCache = new Map<string, any[]>()
|
|||
|
|
results.forEach(({ key, items }) => {
|
|||
|
|
newCache.set(key, items)
|
|||
|
|
})
|
|||
|
|
setLookupItemsCache(newCache)
|
|||
|
|
|
|||
|
|
const instance = cardViewRef.current?.instance()
|
|||
|
|
if (instance) {
|
|||
|
|
instance.repaint()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(() => {
|
|||
|
|
// Hata durumunda sessizce devam et
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{/* Toolbar */}
|
|||
|
|
<Toolbar items={toolbarItems} />
|
|||
|
|
|
|||
|
|
{/* Selection */}
|
|||
|
|
<Selection
|
|||
|
|
mode={gridDto.gridOptions.selectionDto?.mode}
|
|||
|
|
allowSelectAll={gridDto.gridOptions.selectionDto?.allowSelectAll}
|
|||
|
|
selectAllMode={gridDto.gridOptions.selectionDto?.selectAllMode}
|
|||
|
|
showCheckBoxesMode={gridDto.gridOptions.selectionDto?.showCheckBoxesMode}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* Sorting */}
|
|||
|
|
<Sorting mode={gridDto.gridOptions.sortMode} />
|
|||
|
|
|
|||
|
|
{/* Header Filter */}
|
|||
|
|
<HeaderFilter
|
|||
|
|
visible={gridDto.gridOptions.headerFilterDto?.visible}
|
|||
|
|
allowSearch={gridDto.gridOptions.headerFilterDto?.allowSearch}
|
|||
|
|
height={gridDto.gridOptions.headerFilterDto?.height}
|
|||
|
|
searchTimeout={gridDto.gridOptions.headerFilterDto?.searchTimeout}
|
|||
|
|
width={gridDto.gridOptions.headerFilterDto?.width}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* Filter Panel */}
|
|||
|
|
<FilterPanel
|
|||
|
|
visible={gridDto.gridOptions.filterPanelDto?.visible}
|
|||
|
|
filterEnabled={gridDto.gridOptions.filterPanelDto?.filterEnabled}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* Search Panel */}
|
|||
|
|
<SearchPanel
|
|||
|
|
visible={gridDto.gridOptions.searchPanelDto?.visible}
|
|||
|
|
width={gridDto.gridOptions.searchPanelDto?.width}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* Column Chooser */}
|
|||
|
|
<ColumnChooser enabled={true} mode="select" />
|
|||
|
|
|
|||
|
|
{/* Paging */}
|
|||
|
|
<Paging enabled={pagingConfig.enabled} defaultPageSize={pagingConfig.pageSize} />
|
|||
|
|
|
|||
|
|
{/* Pager */}
|
|||
|
|
<Pager
|
|||
|
|
visible={pagerConfig.visible}
|
|||
|
|
showPageSizeSelector={pagerConfig.showPageSizeSelector}
|
|||
|
|
showInfo={pagerConfig.showInfo}
|
|||
|
|
showNavigationButtons={pagerConfig.showNavigationButtons}
|
|||
|
|
allowedPageSizes={pagerConfig.allowedPageSizes}
|
|||
|
|
displayMode={pagerConfig.displayMode as any}
|
|||
|
|
infoText={pagerConfig.infoText}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* Editing */}
|
|||
|
|
<Editing
|
|||
|
|
allowUpdating={
|
|||
|
|
gridDto.gridOptions.editingOptionDto?.allowUpdating &&
|
|||
|
|
checkPermission(gridDto.gridOptions.permissionDto?.u)
|
|||
|
|
}
|
|||
|
|
allowAdding={
|
|||
|
|
gridDto.gridOptions.editingOptionDto?.allowAdding &&
|
|||
|
|
checkPermission(gridDto.gridOptions.permissionDto?.c)
|
|||
|
|
}
|
|||
|
|
allowDeleting={
|
|||
|
|
gridDto.gridOptions.editingOptionDto?.allowDeleting &&
|
|||
|
|
checkPermission(gridDto.gridOptions.permissionDto?.d)
|
|||
|
|
}
|
|||
|
|
confirmDelete={gridDto.gridOptions.editingOptionDto?.confirmDelete}
|
|||
|
|
popup={{
|
|||
|
|
animation: {},
|
|||
|
|
title:
|
|||
|
|
(mode === 'new' ? '✚ ' : '🖊️ ') +
|
|||
|
|
translate('::' + gridDto.gridOptions.editingOptionDto?.popup?.title),
|
|||
|
|
showTitle: gridDto.gridOptions.editingOptionDto?.popup?.showTitle,
|
|||
|
|
width: isPopupFullScreen
|
|||
|
|
? '100%'
|
|||
|
|
: gridDto.gridOptions.editingOptionDto?.popup?.width || 600,
|
|||
|
|
height: isPopupFullScreen
|
|||
|
|
? '100%'
|
|||
|
|
: gridDto.gridOptions.editingOptionDto?.popup?.height || 'auto',
|
|||
|
|
fullScreen: isPopupFullScreen,
|
|||
|
|
hideOnOutsideClick:
|
|||
|
|
gridDto.gridOptions.editingOptionDto?.popup?.hideOnOutsideClick,
|
|||
|
|
resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled,
|
|||
|
|
toolbarItems: [
|
|||
|
|
{
|
|||
|
|
widget: 'dxButton',
|
|||
|
|
toolbar: 'bottom',
|
|||
|
|
location: 'after',
|
|||
|
|
options: {
|
|||
|
|
text: translate('::Save'),
|
|||
|
|
type: 'default',
|
|||
|
|
onClick: () => {
|
|||
|
|
const cardView = cardViewRef.current?.instance()
|
|||
|
|
if (cardView) {
|
|||
|
|
// Form validasyonu yap
|
|||
|
|
const editForm = (cardView as any).getController?.('validating')?.validate?.()
|
|||
|
|
|
|||
|
|
// Eğer validate fonksiyonu yoksa direkt kaydet
|
|||
|
|
if (!editForm) {
|
|||
|
|
cardView.saveEditData()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Validasyon hatası varsa kaydetme
|
|||
|
|
if (editForm && !editForm.brokenRules?.length) {
|
|||
|
|
cardView.saveEditData()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
widget: 'dxButton',
|
|||
|
|
toolbar: 'bottom',
|
|||
|
|
location: 'after',
|
|||
|
|
options: {
|
|||
|
|
text: translate('::Cancel'),
|
|||
|
|
onClick: () => {
|
|||
|
|
const cardView = cardViewRef.current?.instance()
|
|||
|
|
cardView?.cancelEditData()
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
widget: 'dxButton',
|
|||
|
|
toolbar: 'top',
|
|||
|
|
location: 'after',
|
|||
|
|
options: {
|
|||
|
|
icon: isPopupFullScreen ? 'collapse' : 'fullscreen',
|
|||
|
|
hint: isPopupFullScreen
|
|||
|
|
? translate('::Normal Boyut')
|
|||
|
|
: translate('::Tam Ekran'),
|
|||
|
|
stylingMode: 'text',
|
|||
|
|
onClick: () => setIsPopupFullScreen(!isPopupFullScreen),
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
}}
|
|||
|
|
form={{
|
|||
|
|
colCount: gridDto.gridOptions.editingFormDto?.[0]?.colCount || 2,
|
|||
|
|
showValidationSummary: true,
|
|||
|
|
items:
|
|||
|
|
gridDto.gridOptions.editingFormDto?.length > 0
|
|||
|
|
? (() => {
|
|||
|
|
const sortedFormDto = gridDto.gridOptions.editingFormDto
|
|||
|
|
.slice()
|
|||
|
|
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
|||
|
|
|
|||
|
|
const tabbedItems = sortedFormDto.filter(
|
|||
|
|
(e: any) => e.itemType === 'tabbed',
|
|||
|
|
)
|
|||
|
|
const result: any[] = []
|
|||
|
|
|
|||
|
|
const mapFormItem = (i: EditingFormItemDto) => {
|
|||
|
|
let editorOptions: any = {}
|
|||
|
|
try {
|
|||
|
|
if (i.editorOptions) {
|
|||
|
|
editorOptions = JSON.parse(i.editorOptions)
|
|||
|
|
}
|
|||
|
|
} 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(),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// EditorType belirleme
|
|||
|
|
let editorType: any = i.editorType2 || i.editorType
|
|||
|
|
if (i.editorType2 === PlatformEditorTypes.dxGridBox) {
|
|||
|
|
editorType = 'dxDropDownBox'
|
|||
|
|
} else if (i.editorType2) {
|
|||
|
|
editorType = i.editorType2
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Lookup DataSource oluştur
|
|||
|
|
if (
|
|||
|
|
listFormField?.lookupDto &&
|
|||
|
|
listFormField.lookupDto.dataSourceType > 0
|
|||
|
|
) {
|
|||
|
|
const cacheKey = `${listFormField.fieldName}`
|
|||
|
|
let lookupDs = lookupDataSourcesRef.current.get(cacheKey)
|
|||
|
|
|
|||
|
|
if (!lookupDs) {
|
|||
|
|
lookupDs = lookupDataSource(null, listFormField)
|
|||
|
|
lookupDataSourcesRef.current.set(cacheKey, lookupDs)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
editorOptions.dataSource = lookupDs
|
|||
|
|
editorOptions.valueExpr =
|
|||
|
|
listFormField.lookupDto.valueExpr?.toLowerCase() || 'key'
|
|||
|
|
editorOptions.displayExpr =
|
|||
|
|
listFormField.lookupDto.displayExpr?.toLowerCase() || 'name'
|
|||
|
|
editorOptions.searchEnabled = true
|
|||
|
|
editorOptions.showClearButton = true
|
|||
|
|
|
|||
|
|
if (!editorType || editorType === 'dxTextBox') {
|
|||
|
|
editorType = 'dxSelectBox'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const item: any = {
|
|||
|
|
dataField: i.dataField,
|
|||
|
|
editorType: editorType,
|
|||
|
|
colSpan: i.colSpan,
|
|||
|
|
isRequired: i.isRequired,
|
|||
|
|
editorOptions,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Required field için validasyon kuralı ekle
|
|||
|
|
if (i.isRequired) {
|
|||
|
|
item.validationRules = [
|
|||
|
|
{
|
|||
|
|
type: 'required',
|
|||
|
|
message: `${i.dataField.split(':')[1] || i.dataField} zorunludur`,
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (i.dataField.indexOf(':') >= 0) {
|
|||
|
|
item.label = { text: captionize(i.dataField.split(':')[1]) }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return item
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sortedFormDto.forEach((e: any) => {
|
|||
|
|
if (e.itemType !== 'tabbed') {
|
|||
|
|
result.push({
|
|||
|
|
itemType: e.itemType,
|
|||
|
|
colCount: e.colCount || 1,
|
|||
|
|
colSpan: e.colSpan || 1,
|
|||
|
|
caption: e.caption,
|
|||
|
|
items: e.items
|
|||
|
|
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
|||
|
|
.map(mapFormItem),
|
|||
|
|
})
|
|||
|
|
} else if (tabbedItems.length > 0 && e === tabbedItems[0]) {
|
|||
|
|
result.push({
|
|||
|
|
itemType: 'tabbed',
|
|||
|
|
colCount: 1,
|
|||
|
|
colSpan: 1,
|
|||
|
|
tabs: tabbedItems.map((tabbedItem: any) => ({
|
|||
|
|
title: tabbedItem.caption,
|
|||
|
|
colCount: tabbedItem.colCount || 1,
|
|||
|
|
items: tabbedItem.items
|
|||
|
|
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
|
|||
|
|
.map(mapFormItem),
|
|||
|
|
})),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
})()
|
|||
|
|
: undefined,
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* Columns */}
|
|||
|
|
{renderColumns()}
|
|||
|
|
</CardViewDx>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</Container>
|
|||
|
|
</>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default CardView
|