887 lines
29 KiB
TypeScript
887 lines
29 KiB
TypeScript
import { DataGridTypes } from 'devextreme-react/data-grid'
|
||
import { DataType, HorizontalEdge, SortOrder, ValidationRule } from 'devextreme/common'
|
||
import CustomStore from 'devextreme/data/custom_store'
|
||
import { SelectedFilterOperation } from 'devextreme/ui/data_grid'
|
||
import { useCallback, useEffect } from 'react'
|
||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||
import { usePermission } from '@/utils/hooks/usePermission'
|
||
import { usePWA } from '@/utils/hooks/usePWA'
|
||
import { useDialogContext } from '../shared/DialogContext'
|
||
import { dynamicFetch } from '@/services/form.service'
|
||
import { GridColumnData } from './GridColumnData'
|
||
import { useLocation } from 'react-router-dom'
|
||
import {
|
||
ColumnFormatDto,
|
||
EditingFormItemDto,
|
||
GridDto,
|
||
PlatformEditorTypes,
|
||
UiCommandButtonPositionTypeEnum,
|
||
UiLookupDataSourceTypeEnum,
|
||
} from '@/proxy/form/models'
|
||
import { addCss } from './Utils'
|
||
|
||
const cellTemplateMultiValue = (
|
||
cellElement: HTMLElement,
|
||
cellInfo: DataGridTypes.ColumnCellTemplateData<any, any>,
|
||
) => {
|
||
if (cellInfo?.value) {
|
||
const values = Array.isArray(cellInfo.value)
|
||
? cellInfo.value.map((a: any) => {
|
||
const { lookup } = cellInfo.column
|
||
if (lookup && lookup.calculateCellValue) {
|
||
return lookup.calculateCellValue(a)
|
||
}
|
||
return ''
|
||
})
|
||
: [cellInfo.value]
|
||
|
||
// Badge benzeri HTML üret
|
||
const html = values
|
||
.filter((v) => v)
|
||
.map(
|
||
(v) => `
|
||
<div
|
||
style="
|
||
display:inline-block;
|
||
background-color:#dc3545;
|
||
color:#fff;
|
||
border-radius:12px;
|
||
padding:2px 8px;
|
||
margin:2px 2px;
|
||
font-size:12px;
|
||
font-weight:300;
|
||
line-height:1.4;
|
||
white-space:nowrap;
|
||
"
|
||
>
|
||
${v}
|
||
</div>
|
||
`,
|
||
)
|
||
.join('')
|
||
|
||
cellElement.innerHTML = html
|
||
cellElement.title = values.join(',')
|
||
}
|
||
}
|
||
|
||
// Hover preview overlay — singleton, tüm grid hücreleri tarafından paylaşılır
|
||
let __imgPreviewEl: HTMLDivElement | null = null
|
||
|
||
function getImgPreview(): HTMLDivElement {
|
||
if (!__imgPreviewEl) {
|
||
const el = document.createElement('div')
|
||
el.id = '__cellImgPreview'
|
||
el.style.cssText = [
|
||
'position:fixed',
|
||
'z-index:99999',
|
||
'display:none',
|
||
'pointer-events:none',
|
||
'background:#fff',
|
||
'border:1px solid #d1d5db',
|
||
'border-radius:8px',
|
||
'box-shadow:0 8px 32px rgba(0,0,0,0.22)',
|
||
'padding:4px',
|
||
'max-width:320px',
|
||
'max-height:320px',
|
||
'overflow:hidden',
|
||
'transition:opacity 0.15s ease',
|
||
'opacity:0',
|
||
].join(';')
|
||
const img = document.createElement('img')
|
||
img.style.cssText =
|
||
'display:block;max-width:312px;max-height:312px;object-fit:contain;border-radius:4px;'
|
||
el.appendChild(img)
|
||
document.body.appendChild(el)
|
||
__imgPreviewEl = el
|
||
}
|
||
return __imgPreviewEl
|
||
}
|
||
|
||
function showImgPreview(src: string, e: MouseEvent) {
|
||
const el = getImgPreview()
|
||
const imgEl = el.querySelector('img') as HTMLImageElement
|
||
if (imgEl.src !== src) imgEl.src = src
|
||
|
||
const GAP = 12
|
||
const vw = window.innerWidth
|
||
const vh = window.innerHeight
|
||
|
||
el.style.opacity = '0'
|
||
el.style.display = 'block'
|
||
|
||
const pw = el.offsetWidth || 320
|
||
const ph = el.offsetHeight || 320
|
||
let left = e.clientX + GAP
|
||
let top = e.clientY + GAP
|
||
|
||
if (left + pw > vw - 8) left = e.clientX - pw - GAP
|
||
if (top + ph > vh - 8) top = e.clientY - ph - GAP
|
||
if (left < 8) left = 8
|
||
if (top < 8) top = 8
|
||
|
||
el.style.left = `${left}px`
|
||
el.style.top = `${top}px`
|
||
el.style.opacity = '1'
|
||
}
|
||
|
||
function hideImgPreview() {
|
||
if (__imgPreviewEl) {
|
||
__imgPreviewEl.style.opacity = '0'
|
||
__imgPreviewEl.style.display = 'none'
|
||
}
|
||
}
|
||
|
||
const cellTemplateImage = (
|
||
cellElement: HTMLElement,
|
||
cellInfo: DataGridTypes.ColumnCellTemplateData<any, any>,
|
||
) => {
|
||
if (cellInfo?.value) {
|
||
const urls: string[] = Array.isArray(cellInfo.value)
|
||
? cellInfo.value.filter(Boolean)
|
||
: [cellInfo.value].filter(Boolean)
|
||
|
||
//const col = cellInfo.column as any
|
||
//const imgOptions = col?.extras?.imageUploadOptions ?? {}
|
||
//const w: number = imgOptions.width ?? 40
|
||
//const h: number = imgOptions.height ?? 40
|
||
const w: number = 40
|
||
const h: number = 40
|
||
|
||
cellElement.style.cssText += 'display:flex;flex-wrap:wrap;align-items:center;gap:4px;'
|
||
cellElement.innerHTML = ''
|
||
//cellElement.title = urls.join(', ')
|
||
|
||
urls.forEach((url) => {
|
||
const img = document.createElement('img')
|
||
img.src = url
|
||
img.alt = ''
|
||
img.style.cssText = `width:${w}px;height:${h}px;object-fit:cover;border-radius:4px;border:1px solid #ddd;margin:2px;vertical-align:middle;display:inline-block;cursor:zoom-in;`
|
||
|
||
img.addEventListener('mouseenter', (e) => showImgPreview(url, e as MouseEvent))
|
||
img.addEventListener('mousemove', (e) => showImgPreview(url, e as MouseEvent))
|
||
img.addEventListener('mouseleave', hideImgPreview)
|
||
|
||
cellElement.appendChild(img)
|
||
})
|
||
}
|
||
}
|
||
|
||
function calculateFilterExpressionMultiValue(
|
||
this: DataGridTypes.Column,
|
||
filterValue: any,
|
||
selectedFilterOperation: string | null,
|
||
): string | Array<any> {
|
||
if (filterValue) {
|
||
if (selectedFilterOperation == '=' || selectedFilterOperation === null) {
|
||
return [this.dataField, 'contains', filterValue]
|
||
} else if (selectedFilterOperation == '<>') {
|
||
return [this.dataField, 'notcontains', filterValue]
|
||
} else {
|
||
return [this.dataField, selectedFilterOperation, filterValue]
|
||
}
|
||
} else {
|
||
// filterValue null ise isblank veya isnotblank secilmistir
|
||
if (selectedFilterOperation === '=') {
|
||
return [
|
||
this.dataField,
|
||
selectedFilterOperation,
|
||
filterValue,
|
||
'or',
|
||
this.dataField,
|
||
selectedFilterOperation,
|
||
' ',
|
||
]
|
||
} else if (selectedFilterOperation === '<>') {
|
||
return [
|
||
this.dataField,
|
||
selectedFilterOperation,
|
||
filterValue,
|
||
'and',
|
||
this.dataField,
|
||
selectedFilterOperation,
|
||
' ',
|
||
]
|
||
} else {
|
||
return [this.dataField, selectedFilterOperation, filterValue]
|
||
}
|
||
}
|
||
}
|
||
|
||
// lookup cache (module scope) - cache süresini ve boyutunu yönet
|
||
const __lookupCache = new Map<string, { promise: Promise<any[]>; timestamp: number }>()
|
||
const CACHE_DURATION = 5 * 60 * 1000 // 5 dakika
|
||
const MAX_CACHE_SIZE = 100 // Maksimum cache entry sayısı
|
||
|
||
const cachedLoader = (key: string, loader: () => Promise<any[]>) => {
|
||
const now = Date.now()
|
||
const cached = __lookupCache.get(key)
|
||
|
||
// Cache'de var ve süresi dolmamışsa kullan
|
||
if (cached && now - cached.timestamp < CACHE_DURATION) {
|
||
return cached.promise
|
||
}
|
||
|
||
// Cache boyutu limitini aşarsa en eskiyi temizle
|
||
if (__lookupCache.size >= MAX_CACHE_SIZE) {
|
||
const oldestKey = Array.from(__lookupCache.entries()).sort(
|
||
(a, b) => a[1].timestamp - b[1].timestamp,
|
||
)[0][0]
|
||
__lookupCache.delete(oldestKey)
|
||
}
|
||
|
||
const p = Promise.resolve()
|
||
.then(() => loader())
|
||
.then((res) => res ?? [])
|
||
.catch((err) => {
|
||
__lookupCache.delete(key) // hata olursa tekrar denenebilsin
|
||
throw err
|
||
})
|
||
|
||
__lookupCache.set(key, { promise: p, timestamp: now })
|
||
return p
|
||
}
|
||
|
||
const useListFormColumns = ({
|
||
gridDto,
|
||
listFormCode,
|
||
isSubForm,
|
||
gridRef,
|
||
}: {
|
||
gridDto?: GridDto
|
||
listFormCode: string
|
||
isSubForm?: boolean
|
||
gridRef?: any
|
||
}) => {
|
||
const dialog: any = useDialogContext()
|
||
const { translate } = useLocalization()
|
||
const { checkPermission } = usePermission()
|
||
const isPwaMode = usePWA()
|
||
const location = useLocation()
|
||
|
||
useEffect(() => {
|
||
// listFormCode değişince lookup cache temizlensin (farklı form farklı lookuplar)
|
||
__lookupCache.clear()
|
||
}, [listFormCode])
|
||
|
||
const lookupDataSource = useCallback(
|
||
(options: any, colData: any, listFormCode: string) => {
|
||
const { lookupDto } = colData
|
||
const filters = []
|
||
if (lookupDto.cascadeParentFields) {
|
||
if (lookupDto.dataSourceType == UiLookupDataSourceTypeEnum.StaticData) {
|
||
filters.push([
|
||
lookupDto?.cascadeRelationField,
|
||
lookupDto?.cascadeFilterOperator,
|
||
options?.data[lookupDto?.cascadeParentField],
|
||
])
|
||
//TODO: Statik data test edilecek
|
||
} else {
|
||
const data = options?.data ?? options
|
||
for (const cascadeParentField of lookupDto.cascadeParentFields.split(',')) {
|
||
filters.push(data[cascadeParentField])
|
||
}
|
||
}
|
||
}
|
||
//UiLookupDataSourceTypeEnum :
|
||
// Data = 1 (Statik Data),
|
||
// Query = 2 (API'den geliyor fakat API query çalıştırıyor)
|
||
// WebService = 3 (API servisten geliyor)
|
||
if (lookupDto.dataSourceType == UiLookupDataSourceTypeEnum.StaticData) {
|
||
return createLookupStaticDataSource(
|
||
() => JSON.parse(lookupDto?.lookupQuery),
|
||
filters.length ? filters : null,
|
||
`static:${listFormCode}:${colData.fieldName}`, // cache key
|
||
)
|
||
} 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(),
|
||
)
|
||
} else {
|
||
return {
|
||
store: [],
|
||
}
|
||
}
|
||
},
|
||
[listFormCode],
|
||
)
|
||
|
||
const createLookupStaticDataSource = (
|
||
load: () => any,
|
||
filter: any = null,
|
||
key: any = 'static',
|
||
sort: any = 'name',
|
||
) => ({
|
||
store: new CustomStore({
|
||
key,
|
||
loadMode: 'raw',
|
||
load: async (loadOptions) => {
|
||
// load fonksiyonu sync sonuç döndürüyor olabilir, o yüzden Promise.resolve ile sar
|
||
return cachedLoader(`static:${key}`, () => Promise.resolve(load()))
|
||
},
|
||
}),
|
||
sort,
|
||
filter,
|
||
})
|
||
|
||
const createLookupQueryDataSource = (
|
||
listFormCode?: string,
|
||
listFormFieldName?: string,
|
||
filters?: any[],
|
||
) => {
|
||
return new CustomStore({
|
||
loadMode: 'raw',
|
||
load: async (loadOptions) => {
|
||
if (!isSubForm && listFormCode && !window.location.pathname.includes(listFormCode)) {
|
||
return
|
||
}
|
||
|
||
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 null
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
const createLookupApiDataSource = (
|
||
listFormCode?: string,
|
||
lookupQuery?: string,
|
||
filters?: any[],
|
||
keyName?: string,
|
||
) => {
|
||
return new CustomStore({
|
||
key: keyName,
|
||
loadMode: 'raw',
|
||
load: async (loadOptions) => {
|
||
if (!isSubForm && listFormCode && !window.location.pathname.includes(listFormCode)) {
|
||
return
|
||
}
|
||
if (!lookupQuery) {
|
||
return
|
||
}
|
||
|
||
const [method, url, body, keySelector, nameSelector, groupSelector] = lookupQuery.split(';')
|
||
|
||
// body içindeki @paramN'leri filtrelerle değiştir
|
||
let resolvedBody = body
|
||
if (filters?.length) {
|
||
for (let i = 0; i < filters.length; i++) {
|
||
resolvedBody = resolvedBody.replace(new RegExp(`@param${i}`, 'g'), 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
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
const getCommandColumn = useCallback((): GridColumnData | undefined => {
|
||
if (!gridDto) {
|
||
return
|
||
}
|
||
|
||
const hasCreate =
|
||
gridDto.gridOptions.editingOptionDto.allowAdding &&
|
||
checkPermission(gridDto.gridOptions.permissionDto.i)
|
||
|
||
const hasUpdate =
|
||
gridDto.gridOptions.editingOptionDto.allowUpdating &&
|
||
checkPermission(gridDto.gridOptions.permissionDto.u)
|
||
|
||
const hasDelete =
|
||
gridDto.gridOptions.editingOptionDto.allowDeleting &&
|
||
checkPermission(gridDto.gridOptions.permissionDto.d)
|
||
|
||
const hasDetail =
|
||
gridDto.gridOptions.editingOptionDto.allowDetail &&
|
||
checkPermission(gridDto.gridOptions.permissionDto.u)
|
||
|
||
const hasDuplicate =
|
||
gridDto.gridOptions.editingOptionDto.allowDuplicate &&
|
||
checkPermission(gridDto.gridOptions.permissionDto.i)
|
||
|
||
const hasCommandButtons = gridDto.gridOptions.commandColumnDto.length > 0
|
||
|
||
// Eğer hiçbir buton eklenecek durumda değilse: çık
|
||
if (!hasUpdate && !hasDelete && !hasCreate && !hasCommandButtons) {
|
||
return
|
||
}
|
||
|
||
const buttons: any[] = []
|
||
|
||
if (hasUpdate) {
|
||
buttons.push({
|
||
name: 'edit',
|
||
text: translate('::App.Platform.Edit'),
|
||
})
|
||
}
|
||
|
||
if (hasDelete) {
|
||
buttons.push({
|
||
name: 'delete',
|
||
text: translate('::App.Platform.Delete'),
|
||
})
|
||
}
|
||
|
||
if (hasDetail) {
|
||
const item = {
|
||
name: 'detail',
|
||
text: translate('::App.Platform.Detail'),
|
||
onClick: (e: any) => {
|
||
if (typeof e.event?.preventDefault === 'function') {
|
||
e.event.preventDefault()
|
||
}
|
||
|
||
if (!gridDto.gridOptions.keyFieldName) return
|
||
|
||
const detailFormName = gridDto.gridOptions.listFormCode
|
||
const id = e.row.data[gridDto.gridOptions.keyFieldName]
|
||
|
||
const url = `/admin/form/${detailFormName}/${id}`
|
||
|
||
window.open(url, isPwaMode ? '_self' : '_blank')
|
||
},
|
||
}
|
||
|
||
buttons.push(item)
|
||
}
|
||
|
||
if (hasDuplicate) {
|
||
const item = {
|
||
name: 'duplicate',
|
||
text: translate('::App.Platform.Duplicate'),
|
||
onClick: async (e: any) => {
|
||
if (typeof e.event?.preventDefault === 'function') {
|
||
e.event.preventDefault()
|
||
}
|
||
|
||
// Onay penceresi
|
||
const confirmed = window.confirm(
|
||
translate('::App.Platform.DuplicateConfirm') || 'Kopyalama işlemini onaylıyor musunuz?',
|
||
)
|
||
if (!confirmed) return
|
||
|
||
if (!gridDto.gridOptions.keyFieldName) return
|
||
|
||
const id = e.row.data[gridDto.gridOptions.keyFieldName]
|
||
if (!id) return
|
||
|
||
// Backend'e duplicate isteği gönder
|
||
try {
|
||
await dynamicFetch('list-form-data/duplicate', 'POST', null, {
|
||
listFormCode,
|
||
keys: [id],
|
||
data: [gridDto.gridOptions.keyFieldName],
|
||
})
|
||
|
||
// Başarılı ise grid'i yenile
|
||
if (gridRef?.current?.instance()) {
|
||
gridRef.current.instance().refresh()
|
||
}
|
||
} catch (err) {
|
||
// Hata yönetimi
|
||
alert(translate('::App.Platform.DuplicateError'))
|
||
}
|
||
},
|
||
}
|
||
buttons.push(item)
|
||
}
|
||
|
||
gridDto.gridOptions.commandColumnDto.forEach((action) => {
|
||
if (action.buttonPosition !== UiCommandButtonPositionTypeEnum.CommandColumn) return
|
||
if (!checkPermission(action.authName)) return
|
||
|
||
// visibleExpression varsa dinamik fonksiyon oluştur, yoksa statik değer kullan
|
||
let visibleFunc: ((e: any) => boolean) | boolean = action.isVisible
|
||
if (action.visibleExpression) {
|
||
try {
|
||
// visibleExpression string'ini fonksiyona çevir
|
||
// Örnek: "(e) => !e.row.isEditing" veya "(e) => e.row.data.Status === 'Active'"
|
||
visibleFunc = eval(action.visibleExpression)
|
||
} catch (error) {
|
||
console.error('VisibleExpression evaluation error:', error, action.visibleExpression)
|
||
visibleFunc = action.isVisible // Hata durumunda varsayılan değeri kullan
|
||
}
|
||
}
|
||
|
||
const item = {
|
||
visible: visibleFunc,
|
||
icon: action.icon,
|
||
hint: translate('::' + action.hint),
|
||
text: translate('::' + action.text),
|
||
onClick: (e: any) => {
|
||
if (typeof e.event?.preventDefault === 'function') {
|
||
e.event.preventDefault()
|
||
}
|
||
|
||
if (action.url) {
|
||
let url = action.url?.replace('@LISTFORMCODE', listFormCode)
|
||
gridDto.columnFormats.forEach((field) => {
|
||
if (field.fieldName) {
|
||
url = url.replace(`@${field.fieldName}`, e.row.data[field.fieldName])
|
||
}
|
||
})
|
||
window.open(url, isPwaMode ? '_self' : action.urlTarget)
|
||
} else if (action.dialogName) {
|
||
if (action.dialogParameters) {
|
||
const dynamicMap = JSON.parse(action.dialogParameters)
|
||
for (const [key, value] of Object.entries<string>(dynamicMap)) {
|
||
dynamicMap[key] = value.startsWith('@') ? e.row.data[value.replace('@', '')] : value
|
||
}
|
||
dialog.setConfig({
|
||
component: action.dialogName,
|
||
props: dynamicMap,
|
||
onClose: () => {
|
||
// Dialog kapandığında grid'i yenile
|
||
if (gridRef?.current?.instance()) {
|
||
gridRef?.current?.instance().refresh()
|
||
}
|
||
},
|
||
})
|
||
}
|
||
} else if (action.onClick) {
|
||
eval(action.onClick)
|
||
}
|
||
},
|
||
}
|
||
|
||
buttons.push(item)
|
||
})
|
||
|
||
// Buton sayısına göre dinamik genişlik hesapla
|
||
// Her buton için ~35-40px + padding
|
||
const calculatedWidth = Math.min(buttons.length * 40 + 50, 200)
|
||
|
||
const column = {
|
||
type: 'buttons',
|
||
width: calculatedWidth,
|
||
minWidth: calculatedWidth,
|
||
buttons,
|
||
allowResizing: true,
|
||
}
|
||
|
||
return column as GridColumnData
|
||
}, [gridDto, checkPermission, translate, listFormCode, isPwaMode, dialog, gridRef])
|
||
|
||
const getColumns = useCallback(
|
||
(columnFormats: ColumnFormatDto[]) => {
|
||
const columns: GridColumnData[] = []
|
||
|
||
if (!gridDto || !columnFormats) {
|
||
return columns
|
||
}
|
||
columnFormats.forEach((colData) => {
|
||
if (!colData.canRead || !colData.isActive) {
|
||
return
|
||
}
|
||
const column: GridColumnData = {}
|
||
column.colData = colData // Onemli: Baska event-callback lerde kullanmak icin eklendi, colData.lookupDto?.editorTemplateType
|
||
//column.showEditorAlways = true
|
||
column.dataField = colData.fieldName
|
||
if (colData.dataType) column.dataType = colData.dataType as DataType
|
||
if (colData.captionName) column.caption = translate('::' + colData.captionName)
|
||
if (colData.width > 0) column.width = colData.width
|
||
column.visible = colData.visible
|
||
|
||
column.alignment = colData.alignment
|
||
column.format = colData.format
|
||
|
||
let editorOptions: any = {}
|
||
if (colData.editorOptions) {
|
||
try {
|
||
editorOptions =
|
||
typeof colData.editorOptions === 'string'
|
||
? JSON.parse(colData.editorOptions)
|
||
: colData.editorOptions
|
||
} catch {
|
||
editorOptions = {}
|
||
}
|
||
}
|
||
|
||
column.editorOptions = { ...editorOptions }
|
||
|
||
// Format bilgisini öncelik sırasına göre ata
|
||
if (column.editorOptions.displayFormat) {
|
||
column.format = column.editorOptions.displayFormat
|
||
} else if (column.editorOptions.format) {
|
||
column.format = column.editorOptions.format
|
||
}
|
||
|
||
// columnCustomizationDto
|
||
column.fixed = colData.columnCustomizationDto?.fixed
|
||
column.fixedPosition = colData.columnCustomizationDto?.fixedPosition as HorizontalEdge
|
||
column.allowReordering = colData.columnCustomizationDto?.allowReordering
|
||
|
||
// sort
|
||
if (colData.sortIndex >= 0) {
|
||
column.sortIndex = colData.sortIndex
|
||
column.sortOrder = colData.sortDirection as SortOrder
|
||
}
|
||
|
||
// filterRow
|
||
column.allowFiltering = colData.columnFilterDto?.allowFiltering
|
||
column.selectedFilterOperation = colData.columnFilterDto
|
||
?.selectedFilterOperation as SelectedFilterOperation
|
||
column.filterValue = colData.columnFilterDto?.filterValue
|
||
|
||
// headerFilter
|
||
column.allowHeaderFiltering = colData.columnHeaderDto?.allowHeaderFiltering
|
||
if (column.allowHeaderFiltering == true) {
|
||
column.headerFilter = {}
|
||
column.headerFilter.allowSearch = colData.columnHeaderDto?.allowSearch
|
||
column.headerFilter.dataSource = colData.columnHeaderDto?.dataSource
|
||
}
|
||
|
||
// search
|
||
column.allowSearch = colData.allowSearch
|
||
|
||
//export
|
||
column.allowExporting = colData.canExport
|
||
|
||
// grouping
|
||
column.allowGrouping = colData.columnGroupingDto?.allowGrouping
|
||
column.autoExpandGroup = colData.columnGroupingDto?.autoExpandGroup
|
||
if (colData.columnGroupingDto.groupIndex)
|
||
column.groupIndex = colData.columnGroupingDto?.groupIndex
|
||
|
||
// constsa dinamik olarak css verilerini ekle
|
||
if (colData.columnCssClass) {
|
||
column.cssClass = colData.columnCssClass
|
||
if (colData.columnCssValue) {
|
||
addCss(colData.columnCssValue)
|
||
}
|
||
}
|
||
|
||
column.allowEditing = colData?.allowEditing || colData?.allowAdding
|
||
|
||
// #region lookup ayarlari
|
||
if (colData.lookupDto?.dataSourceType) {
|
||
// UiColumnEditorTemplateTypeEnum : None:0, Table:1, TagBox:2
|
||
const allItems = gridDto.gridOptions.editingFormDto.flatMap((group) => group.items)
|
||
const formItem = allItems.find((a) => a?.dataField === colData.fieldName)
|
||
if (formItem?.editorType2 === PlatformEditorTypes.dxTagBox) {
|
||
column.extras = {
|
||
multiValue: true,
|
||
editorOptions: formItem.editorOptions,
|
||
tagBoxOptions: formItem.tagBoxOptions,
|
||
}
|
||
column.editCellTemplate = 'cellEditTagBox'
|
||
column.calculateFilterExpression = calculateFilterExpressionMultiValue
|
||
column.cellTemplate = cellTemplateMultiValue
|
||
} else if (formItem?.editorType2 === PlatformEditorTypes.dxGridBox) {
|
||
column.extras = {
|
||
multiValue: false,
|
||
editorOptions: formItem.editorOptions,
|
||
gridBoxOptions: formItem.gridBoxOptions,
|
||
}
|
||
column.editCellTemplate = 'cellEditGridBox'
|
||
column.cellTemplate = cellTemplateMultiValue
|
||
if (formItem.gridBoxOptions?.selectionMode === 'multiple') {
|
||
column.calculateFilterExpression = calculateFilterExpressionMultiValue
|
||
column.extras.multiValue = true
|
||
}
|
||
}
|
||
|
||
column.lookup = {
|
||
valueExpr: colData.lookupDto?.valueExpr?.toLowerCase(),
|
||
displayExpr: colData.lookupDto?.displayExpr?.toLowerCase(),
|
||
dataSource: (o) => lookupDataSource(o, colData, listFormCode),
|
||
}
|
||
//column.lookup.dataSource = lookupDataSource(null, colData)
|
||
|
||
//cascadeEmptyFields verisi dolu ise bu kolon/field bir parent field dir
|
||
if (colData.lookupDto.cascadeEmptyFields) {
|
||
// parent field guncellendigi zaman bu fonksiyon cagrilir
|
||
column.setCellValue = function (rowData: any, value: any) {
|
||
if (!colData.fieldName) return
|
||
//console.log({ rowData, value, colData })
|
||
rowData[colData.fieldName] = Array.isArray(value) ? value[0] : value
|
||
// cascadeEmptyFields alani aralarinda virgul olacak sekilde bosaltilmak istenen alanlari saklar
|
||
colData?.lookupDto?.cascadeEmptyFields?.split(',').forEach((emptyField: any) => {
|
||
rowData[emptyField] = null
|
||
})
|
||
}
|
||
}
|
||
}
|
||
// #endregion
|
||
|
||
// #region image upload editor
|
||
if (!colData.lookupDto?.dataSourceType) {
|
||
const allFormItems = gridDto.gridOptions.editingFormDto.flatMap((group) => group.items)
|
||
const imageFormItem = allFormItems.find((a) => a?.dataField === colData.fieldName)
|
||
if (imageFormItem?.editorType2 === PlatformEditorTypes.dxImageUpload) {
|
||
// imageUploadOptions'ı önce imageFormItem.imageUploadOptions'dan al,
|
||
// yoksa editorOptions JSON içinden parse et (admin panelinde editorOptions ile yapılandırılır)
|
||
let imageUploadOptions = imageFormItem.imageUploadOptions
|
||
if (!imageUploadOptions && imageFormItem.editorOptions) {
|
||
try {
|
||
imageUploadOptions = JSON.parse(imageFormItem.editorOptions)
|
||
} catch {
|
||
imageUploadOptions = undefined
|
||
}
|
||
}
|
||
// Kolon editorOptions'ından da dene (column-level config)
|
||
if (!imageUploadOptions && colData.editorOptions) {
|
||
try {
|
||
imageUploadOptions =
|
||
typeof colData.editorOptions === 'string'
|
||
? JSON.parse(colData.editorOptions)
|
||
: (colData.editorOptions as any)
|
||
} catch {
|
||
imageUploadOptions = undefined
|
||
}
|
||
}
|
||
column.extras = {
|
||
multiValue: imageUploadOptions?.multiple ?? false,
|
||
editorOptions: imageFormItem.editorOptions,
|
||
imageUploadOptions: imageUploadOptions,
|
||
}
|
||
column.editCellTemplate = 'cellEditImageUpload'
|
||
column.cellTemplate = cellTemplateImage as any
|
||
}
|
||
}
|
||
// #endregion image upload editor
|
||
|
||
if (colData.validationRuleDto) {
|
||
// for server side validation : https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/DataValidation/jQuery/Light/
|
||
column.validationRules = colData.validationRuleDto as ValidationRule[]
|
||
}
|
||
|
||
columns.push(column)
|
||
})
|
||
|
||
return columns
|
||
},
|
||
[gridDto, lookupDataSource, translate, checkPermission, dialog, isPwaMode, listFormCode],
|
||
)
|
||
|
||
const getBandedColumns = useCallback(() => {
|
||
if (!gridDto) {
|
||
return
|
||
}
|
||
|
||
const columns: GridColumnData[] = []
|
||
const insertedColumns: (string | undefined)[] = []
|
||
|
||
const commandColumn = getCommandColumn()
|
||
if (commandColumn) {
|
||
columns.push(commandColumn)
|
||
}
|
||
|
||
for (const col of gridDto.columnFormats) {
|
||
if (!col.fieldName) {
|
||
return
|
||
}
|
||
if (insertedColumns.some((a) => a === col.fieldName)) {
|
||
// kolon zaten eklenmis ise islem yapma
|
||
return
|
||
}
|
||
|
||
if (col.bandName) {
|
||
// banded kolon ise; en fazla iki kirilima kadar eklenir
|
||
const bands = col.bandName.split(':') // ic ice banded kolon const ise aralarinda : kullanilir
|
||
if (bands.length > 1) {
|
||
// band + band + column seklinde ise :
|
||
|
||
let topBand = columns.find((e) => e.caption == bands[0] && e.isBand == true)
|
||
if (!topBand) {
|
||
// en ustteki band ilk defa ekleniyor ise
|
||
topBand = { caption: bands[0], columns: [] as GridColumnData[], isBand: true }
|
||
columns.push(topBand)
|
||
}
|
||
topBand.columns ??= [] as GridColumnData[]
|
||
|
||
const band2 = { caption: bands[1], columns: [] as GridColumnData[], isBand: true }
|
||
topBand.columns.push(band2)
|
||
|
||
const rData = gridDto.columnFormats.filter((e) => e.bandName == col.bandName)
|
||
const cols = getColumns(rData) as GridColumnData[]
|
||
band2.columns.push(...cols)
|
||
insertedColumns.push(...cols.map((e) => e.dataField))
|
||
} else {
|
||
// band + column
|
||
const band = { caption: bands[0], columns: [] as GridColumnData[], isBand: true }
|
||
const rData = gridDto.columnFormats.filter((e) => e.bandName == bands[0])
|
||
const cols = getColumns(rData) as GridColumnData[]
|
||
band.columns.push(...cols)
|
||
columns.push(band)
|
||
insertedColumns.push(...cols.map((e) => e.dataField))
|
||
}
|
||
} else {
|
||
// band a bagli olmayan kolonlar
|
||
const cols = getColumns([col])
|
||
//console.log({ col, cols: JSON.stringify(cols) })
|
||
columns.push(...cols)
|
||
insertedColumns.push(...cols.map((e) => e.dataField))
|
||
}
|
||
}
|
||
|
||
// FormEditingExtraItem
|
||
// Devexpress Gridde kaydete basılınca
|
||
// formda gözükecek olan EditingFormDto.Items elemanları gelmiyor
|
||
// Sadece grid'de tanımlanmış columnları getiriyor.
|
||
// Bu elemanları da getirmesi için aşağıdaki şekilde,
|
||
// columns'da olmayan alanları da gizli olarak ekliyoruz
|
||
if (columns?.length) {
|
||
gridDto.gridOptions.editingFormDto.forEach((group) => {
|
||
group.items?.forEach((item: EditingFormItemDto) => {
|
||
if (!columns.some((a) => a.dataField === item.dataField)) {
|
||
columns.push({
|
||
dataField: item.dataField,
|
||
visible: false,
|
||
showInColumnChooser: false,
|
||
})
|
||
insertedColumns.push(item.dataField)
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
return columns
|
||
}, [gridDto, getColumns, getCommandColumn])
|
||
|
||
return {
|
||
getBandedColumns,
|
||
}
|
||
}
|
||
|
||
export { useListFormColumns }
|