Scheduler Appointment FormView düzenlemesi

This commit is contained in:
Sedat Öztürk 2025-12-04 01:29:25 +03:00
parent 36907db226
commit 98d748c398
5 changed files with 396 additions and 21 deletions

View file

@ -2135,7 +2135,7 @@ public class ListFormSeeder_Maintenance : IDataSeedContributor, ITransientDepend
new EditingFormItemDto { Order = 4, DataField = "Priority", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 5, DataField = "Status", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 6, DataField = "EmployeeId", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 7, DataField = "Subject", ColSpan = 2, EditorType2 = EditorTypes.dxTextArea },
new EditingFormItemDto { Order = 7, DataField = "Subject", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxTextArea },
new EditingFormItemDto { Order = 8, DataField = "ScheduledStart", ColSpan = 1, EditorType2 = EditorTypes.dxDateBox, EditorOptions=EditorOptionValues.DateTimeFormat },
new EditingFormItemDto { Order = 9, DataField = "ScheduledEnd", ColSpan = 1, EditorType2 = EditorTypes.dxDateBox, EditorOptions=EditorOptionValues.DateTimeFormat },
new EditingFormItemDto { Order = 10, DataField = "ActualStart", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxDateBox, EditorOptions=EditorOptionValues.DateTimeFormat },
@ -2292,6 +2292,7 @@ public class ListFormSeeder_Maintenance : IDataSeedContributor, ITransientDepend
IsActive = true,
IsDeleted = false,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
@ -2804,7 +2805,7 @@ public class ListFormSeeder_Maintenance : IDataSeedContributor, ITransientDepend
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
new() {
Order=1, ColCount=2, ColSpan=1, ItemType="group", Items= [
new EditingFormItemDto { Order = 1, DataField = "Subject", ColSpan = 2, EditorType2 = EditorTypes.dxTextArea },
new EditingFormItemDto { Order = 1, DataField = "Subject", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxTextArea },
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 },
@ -2813,6 +2814,7 @@ public class ListFormSeeder_Maintenance : IDataSeedContributor, ITransientDepend
new EditingFormItemDto { Order = 7, DataField = "ScheduledStart", ColSpan = 1, EditorType2 = EditorTypes.dxDateBox, EditorOptions=EditorOptionValues.DateTimeFormat },
new EditingFormItemDto { Order = 8, DataField = "ScheduledEnd", ColSpan = 1, EditorType2 = EditorTypes.dxDateBox, EditorOptions=EditorOptionValues.DateTimeFormat },
new EditingFormItemDto { Order = 9, DataField = "EstimatedCost", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat },
new EditingFormItemDto { Order = 10, DataField = "Description", ColSpan = 2, EditorType2 = EditorTypes.dxTextArea },
]}
}),
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
@ -2851,6 +2853,7 @@ public class ListFormSeeder_Maintenance : IDataSeedContributor, ITransientDepend
IsActive = true,
IsDeleted = false,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
@ -2998,6 +3001,21 @@ public class ListFormSeeder_Maintenance : IDataSeedContributor, ITransientDepend
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Description",
Width = 300,
ListOrderNo = 11,
Visible = true,
IsActive = true,
IsDeleted = false,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
]);
#endregion
}

View file

@ -149,9 +149,12 @@ public class QueryManager : PlatformDomainService, IQueryManager
};
var updateFields = parameters.Select(a => $"\"{a.Key}\" = @{a.Key}").ToList();
var val = QueryHelper.GetFormattedValue(listForm.KeyFieldDbSourceType, keys);
if (!parameters.ContainsKey(listForm.KeyFieldName))
{
parameters.Add(
listForm.KeyFieldName,
val.GetType().IsArray ? val : new object[] { val });
}
sql = $"UPDATE \"{listForm.SelectCommand}\" SET {string.Join(',', updateFields)} WHERE {where}";
}
else if (op == OperationEnum.Delete)

View file

@ -55,6 +55,7 @@ import {
addJs,
autoNumber,
controlStyleCondition,
extractSearchParamsFields,
GridExtraFilterState,
setFormEditingExtraItemValues,
setGridPanelColor,
@ -159,18 +160,6 @@ const Grid = (props: GridProps) => {
gridRef,
})
function extractSearchParamsFields(filter: any): [string, string, any][] {
if (!Array.isArray(filter)) return []
// [field, op, val] formu mu?
if (filter.length === 3 && typeof filter[0] === 'string') {
return [filter as [string, string, any]]
}
// içinde başka filter arrayleri olabilir
return filter.flatMap((f) => extractSearchParamsFields(f))
}
async function getSelectedRowKeys() {
const grd = gridRef.current?.instance()
if (!grd) {

View file

@ -1,6 +1,6 @@
import Container from '@/components/shared/Container'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import { GridDto } from '@/proxy/form/models'
import { GridDto, PlatformEditorTypes, UiLookupDataSourceTypeEnum } from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Scheduler, {
Editing,
@ -22,6 +22,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 { SimpleItemWithColData } from '../form/types'
interface SchedulerViewProps {
listFormCode: string
@ -41,11 +45,13 @@ const SchedulerView = (props: SchedulerViewProps) => {
const refListFormCode = useRef('')
const widgetGroupRef = useRef<HTMLDivElement>(null)
const { checkPermission } = usePermission()
const { smaller } = useResponsive()
const [schedulerDataSource, setSchedulerDataSource] = useState<CustomStore<any, any>>()
const [gridDto, setGridDto] = useState<GridDto>()
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
const [currentView, setCurrentView] = useState<string>('week')
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
const layout = layoutTypes.scheduler || 'scheduler'
useEffect(() => {
@ -77,6 +83,12 @@ const SchedulerView = (props: SchedulerViewProps) => {
}, [listFormCode])
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: schedulerRef })
const { getBandedColumns } = useListFormColumns({
gridDto,
listFormCode,
isSubForm,
gridRef: schedulerRef,
})
useEffect(() => {
if (!gridDto) {
@ -157,9 +169,350 @@ const SchedulerView = (props: SchedulerViewProps) => {
setCurrentView(value)
}, [])
const onAppointmentFormOpening = useCallback((e: any) => {
// Özelleştirme yapılabilir
}, [])
const onAppointmentFormOpening = useCallback(
(e: any) => {
if (!gridDto) return
// Popup ayarlarını her açılışta yeniden yapılandır
e.popup.option('title', 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('resizeEnabled', gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled)
e.popup.option('fullScreen', isPopupFullScreen)
// Toolbar butonlarını ekle
const toolbarItems = [
{
widget: 'dxButton',
toolbar: 'bottom',
location: 'after',
options: {
text: translate('::Save'),
type: 'default',
onClick: async () => {
const formInstance = e.form
const validationResult = formInstance.validate()
if (validationResult.isValid) {
// Form verilerini al
const formData = formInstance.option('formData')
try {0
// Scheduler instance'ını al
const scheduler = schedulerRef.current?.instance()
if (e.appointmentData && scheduler) {
// Yeni appointment mı yoksa güncelleme mi kontrol et
if (
e.appointmentData.id ||
e.appointmentData[gridDto.gridOptions.keyFieldName || 'id']
) {
// Güncelleme
await scheduler.updateAppointment(e.appointmentData, formData)
} else {
// Yeni ekleme
await scheduler.addAppointment(formData)
}
// Popup'ı kapat
e.popup.hide()
// RefreshData varsa çağır
if (props.refreshData) {
await props.refreshData()
}
}
} catch (error) {
console.error('Appointment save error:', error)
}
}
},
},
},
{
widget: 'dxButton',
toolbar: 'bottom',
location: 'after',
options: {
text: translate('::Cancel'),
onClick: () => {
e.cancel = true
e.popup.hide()
},
},
},
{
widget: 'dxButton',
toolbar: 'top',
location: 'after',
options: {
icon: 'fullscreen',
hint: translate('::Tam Ekran'),
stylingMode: 'text',
onClick: () => {
// 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('toolbarItems', toolbarItems)
// EditingFormDto'dan form items oluştur
const formItems: any[] = []
if (gridDto.gridOptions.editingFormDto?.length > 0) {
const sortedFormDto = gridDto.gridOptions.editingFormDto
.slice()
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
sortedFormDto.forEach((group: any) => {
const groupItems = (group.items || []).map((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)
// Label oluştur
const label: any = {
text: listFormField?.captionName
? translate('::' + listFormField.captionName)
: i.dataField,
}
// EditorType belirleme - Grid'deki gibi
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) {
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',
loadMode: 'raw',
load: async () => {
try {
const { dynamicFetch } = await import('@/services/form.service')
const response = await dynamicFetch('list-form-select/lookup', 'POST', null, {
listFormCode,
listFormFieldName: fieldName,
filters: [],
})
return (response.data ?? []).map((a: any) => ({
key: a.Key,
name: a.Name,
group: a.Group,
}))
} catch (error) {
console.error('Lookup load error:', error)
return []
}
},
})
editorOptions.valueExpr = 'key'
editorOptions.displayExpr = 'name'
} else if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.StaticData) {
if (lookup.lookupQuery) {
try {
const staticData = JSON.parse(lookup.lookupQuery)
editorOptions.dataSource = staticData
editorOptions.valueExpr = lookup.valueExpr || 'key'
editorOptions.displayExpr = lookup.displayExpr || 'name'
} catch (error) {
console.error('Static data parse error:', error)
}
}
}
}
// Validation rules
const validationRules: any[] = []
if (i.isRequired) {
validationRules.push({ type: 'required' })
}
return {
label,
dataField: i.dataField,
editorType,
colSpan: i.colSpan,
editorOptions,
validationRules: validationRules.length > 0 ? validationRules : undefined,
}
})
if (group.itemType === 'group') {
const groupItem: any = {
itemType: 'group',
colCount: group.colCount || 1,
items: groupItems,
}
// Caption null değilse ekle
if (group.caption) {
groupItem.caption = translate('::' + group.caption)
}
formItems.push(groupItem)
} else if (group.itemType === 'tabbed') {
formItems.push({
itemType: 'tabbed',
tabs: (group.tabs || []).map((tab: any) => ({
title: translate('::' + tab.title),
colCount: tab.colCount || 2,
items: (tab.items || []).map((i: EditingFormItemDto) => {
// Tab içindeki itemlar için de aynı mapping
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,
)
const label: any = {
text: listFormField?.captionName
? translate('::' + listFormField.captionName)
: i.dataField,
}
// EditorType belirleme - Grid'deki gibi
let editorType: any = i.editorType2 || i.editorType
if (i.editorType2 === PlatformEditorTypes.dxGridBox) {
editorType = 'dxDropDownBox'
} else if (i.editorType2) {
editorType = i.editorType2
}
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',
loadMode: 'raw',
load: async () => {
try {
const { dynamicFetch } = await import('@/services/form.service')
const response = await dynamicFetch(
'list-form-select/lookup',
'POST',
null,
{
listFormCode,
listFormFieldName: fieldName,
filters: [],
},
)
return (response.data ?? []).map((a: any) => ({
key: a.Key,
name: a.Name,
group: a.Group,
}))
} catch (error) {
console.error('Lookup load error:', error)
return []
}
},
})
editorOptions.valueExpr = 'key'
editorOptions.displayExpr = 'name'
} else if (lookup.dataSourceType === UiLookupDataSourceTypeEnum.StaticData) {
if (lookup.lookupQuery) {
try {
const staticData = JSON.parse(lookup.lookupQuery)
editorOptions.dataSource = staticData
editorOptions.valueExpr = lookup.valueExpr || 'key'
editorOptions.displayExpr = lookup.displayExpr || 'name'
} catch (error) {
console.error('Static data parse error:', error)
}
}
}
}
const validationRules: any[] = []
if (i.isRequired) {
validationRules.push({ type: 'required' })
}
return {
label,
dataField: i.dataField,
editorType,
colSpan: i.colSpan,
editorOptions,
validationRules: validationRules.length > 0 ? validationRules : undefined,
}
}),
})),
})
} else {
// No group, add items directly
formItems.push(...groupItems)
}
})
}
// Form'a items'ı set et ve colCount'u ayarla
e.form.option('showValidationSummary', false)
e.form.option('items', formItems)
},
[gridDto, translate, isPopupFullScreen, listFormCode],
)
return (
<>

View file

@ -10,6 +10,18 @@ export interface GridExtraFilterState {
value: string
}
export function extractSearchParamsFields(filter: any): [string, string, any][] {
if (!Array.isArray(filter)) return []
// [field, op, val] formu mu?
if (filter.length === 3 && typeof filter[0] === 'string') {
return [filter as [string, string, any]]
}
// içinde başka filter arrayleri olabilir
return filter.flatMap((f) => extractSearchParamsFields(f))
}
// sayfaya dinamik olarak css style ekler
export function addCss(css: string) {
if (css.startsWith('http') || css.startsWith('/')) {