Scheduler düzenlemeleri

This commit is contained in:
Sedat ÖZTÜRK 2026-07-29 13:04:07 +03:00
parent 2044abdca2
commit b0e78ac619
8 changed files with 500 additions and 33 deletions

View file

@ -34,9 +34,9 @@ public class SchedulerOptionDto
/// </summary>
public int StartDayHour { get; set; } = 8;
/// <summary>
/// Günün bitiş saati (örn: "18" = 18:00)
/// Günün bitiş saati (örn: "20" = 20:00)
/// </summary>
public int EndDayHour { get; set; } = 18;
public int EndDayHour { get; set; } = 20;
/// <summary>
/// Varsayılan görünüm (örn: "week", "month", "day")
/// </summary>

View file

@ -0,0 +1,18 @@
IF OBJECT_ID(N'[dbo].[Pro_D_Scheduler]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Pro_D_Scheduler]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[Text] nvarchar(300) NOT NULL,
[StartDate] datetime2 NOT NULL,
[EndDate] datetime2 NOT NULL,
[AllDay] bit NOT NULL DEFAULT 0,
[RecurrenceRule] nvarchar(500) NULL,
[RecurrenceException] nvarchar(MAX) NULL,
CONSTRAINT [PK_Pro_D_Scheduler] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -83,7 +83,7 @@
"RecurrenceRuleExpr": null,
"RecurrenceExceptionExpr": null,
"StartDayHour": 8,
"EndDayHour": 18,
"EndDayHour": 20,
"DefaultView": "week",
"ShowAllDayPanel": true,
"CellDuration": 30,

View file

@ -0,0 +1,275 @@
{
"Wizard": {
"WizardName": "Scheduler",
"ListFormCode": "App.Wizard.Scheduler",
"MenuCode": "App.Wizard.Scheduler",
"MenuOrder": 4,
"IsTenant": false,
"IsBranch": false,
"IsOrganizationUnit": false,
"AllowAdding": true,
"AllowUpdating": true,
"AllowDeleting": true,
"AllowDetail": false,
"ConfirmDelete": true,
"DefaultLayout": "scheduler",
"Grid": true,
"Card": true,
"Pivot": true,
"Tree": false,
"Chart": true,
"Gantt": false,
"Scheduler": true,
"Todo": false,
"LanguageTextMenuEn": "Scheduler",
"LanguageTextMenuTr": "Scheduler",
"LanguageTextTitleEn": "Scheduler",
"LanguageTextTitleTr": "Scheduler",
"LanguageTextDescEn": "Scheduler",
"LanguageTextDescTr": "Scheduler",
"LanguageTextMenuParentEn": "Project",
"LanguageTextMenuParentTr": "Project",
"PermissionGroupName": "App.Wizard.Project",
"MenuParentCode": "App.Wizard.Project",
"MenuParentShortName": "Pro",
"MenuParentIcon": "FcVideoProjector",
"MenuIcon": "FaGlassCheers",
"DataSourceCode": "Default",
"DataSourceConnectionString": "",
"SelectCommandType": 1,
"SelectCommand": "Pro_D_Scheduler",
"KeyFieldName": "Id",
"KeyFieldDbSourceType": 9,
"TreeOptionDto": {
"KeyExpr": "",
"ParentIdExpr": "",
"HasItemsExpr": "",
"RootValue": "",
"ExpandedRowKeys": [],
"AutoExpandAll": false,
"RecursiveSelection": false,
"TitleExpr": null,
"StartExpr": null,
"EndExpr": null,
"ProgressExpr": null
},
"GanttOptionDto": {
"KeyExpr": "",
"ParentIdExpr": "",
"HasItemsExpr": "",
"RootValue": "",
"ExpandedRowKeys": [],
"AutoExpandAll": false,
"RecursiveSelection": false,
"TitleExpr": "",
"ScaleType": "weeks",
"StartExpr": "",
"EndExpr": "",
"ProgressExpr": "",
"AllowEditing": false,
"AllowTaskAdding": false,
"AllowTaskUpdating": false,
"AllowTaskDeleting": false,
"AllowDependencyAdding": false,
"AllowDependencyDeleting": false,
"AllowResourceAdding": false,
"AllowResourceDeleting": false
},
"SchedulerOptionDto": {
"TextExpr": "Text",
"StartDateExpr": "StartDate",
"EndDateExpr": "EndDate",
"AllDayExpr": "AllDay",
"RecurrenceRuleExpr": "RecurrenceRule",
"RecurrenceExceptionExpr": "RecurrenceException",
"StartDayHour": 8,
"EndDayHour": 20,
"DefaultView": "week",
"ShowAllDayPanel": true,
"CellDuration": 30,
"FirstDayOfWeek": 1,
"CrossScrollingEnabled": true,
"AllowResizing": true,
"AllowDragging": true,
"AllowDeleting": true,
"AllowEditing": true,
"AllowAdding": true
},
"TodoOptionDto": {
"TitleExpr": "",
"StatusExpr": "",
"DescriptionExpr": "",
"DueDateExpr": "",
"TagExpr": "",
"AssigneeExpr": "",
"PriorityExpr": "",
"CompletedExpr": "",
"OrderExpr": "",
"StatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
"AllowDragging": true
},
"WorkflowDto": {
"ApprovalUserFieldName": "",
"ApprovalDateFieldName": "",
"ApprovalStatusFieldName": "",
"ApprovalDescriptionFieldName": "",
"ApprovalIsFilterUserName": false,
"Criteria": []
},
"Groups": [
{
"Caption": "",
"ColCount": 1,
"Items": [
{
"FieldName": "Id",
"CaptionName": "App.Listform.ListformField.Id",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 9,
"TurkishCaption": "Id",
"EnglishCaption": "Id",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "Text",
"CaptionName": "App.Listform.ListformField.Text",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 16,
"TurkishCaption": "Text",
"EnglishCaption": "Text",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "StartDate",
"CaptionName": "App.Listform.ListformField.StartDate",
"EditorType": "dxDateBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 6,
"TurkishCaption": "Start Date",
"EnglishCaption": "Start Date",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "EndDate",
"CaptionName": "App.Listform.ListformField.EndDate",
"EditorType": "dxDateBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 6,
"TurkishCaption": "End Date",
"EnglishCaption": "End Date",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "AllDay",
"CaptionName": "App.Listform.ListformField.AllDay",
"EditorType": "dxCheckBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 3,
"TurkishCaption": "All Day",
"EnglishCaption": "All Day",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "RecurrenceRule",
"CaptionName": "App.Listform.ListformField.RecurrenceRule",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": true,
"DbSourceType": 16,
"TurkishCaption": "Recurrence Rule",
"EnglishCaption": "Recurrence Rule",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "RecurrenceException",
"CaptionName": "App.Listform.ListformField.RecurrenceException",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": true,
"DbSourceType": 16,
"TurkishCaption": "Recurrence Exception",
"EnglishCaption": "Recurrence Exception",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
}
]
}
],
"SubForms": [],
"Widgets": []
},
"IsDeletedField": false,
"IsCreatedField": false,
"InsertedRecords": {
"LanguageKeys": [
"App.Wizard.Scheduler",
"App.Wizard.Scheduler.Title",
"App.Wizard.Scheduler.Desc",
"App.Listform.ListformField.AllDay",
"App.Listform.ListformField.RecurrenceRule",
"App.Listform.ListformField.RecurrenceException"
],
"PermissionGroupNames": [],
"PermissionNames": [
"App.Wizard.Scheduler",
"App.Wizard.Scheduler.Create",
"App.Wizard.Scheduler.Update",
"App.Wizard.Scheduler.Delete",
"App.Wizard.Scheduler.Export",
"App.Wizard.Scheduler.Import",
"App.Wizard.Scheduler.Note"
],
"MenuCodes": [
"App.Wizard.Scheduler"
],
"DataSourceCodes": []
}
}

View file

@ -290,10 +290,77 @@ div.dialog-after-open > div.dialog-content.maximized {
.dx-datagrid-header-panel > .dx-toolbar .dx-toolbar-after .dx-button-text,
.dx-treelist-header-panel > .dx-toolbar .dx-toolbar-after .dx-button-text,
.dx-scheduler-header-panel .dx-toolbar .dx-button-text,
.scheduler-toolbar-action .dx-button-text,
.dx-gantt-toolbar-wrapper .dx-button-text {
text-transform: none !important;
}
.scheduler-toolbar-action .dx-button-text,
.scheduler-toolbar-action .dx-icon {
color: #000 !important;
}
.dx-scheduler-navigator .dx-button-text,
.dx-scheduler-navigator .dx-icon,
.dx-scheduler-today .dx-button-text,
.dx-scheduler-today .dx-icon,
.dx-scheduler-view-switcher .dx-button-text,
.dx-scheduler-view-switcher .dx-icon {
color: #000 !important;
}
.dark .scheduler-toolbar-action .dx-button-text,
.dark .scheduler-toolbar-action .dx-icon,
.dark .dx-scheduler-navigator .dx-button-text,
.dark .dx-scheduler-navigator .dx-icon,
.dark .dx-scheduler-today .dx-button-text,
.dark .dx-scheduler-today .dx-icon,
.dark .dx-scheduler-view-switcher .dx-button-text,
.dark .dx-scheduler-view-switcher .dx-icon {
color: #fff !important;
}
.scheduler-appointment-preview,
.scheduler-appointment-preview * {
text-align: left !important;
}
.scheduler-appointment-preview {
width: min(22rem, calc(100vw - 2rem));
}
.scheduler-appointment-text {
display: -webkit-box !important;
overflow: hidden !important;
line-height: 1.25;
white-space: normal !important;
text-overflow: clip !important;
overflow-wrap: anywhere;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
}
.scheduler-appointment-text p {
display: inline;
margin: 0;
}
.scheduler-appointment-preview-text {
display: -webkit-box;
overflow: hidden;
line-height: 1.35;
white-space: normal !important;
overflow-wrap: anywhere;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
}
.scheduler-appointment-preview-text p {
display: inline;
margin: 0;
}
.dx-datagrid-header-panel > .dx-toolbar .dx-toolbar-menu-container,
.dx-treelist-header-panel > .dx-toolbar .dx-toolbar-menu-container {
margin-left: 2px !important;

View file

@ -248,7 +248,7 @@ function FormTabScheduler(props: FormEditProps) {
type="number"
autoComplete="off"
name="schedulerOptionDto.endDayHour"
placeholder="18"
placeholder="20"
component={Input}
min={0}
max={24}

View file

@ -109,7 +109,7 @@ const initialValues: ListFormWizardDto = {
recurrenceRuleExpr: '',
recurrenceExceptionExpr: '',
startDayHour: 8,
endDayHour: 18,
endDayHour: 20,
defaultView: 'week',
showAllDayPanel: true,
cellDuration: 30,

View file

@ -21,12 +21,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,
autoNumber,
getEditingFormGroups,
} from './Utils'
import { addCss, addJs, autoNumber, getEditingFormGroups } from './Utils'
import { layoutTypes } from '../admin/listForm/edit/types'
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
import { ROUTES_ENUM } from '@/routes/route.constant'
@ -36,6 +31,7 @@ import { Loading } from '@/components/shared'
import { usePermission } from '@/utils/hooks/usePermission'
import { RowMode, SimpleItemWithColData } from '../form/types'
import { captionize } from 'devextreme/core/utils/inflector'
import { formatDate } from 'devextreme/localization'
import type { AppointmentFormOpeningEvent } from 'devextreme/ui/scheduler'
interface SchedulerViewProps {
@ -174,6 +170,84 @@ const SchedulerView = (props: SchedulerViewProps) => {
setCurrentView(value)
}, [])
const getAppointmentHtml = useCallback(
(appointmentData: Record<string, any>) => {
const textExpr = gridDto?.gridOptions.schedulerOptionDto?.textExpr || 'text'
return String(appointmentData?.[textExpr] ?? '')
},
[gridDto],
)
const getAppointmentTimeText = useCallback(
(appointmentData: Record<string, any>) => {
const schedulerOptions = gridDto?.gridOptions.schedulerOptionDto
const startDate = appointmentData?.[schedulerOptions?.startDateExpr || 'startDate']
const endDate = appointmentData?.[schedulerOptions?.endDateExpr || 'endDate']
if (!startDate || !endDate) {
return ''
}
return `${formatDate(new Date(startDate), 'shortTime')} - ${formatDate(
new Date(endDate),
'shortTime',
)}`
},
[gridDto],
)
const renderAppointment = useCallback(
(data: any) => {
const appointmentData = data.targetedAppointmentData || data.appointmentData || {}
return (
<>
<div
className="dx-scheduler-appointment-title scheduler-appointment-text"
dangerouslySetInnerHTML={{ __html: getAppointmentHtml(appointmentData) }}
/>
<div className="dx-scheduler-appointment-content-date">
{getAppointmentTimeText(appointmentData)}
</div>
</>
)
},
[getAppointmentHtml, getAppointmentTimeText],
)
const renderAppointmentTooltip = useCallback(
(data: any) => {
const appointmentData = data.targetedAppointmentData || data.appointmentData || {}
return (
<div className="scheduler-appointment-preview flex min-w-72 items-center gap-3 px-4 py-3">
<div className="min-w-0 flex-1 text-left">
<div
className="scheduler-appointment-preview-text font-semibold"
dangerouslySetInnerHTML={{ __html: getAppointmentHtml(appointmentData) }}
/>
<div className="text-xs">{getAppointmentTimeText(appointmentData)}</div>
</div>
{gridDto?.gridOptions.schedulerOptionDto?.allowDeleting && (
<button
type="button"
className="shrink-0 self-start dx-button dx-button-mode-text dx-widget dx-button-has-icon"
onClick={(event) => {
event.stopPropagation()
schedulerRef.current?.instance().deleteAppointment(data.appointmentData)
}}
>
<span className="dx-button-content">
<i className="dx-icon dx-icon-trash" />
</span>
</button>
)}
</div>
)
},
[getAppointmentHtml, getAppointmentTimeText, gridDto],
)
const onAppointmentFormOpening = useCallback(
(e: AppointmentFormOpeningEvent) => {
if (!gridDto) return
@ -194,7 +268,11 @@ const SchedulerView = (props: SchedulerViewProps) => {
)
e.popup.option('showTitle', gridDto.gridOptions.editingOptionDto?.popup?.showTitle)
e.popup.option('width', gridDto.gridOptions.editingOptionDto?.popup?.width)
e.popup.option('height', gridDto.gridOptions.editingOptionDto?.popup?.height)
e.popup.option('height', isPopupFullScreen ? '100%' : 'auto')
e.popup.option(
'maxHeight',
isPopupFullScreen ? '100%' : gridDto.gridOptions.editingOptionDto?.popup?.height || '90vh',
)
e.popup.option('resizeEnabled', gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled)
e.popup.option('fullScreen', isPopupFullScreen)
@ -282,11 +360,13 @@ const SchedulerView = (props: SchedulerViewProps) => {
if (newFullScreenState) {
e.popup.option('width', '100%')
e.popup.option('height', '100%')
e.popup.option('maxHeight', '100%')
} else {
e.popup.option('width', gridDto.gridOptions.editingOptionDto?.popup?.width || 600)
e.popup.option('height', 'auto')
e.popup.option(
'height',
gridDto.gridOptions.editingOptionDto?.popup?.height || 'auto',
'maxHeight',
gridDto.gridOptions.editingOptionDto?.popup?.height || '90vh',
)
}
},
@ -429,7 +509,9 @@ const SchedulerView = (props: SchedulerViewProps) => {
editorScript: i.editorScript,
}
if (i.fieldName?.indexOf(':') >= 0) {
if (i.captionName) {
item.label = { text: translate('::' + i.captionName) }
} else if (i.fieldName?.indexOf(':') >= 0) {
item.label = { text: captionize(i.fieldName.split(':')[1]) }
}
@ -446,15 +528,25 @@ const SchedulerView = (props: SchedulerViewProps) => {
return item
}
const isVisibleFormItem = (item: SimpleItemWithColData) => {
if (currentMode === 'new') {
return item.canCreate && item.allowAdding
}
if (currentMode === 'edit') {
return item.canUpdate && item.allowEditing
}
return false
}
sortedFormDto.forEach((e: any) => {
// Items'ları da order'a göre sırala
const sortedItems = (e.items || [])
.slice()
.sort((a: any, b: any) =>
(a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1,
)
.sort((a: any, b: any) => ((a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1))
const groupItems = sortedItems.map(mapFormItem)
const groupItems = sortedItems.map(mapFormItem).filter(isVisibleFormItem)
if (e.itemType !== 'tabbed') {
// Grup kullanmadan direkt items'ları ekle - form'un colCount'u geçerli olsun
@ -478,14 +570,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
(a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 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
}),
.filter(isVisibleFormItem),
}
}),
})
@ -575,10 +660,12 @@ const SchedulerView = (props: SchedulerViewProps) => {
gridDto.gridOptions.schedulerOptionDto?.recurrenceExceptionExpr
}
startDayHour={gridDto.gridOptions.schedulerOptionDto?.startDayHour || 8}
endDayHour={gridDto.gridOptions.schedulerOptionDto?.endDayHour || 18}
endDayHour={gridDto.gridOptions.schedulerOptionDto?.endDayHour || 20}
currentView={currentView}
onCurrentViewChange={onCurrentViewChange}
onAppointmentFormOpening={onAppointmentFormOpening}
appointmentRender={renderAppointment}
appointmentTooltipRender={renderAppointmentTooltip}
onAppointmentAdding={() => {
props.refreshData?.()
}}
@ -610,28 +697,36 @@ const SchedulerView = (props: SchedulerViewProps) => {
| 5
| 6) || 1
}
adaptivityEnabled={true}
adaptivityEnabled={false}
>
<Toolbar>
<Item name="dateNavigator" />
<Item name="today" />
<Item name="viewSwitcher" />
<Item
cssClass="scheduler-toolbar-action"
location="after"
widget="dxButton"
locateInMenu="auto"
showText="always"
options={{
icon: 'refresh',
text: translate('::ListForms.ListForm.Refresh'),
hint: translate('::ListForms.ListForm.Refresh'),
onClick: handleRefresh,
}}
/>
{checkPermission('App.Listforms.Listform.Update') && (
<Item
cssClass="scheduler-toolbar-action"
location="after"
widget="dxButton"
locateInMenu="auto"
showText="always"
options={{
icon: 'preferences',
hint: 'Settings',
text: translate('::ListForms.ListForm.Manage'),
hint: translate('::ListForms.ListForm.Manage'),
onClick: settingButtonClick,
}}
/>
@ -646,9 +741,21 @@ const SchedulerView = (props: SchedulerViewProps) => {
allowDragging={gridDto.gridOptions.schedulerOptionDto?.allowDragging ?? false}
/>
<View type="day" name={translate('::App.Listform.ListformField.Day')} />
<View type="week" name={translate('::ListForms.SchedulerOptions.Week')} />
<View type="workWeek" name={translate('::ListForms.SchedulerOptions.WorkWeek')} />
<View
type="day"
name={translate('::App.Listform.ListformField.Day')}
maxAppointmentsPerCell="unlimited"
/>
<View
type="week"
name={translate('::ListForms.SchedulerOptions.Week')}
maxAppointmentsPerCell="unlimited"
/>
<View
type="workWeek"
name={translate('::ListForms.SchedulerOptions.WorkWeek')}
maxAppointmentsPerCell="unlimited"
/>
<View type="month" name={translate('::ListForms.SchedulerOptions.Month')} />
<View
type="timelineDay"