SchedulerView mode ve tabbed style düzenlemesi

This commit is contained in:
Sedat ÖZTÜRK 2025-12-04 10:33:27 +03:00
parent cbfb5ccef6
commit 32720abac2
2 changed files with 151 additions and 87 deletions

View file

@ -2801,11 +2801,11 @@ public class ListFormSeeder_Maintenance : IDataSeedContributor, ITransientDepend
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson,
PagerOptionJson = DefaultPagerOptionJson,
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 800, 450, true, true, true, true, false),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 800, 500, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
new() {
Order=1, ColCount=2, ColSpan=1, ItemType="group", Items= [
new EditingFormItemDto { Order = 1, DataField = "Subject", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxTextArea },
new EditingFormItemDto { Order = 1, DataField = "Subject", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 2, DataField = "WorkcenterId", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 3, DataField = "WorkorderTypeId", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 4, DataField = "Priority", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox },

View file

@ -1,6 +1,11 @@
import Container from '@/components/shared/Container'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import { GridDto, PlatformEditorTypes, UiLookupDataSourceTypeEnum } from '@/proxy/form/models'
import {
DbTypeEnum,
GridDto,
PlatformEditorTypes,
UiLookupDataSourceTypeEnum,
} from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Scheduler, {
Editing,
@ -14,7 +19,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import { getList } from '@/services/form.service'
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
import { addCss, addJs } from './Utils'
import { addCss, addJs, autoNumber } from './Utils'
import { layoutTypes } from '../admin/listForm/edit/types'
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
import { ROUTES_ENUM } from '@/routes/route.constant'
@ -22,10 +27,10 @@ import { usePWA } from '@/utils/hooks/usePWA'
import CustomStore from 'devextreme/data/custom_store'
import { Loading } from '@/components/shared'
import { usePermission } from '@/utils/hooks/usePermission'
import { useListFormColumns } from './useListFormColumns'
import { EditingFormItemDto } from '@/proxy/form/models'
import useResponsive from '@/utils/hooks/useResponsive'
import { colSpan } from '@/components/ui/Widget/iconList'
import { RowMode, SimpleItemWithColData } from '../form/types'
import { captionize } from 'devextreme/core/utils/inflector'
import { AppointmentFormOpeningEvent } from 'devextreme/ui/scheduler'
interface SchedulerViewProps {
listFormCode: string
@ -40,6 +45,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
const { translate } = useLocalization()
const isPwaMode = usePWA()
const [mode, setMode] = useState<RowMode>('view')
const schedulerRef = useRef<SchedulerRef>()
const refListFormCode = useRef('')
@ -82,12 +88,6 @@ const SchedulerView = (props: SchedulerViewProps) => {
}, [listFormCode])
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: schedulerRef })
const { getBandedColumns } = useListFormColumns({
gridDto,
listFormCode,
isSubForm,
gridRef: schedulerRef,
})
useEffect(() => {
if (!gridDto) {
@ -169,19 +169,31 @@ const SchedulerView = (props: SchedulerViewProps) => {
}, [])
const onAppointmentFormOpening = useCallback(
(e: any) => {
(e: AppointmentFormOpeningEvent) => {
if (!gridDto) return
// Yeni appointment mı yoksa düzenleme mi kontrol et
const isNewAppointment =
!e.appointmentData ||
(!e.appointmentData.id && !e.appointmentData[gridDto.gridOptions.keyFieldName || 'id'])
const currentMode = isNewAppointment ? 'new' : 'edit'
setMode(currentMode)
// Popup ayarlarını her açılışta yeniden yapılandır
e.popup.option('title', translate('::' + gridDto.gridOptions.editingOptionDto?.popup?.title))
e.popup.option(
'title',
(currentMode === 'new' ? '✚ ' : '🖊️ ') +
translate('::' + gridDto.gridOptions.editingOptionDto?.popup?.title),
)
e.popup.option('showTitle', gridDto.gridOptions.editingOptionDto?.popup?.showTitle)
e.popup.option('width', gridDto.gridOptions.editingOptionDto?.popup?.width || 800)
e.popup.option('height', gridDto.gridOptions.editingOptionDto?.popup?.height || 600)
e.popup.option('width', gridDto.gridOptions.editingOptionDto?.popup?.width)
e.popup.option('height', gridDto.gridOptions.editingOptionDto?.popup?.height)
e.popup.option('resizeEnabled', gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled)
e.popup.option('fullScreen', isPopupFullScreen)
// Toolbar butonlarını ekle
const toolbarItems = [
e.popup.option('toolbarItems', [
{
widget: 'dxButton',
toolbar: 'bottom',
@ -197,7 +209,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
// Form verilerini al
const formData = formInstance.option('formData')
try {0
try {
// Scheduler instance'ını al
const scheduler = schedulerRef.current?.instance()
@ -253,40 +265,31 @@ const SchedulerView = (props: SchedulerViewProps) => {
// Popup'tan mevcut fullScreen durumunu al
const currentFullScreen = e.popup.option('fullScreen')
const newFullScreenState = !currentFullScreen
// State'i güncelle
setIsPopupFullScreen(newFullScreenState)
// Popup'ı güncelle
e.popup.option('fullScreen', newFullScreenState)
// Width ve height'i da güncelle
if (newFullScreenState) {
e.popup.option('width', '100%')
e.popup.option('height', '100%')
} else {
e.popup.option('width', gridDto.gridOptions.editingOptionDto?.popup?.width || 600)
e.popup.option('height', gridDto.gridOptions.editingOptionDto?.popup?.height || 'auto')
}
// Button icon ve hint'i güncelle
const button = e.popup._$element.find('.dx-toolbar-after .dx-button').last()
if (button.length) {
const buttonInstance = button.dxButton('instance')
if (buttonInstance) {
buttonInstance.option('icon', newFullScreenState ? 'collapse' : 'fullscreen')
buttonInstance.option('hint', newFullScreenState ? translate('::Normal Boyut') : translate('::Tam Ekran'))
}
e.popup.option(
'height',
gridDto.gridOptions.editingOptionDto?.popup?.height || 'auto',
)
}
},
},
},
]
e.popup.option('toolbarItems', toolbarItems)
])
// EditingFormDto'dan form items oluştur
const formItems: any[] = []
const result: any[] = []
if (gridDto.gridOptions.editingFormDto?.length > 0) {
const sortedFormDto = gridDto.gridOptions.editingFormDto
@ -308,6 +311,42 @@ const SchedulerView = (props: SchedulerViewProps) => {
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:ssxxx',
displayFormat: 'shortDateShortTime',
},
...editorOptions,
}
}
// Set defaultValue for @AUTONUMBER fields
if (
typeof listFormField?.defaultValue === 'string' &&
listFormField?.defaultValue === '@AUTONUMBER' &&
currentMode === 'new'
) {
editorOptions = {
...editorOptions,
value: autoNumber(),
}
}
// EditorType belirleme
let editorType: any = i.editorType2 || i.editorType
if (i.editorType2 === PlatformEditorTypes.dxGridBox) {
@ -320,11 +359,6 @@ const SchedulerView = (props: SchedulerViewProps) => {
if (listFormField?.lookupDto) {
const lookup = listFormField.lookupDto
// EditorType'ı dxSelectBox olarak ayarla
if (!editorType || editorType === 'dxTextBox') {
editorType = 'dxSelectBox'
}
if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.Query) {
editorOptions.dataSource = new CustomStore({
key: 'key',
@ -365,81 +399,111 @@ const SchedulerView = (props: SchedulerViewProps) => {
}
}
// Validation rules
const validationRules: any[] = []
if (i.isRequired) {
validationRules.push({ type: 'required' })
}
const item: any = {
const item: SimpleItemWithColData = {
canRead: listFormField?.canRead ?? false,
canUpdate: listFormField?.canUpdate ?? false,
canCreate: listFormField?.canCreate ?? false,
canExport: listFormField?.canExport ?? false,
dataField: i.dataField,
name: i.dataField,
editorType,
editorType2: i.editorType2,
editorType:
i.editorType2 == PlatformEditorTypes.dxGridBox ? 'dxDropDownBox' : i.editorType2,
colSpan: i.colSpan,
isRequired: i.isRequired,
editorOptions,
validationRules: validationRules.length > 0 ? validationRules : undefined,
editorScript: i.editorScript,
}
// Label sadece caption varsa ekle
if (listFormField?.captionName) {
item.label = { text: translate('::' + listFormField.captionName) }
if (i.dataField.indexOf(':') >= 0) {
item.label = { text: captionize(i.dataField.split(':')[1]) }
}
if ((currentMode == 'edit' && !item.canUpdate) || (currentMode == 'new' && !item.canCreate)) {
item.editorOptions = {
...item.editorOptions,
readOnly: true,
}
}
return item
}
sortedFormDto.forEach((group: any) => {
sortedFormDto.forEach((e: any) => {
// Items'ları da order'a göre sırala
const sortedItems = (group.items || [])
const sortedItems = (e.items || [])
.slice()
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
const groupItems = sortedItems.map(mapFormItem)
if (group.itemType === 'group') {
if (e.itemType !== 'tabbed') {
// Grup kullanmadan direkt items'ları ekle - form'un colCount'u geçerli olsun
formItems.push(...groupItems)
} else if (group.itemType === 'tabbed' && tabbedItems.length > 0 && group === tabbedItems[0]) {
// Tüm tabbed items'ları tek bir tabbed item olarak ekle (Grid'deki gibi)
formItems.push({
result.push(...groupItems)
} else if (e.itemType === 'tabbed' && tabbedItems.length > 0 && e === tabbedItems[0]) {
// Tabbed için caption OLMAMALI - sadece tabs array içinde title kullan
result.push({
itemType: 'tabbed',
// colCount ve colSpan kaldırıldı - tab'ın tüm genişliği kullanması için
// colCount tabbed item için OLMAMALI - sadece colSpan
colSpan: 1,
// caption kullanma! Tabs içindeki title'lar yeterli
tabs: tabbedItems.map((tabbedItem: any) => {
// Tab items'larını order'a göre sırala
const sortedTabItems = (tabbedItem.items || [])
.slice()
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
// Backend'den gelen colCount ve colSpan değerlerini kullan
const effectiveColCount = tabbedItem.colCount || 2
return {
title: translate('::' + tabbedItem.caption), // Backend'den caption geliyor
colCount: tabbedItem.colCount || 2, // Tab içindeki sütun sayısı
items: sortedTabItems.map(mapFormItem),
title: tabbedItem.caption, // Her tab'ın title'ı
colCount: effectiveColCount,
items: tabbedItem.items
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
.map(mapFormItem)
.filter((a: any) => {
if (currentMode === 'new') {
return a.canCreate || a.canRead
} else if (currentMode === 'edit') {
return a.canUpdate || a.canRead
}
return false
}),
}
}),
})
} else {
// No group, add items directly
formItems.push(...groupItems)
}
})
}
// Form'u tamamen yeniden yapılandır
// Tabbed varsa form colCount 1, yoksa backend'den gelen değer
const hasTabbedItems = formItems.some((item: any) => item.itemType === 'tabbed')
const formConfig = {
colCount: hasTabbedItems ? 1 : (gridDto.gridOptions.editingFormDto?.[0]?.colCount || 2),
showValidationSummary: false,
items: formItems,
const hasTabbedItems = result.some((item: any) => item.itemType === 'tabbed')
// Form'u tamamen yeniden baştan yapılandır
e.form.option(
'colCount',
hasTabbedItems ? 1 : gridDto.gridOptions.editingFormDto?.[0]?.colCount || 2,
)
e.form.option(
'colCount',
hasTabbedItems ? 1 : gridDto.gridOptions.editingFormDto?.[0]?.colCount || 2,
)
e.form.option('showValidationSummary', false)
e.form.option('items', result)
// Yeni versiyonlarda bu şekilde Style değiştirme işlemi yapmak zorunda olmayabiliriz.
// Yani geçici olarak eklenmiştir.
// Tabbed varsa belirli class'lara sahip elementi bul ve flex-direction'ı değiştir
if (hasTabbedItems) {
setTimeout(() => {
const targetElement = document.querySelector('.dx-item-content.dx-box-item-content.dx-box-flex.dx-box.dx-widget.dx-collection')
if (targetElement) {
const htmlElement = targetElement as HTMLElement
// Mevcut style'ı al ve flex-direction: row'u column'a çevir
const currentStyle = htmlElement.getAttribute('style') || ''
const updatedStyle = currentStyle.replace(/flex-direction:\s*row/gi, 'flex-direction: column')
htmlElement.setAttribute('style', updatedStyle)
}
}, 100)
}
// Form'u tamamen yeniden baştan yapılandır
e.form.option('colCount', formConfig.colCount)
e.form.option('showValidationSummary', formConfig.showValidationSummary)
e.form.option('items', formConfig.items)
// Form'u repaint et
e.form.repaint()
},
[gridDto, translate, isPopupFullScreen, listFormCode],
)