1472 lines
51 KiB
TypeScript
1472 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
|