From dadf6d1ed839dd63fe13919c1c396b16b0971fa7 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Mon, 23 Mar 2026 22:20:33 +0800 Subject: [PATCH 1/8] implement rendering --- .../scss/widgets/fluent/scheduler/_index.scss | 14 +- .../widgets/generic/scheduler/_index.scss | 14 +- .../widgets/material/scheduler/_index.scss | 14 +- .../scheduler/appointments/m_text_utils.ts | 39 ++-- .../appointment/agenda_appointment.ts | 112 ++++++++++ .../appointment/base_appointment.ts | 160 +++++++++++++ .../appointment/grid_appointment.ts | 72 ++++++ .../appointments_new/appointment_collector.ts | 130 +++++++++++ .../appointments_new/appointments.ts | 211 ++++++++++++++++++ .../scheduler/appointments_new/const.ts | 33 +++ .../appointments_new/get_view_model_diff.ts | 157 +++++++++++++ .../scheduler/appointments_new/utils.ts | 17 ++ .../js/__internal/scheduler/m_classes.ts | 1 + .../m_compact_appointments_helper.ts | 1 + .../js/__internal/scheduler/m_scheduler.ts | 109 +++++++-- .../js/__internal/scheduler/m_subscribes.ts | 40 ++-- .../js/__internal/scheduler/types.ts | 8 +- .../utils/get_targeted_appointment.ts | 48 ++-- 18 files changed, 1082 insertions(+), 98 deletions(-) create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/const.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/get_view_model_diff.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/utils.ts diff --git a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss index 1e86184428c2..ca168e9e7293 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss @@ -209,25 +209,25 @@ $fluent-scheduler-agenda-time-panel-cell-padding: 8px; &.dx-scheduler-appointment-recurrence { @container (max-height: #{$fluent-scheduler-appointment-15min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $fluent-scheduler-appointment-10min-recurrence-padding-right; } } @container (min-height: #{$fluent-scheduler-appointment-15min-height}) and (max-height: #{$fluent-scheduler-appointment-20min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $fluent-scheduler-appointment-15min-recurrence-padding-right; } } @container (min-height: #{$fluent-scheduler-appointment-20min-height}) and (max-height: #{$fluent-scheduler-appointment-25min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $fluent-scheduler-appointment-20min-recurrence-padding-right; } } } - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { @container (max-height: #{$fluent-scheduler-appointment-25min-height}) { display: flex; align-items: center; @@ -245,7 +245,7 @@ $fluent-scheduler-agenda-time-panel-cell-padding: 8px; } @container (max-height: #{$fluent-scheduler-appointment-15min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { .dx-scheduler-appointment-title { font-size: $fluent-scheduler-appointment-10min-title-font-size; line-height: $fluent-scheduler-appointment-10min-title-line-height; @@ -259,7 +259,7 @@ $fluent-scheduler-agenda-time-panel-cell-padding: 8px; } @container (min-height: #{$fluent-scheduler-appointment-15min-height}) and (max-height: #{$fluent-scheduler-appointment-20min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { .dx-scheduler-appointment-title { font-size: $fluent-scheduler-appointment-15min-title-font-size; line-height: $fluent-scheduler-appointment-15min-title-line-height; @@ -273,7 +273,7 @@ $fluent-scheduler-agenda-time-panel-cell-padding: 8px; } @container (min-height: #{$fluent-scheduler-appointment-20min-height}) and (max-height: #{$fluent-scheduler-appointment-25min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { .dx-scheduler-appointment-recurrence-icon { right: $fluent-scheduler-appointment-20min-icon-right; } diff --git a/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss index 16f816c52700..fc82711b346a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss @@ -587,25 +587,25 @@ $generic-scheduler-agenda-group-header-padding: $generic-scheduler-agenda-time-c &.dx-scheduler-appointment-recurrence { @container (max-height: #{$generic-scheduler-appointment-15min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $generic-scheduler-appointment-10min-recurrence-padding-right; } } @container (min-height: #{$generic-scheduler-appointment-15min-height}) and (max-height: #{$generic-scheduler-appointment-20min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $generic-scheduler-appointment-15min-recurrence-padding-right; } } @container (min-height: #{$generic-scheduler-appointment-20min-height}) and (max-height: #{$generic-scheduler-appointment-25min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $generic-scheduler-appointment-20min-recurrence-padding-right; } } } - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { @container (max-height: #{$generic-scheduler-appointment-25min-height}) { display: flex; align-items: center; @@ -623,7 +623,7 @@ $generic-scheduler-agenda-group-header-padding: $generic-scheduler-agenda-time-c } @container (max-height: #{$generic-scheduler-appointment-15min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { .dx-scheduler-appointment-title { font-size: $generic-scheduler-appointment-10min-title-font-size; line-height: $generic-scheduler-appointment-10min-title-line-height; @@ -637,7 +637,7 @@ $generic-scheduler-agenda-group-header-padding: $generic-scheduler-agenda-time-c } @container (min-height: #{$generic-scheduler-appointment-15min-height}) and (max-height: #{$generic-scheduler-appointment-20min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { .dx-scheduler-appointment-title { font-size: $generic-scheduler-appointment-15min-title-font-size; line-height: $generic-scheduler-appointment-15min-title-line-height; @@ -652,7 +652,7 @@ $generic-scheduler-agenda-group-header-padding: $generic-scheduler-agenda-time-c @if $size == "compact" { @container (max-height: #{$generic-scheduler-appointment-10min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { display: none; } } diff --git a/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss index c7974b5d62bc..8a65dd85500b 100644 --- a/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss @@ -182,25 +182,25 @@ $material-scheduler-agenda-time-panel-cell-padding: 8px; &.dx-scheduler-appointment-recurrence { @container (max-height: #{$material-scheduler-appointment-15min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $material-scheduler-appointment-10min-recurrence-padding-right; } } @container (min-height: #{$material-scheduler-appointment-15min-height}) and (max-height: #{$material-scheduler-appointment-20min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $material-scheduler-appointment-15min-recurrence-padding-right; } } @container (min-height: #{$material-scheduler-appointment-20min-height}) and (max-height: #{$material-scheduler-appointment-25min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { padding-right: $material-scheduler-appointment-20min-recurrence-padding-right; } } } - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { @container (max-height: #{$material-scheduler-appointment-25min-height}) { display: flex; align-items: center; @@ -218,7 +218,7 @@ $material-scheduler-agenda-time-panel-cell-padding: 8px; } @container (max-height: #{$material-scheduler-appointment-15min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { .dx-scheduler-appointment-title { font-size: $material-scheduler-appointment-10min-title-font-size; line-height: $material-scheduler-appointment-10min-title-line-height; @@ -232,7 +232,7 @@ $material-scheduler-agenda-time-panel-cell-padding: 8px; } @container (min-height: #{$material-scheduler-appointment-15min-height}) and (max-height: #{$material-scheduler-appointment-20min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { .dx-scheduler-appointment-title { font-size: $material-scheduler-appointment-15min-title-font-size; line-height: $material-scheduler-appointment-15min-title-line-height; @@ -246,7 +246,7 @@ $material-scheduler-agenda-time-panel-cell-padding: 8px; } @container (min-height: #{$material-scheduler-appointment-20min-height}) and (max-height: #{$material-scheduler-appointment-25min-height}) { - .dx-item-content.dx-scheduler-appointment-content { + .dx-scheduler-appointment-content { .dx-scheduler-appointment-recurrence-icon { right: $material-scheduler-appointment-20min-icon-right; } diff --git a/packages/devextreme/js/__internal/scheduler/appointments/m_text_utils.ts b/packages/devextreme/js/__internal/scheduler/appointments/m_text_utils.ts index 7a0d2c7e03d4..d7628c654493 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments/m_text_utils.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments/m_text_utils.ts @@ -1,36 +1,30 @@ import dateLocalization from '@js/common/core/localization/date'; import dateUtils from '@js/core/utils/date'; +import type { TargetedAppointment, ViewType } from '../types'; + export enum DateFormatType { DATETIME = 'DATETIME', TIME = 'TIME', DATE = 'DATE', } -export const createFormattedDateText = (options) => { - const { - startDate, - endDate, - allDay, - format, - } = options; - - const formatType = format || getFormatType(startDate, endDate, allDay); - - return formatDates(startDate, endDate, formatType); -}; - -export const getFormatType = (startDate, endDate, isAllDay, isDateAndTimeView?) => { +export const getFormatType = ( + startDate: Date, + endDate: Date, + isAllDay?: boolean, + viewType?: ViewType, +): DateFormatType => { if (isAllDay) { return DateFormatType.DATE; } - if (isDateAndTimeView && dateUtils.sameDate(startDate, endDate)) { + if (viewType !== 'month' && dateUtils.sameDate(startDate, endDate)) { return DateFormatType.TIME; } return DateFormatType.DATETIME; }; -export const formatDates = (startDate, endDate, formatType) => { +export const formatDates = (startDate: Date, endDate: Date, formatType: DateFormatType): string => { const dateFormat = 'monthandday'; const timeFormat = 'shorttime'; const isSameDate = startDate.getDate() === endDate.getDate(); @@ -50,6 +44,17 @@ export const formatDates = (startDate, endDate, formatType) => { case DateFormatType.DATE: return `${dateLocalization.format(startDate, dateFormat)}${isSameDate ? '' : ` - ${dateLocalization.format(endDate, dateFormat)}`}`; default: - return undefined; + return ''; } }; + +export const createFormattedDateText = ( + targetedAppointmentData: TargetedAppointment, + format: DateFormatType, + viewType?: ViewType, +): string => { + const { displayStartDate: startDate, displayEndDate: endDate, allDay } = targetedAppointmentData; + const formatType = format ?? getFormatType(startDate, endDate, allDay, viewType); + + return formatDates(startDate, endDate, formatType); +}; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts new file mode 100644 index 000000000000..932c005811c9 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts @@ -0,0 +1,112 @@ +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import type { AppointmentAgendaViewModel } from '@ts/scheduler/view_model/types'; + +import { + AGENDA_APPOINTMENT_CLASSES, ALL_DAY_TEXT, APPOINTMENT_CLASSES, RECURRING_LABEL, +} from '../const'; +import type { BaseAppointmentProperties } from './base_appointment'; +import { BaseAppointment } from './base_appointment'; + +export interface AgendaAppointmentProperties extends BaseAppointmentProperties { + viewModel: AppointmentAgendaViewModel; +} + +export class AgendaAppointment extends BaseAppointment { + protected override applyElementClasses(): void { + super.applyElementClasses(); + + this.$element() + .toggleClass(AGENDA_APPOINTMENT_CLASSES.LAST_IN_DATE, this.option().viewModel.isLastInGroup); + } + + protected override defaultAppointmentTemplate( + $container: dxElementWrapper, + ): dxElementWrapper { + this.renderMarker($container); + this.renderInfo($container); + + return $container; + } + + private renderMarker($container: dxElementWrapper): void { + const $leftContainer = $('
') + .addClass('dx-scheduler-agenda-appointment-left-layout') + .appendTo($container); + + const $marker = $('
') + .addClass(AGENDA_APPOINTMENT_CLASSES.MARKER) + .appendTo($leftContainer); + + // eslint-disable-next-line no-void + void super.getColor().then((color) => { + if (color) { + $marker.css('backgroundColor', color); + } + }); + + if (this.isRecurring()) { + $('') + .addClass(`${APPOINTMENT_CLASSES.RECURRENCE_ICON} dx-icon-repeat`) + .attr('aria-label', RECURRING_LABEL) + .appendTo($marker); + } + } + + private renderInfo($container: dxElementWrapper): void { + const $rightContainer = $('
') + .addClass('dx-scheduler-agenda-appointment-right-layout') + .appendTo($container); + + $('
') + .addClass(APPOINTMENT_CLASSES.TITLE) + .text(this.getTitleText()) + .appendTo($rightContainer); + + const $contentDetails = $('
') + .addClass(APPOINTMENT_CLASSES.DETAILS) + .appendTo($rightContainer); + + $('
') + .addClass(APPOINTMENT_CLASSES.DATE) + .text(this.getDateText()) + .appendTo($contentDetails); + + if (this.isAllDay()) { + $('
') + .text(ALL_DAY_TEXT) + .addClass(APPOINTMENT_CLASSES.ALL_DAY_TEXT) + .prependTo($contentDetails); + } + + this.renderResourceList($contentDetails); + } + + private renderResourceList($contentDetails: dxElementWrapper): void { + const resourceManager = this.option().getResourceManager(); + + // eslint-disable-next-line no-void + void resourceManager + .getAppointmentResourcesValues(this.targetedAppointmentData) + .then((resources) => { + const container = $('
') + .addClass(AGENDA_APPOINTMENT_CLASSES.RESOURCE_LIST) + .appendTo($contentDetails); + + resources.forEach((resource) => { + const itemContainer = $('
') + .addClass(AGENDA_APPOINTMENT_CLASSES.RESOURCE_ITEM) + .appendTo(container); + + $('
') + .text(`${resource.label}:`) + .appendTo(itemContainer); + + $('
') + .addClass(AGENDA_APPOINTMENT_CLASSES.RESOURCE_ITEM_VALUE) + .text(resource.values.join(', ')) + .appendTo(itemContainer); + }); + }); + } +} diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts new file mode 100644 index 000000000000..ee867dd0d4db --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts @@ -0,0 +1,160 @@ +import messageLocalization from '@js/common/core/localization/message'; +import registerComponent from '@js/core/component_registrator'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import { when } from '@js/core/utils/deferred'; +import type { Properties as SchedulerProperties } from '@js/ui/scheduler'; +import { getPublicElement } from '@ts/core/m_element'; +import { FunctionTemplate } from '@ts/core/templates/m_function_template'; +import type { DefaultActionArgs } from '@ts/core/widget/component'; +import type { DOMComponentProperties } from '@ts/core/widget/dom_component'; +import DOMComponent from '@ts/core/widget/dom_component'; +import { createFormattedDateText, DateFormatType } from '@ts/scheduler/appointments/m_text_utils'; +import type { SafeAppointment, TargetedAppointment } from '@ts/scheduler/types'; +import type { AppointmentDataAccessor } from '@ts/scheduler/utils/data_accessor/appointment_data_accessor'; +import { getTargetedAppointment } from '@ts/scheduler/utils/get_targeted_appointment'; +import type { ResourceManager } from '@ts/scheduler/utils/resource_manager/resource_manager'; +import type { AppointmentAgendaViewModel, AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; + +import { APPOINTMENT_CLASSES } from '../const'; + +export interface AppointmentRenderedEvent extends DefaultActionArgs { + appointmentData: SafeAppointment; + targetedAppointmentData: TargetedAppointment; +} + +export interface BaseAppointmentProperties + // eslint-disable-next-line @typescript-eslint/no-explicit-any + extends DOMComponentProperties> +{ + viewModel: AppointmentItemViewModel | AppointmentAgendaViewModel; + appointmentTemplate: SchedulerProperties['appointmentTemplate']; + + onAppointmentRendered: (e: AppointmentRenderedEvent) => void; + + getResourceManager: () => ResourceManager; + getDataAccessor: () => AppointmentDataAccessor; +} + +export class BaseAppointment< + TProperties extends BaseAppointmentProperties = BaseAppointmentProperties, +> extends DOMComponent, TProperties> { + protected targetedAppointmentData!: TargetedAppointment; + + private appointmentRenderedAction!: BaseAppointmentProperties['onAppointmentRendered']; + + override _init(): void { + super._init(); + + const { viewModel } = this.option(); + this.targetedAppointmentData = getTargetedAppointment( + viewModel.itemData, + viewModel, + this.option().getDataAccessor(), + this.option().getResourceManager(), + ); + + this._templateManager.addDefaultTemplates({ + appointment: new FunctionTemplate((options) => { + this.defaultAppointmentTemplate($(options.container)); + }), + }); + + this.appointmentRenderedAction = this._createActionByOption('onAppointmentRendered', { + excludeValidators: ['disabled', 'readOnly'], + }); + } + + override _initMarkup(): void { + super._initMarkup(); + + this.resize(); + this.applyElementClasses(); + this.renderContentTemplate(); + } + + public resize(): void { } + + protected applyElementClasses(): void { + this.$element() + .addClass(APPOINTMENT_CLASSES.CONTAINER) + .toggleClass(APPOINTMENT_CLASSES.RECURRING, this.isRecurring()); + } + + protected async getColor(): Promise { + const { viewModel } = this.option(); + const resourceManager = this.option().getResourceManager(); + + const color = await resourceManager.getAppointmentColor({ + itemData: viewModel.itemData, + groupIndex: viewModel.groupIndex, + }); + + return color; + } + + protected getTitleText(): string { + const dataAccessor = this.option().getDataAccessor(); + const titleText = dataAccessor.get('text', this.targetedAppointmentData) ?? messageLocalization.format('dxScheduler-noSubject'); + + return titleText; + } + + protected getDateText(): string { + const dataAccessor = this.option().getDataAccessor(); + const allDay = dataAccessor.get('allDay', this.targetedAppointmentData); + + const dateText = createFormattedDateText( + this.targetedAppointmentData, + allDay ? DateFormatType.DATE : DateFormatType.TIME, + ); + + return dateText; + } + + protected isRecurring(): boolean { + const dataAccessor = this.option().getDataAccessor(); + const recurrenceRule = dataAccessor.get('recurrenceRule', this.targetedAppointmentData); + + return Boolean(recurrenceRule); + } + + protected isAllDay(): boolean { + const dataAccessor = this.option().getDataAccessor(); + const allDay = dataAccessor.get('allDay', this.targetedAppointmentData); + + return Boolean(allDay); + } + + private renderContentTemplate(): void { + const $content = $('
') + .addClass(APPOINTMENT_CLASSES.CONTENT) + .appendTo(this.$element()); + + const template = this._getTemplateByOption('appointmentTemplate'); + const { viewModel } = this.option(); + + const $renderPromise = template.render({ + container: getPublicElement($content), + model: { + appointmentData: viewModel.itemData, + targetedAppointmentData: this.targetedAppointmentData, + }, + }); + + when($renderPromise).done(() => { + // @ts-expect-error 'component' property is set by the action + this.appointmentRenderedAction({ + appointmentData: viewModel.itemData, + targetedAppointmentData: this.targetedAppointmentData, + }); + }); + } + + protected defaultAppointmentTemplate($container: dxElementWrapper): dxElementWrapper { + return $container; + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +registerComponent('dxSchedulerAppointment', BaseAppointment as any); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.ts new file mode 100644 index 000000000000..ee89e2dfd819 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.ts @@ -0,0 +1,72 @@ +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; + +import type { AppointmentItemViewModel } from '../../view_model/types'; +import { ALL_DAY_TEXT, APPOINTMENT_CLASSES, RECURRING_LABEL } from '../const'; +import type { BaseAppointmentProperties } from './base_appointment'; +import { BaseAppointment } from './base_appointment'; + +export interface GridAppointmentProperties extends BaseAppointmentProperties { + viewModel: AppointmentItemViewModel; +} + +export class GridAppointment extends BaseAppointment { + override _initMarkup(): void { + super._initMarkup(); + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.applyElementColor(); + } + + public resize(): void { + this.$element().css({ + height: this.option().viewModel.height, + width: this.option().viewModel.width, + top: this.option().viewModel.top, + left: this.option().viewModel.left, + }); + } + + private async applyElementColor(): Promise { + const color = await super.getColor(); + + if (color) { + this.$element().css('backgroundColor', color); + } + } + + protected override defaultAppointmentTemplate( + $container: dxElementWrapper, + ): dxElementWrapper { + $('
') + .text(this.getTitleText()) + .addClass(APPOINTMENT_CLASSES.TITLE) + .appendTo($container); + + if (this.isRecurring()) { + $('') + .addClass(`${APPOINTMENT_CLASSES.RECURRENCE_ICON} dx-icon-repeat`) + .attr('aria-label', RECURRING_LABEL) + .attr('role', 'img') + .appendTo($container); + } + + const $contentDetails = $('
') + .addClass(APPOINTMENT_CLASSES.DETAILS) + .appendTo($container); + + $('
') + .addClass(APPOINTMENT_CLASSES.DATE) + .text(this.getDateText()) + .appendTo($contentDetails); + + if (this.isAllDay()) { + $('
') + .text(ALL_DAY_TEXT) + .addClass(APPOINTMENT_CLASSES.ALL_DAY_TEXT) + .prependTo($contentDetails); + } + + return $container; + } +} diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts new file mode 100644 index 000000000000..9e4ef78ff951 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts @@ -0,0 +1,130 @@ +import dateLocalization from '@js/common/core/localization/date'; +import messageLocalization from '@js/common/core/localization/message'; +import registerComponent from '@js/core/component_registrator'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import type { Properties as SchedulerProperties } from '@js/ui/scheduler'; +import { FunctionTemplate } from '@ts/core/templates/m_function_template'; +import type { DOMComponentProperties } from '@ts/core/widget/dom_component'; +import DOMComponent from '@ts/core/widget/dom_component'; +import type { TargetedAppointment } from '@ts/scheduler/types'; +import type { AppointmentDataAccessor } from '@ts/scheduler/utils/data_accessor/appointment_data_accessor'; +import { getTargetedAppointment } from '@ts/scheduler/utils/get_targeted_appointment'; +import type { ResourceManager } from '@ts/scheduler/utils/resource_manager/resource_manager'; +import type { AppointmentCollectorViewModel, AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; +import Button from '@ts/ui/button'; + +import { APPOINTMENT_COLLECTOR_CLASSES } from './const'; + +export interface AppointmentCollectorProperties + extends DOMComponentProperties +{ + viewModel: AppointmentCollectorViewModel; + appointmentCollectorTemplate: SchedulerProperties['appointmentCollectorTemplate']; + + getResourceManager: () => ResourceManager; + getDataAccessor: () => AppointmentDataAccessor; +} + +export class AppointmentCollector + extends DOMComponent { + private targetedAppointmentsData!: TargetedAppointment[]; + + override _init(): void { + super._init(); + + const { viewModel } = this.option(); + this.targetedAppointmentsData = viewModel.items.map((item: AppointmentItemViewModel) => ( + getTargetedAppointment( + item.itemData, + item, + this.option().getDataAccessor(), + this.option().getResourceManager(), + ) + )); + + this._templateManager.addDefaultTemplates({ + appointmentCollector: new FunctionTemplate((options) => { + this.defaultAppointmentCollectorTemplate($(options.container)); + }), + }); + } + + override _initMarkup(): void { + super._initMarkup(); + + this.applyElementClasses(); + this.applyElementAria(); + this.resize(); + this.renderContentTemplate(); + } + + override dispose(): void { + super.dispose(); + } + + public resize(): void { + this.$element().css({ + top: this.option().viewModel.top, + left: this.option().viewModel.left, + }); + } + + private applyElementClasses(): void { + this.$element() + .addClass(APPOINTMENT_COLLECTOR_CLASSES.CONTAINER) + .toggleClass(APPOINTMENT_COLLECTOR_CLASSES.COMPACT, this.option().viewModel.isCompact); + } + + private applyElementAria(): void { + const localizeDate = (date: Date): string => ( + `${dateLocalization.format(date, 'monthAndDay')}, ${dateLocalization.format(date, 'year')}` + ); + + const startDateText = localizeDate(this.targetedAppointmentsData[0].displayStartDate); + const endDateText = localizeDate(this.targetedAppointmentsData[0].displayEndDate); + + const dateText = startDateText === endDateText + ? startDateText + : `${startDateText} - ${endDateText}`; + + this.$element() + .attr('aria-roledescription', dateText); + } + + private renderContentTemplate(): void { + const template = this._getTemplateByOption('appointmentCollectorTemplate'); + + this._createComponent(this.$element(), Button, { + type: 'default', + width: this.option().viewModel.width, + height: this.option().viewModel.height, + template, + onClick: () => { + // TODO: show tooltip + }, + }); + } + + private defaultAppointmentCollectorTemplate( + $container: dxElementWrapper, + ): dxElementWrapper { + const { viewModel } = this.option(); + const count = viewModel.items.length; + const text = viewModel.isCompact + ? count + // eslint-disable-next-line @typescript-eslint/no-explicit-any + : (messageLocalization.getFormatter('dxScheduler-moreAppointments') as any)(count); + + $('') + .text(text) + .appendTo($container); + + $container.addClass(APPOINTMENT_COLLECTOR_CLASSES.CONTENT); + + return $container; + } +} + +// eslint-disable-next-line +registerComponent('dxSchedulerAppointmentCollector', AppointmentCollector as any); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts new file mode 100644 index 000000000000..75025e6ecefd --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts @@ -0,0 +1,211 @@ +import registerComponent from '@js/core/component_registrator'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import type { Properties as SchedulerProperties } from '@js/ui/scheduler'; +import { domAdapter } from '@ts/core/m_dom_adapter'; +import type { DOMComponentProperties } from '@ts/core/widget/dom_component'; +import DOMComponent from '@ts/core/widget/dom_component'; +import type { OptionChanged } from '@ts/core/widget/types'; + +import type { AppointmentDataAccessor } from '../utils/data_accessor/appointment_data_accessor'; +import type { ResourceManager } from '../utils/resource_manager/resource_manager'; +import type { AppointmentDataSource } from '../view_model/m_appointment_data_source'; +import type { AppointmentViewModelPlain } from '../view_model/types'; +import { AgendaAppointment } from './appointment/agenda_appointment'; +import type { BaseAppointmentProperties } from './appointment/base_appointment'; +import { GridAppointment } from './appointment/grid_appointment'; +import { AppointmentCollector } from './appointment_collector'; +import { APPOINTMENTS_CONTAINER_CLASS } from './const'; +import type { DiffItem } from './get_view_model_diff'; +import { getViewModelDiff } from './get_view_model_diff'; +import { isCollectorViewModel as isAppointmentCollectorViewModel, isGridAppointmentViewModel } from './utils'; + +export interface AppointmentsProperties extends DOMComponentProperties { + tabIndex: number; + viewModel: AppointmentViewModelPlain[]; + items: AppointmentViewModelPlain[]; // TODO: legacy compatibility + allowDrag: boolean; + allowResize: boolean; + $allDayContainer: dxElementWrapper | null; + + appointmentTemplate: SchedulerProperties['appointmentTemplate']; + appointmentCollectorTemplate: SchedulerProperties['appointmentCollectorTemplate']; + + onAppointmentRendered: BaseAppointmentProperties['onAppointmentRendered']; + onAppointmentClick: () => void; + onAppointmentDblClick: () => void; + onAppointmentContextMenu: () => void; + + getAppointmentDataSource: () => AppointmentDataSource; + getResourceManager: () => ResourceManager; + getDataAccessor: () => AppointmentDataAccessor +} + +type AppointmentComponent = GridAppointment | AgendaAppointment | AppointmentCollector; + +export class Appointments extends DOMComponent { + private appointmentBySortIndex: Record = {}; + + private get $allDayContainer(): dxElementWrapper | null { + return this.option().$allDayContainer; + } + + private get $commonContainer(): dxElementWrapper { + return this.$element(); + } + + override _initMarkup(): void { + super._initMarkup(); + + this.$element().addClass(APPOINTMENTS_CONTAINER_CLASS); + } + + override _getDefaultOptions(): AppointmentsProperties { + return { + ...super._getDefaultOptions(), + tabIndex: 0, + viewModel: [], + allowDrag: false, + allowResize: false, + $allDayContainer: null, + appointmentTemplate: 'appointment', + appointmentCollectorTemplate: 'appointmentCollector', + onAppointmentRendered: (): void => {}, + onAppointmentClick: (): void => {}, + onAppointmentDblClick: (): void => {}, + onAppointmentContextMenu: (): void => {}, + }; + } + + override _optionChanged(args: OptionChanged): void { + switch (args.name) { + case 'items': { // TODO: legacy compatibility + this.option('viewModel', args.value); + break; + } + case 'viewModel': { + const diff = this.getViewModelDiff(args.value ?? [], args.previousValue ?? []); + this.renderViewModelDiff(diff); + break; + } + default: + break; + } + } + + public updateResizableArea(): void { /* TODO: legacy compatibility */ } + + public moveAppointmentBack(): void { /* TODO: legacy compatibility */ } + + public focus(): void { /* TODO: legacy compatibility */ } + + public _renderAppointmentTemplate(): void { /* TODO: legacy compatibility */ } + + private getAppointmentElement(sortedIndex: number): dxElementWrapper { + return this.appointmentBySortIndex[sortedIndex].$element(); + } + + private getViewModelDiff( + newViewModel: AppointmentViewModelPlain[], + oldViewModel: AppointmentViewModelPlain[], + ): DiffItem[] { + return getViewModelDiff(oldViewModel, newViewModel, this.option().getAppointmentDataSource()); + } + + private renderViewModelDiff(viewModelDiff: DiffItem[]): void { + const allDayFragment = domAdapter.createDocumentFragment(); + const commonFragment = domAdapter.createDocumentFragment(); + + const newAppointmentBySortedIndex: Record = {}; + + const isRepaintAll = viewModelDiff.every( + (item) => Boolean(item.needToAdd ?? item.needToRemove), + ); + + if (isRepaintAll) { + this.$allDayContainer?.empty(); + this.$commonContainer.empty(); + } + + viewModelDiff.forEach((diffItem) => { + const { allDay, sortedIndex } = diffItem.item; + + switch (true) { + case diffItem.needToRemove && !isRepaintAll: { + this.getAppointmentElement(sortedIndex).remove(); + break; + } + case diffItem.needToAdd: { + const fragment = allDay ? allDayFragment : commonFragment; + const appointment = this.renderAppointment(fragment, diffItem.item); + + newAppointmentBySortedIndex[sortedIndex] = appointment; + break; + } + case diffItem.needToResize: { + const appointment = this.appointmentBySortIndex[sortedIndex]; + appointment.option('viewModel', diffItem.item); + appointment.resize(); + + newAppointmentBySortedIndex[sortedIndex] = this.appointmentBySortIndex[sortedIndex]; + break; + } + default: + newAppointmentBySortedIndex[sortedIndex] = this.appointmentBySortIndex[sortedIndex]; + } + }); + + this.appointmentBySortIndex = newAppointmentBySortedIndex; + + this.$allDayContainer?.get(0).appendChild(allDayFragment); + this.$commonContainer.get(0).appendChild(commonFragment); + } + + private renderAppointment( + fragment: DocumentFragment, + appointmentViewModel: AppointmentViewModelPlain, + ): AppointmentComponent { + const $element = $('
'); + + fragment.appendChild($element.get(0)); + + if (isAppointmentCollectorViewModel(appointmentViewModel)) { + return this._createComponent($element, AppointmentCollector, { + viewModel: appointmentViewModel, + appointmentCollectorTemplate: this.option().appointmentCollectorTemplate, + getResourceManager: this.option().getResourceManager, + getDataAccessor: this.option().getDataAccessor, + }); + } + + const config = { + appointmentTemplate: this.option().appointmentTemplate, + onAppointmentRendered: this.option().onAppointmentRendered, + getResourceManager: this.option().getResourceManager, + getDataAccessor: this.option().getDataAccessor, + }; + + if (isGridAppointmentViewModel(appointmentViewModel)) { + return this._createComponent( + $element, + GridAppointment, + { + ...config, + viewModel: appointmentViewModel, + }, + ); + } + + return this._createComponent( + $element, + AgendaAppointment, + { + ...config, + viewModel: appointmentViewModel, + }, + ); + } +} + +// eslint-disable-next-line +registerComponent('dxSchedulerAppointments', Appointments as any); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/const.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/const.ts new file mode 100644 index 000000000000..838c6c783bc7 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/const.ts @@ -0,0 +1,33 @@ +import messageLocalization from '@js/common/core/localization/message'; + +export const ALL_DAY_TEXT = ` ${messageLocalization.format('dxScheduler-allDay')}: `; +export const RECURRING_LABEL = messageLocalization.format('dxScheduler-appointmentAriaLabel-recurring'); + +export const APPOINTMENTS_CONTAINER_CLASS = 'dx-scheduler-scrollable-appointments'; + +export const APPOINTMENT_COLLECTOR_CLASSES = { + CONTAINER: 'dx-scheduler-appointment-collector', + COMPACT: 'dx-scheduler-appointment-collector-compact', + CONTENT: 'dx-scheduler-appointment-collector-content', +}; + +export const APPOINTMENT_CLASSES = { + CONTAINER: 'dx-scheduler-appointment', + RECURRING: 'dx-scheduler-appointment-recurrence', + CONTENT: 'dx-scheduler-appointment-content', + ARIA_DESCRIPTION: 'dx-scheduler-appointment-aria-description', + STRIP: 'dx-scheduler-appointment-strip', + TITLE: 'dx-scheduler-appointment-title', + RECURRENCE_ICON: 'dx-scheduler-appointment-recurrence-icon', + DETAILS: 'dx-scheduler-appointment-content-details', + DATE: 'dx-scheduler-appointment-content-date', + ALL_DAY_TEXT: 'dx-scheduler-appointment-content-allday', +}; + +export const AGENDA_APPOINTMENT_CLASSES = { + LAST_IN_DATE: 'dx-scheduler-last-in-date-agenda-appointment', + MARKER: 'dx-scheduler-agenda-appointment-marker', + RESOURCE_LIST: 'dx-scheduler-appointment-resource-list', + RESOURCE_ITEM: 'dx-scheduler-appointment-resource-item', + RESOURCE_ITEM_VALUE: 'dx-scheduler-appointment-resource-item-value', +}; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/get_view_model_diff.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/get_view_model_diff.ts new file mode 100644 index 000000000000..9e2725488158 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/get_view_model_diff.ts @@ -0,0 +1,157 @@ +import { equalByValue } from '@js/core/utils/common'; + +import type { SafeAppointment } from '../types'; +import type { AppointmentDataSource } from '../view_model/m_appointment_data_source'; +import type { AppointmentViewModelPlain } from '../view_model/types'; +import { isAgendaAppointmentViewModel, isCollectorViewModel } from './utils'; + +type Item = AppointmentViewModelPlain; + +const getObjectToCompare = (item: Item, includeDimensions: boolean): object => { + if (isAgendaAppointmentViewModel(item)) { + return {}; + } + + const result = isCollectorViewModel(item) + ? { + allDay: item.allDay, + groupIndex: item.groupIndex, + items: item.items.length, + } + : { + allDay: item.allDay, + groupIndex: item.groupIndex, + direction: item.direction, + reduced: item.reduced, + partIndex: item.partIndex, + partTotalCount: item.partTotalCount, + rowIndex: item.rowIndex, + columnIndex: item.columnIndex, + }; + + return includeDimensions + ? { + ...result, + left: item.left, + top: item.top, + height: item.height, + width: item.width, + } + : result; +}; + +const isAppointmentDataChanged = ( + appointmentData: SafeAppointment, + appointmentDataSource: AppointmentDataSource, +): boolean => { + const updatedAppointmentData = appointmentDataSource.getUpdatedAppointment(); + + if (updatedAppointmentData === appointmentData) { + return true; + } + + const updateAppointmentKeys = appointmentDataSource.getUpdatedAppointmentKeys(); + + return updateAppointmentKeys.some((item) => appointmentData[item.key] === item.value); +}; + +export interface DiffItem { + needToAdd?: boolean; + needToRemove?: boolean; + needToResize?: boolean + item: Item; +} + +export function getArraysDiff(options: { + a: Item[], + b: Item[], + match: (x: Item, y: Item) => boolean, + equal: (x: Item, y: Item) => boolean, + canResize: (x: Item, y: Item) => boolean, +}): DiffItem[] { + const { + a, b, match, equal, canResize, + } = options; + const n = a.length; + const m = b.length; + + const dp: number[][] = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + + for (let i = 1; i <= n; i += 1) { + const ai = a[i - 1]; + for (let j = 1; j <= m; j += 1) { + dp[i][j] = match(ai, b[j - 1]) + ? dp[i - 1][j - 1] + 1 + : Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + + const result: DiffItem[] = []; + let i = n; + let j = m; + + while (i > 0 && j > 0) { + const ai = a[i - 1]; + const bj = b[j - 1]; + + if (match(ai, bj)) { + if (equal(ai, bj)) { + result.push({ item: bj }); + } else if (canResize(ai, bj)) { + result.push({ item: bj, needToResize: true }); + } else { + result.push({ item: ai, needToRemove: true }); + result.push({ item: bj, needToAdd: true }); + } + + i -= 1; + j -= 1; + } else if (dp[i - 1][j] >= dp[i][j - 1]) { + result.push({ item: ai, needToRemove: true }); + i -= 1; + } else { + result.push({ item: bj, needToAdd: true }); + j -= 1; + } + } + + while (i > 0) { + result.push({ item: a[i - 1], needToRemove: true }); + i -= 1; + } + while (j > 0) { + result.push({ item: b[j - 1], needToAdd: true }); + j -= 1; + } + + result.reverse(); + return result; +} + +export const getViewModelDiff = ( + oldViewModel: Item[], + newViewModel: Item[], + appointmentDataSource: AppointmentDataSource, +): DiffItem[] => { + const match = (a: Item, b: Item): boolean => ( + a.itemData === b.itemData && !isAppointmentDataChanged(b.itemData, appointmentDataSource) + ); + + const equal = (a: Item, b: Item): boolean => ( + equalByValue(getObjectToCompare(a, true), getObjectToCompare(b, true)) + ); + + const canResize = (a: Item, b: Item): boolean => ( + equalByValue(getObjectToCompare(a, false), getObjectToCompare(b, false)) + ); + + const result = getArraysDiff({ + a: oldViewModel, + b: newViewModel, + match, + equal, + canResize, + }); + + return result; +}; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/utils.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils.ts new file mode 100644 index 000000000000..fffbfd49656f --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils.ts @@ -0,0 +1,17 @@ +import type { + AppointmentAgendaViewModel, + AppointmentCollectorViewModel, + AppointmentItemViewModel, + AppointmentViewModelPlain, +} from '../view_model/types'; + +export const isAgendaAppointmentViewModel = (appointmentViewModel: AppointmentViewModelPlain): + appointmentViewModel is AppointmentAgendaViewModel => 'isAgendaModel' in appointmentViewModel; + +export const isCollectorViewModel = (appointmentViewModel: AppointmentViewModelPlain): + appointmentViewModel is AppointmentCollectorViewModel => 'isCompact' in appointmentViewModel; + +export const isGridAppointmentViewModel = (appointmentViewModel: AppointmentViewModelPlain): + appointmentViewModel is AppointmentItemViewModel => ( + !isAgendaAppointmentViewModel(appointmentViewModel) && !isCollectorViewModel(appointmentViewModel) +); diff --git a/packages/devextreme/js/__internal/scheduler/m_classes.ts b/packages/devextreme/js/__internal/scheduler/m_classes.ts index bc75e49b2287..3e06d8bc1334 100644 --- a/packages/devextreme/js/__internal/scheduler/m_classes.ts +++ b/packages/devextreme/js/__internal/scheduler/m_classes.ts @@ -1,3 +1,4 @@ +// TODO: delete unused classes when old impl is removed export const FIXED_CONTAINER_CLASS = 'dx-scheduler-fixed-appointments'; export const REDUCED_APPOINTMENT_CLASS = 'dx-scheduler-appointment-reduced'; export const REDUCED_APPOINTMENT_ICON = 'dx-scheduler-appointment-reduced-icon'; diff --git a/packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts b/packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts index 4214cf6750ec..4664d3cf98e3 100644 --- a/packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts +++ b/packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts @@ -14,6 +14,7 @@ const APPOINTMENT_COLLECTOR_CLASS = 'dx-scheduler-appointment-collector'; const COMPACT_APPOINTMENT_COLLECTOR_CLASS = `${APPOINTMENT_COLLECTOR_CLASS}-compact`; const APPOINTMENT_COLLECTOR_CONTENT_CLASS = `${APPOINTMENT_COLLECTOR_CLASS}-content`; +// TODO: delete this file when old impl is removed export class CompactAppointmentsHelper { elements: any[] = []; diff --git a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts index de3f4a0fe049..0d6c9105326f 100644 --- a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts @@ -3,6 +3,7 @@ import dateLocalization from '@js/common/core/localization/date'; import messageLocalization from '@js/common/core/localization/message'; import registerComponent from '@js/core/component_registrator'; import config from '@js/core/config'; +import type { DxElement } from '@js/core/element'; import { getPublicElement } from '@js/core/element'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; @@ -30,6 +31,7 @@ import DataHelperMixin from '@js/data_helper'; import { custom as customDialog } from '@js/ui/dialog'; import type { Appointment, AppointmentTooltipShowingEvent, FirstDayOfWeek, Occurrence, + Properties as SchedulerProperties, } from '@js/ui/scheduler'; import errors from '@js/ui/widget/ui.errors'; import { dateUtilsTs } from '@ts/core/utils/date'; @@ -41,6 +43,8 @@ import { AppointmentForm as AppointmentLegacyForm } from './appointment_popup/m_ import { ACTION_TO_APPOINTMENT, AppointmentPopup as AppointmentLegacyPopup } from './appointment_popup/m_legacy_popup'; import { AppointmentPopup } from './appointment_popup/m_popup'; import AppointmentCollection from './appointments/m_appointment_collection'; +import type { AppointmentsProperties } from './appointments_new/appointments'; +import { Appointments } from './appointments_new/appointments'; import NotifyScheduler from './base/m_widget_notify_scheduler'; import { SchedulerHeader } from './header/m_header'; import type { HeaderOptions } from './header/types'; @@ -225,6 +229,8 @@ class Scheduler extends SchedulerOptionsBaseWidget { private timeZonesPromise!: Promise; + private appointmentRenderedAction!: SchedulerProperties['onAppointmentRendered']; + get timeZoneCalculator() { if (!this._timeZoneCalculator) { this._timeZoneCalculator = createTimeZoneCalculator(this.option('timeZone')); @@ -310,18 +316,31 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.updateOption('header', name, value); break; case 'currentView': - this._appointments.option({ - items: [], - allowDrag: this.allowDragging(), - allowResize: this.allowResizing(), - itemTemplate: this.getAppointmentTemplate('appointmentTemplate'), - }); + + if (this.option('_newAppointments')) { + this._appointments.option({ + viewModel: [], + allowDrag: this.allowDragging(), + allowResize: this.allowResizing(), + // TODO: update appointmentTemplate and appointmentCollectorTemplate + }); + } else { + this._appointments.option({ + items: [], + allowDrag: this.allowDragging(), + allowResize: this.allowResizing(), + itemTemplate: this.getAppointmentTemplate('appointmentTemplate'), + }); + } this.postponeResourceLoading().done(() => { this.refreshWorkSpace(); this.header?.option(this.headerConfig()); this.setRemoteFilterIfNeeded(); - this._appointments.option('allowAllDayResize', value !== 'day'); + + if (!this.option('_newAppointments')) { + this._appointments.option('allowAllDayResize', value !== 'day'); + } }); // NOTE: // Calling postponed operations (promises) here, because when we update options with @@ -362,7 +381,10 @@ class Scheduler extends SchedulerOptionsBaseWidget { this._appointments.option('items', []); this.updateOption('workSpace', name, value); - this._appointments.repaint(); + if (!this.option('_newAppointments')) { + // TODO: no need to call repaint on new appointments + this._appointments.repaint(); + } this.setRemoteFilterIfNeeded(); this.postponeDataSourceLoading(); @@ -374,7 +396,10 @@ class Scheduler extends SchedulerOptionsBaseWidget { this._appointments.option('items', []); this.updateOption('workSpace', 'viewOffset', this.normalizeViewOffsetValue(value)); - this._appointments.repaint(); + if (!this.option('_newAppointments')) { + // TODO: no need to call repaint on new appointments + this._appointments.repaint(); + } this.setRemoteFilterIfNeeded(); this.postponeDataSourceLoading(); @@ -390,7 +415,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.actions[name] = this._createActionByOption(name); break; case 'onAppointmentRendered': - this._appointments.option('onItemRendered', this.getAppointmentRenderedAction()); + if (this.option('_newAppointments')) { + this.createAppointmentRenderedAction(); + } else { + this._appointments.option('onItemRendered', this.getAppointmentRenderedAction()); + } break; case 'onAppointmentClick': this._appointments.option('onItemClick', this._createActionByOption(name)); @@ -761,6 +790,12 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.resourceManager = new ResourceManager(this.option('resources')); this.notifyScheduler = new NotifyScheduler({ scheduler: this }); + + this.createAppointmentRenderedAction(); + } + + private createAppointmentRenderedAction() { + this.appointmentRenderedAction = this._createActionByOption('onAppointmentRendered'); } createAppointmentDataSource() { @@ -794,6 +829,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { super._initTemplates(); } + // TODO: delete this method when old impl is removed private initAppointmentTemplate() { const { expr } = this._dataAccessors; const createGetter = (property) => compileGetter(`appointmentData.${property}`); @@ -952,6 +988,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { }; } + // TODO: delete this method when old impl is removed private getAppointmentRenderedAction() { return this._createActionByOption('onAppointmentRendered', { excludeValidators: ['disabled', 'readOnly'], @@ -1002,9 +1039,30 @@ class Scheduler extends SchedulerOptionsBaseWidget { this._layoutManager = new AppointmentLayoutManager(this); - // @ts-expect-error - this._appointments = this._createComponent('
', AppointmentCollection, this.appointmentsConfig()); - this._appointments.option('itemTemplate', this.getAppointmentTemplate('appointmentTemplate')); + if (this.option('_newAppointments')) { + const appointmentsConfig: Partial = { + // TODO: set custom templates + appointmentTemplate: 'appointment', + appointmentCollectorTemplate: 'appointmentCollector', + onAppointmentRendered: (e) => { + // @ts-expect-error 'component' property is set by action + this.appointmentRenderedAction({ + appointmentElement: e.element as DxElement, + appointmentData: e.appointmentData, + targetedAppointmentData: e.targetedAppointmentData, + }); + }, + getResourceManager: () => this.resourceManager, + getAppointmentDataSource: () => this.appointmentDataSource, + getDataAccessor: () => this._dataAccessors, + }; + // @ts-expect-error + this._appointments = this._createComponent('
', Appointments, appointmentsConfig); + } else { + // @ts-expect-error + this._appointments = this._createComponent('
', AppointmentCollection, this.appointmentsConfig()); + this._appointments.option('itemTemplate', this.getAppointmentTemplate('appointmentTemplate')); + } this.appointmentTooltip = new (this.option('adaptivityEnabled') ? MobileTooltipStrategy @@ -1172,10 +1230,14 @@ class Scheduler extends SchedulerOptionsBaseWidget { this._workSpace && this.cleanWorkspace(); this.renderWorkSpace(); - this._appointments.option({ - fixedContainer: this._workSpace.getFixedContainer(), - allDayContainer: this._workSpace.getAllDayContainer(), - }); + if (this.option('_newAppointments')) { + this._appointments.option('$allDayContainer', this._workSpace.getAllDayContainer()); + } else { + this._appointments.option({ + fixedContainer: this._workSpace.getFixedContainer(), + allDayContainer: this._workSpace.getAllDayContainer(), + }); + } this.waitAsyncTemplate(() => this.workSpaceRecalculation?.resolve()); this.createAppointmentDataSource(); @@ -1440,10 +1502,15 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.renderWorkSpace(); if (this.readyToRenderAppointments) { - this._appointments.option({ - fixedContainer: this._workSpace.getFixedContainer(), - allDayContainer: this._workSpace.getAllDayContainer(), - }); + if (this.option('_newAppointments')) { + this._appointments.option('$allDayContainer', this._workSpace.getAllDayContainer()); + } else { + this._appointments.option({ + fixedContainer: this._workSpace.getFixedContainer(), + allDayContainer: this._workSpace.getAllDayContainer(), + }); + } + this.waitAsyncTemplate(() => this.workSpaceRecalculation.resolve()); } } diff --git a/packages/devextreme/js/__internal/scheduler/m_subscribes.ts b/packages/devextreme/js/__internal/scheduler/m_subscribes.ts index ceb9b6ae1979..2466ad7213fb 100644 --- a/packages/devextreme/js/__internal/scheduler/m_subscribes.ts +++ b/packages/devextreme/js/__internal/scheduler/m_subscribes.ts @@ -4,7 +4,7 @@ import $ from '@js/core/renderer'; import dateUtils from '@js/core/utils/date'; import { extend } from '@js/core/utils/extend'; -import { formatDates, getFormatType } from './appointments/m_text_utils'; +import { createFormattedDateText } from './appointments/m_text_utils'; import { getDeltaTime } from './appointments/resizing/get_delta_time'; import { VERTICAL_VIEW_TYPES } from './constants'; import type Scheduler from './m_scheduler'; @@ -117,25 +117,33 @@ const subscribes = { this.hideAppointmentTooltip(); }, + // TODO: delete this method when old impl is removed createFormattedDateText( appointment: AppointmentTooltipItem['appointment'], - targetedAppointmentRaw: AppointmentTooltipItem['targetedAppointment'], + targetedAppointment: TargetedAppointment, format?: string, ) { - const targetedAppointment = { - ...appointment, - ...targetedAppointmentRaw, - } as TargetedAppointment; - const adapter = new AppointmentAdapter(targetedAppointment, this._dataAccessors); - // pull out time zone converting from appointment adapter for knockout (T947938) - const startDate = targetedAppointment.displayStartDate || this.timeZoneCalculator.createDate(adapter.startDate, 'toGrid'); - const endDate = targetedAppointment.displayEndDate || this.timeZoneCalculator.createDate(adapter.endDate, 'toGrid'); - const formatType = format ?? getFormatType(startDate, endDate, adapter.allDay, this.currentView.type !== 'month'); - + const text = this._dataAccessors.get('text', targetedAppointment) ?? ''; return { - text: adapter.text || messageLocalization.format('dxScheduler-noSubject'), - formatDate: formatDates(startDate, endDate, formatType), + text: text || messageLocalization.format('dxScheduler-noSubject'), + formatDate: createFormattedDateText(targetedAppointment, format as any, this.currentView.type), }; + // return createFormattedDateText(targetedAppointment, format as any, this.currentView.type); + + // const targetedAppointment = { + // ...appointment, + // ...targetedAppointmentRaw, + // } as TargetedAppointment; + // const adapter = new AppointmentAdapter(targetedAppointment, this._dataAccessors); + // // pull out time zone converting from appointment adapter for knockout (T947938) + // const startDate = targetedAppointment.displayStartDate || this.timeZoneCalculator.createDate(adapter.startDate, 'toGrid'); + // const endDate = targetedAppointment.displayEndDate || this.timeZoneCalculator.createDate(adapter.endDate, 'toGrid'); + // const formatType = format ?? getFormatType(startDate, endDate, adapter.allDay, this.currentView.type !== 'month'); + + // return { + // text: adapter.text || messageLocalization.format('dxScheduler-noSubject'), + // formatDate: formatDates(startDate, endDate, formatType), + // }; }, getResizableAppointmentArea(options) { @@ -220,10 +228,12 @@ const subscribes = { return updatedEndDate; }, + // TODO: delete this method when old impl is removed renderCompactAppointments(options: CompactAppointmentOptions): dxElementWrapper { return this._compactAppointmentsHelper.render(options); }, + // TODO: delete this method when old impl is removed clearCompactAppointments() { this._compactAppointmentsHelper.clear(); }, @@ -232,6 +242,7 @@ const subscribes = { return this._workSpace._getGroupCount(); }, + // TODO: delete this method when old impl is removed mapAppointmentFields(config) { const { itemData, itemElement, targetedAppointment } = config; const targetedData = targetedAppointment || this.getTargetedAppointment(itemData, itemElement); @@ -271,6 +282,7 @@ const subscribes = { return this.forceMaxAppointmentPerCell(); }, + // TODO: delete this method when old impl is removed getTargetedAppointmentData(appointment, element) { return this.getTargetedAppointment(appointment, element); }, diff --git a/packages/devextreme/js/__internal/scheduler/types.ts b/packages/devextreme/js/__internal/scheduler/types.ts index 25f85f1d6836..2b4df4b92ec7 100644 --- a/packages/devextreme/js/__internal/scheduler/types.ts +++ b/packages/devextreme/js/__internal/scheduler/types.ts @@ -13,11 +13,9 @@ export type RenderStrategyName = 'horizontal' | 'horizontalMonth' | 'horizontalM export type FilterItemType = Record | string | number; export type HeaderCellTextFormat = string | ((date: Date) => string); -export interface SafeAppointment extends Appointment { - startDate: Date | string; - endDate: Date | string; -} -export interface TargetedAppointment extends SafeAppointment { +export interface SafeAppointment extends Appointment {} + +export interface TargetedAppointment extends Appointment { displayStartDate: Date; displayEndDate: Date; } diff --git a/packages/devextreme/js/__internal/scheduler/utils/get_targeted_appointment.ts b/packages/devextreme/js/__internal/scheduler/utils/get_targeted_appointment.ts index 40c86437608e..a8b88ab26804 100644 --- a/packages/devextreme/js/__internal/scheduler/utils/get_targeted_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/utils/get_targeted_appointment.ts @@ -10,59 +10,67 @@ import { getLeafGroupValues } from './resource_manager/group_utils'; import type { ResourceManager } from './resource_manager/resource_manager'; const setTargetedAppointmentResources = ( - rawAppointment: SafeAppointment, - settings: AppointmentAgendaViewModel | AppointmentItemViewModel, + targetedAppointment: TargetedAppointment, + appointmentViewModel: AppointmentAgendaViewModel | AppointmentItemViewModel, resourceManager: ResourceManager, ): void => { const { groups, resourceById, groupsLeafs } = resourceManager; if (groups.length) { - const cellGroups = getLeafGroupValues(groupsLeafs, settings.groupIndex); - setAppointmentGroupValues(rawAppointment, resourceById, cellGroups); + const cellGroups = getLeafGroupValues(groupsLeafs, appointmentViewModel.groupIndex); + setAppointmentGroupValues(targetedAppointment, resourceById, cellGroups); } }; +// TODO: remove first parameter when old impl is removed export const getTargetedAppointmentFromInfo = ( rawAppointment: SafeAppointment, - settings: AppointmentAgendaViewModel | AppointmentItemViewModel, + appointmentViewModel: AppointmentAgendaViewModel | AppointmentItemViewModel, dataAccessor: AppointmentDataAccessor, resourceManager: ResourceManager, usePartialDates = false, ): TargetedAppointment => { - const { info } = settings; + const { itemData, info } = appointmentViewModel; - const rawTargetedAppointment = { ...rawAppointment } as TargetedAppointment; - dataAccessor.set('startDate', rawTargetedAppointment, new Date(info.sourceAppointment.startDate)); - dataAccessor.set('endDate', rawTargetedAppointment, new Date(info.sourceAppointment.endDate)); const displayDates = usePartialDates && 'partialDates' in info ? info.partialDates : info.appointment; - rawTargetedAppointment.displayStartDate = new Date(displayDates.startDate); - rawTargetedAppointment.displayEndDate = new Date(displayDates.endDate); - setTargetedAppointmentResources(rawTargetedAppointment, settings, resourceManager); - return rawTargetedAppointment; + const targetedAppointment: TargetedAppointment = { + ...itemData, + displayStartDate: new Date(displayDates.startDate), + displayEndDate: new Date(displayDates.endDate), + }; + + dataAccessor.set('startDate', targetedAppointment, new Date(info.sourceAppointment.startDate)); + dataAccessor.set('endDate', targetedAppointment, new Date(info.sourceAppointment.endDate)); + + setTargetedAppointmentResources(targetedAppointment, appointmentViewModel, resourceManager); + + return targetedAppointment; }; +// TODO: remove first parameter when old impl is removed export const getTargetedAppointment = ( rawAppointment: SafeAppointment, - settings: AppointmentViewModelPlain, + appointmentViewModel: AppointmentViewModelPlain, dataAccessor: AppointmentDataAccessor, resourceManager: ResourceManager, ): TargetedAppointment => { - const startDate = dataAccessor.get('startDate', rawAppointment); - const endDate = dataAccessor.get('endDate', rawAppointment); + const { itemData } = appointmentViewModel; + const startDate = dataAccessor.get('startDate', itemData); + const endDate = dataAccessor.get('endDate', itemData); - if (!('info' in settings)) { + if (!('info' in appointmentViewModel)) { return { - ...rawAppointment, + ...itemData, displayStartDate: startDate, displayEndDate: endDate, }; } return getTargetedAppointmentFromInfo( - rawAppointment, - settings, + itemData, + appointmentViewModel, dataAccessor, resourceManager, ); From 9e03e0c9d8c4de704687ed938c8aa04faa95e11a Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Tue, 24 Mar 2026 13:24:10 +0800 Subject: [PATCH 2/8] fix tests --- .../scheduler/appointments/m_text_utils.ts | 39 ++++++------ .../appointment/base_appointment.ts | 34 +++++------ .../appointments_new/appointment_collector.ts | 32 +++------- .../appointments_new/appointments.ts | 24 +++++++- .../appointments_new/get_view_model_diff.ts | 2 +- .../utils/appointment_text.ts | 60 +++++++++++++++++++ .../utils/get_targeted_appointment.ts | 48 +++++++++++++++ .../{utils.ts => utils/type_helpers.ts} | 2 +- .../js/__internal/scheduler/m_scheduler.ts | 1 + .../js/__internal/scheduler/m_subscribes.ts | 36 +++++------ .../utils/get_targeted_appointment.ts | 49 +++++++-------- 11 files changed, 207 insertions(+), 120 deletions(-) create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/utils/appointment_text.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.ts rename packages/devextreme/js/__internal/scheduler/appointments_new/{utils.ts => utils/type_helpers.ts} (95%) diff --git a/packages/devextreme/js/__internal/scheduler/appointments/m_text_utils.ts b/packages/devextreme/js/__internal/scheduler/appointments/m_text_utils.ts index d7628c654493..7a0d2c7e03d4 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments/m_text_utils.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments/m_text_utils.ts @@ -1,30 +1,36 @@ import dateLocalization from '@js/common/core/localization/date'; import dateUtils from '@js/core/utils/date'; -import type { TargetedAppointment, ViewType } from '../types'; - export enum DateFormatType { DATETIME = 'DATETIME', TIME = 'TIME', DATE = 'DATE', } -export const getFormatType = ( - startDate: Date, - endDate: Date, - isAllDay?: boolean, - viewType?: ViewType, -): DateFormatType => { +export const createFormattedDateText = (options) => { + const { + startDate, + endDate, + allDay, + format, + } = options; + + const formatType = format || getFormatType(startDate, endDate, allDay); + + return formatDates(startDate, endDate, formatType); +}; + +export const getFormatType = (startDate, endDate, isAllDay, isDateAndTimeView?) => { if (isAllDay) { return DateFormatType.DATE; } - if (viewType !== 'month' && dateUtils.sameDate(startDate, endDate)) { + if (isDateAndTimeView && dateUtils.sameDate(startDate, endDate)) { return DateFormatType.TIME; } return DateFormatType.DATETIME; }; -export const formatDates = (startDate: Date, endDate: Date, formatType: DateFormatType): string => { +export const formatDates = (startDate, endDate, formatType) => { const dateFormat = 'monthandday'; const timeFormat = 'shorttime'; const isSameDate = startDate.getDate() === endDate.getDate(); @@ -44,17 +50,6 @@ export const formatDates = (startDate: Date, endDate: Date, formatType: DateForm case DateFormatType.DATE: return `${dateLocalization.format(startDate, dateFormat)}${isSameDate ? '' : ` - ${dateLocalization.format(endDate, dateFormat)}`}`; default: - return ''; + return undefined; } }; - -export const createFormattedDateText = ( - targetedAppointmentData: TargetedAppointment, - format: DateFormatType, - viewType?: ViewType, -): string => { - const { displayStartDate: startDate, displayEndDate: endDate, allDay } = targetedAppointmentData; - const formatType = format ?? getFormatType(startDate, endDate, allDay, viewType); - - return formatDates(startDate, endDate, formatType); -}; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts index ee867dd0d4db..dc85ba6f9773 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts @@ -9,14 +9,13 @@ import { FunctionTemplate } from '@ts/core/templates/m_function_template'; import type { DefaultActionArgs } from '@ts/core/widget/component'; import type { DOMComponentProperties } from '@ts/core/widget/dom_component'; import DOMComponent from '@ts/core/widget/dom_component'; -import { createFormattedDateText, DateFormatType } from '@ts/scheduler/appointments/m_text_utils'; import type { SafeAppointment, TargetedAppointment } from '@ts/scheduler/types'; import type { AppointmentDataAccessor } from '@ts/scheduler/utils/data_accessor/appointment_data_accessor'; -import { getTargetedAppointment } from '@ts/scheduler/utils/get_targeted_appointment'; import type { ResourceManager } from '@ts/scheduler/utils/resource_manager/resource_manager'; import type { AppointmentAgendaViewModel, AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; import { APPOINTMENT_CLASSES } from '../const'; +import { DateFormatType, getDateTextFromTargetAppointment } from '../utils/appointment_text'; export interface AppointmentRenderedEvent extends DefaultActionArgs { appointmentData: SafeAppointment; @@ -28,6 +27,7 @@ export interface BaseAppointmentProperties extends DOMComponentProperties> { viewModel: AppointmentItemViewModel | AppointmentAgendaViewModel; + targetedAppointmentData: TargetedAppointment; appointmentTemplate: SchedulerProperties['appointmentTemplate']; onAppointmentRendered: (e: AppointmentRenderedEvent) => void; @@ -39,21 +39,19 @@ export interface BaseAppointmentProperties export class BaseAppointment< TProperties extends BaseAppointmentProperties = BaseAppointmentProperties, > extends DOMComponent, TProperties> { - protected targetedAppointmentData!: TargetedAppointment; + protected get targetedAppointmentData(): TargetedAppointment { + return this.option().targetedAppointmentData; + } + + protected get appointmentData(): SafeAppointment { + return this.option().viewModel.itemData; + } private appointmentRenderedAction!: BaseAppointmentProperties['onAppointmentRendered']; override _init(): void { super._init(); - const { viewModel } = this.option(); - this.targetedAppointmentData = getTargetedAppointment( - viewModel.itemData, - viewModel, - this.option().getDataAccessor(), - this.option().getResourceManager(), - ); - this._templateManager.addDefaultTemplates({ appointment: new FunctionTemplate((options) => { this.defaultAppointmentTemplate($(options.container)); @@ -95,18 +93,16 @@ export class BaseAppointment< protected getTitleText(): string { const dataAccessor = this.option().getDataAccessor(); - const titleText = dataAccessor.get('text', this.targetedAppointmentData) ?? messageLocalization.format('dxScheduler-noSubject'); + const titleText = dataAccessor.get('text', this.appointmentData) + ?? messageLocalization.format('dxScheduler-noSubject'); return titleText; } protected getDateText(): string { - const dataAccessor = this.option().getDataAccessor(); - const allDay = dataAccessor.get('allDay', this.targetedAppointmentData); - - const dateText = createFormattedDateText( + const dateText = getDateTextFromTargetAppointment( this.targetedAppointmentData, - allDay ? DateFormatType.DATE : DateFormatType.TIME, + this.isAllDay() ? DateFormatType.DATE : DateFormatType.TIME, ); return dateText; @@ -114,14 +110,14 @@ export class BaseAppointment< protected isRecurring(): boolean { const dataAccessor = this.option().getDataAccessor(); - const recurrenceRule = dataAccessor.get('recurrenceRule', this.targetedAppointmentData); + const recurrenceRule = dataAccessor.get('recurrenceRule', this.appointmentData); return Boolean(recurrenceRule); } protected isAllDay(): boolean { const dataAccessor = this.option().getDataAccessor(); - const allDay = dataAccessor.get('allDay', this.targetedAppointmentData); + const allDay = dataAccessor.get('allDay', this.appointmentData); return Boolean(allDay); } diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts index 9e4ef78ff951..6a5f2d12adbc 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts @@ -3,16 +3,13 @@ import messageLocalization from '@js/common/core/localization/message'; import registerComponent from '@js/core/component_registrator'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; +import Button from '@js/ui/button'; import type { Properties as SchedulerProperties } from '@js/ui/scheduler'; import { FunctionTemplate } from '@ts/core/templates/m_function_template'; import type { DOMComponentProperties } from '@ts/core/widget/dom_component'; import DOMComponent from '@ts/core/widget/dom_component'; import type { TargetedAppointment } from '@ts/scheduler/types'; -import type { AppointmentDataAccessor } from '@ts/scheduler/utils/data_accessor/appointment_data_accessor'; -import { getTargetedAppointment } from '@ts/scheduler/utils/get_targeted_appointment'; -import type { ResourceManager } from '@ts/scheduler/utils/resource_manager/resource_manager'; -import type { AppointmentCollectorViewModel, AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; -import Button from '@ts/ui/button'; +import type { AppointmentCollectorViewModel } from '@ts/scheduler/view_model/types'; import { APPOINTMENT_COLLECTOR_CLASSES } from './const'; @@ -20,29 +17,15 @@ export interface AppointmentCollectorProperties extends DOMComponentProperties { viewModel: AppointmentCollectorViewModel; + targetedAppointmentData: TargetedAppointment; appointmentCollectorTemplate: SchedulerProperties['appointmentCollectorTemplate']; - - getResourceManager: () => ResourceManager; - getDataAccessor: () => AppointmentDataAccessor; } export class AppointmentCollector extends DOMComponent { - private targetedAppointmentsData!: TargetedAppointment[]; - override _init(): void { super._init(); - const { viewModel } = this.option(); - this.targetedAppointmentsData = viewModel.items.map((item: AppointmentItemViewModel) => ( - getTargetedAppointment( - item.itemData, - item, - this.option().getDataAccessor(), - this.option().getResourceManager(), - ) - )); - this._templateManager.addDefaultTemplates({ appointmentCollector: new FunctionTemplate((options) => { this.defaultAppointmentCollectorTemplate($(options.container)); @@ -81,8 +64,10 @@ export class AppointmentCollector `${dateLocalization.format(date, 'monthAndDay')}, ${dateLocalization.format(date, 'year')}` ); - const startDateText = localizeDate(this.targetedAppointmentsData[0].displayStartDate); - const endDateText = localizeDate(this.targetedAppointmentsData[0].displayEndDate); + const { targetedAppointmentData } = this.option(); + + const startDateText = localizeDate(targetedAppointmentData.displayStartDate); + const endDateText = localizeDate(targetedAppointmentData.displayEndDate); const dateText = startDateText === endDateText ? startDateText @@ -100,9 +85,6 @@ export class AppointmentCollector width: this.option().viewModel.width, height: this.option().viewModel.height, template, - onClick: () => { - // TODO: show tooltip - }, }); } diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts index 75025e6ecefd..05200aa1c3cb 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts @@ -7,6 +7,7 @@ import type { DOMComponentProperties } from '@ts/core/widget/dom_component'; import DOMComponent from '@ts/core/widget/dom_component'; import type { OptionChanged } from '@ts/core/widget/types'; +import type { TargetedAppointment } from '../types'; import type { AppointmentDataAccessor } from '../utils/data_accessor/appointment_data_accessor'; import type { ResourceManager } from '../utils/resource_manager/resource_manager'; import type { AppointmentDataSource } from '../view_model/m_appointment_data_source'; @@ -18,7 +19,8 @@ import { AppointmentCollector } from './appointment_collector'; import { APPOINTMENTS_CONTAINER_CLASS } from './const'; import type { DiffItem } from './get_view_model_diff'; import { getViewModelDiff } from './get_view_model_diff'; -import { isCollectorViewModel as isAppointmentCollectorViewModel, isGridAppointmentViewModel } from './utils'; +import { getTargetedAppointment } from './utils/get_targeted_appointment'; +import { isCollectorViewModel as isAppointmentCollectorViewModel, isGridAppointmentViewModel } from './utils/type_helpers'; export interface AppointmentsProperties extends DOMComponentProperties { tabIndex: number; @@ -169,17 +171,19 @@ export class Appointments extends DOMComponent { + if (isAllDay) { + return DateFormatType.DATE; + } + if (viewType !== 'month' && dateUtils.sameDate(startDate, endDate)) { + return DateFormatType.TIME; + } + return DateFormatType.DATETIME; +}; + +export const getDateText = (startDate: Date, endDate: Date, formatType: DateFormatType): string => { + const dateFormat = 'monthandday'; + const timeFormat = 'shorttime'; + const isSameDate = startDate.getDate() === endDate.getDate(); + + switch (formatType) { + case DateFormatType.DATETIME: + return [ + dateLocalization.format(startDate, dateFormat), + ' ', + dateLocalization.format(startDate, timeFormat), + ' - ', + isSameDate ? '' : `${dateLocalization.format(endDate, dateFormat)} `, + dateLocalization.format(endDate, timeFormat), + ].join(''); + case DateFormatType.TIME: + return `${dateLocalization.format(startDate, timeFormat)} - ${dateLocalization.format(endDate, timeFormat)}`; + case DateFormatType.DATE: + return `${dateLocalization.format(startDate, dateFormat)}${isSameDate ? '' : ` - ${dateLocalization.format(endDate, dateFormat)}`}`; + default: + return ''; + } +}; + +export const getDateTextFromTargetAppointment = ( + targetedAppointmentData: TargetedAppointment, + format: DateFormatType, + viewType?: ViewType, +): string => { + const { displayStartDate: startDate, displayEndDate: endDate, allDay } = targetedAppointmentData; + const formatType = format ?? getFormatType(startDate, endDate, allDay, viewType); + + return getDateText(startDate, endDate, formatType); +}; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.ts new file mode 100644 index 000000000000..3f535cc32b5a --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.ts @@ -0,0 +1,48 @@ +import type { AppointmentDataAccessor } from '@ts/scheduler/utils/data_accessor/appointment_data_accessor'; +import { setAppointmentGroupValues } from '@ts/scheduler/utils/resource_manager/appointment_groups_utils'; +import { getLeafGroupValues } from '@ts/scheduler/utils/resource_manager/group_utils'; +import type { ResourceManager } from '@ts/scheduler/utils/resource_manager/resource_manager'; + +import type { TargetedAppointment } from '../../types'; +import type { + AppointmentAgendaViewModel, + AppointmentItemViewModel, +} from '../../view_model/types'; + +const setTargetedAppointmentResources = ( + targetedAppointment: TargetedAppointment, + appointmentViewModel: AppointmentAgendaViewModel | AppointmentItemViewModel, + resourceManager: ResourceManager, +): void => { + const { groups, resourceById, groupsLeafs } = resourceManager; + if (groups.length) { + const cellGroups = getLeafGroupValues(groupsLeafs, appointmentViewModel.groupIndex); + setAppointmentGroupValues(targetedAppointment, resourceById, cellGroups); + } +}; + +// what's the diff between info.appointment and info.sourceAppointment +export const getTargetedAppointment = ( + appointmentViewModel: AppointmentItemViewModel | AppointmentAgendaViewModel, + dataAccessor: AppointmentDataAccessor, + resourceManager: ResourceManager, +): TargetedAppointment => { + const { info, itemData } = appointmentViewModel; + + const displayDates = 'partialDates' in info + ? info.partialDates + : info.appointment; + + const targetedAppointment: TargetedAppointment = { + ...itemData, + displayStartDate: new Date(displayDates.startDate), + displayEndDate: new Date(displayDates.endDate), + }; + + dataAccessor.set('startDate', targetedAppointment, new Date(info.sourceAppointment.startDate)); + dataAccessor.set('endDate', targetedAppointment, new Date(info.sourceAppointment.endDate)); + + setTargetedAppointmentResources(targetedAppointment, appointmentViewModel, resourceManager); + + return targetedAppointment; +}; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/utils.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/type_helpers.ts similarity index 95% rename from packages/devextreme/js/__internal/scheduler/appointments_new/utils.ts rename to packages/devextreme/js/__internal/scheduler/appointments_new/utils/type_helpers.ts index fffbfd49656f..9bca4448bf3d 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/utils.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/type_helpers.ts @@ -3,7 +3,7 @@ import type { AppointmentCollectorViewModel, AppointmentItemViewModel, AppointmentViewModelPlain, -} from '../view_model/types'; +} from '../../view_model/types'; export const isAgendaAppointmentViewModel = (appointmentViewModel: AppointmentViewModelPlain): appointmentViewModel is AppointmentAgendaViewModel => 'isAgendaModel' in appointmentViewModel; diff --git a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts index 0d6c9105326f..5cc0d7265177 100644 --- a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts @@ -1762,6 +1762,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { return rawResult; } + // TODO: delete this method when old impl is removed getTargetedAppointment(appointment: SafeAppointment, element: dxElementWrapper): TargetedAppointment { const settings = utils.dataAccessors.getAppointmentSettings(element)!; return getTargetedAppointment( diff --git a/packages/devextreme/js/__internal/scheduler/m_subscribes.ts b/packages/devextreme/js/__internal/scheduler/m_subscribes.ts index 2466ad7213fb..5c1e880c819a 100644 --- a/packages/devextreme/js/__internal/scheduler/m_subscribes.ts +++ b/packages/devextreme/js/__internal/scheduler/m_subscribes.ts @@ -4,8 +4,8 @@ import $ from '@js/core/renderer'; import dateUtils from '@js/core/utils/date'; import { extend } from '@js/core/utils/extend'; -import { createFormattedDateText } from './appointments/m_text_utils'; import { getDeltaTime } from './appointments/resizing/get_delta_time'; +import { getDateText, getFormatType } from './appointments_new/utils/appointment_text'; import { VERTICAL_VIEW_TYPES } from './constants'; import type Scheduler from './m_scheduler'; import { utils } from './m_utils'; @@ -120,30 +120,24 @@ const subscribes = { // TODO: delete this method when old impl is removed createFormattedDateText( appointment: AppointmentTooltipItem['appointment'], - targetedAppointment: TargetedAppointment, + targetedAppointmentRaw: TargetedAppointment, format?: string, ) { - const text = this._dataAccessors.get('text', targetedAppointment) ?? ''; + const targetedAppointment = { + ...appointment, + ...targetedAppointmentRaw, + } as TargetedAppointment; + + const adapter = new AppointmentAdapter(targetedAppointment, this._dataAccessors); + // pull out time zone converting from appointment adapter for knockout (T947938) + const startDate = targetedAppointment.displayStartDate || this.timeZoneCalculator.createDate(adapter.startDate, 'toGrid'); + const endDate = targetedAppointment.displayEndDate || this.timeZoneCalculator.createDate(adapter.endDate, 'toGrid'); + const formatType = format ?? getFormatType(startDate, endDate, adapter.allDay, this.currentView.type); + return { - text: text || messageLocalization.format('dxScheduler-noSubject'), - formatDate: createFormattedDateText(targetedAppointment, format as any, this.currentView.type), + text: adapter.text || messageLocalization.format('dxScheduler-noSubject'), + formatDate: getDateText(startDate, endDate, formatType as any), }; - // return createFormattedDateText(targetedAppointment, format as any, this.currentView.type); - - // const targetedAppointment = { - // ...appointment, - // ...targetedAppointmentRaw, - // } as TargetedAppointment; - // const adapter = new AppointmentAdapter(targetedAppointment, this._dataAccessors); - // // pull out time zone converting from appointment adapter for knockout (T947938) - // const startDate = targetedAppointment.displayStartDate || this.timeZoneCalculator.createDate(adapter.startDate, 'toGrid'); - // const endDate = targetedAppointment.displayEndDate || this.timeZoneCalculator.createDate(adapter.endDate, 'toGrid'); - // const formatType = format ?? getFormatType(startDate, endDate, adapter.allDay, this.currentView.type !== 'month'); - - // return { - // text: adapter.text || messageLocalization.format('dxScheduler-noSubject'), - // formatDate: formatDates(startDate, endDate, formatType), - // }; }, getResizableAppointmentArea(options) { diff --git a/packages/devextreme/js/__internal/scheduler/utils/get_targeted_appointment.ts b/packages/devextreme/js/__internal/scheduler/utils/get_targeted_appointment.ts index a8b88ab26804..5df7c6eeaf29 100644 --- a/packages/devextreme/js/__internal/scheduler/utils/get_targeted_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/utils/get_targeted_appointment.ts @@ -1,3 +1,4 @@ +// TODO: remove this file after old impl is deleted import type { SafeAppointment, TargetedAppointment } from '../types'; import type { AppointmentAgendaViewModel, @@ -10,67 +11,59 @@ import { getLeafGroupValues } from './resource_manager/group_utils'; import type { ResourceManager } from './resource_manager/resource_manager'; const setTargetedAppointmentResources = ( - targetedAppointment: TargetedAppointment, - appointmentViewModel: AppointmentAgendaViewModel | AppointmentItemViewModel, + rawAppointment: SafeAppointment, + settings: AppointmentAgendaViewModel | AppointmentItemViewModel, resourceManager: ResourceManager, ): void => { const { groups, resourceById, groupsLeafs } = resourceManager; if (groups.length) { - const cellGroups = getLeafGroupValues(groupsLeafs, appointmentViewModel.groupIndex); - setAppointmentGroupValues(targetedAppointment, resourceById, cellGroups); + const cellGroups = getLeafGroupValues(groupsLeafs, settings.groupIndex); + setAppointmentGroupValues(rawAppointment, resourceById, cellGroups); } }; -// TODO: remove first parameter when old impl is removed export const getTargetedAppointmentFromInfo = ( rawAppointment: SafeAppointment, - appointmentViewModel: AppointmentAgendaViewModel | AppointmentItemViewModel, + settings: AppointmentAgendaViewModel | AppointmentItemViewModel, dataAccessor: AppointmentDataAccessor, resourceManager: ResourceManager, usePartialDates = false, ): TargetedAppointment => { - const { itemData, info } = appointmentViewModel; + const { info } = settings; + const rawTargetedAppointment = { ...rawAppointment } as TargetedAppointment; + dataAccessor.set('startDate', rawTargetedAppointment, new Date(info.sourceAppointment.startDate)); + dataAccessor.set('endDate', rawTargetedAppointment, new Date(info.sourceAppointment.endDate)); const displayDates = usePartialDates && 'partialDates' in info ? info.partialDates : info.appointment; + rawTargetedAppointment.displayStartDate = new Date(displayDates.startDate); + rawTargetedAppointment.displayEndDate = new Date(displayDates.endDate); + setTargetedAppointmentResources(rawTargetedAppointment, settings, resourceManager); - const targetedAppointment: TargetedAppointment = { - ...itemData, - displayStartDate: new Date(displayDates.startDate), - displayEndDate: new Date(displayDates.endDate), - }; - - dataAccessor.set('startDate', targetedAppointment, new Date(info.sourceAppointment.startDate)); - dataAccessor.set('endDate', targetedAppointment, new Date(info.sourceAppointment.endDate)); - - setTargetedAppointmentResources(targetedAppointment, appointmentViewModel, resourceManager); - - return targetedAppointment; + return rawTargetedAppointment; }; -// TODO: remove first parameter when old impl is removed export const getTargetedAppointment = ( rawAppointment: SafeAppointment, - appointmentViewModel: AppointmentViewModelPlain, + settings: AppointmentViewModelPlain, dataAccessor: AppointmentDataAccessor, resourceManager: ResourceManager, ): TargetedAppointment => { - const { itemData } = appointmentViewModel; - const startDate = dataAccessor.get('startDate', itemData); - const endDate = dataAccessor.get('endDate', itemData); + const startDate = dataAccessor.get('startDate', rawAppointment); + const endDate = dataAccessor.get('endDate', rawAppointment); - if (!('info' in appointmentViewModel)) { + if (!('info' in settings)) { return { - ...itemData, + ...rawAppointment, displayStartDate: startDate, displayEndDate: endDate, }; } return getTargetedAppointmentFromInfo( - itemData, - appointmentViewModel, + rawAppointment, + settings, dataAccessor, resourceManager, ); From f291e0a5778abe21614cdd11fe715be2ca5ff7f9 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Tue, 24 Mar 2026 16:17:22 +0800 Subject: [PATCH 3/8] fix tests --- .../appointment/agenda_appointment.ts | 2 +- .../appointment/base_appointment.ts | 18 +++++++++++----- .../appointment/grid_appointment.ts | 21 +++++++++++++------ .../appointments_new/appointment_collector.ts | 2 +- .../appointments_new/appointments.ts | 5 +++-- .../scheduler/appointments_new/const.ts | 7 ++++++- .../{appointment_text.ts => get_date_text.ts} | 6 +++--- .../js/__internal/scheduler/m_subscribes.ts | 4 ++-- 8 files changed, 44 insertions(+), 21 deletions(-) rename packages/devextreme/js/__internal/scheduler/appointments_new/utils/{appointment_text.ts => get_date_text.ts} (92%) diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts index 932c005811c9..1d72778d2258 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts @@ -39,7 +39,7 @@ export class AgendaAppointment extends BaseAppointment { + void super.getResourceColor().then((color) => { if (color) { $marker.css('backgroundColor', color); } diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts index dc85ba6f9773..db64012ae368 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts @@ -14,8 +14,8 @@ import type { AppointmentDataAccessor } from '@ts/scheduler/utils/data_accessor/ import type { ResourceManager } from '@ts/scheduler/utils/resource_manager/resource_manager'; import type { AppointmentAgendaViewModel, AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; -import { APPOINTMENT_CLASSES } from '../const'; -import { DateFormatType, getDateTextFromTargetAppointment } from '../utils/appointment_text'; +import { APPOINTMENT_CLASSES, APPOINTMENT_TYPE_CLASSES } from '../const'; +import { DateFormatType, getDateTextFromTargetAppointment } from '../utils/get_date_text'; export interface AppointmentRenderedEvent extends DefaultActionArgs { appointmentData: SafeAppointment; @@ -68,6 +68,7 @@ export class BaseAppointment< this.resize(); this.applyElementClasses(); + this.applyAria(); this.renderContentTemplate(); } @@ -76,10 +77,16 @@ export class BaseAppointment< protected applyElementClasses(): void { this.$element() .addClass(APPOINTMENT_CLASSES.CONTAINER) - .toggleClass(APPOINTMENT_CLASSES.RECURRING, this.isRecurring()); + .toggleClass(APPOINTMENT_TYPE_CLASSES.RECURRING, this.isRecurring()) + .toggleClass(APPOINTMENT_TYPE_CLASSES.ALL_DAY, this.isAllDay()); } - protected async getColor(): Promise { + protected applyAria(): void { + this.$element() + .attr('role', 'button'); + } + + protected async getResourceColor(): Promise { const { viewModel } = this.option(); const resourceManager = this.option().getResourceManager(); @@ -152,5 +159,6 @@ export class BaseAppointment< } } +// TODO: rename to dxSchedulerAppointment when old impl is removed // eslint-disable-next-line @typescript-eslint/no-explicit-any -registerComponent('dxSchedulerAppointment', BaseAppointment as any); +registerComponent('dxSchedulerNewAppointment', BaseAppointment as any); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.ts index ee89e2dfd819..f99214dca101 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.ts @@ -2,7 +2,9 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import type { AppointmentItemViewModel } from '../../view_model/types'; -import { ALL_DAY_TEXT, APPOINTMENT_CLASSES, RECURRING_LABEL } from '../const'; +import { + ALL_DAY_TEXT, APPOINTMENT_CLASSES, APPOINTMENT_TYPE_CLASSES, RECURRING_LABEL, +} from '../const'; import type { BaseAppointmentProperties } from './base_appointment'; import { BaseAppointment } from './base_appointment'; @@ -27,12 +29,11 @@ export class GridAppointment extends BaseAppointment }); } - private async applyElementColor(): Promise { - const color = await super.getColor(); + protected override applyElementClasses(): void { + super.applyElementClasses(); - if (color) { - this.$element().css('backgroundColor', color); - } + this.$element() + .toggleClass(APPOINTMENT_TYPE_CLASSES.EMPTY, this.option().viewModel.empty); } protected override defaultAppointmentTemplate( @@ -69,4 +70,12 @@ export class GridAppointment extends BaseAppointment return $container; } + + private async applyElementColor(): Promise { + const color = await super.getResourceColor(); + + if (color) { + this.$element().css('backgroundColor', color); + } + } } diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts index 6a5f2d12adbc..b9637e1363b9 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts @@ -109,4 +109,4 @@ export class AppointmentCollector } // eslint-disable-next-line -registerComponent('dxSchedulerAppointmentCollector', AppointmentCollector as any); +registerComponent('dxSchedulerNewAppointmentCollector', AppointmentCollector as any); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts index 05200aa1c3cb..2f052eb73d9d 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts @@ -40,7 +40,7 @@ export interface AppointmentsProperties extends DOMComponentProperties AppointmentDataSource; getResourceManager: () => ResourceManager; - getDataAccessor: () => AppointmentDataAccessor + getDataAccessor: () => AppointmentDataAccessor; } type AppointmentComponent = GridAppointment | AgendaAppointment | AppointmentCollector; @@ -225,5 +225,6 @@ export class Appointments extends DOMComponent: rename to dxSchedulerAppointments when old impl is removed // eslint-disable-next-line -registerComponent('dxSchedulerAppointments', Appointments as any); +registerComponent('dxSchedulerNewAppointments', Appointments as any); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/const.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/const.ts index 838c6c783bc7..60e09c8a8aca 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/const.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/const.ts @@ -11,9 +11,14 @@ export const APPOINTMENT_COLLECTOR_CLASSES = { CONTENT: 'dx-scheduler-appointment-collector-content', }; +export const APPOINTMENT_TYPE_CLASSES = { + EMPTY: 'dx-scheduler-appointment-empty', + ALL_DAY: 'dx-scheduler-all-day-appointment', + RECURRING: 'dx-scheduler-appointment-recurrence', +}; + export const APPOINTMENT_CLASSES = { CONTAINER: 'dx-scheduler-appointment', - RECURRING: 'dx-scheduler-appointment-recurrence', CONTENT: 'dx-scheduler-appointment-content', ARIA_DESCRIPTION: 'dx-scheduler-appointment-aria-description', STRIP: 'dx-scheduler-appointment-strip', diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/appointment_text.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_date_text.ts similarity index 92% rename from packages/devextreme/js/__internal/scheduler/appointments_new/utils/appointment_text.ts rename to packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_date_text.ts index 512d6b5cb613..1af8ecc09ea7 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/appointment_text.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_date_text.ts @@ -9,7 +9,7 @@ export enum DateFormatType { DATE = 'DATE', } -export const getFormatType = ( +export const getDateFormatType = ( startDate: Date, endDate: Date, isAllDay?: boolean, @@ -50,11 +50,11 @@ export const getDateText = (startDate: Date, endDate: Date, formatType: DateForm export const getDateTextFromTargetAppointment = ( targetedAppointmentData: TargetedAppointment, - format: DateFormatType, + format?: DateFormatType, viewType?: ViewType, ): string => { const { displayStartDate: startDate, displayEndDate: endDate, allDay } = targetedAppointmentData; - const formatType = format ?? getFormatType(startDate, endDate, allDay, viewType); + const formatType = format ?? getDateFormatType(startDate, endDate, allDay, viewType); return getDateText(startDate, endDate, formatType); }; diff --git a/packages/devextreme/js/__internal/scheduler/m_subscribes.ts b/packages/devextreme/js/__internal/scheduler/m_subscribes.ts index 5c1e880c819a..a4b0af7cb982 100644 --- a/packages/devextreme/js/__internal/scheduler/m_subscribes.ts +++ b/packages/devextreme/js/__internal/scheduler/m_subscribes.ts @@ -5,7 +5,7 @@ import dateUtils from '@js/core/utils/date'; import { extend } from '@js/core/utils/extend'; import { getDeltaTime } from './appointments/resizing/get_delta_time'; -import { getDateText, getFormatType } from './appointments_new/utils/appointment_text'; +import { getDateFormatType, getDateText } from './appointments_new/utils/get_date_text'; import { VERTICAL_VIEW_TYPES } from './constants'; import type Scheduler from './m_scheduler'; import { utils } from './m_utils'; @@ -132,7 +132,7 @@ const subscribes = { // pull out time zone converting from appointment adapter for knockout (T947938) const startDate = targetedAppointment.displayStartDate || this.timeZoneCalculator.createDate(adapter.startDate, 'toGrid'); const endDate = targetedAppointment.displayEndDate || this.timeZoneCalculator.createDate(adapter.endDate, 'toGrid'); - const formatType = format ?? getFormatType(startDate, endDate, adapter.allDay, this.currentView.type); + const formatType = format ?? getDateFormatType(startDate, endDate, adapter.allDay, this.currentView.type); return { text: adapter.text || messageLocalization.format('dxScheduler-noSubject'), From 7bf2f8864ecd71d6c3eec9071e8fdd70d68def65 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 25 Mar 2026 12:44:41 +0800 Subject: [PATCH 4/8] update etalons --- ...lse-horizontal-rtl (fluent.blue.light).png | Bin 22713 -> 22712 bytes ...crolling=false-rtl (fluent.blue.light).png | Bin 20845 -> 20843 bytes ...false-vertical-rtl (fluent.blue.light).png | Bin 19696 -> 19696 bytes ...rue-horizontal-rtl (fluent.blue.light).png | Bin 22709 -> 22709 bytes ...Scrolling=true-rtl (fluent.blue.light).png | Bin 20873 -> 20871 bytes ...=true-vertical-rtl (fluent.blue.light).png | Bin 19695 -> 19696 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=false-horizontal-rtl (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=false-horizontal-rtl (fluent.blue.light).png index 2951d4d89383c2c4b5fa3f63fff031900bbcf56f..bf96f9c74b093c4f13e820cc339f5eaebc9b9052 100644 GIT binary patch delta 15707 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 F{|kCowlx3% diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=false-rtl (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=false-rtl (fluent.blue.light).png index dca1873c3d78ef9cee6a214968284af509510acd..24c3b7413f901e806fee9e0799733487806dd39a 100644 GIT binary patch literal 20843 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 delta 16223 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 gE{if^`ZsYf36nThf(eb`5 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 diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=false-vertical-rtl (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=false-vertical-rtl (fluent.blue.light).png index d1d08a3b246200f99c7e2025bec1d053c857c81d..e1ecc9630f1b20ee8b7f6a38737789447c66354e 100644 GIT binary patch delta 15927 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 bbB$7#B{09X%~f&>{8LuYkS~{g_U``yqjt{* diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=true-horizontal-rtl (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=true-horizontal-rtl (fluent.blue.light).png index d4bd39f56bbafc4db948505949bd9831e38a35d6..e1b2a962a1e88fad008949d8d7441b411fcb9c7b 100644 GIT binary patch literal 22709 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 Xr)nZzZF~hhj+nvI)z4*}Q$iB}BdP?J diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=true-rtl (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=true-rtl (fluent.blue.light).png index 6d8ef1223996572f217e9ede313ef3efe6566d86..a88b23695a12b52e4964a4ad98770f723ef4324e 100644 GIT binary patch literal 20871 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 literal 20873 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 diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=true-vertical-rtl (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/adaptive/etalons/view=day-crossScrolling=true-vertical-rtl (fluent.blue.light).png index 412497c1063a6e33dea75959ef6e9f69edd4b61b..53eec7dc5e16adf6a38636cd35b3e00ea2df2980 100644 GIT binary patch literal 19696 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

9v&W;Nt{AqYD!Am(&0H$u!spe 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 Date: Wed, 25 Mar 2026 22:37:53 +0800 Subject: [PATCH 5/8] implement tests --- .../appointment/agenda_appointment.test.ts | 228 ++++++++++++ .../appointment/base_appointment.test.ts | 122 ++++++ .../appointment/grid_appointment.test.ts | 250 +++++++++++++ .../appointment/utils.test.ts | 126 +++++++ .../appointment_collector.test.ts | 196 ++++++++++ .../appointments_new/appointment_collector.ts | 5 - .../appointments_new/appointments.test.ts | 348 ++++++++++++++++++ .../appointments_new/appointments.ts | 16 +- .../utils/get_targeted_appointment.test.ts | 130 +++++++ .../utils/get_view_model_diff.test.ts | 281 ++++++++++++++ .../{ => utils}/get_view_model_diff.ts | 24 +- .../js/__internal/scheduler/m_scheduler.ts | 2 +- 12 files changed, 1696 insertions(+), 32 deletions(-) create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.test.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment/utils.test.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.test.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.test.ts create mode 100644 packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.test.ts rename packages/devextreme/js/__internal/scheduler/appointments_new/{ => utils}/get_view_model_diff.ts (93%) diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts new file mode 100644 index 000000000000..d07249012c3a --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts @@ -0,0 +1,228 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import $ from '@js/core/renderer'; +import type { SafeAppointment } from '@ts/scheduler/types'; +import type { ResourceConfig } from '@ts/scheduler/utils/loader/types'; +import type { AppointmentAgendaViewModel } from '@ts/scheduler/view_model/types'; + +import fx from '../../../common/core/animation/fx'; +import { AGENDA_APPOINTMENT_CLASSES, APPOINTMENT_CLASSES } from '../const'; +import type { AgendaAppointmentProperties } from './agenda_appointment'; +import { AgendaAppointment } from './agenda_appointment'; +import { getBaseMockedAppointmentProperties, mockAgendaViewModel } from './utils.test'; + +const getAgendaAppointmentProperties = (options: { + appointmentData: SafeAppointment; + partialViewModel?: Partial; + resources?: ResourceConfig[] +}): AgendaAppointmentProperties => { + const viewModel = mockAgendaViewModel(options.appointmentData, options.partialViewModel); + const result = getBaseMockedAppointmentProperties({ ...options, viewModel }); + + return { + ...result, + viewModel: result.viewModel as AppointmentAgendaViewModel, + }; +}; + +const createAgendaAppointment = async ( + properties: AgendaAppointmentProperties, +): Promise => { + const $element = $('.root'); + + // @ts-expect-error + const instance = new AgendaAppointment($element, properties); + + // Await for resources + await new Promise(process.nextTick); + + return instance; +}; + +const defaultAppointmentData = { + text: 'Test appointment', + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 1, 10, 0), +}; + +describe('AgendaAppointment', () => { + beforeEach(() => { + fx.off = true; + + const $container = $('

') + .addClass('container') + .appendTo(document.body); + + $('
') + .addClass('root') + .appendTo($container); + }); + + afterEach(() => { + $('.container').remove(); + fx.off = false; + jest.useRealTimers(); + }); + + describe('Classes', () => { + it.each([ + true, false, + ])('should have correct class for viewModel.lastInGroup = %o', async (isLastInGroup) => { + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { isLastInGroup }, + }), + ); + + expect( + instance.$element().hasClass(AGENDA_APPOINTMENT_CLASSES.LAST_IN_DATE), + ).toBe(isLastInGroup); + }); + }); + + describe('Title', () => { + it('should have correct title text', async () => { + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, text: 'Test title' }, + }), + ); + + const $title = instance.$element().find(`.${APPOINTMENT_CLASSES.TITLE}`); + + expect($title.text()).toBe('Test title'); + }); + + it('should have emptry title text if appointment has no text', async () => { + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, text: undefined }, + }), + ); + + const $title = instance.$element().find(`.${APPOINTMENT_CLASSES.TITLE}`); + expect($title.text()).toBe('(No subject)'); + }); + }); + + describe('Date text', () => { + it('should have correct date text', async () => { + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData: { + ...defaultAppointmentData, + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 1, 10, 0), + }, + }), + ); + + const $date = instance.$element().find(`.${APPOINTMENT_CLASSES.DATE}`); + + expect($date.text()).toBe('9:00 AM - 10:00 AM'); + }); + }); + + describe('Recurrence', () => { + it.each([ + true, false, + ])('should have correct recurrence icon visibility for isRecurring = %o', async (isRecurring) => { + const appointmentData = isRecurring + ? { ...defaultAppointmentData, recurrenceRule: 'FREQ=DAILY' } + : defaultAppointmentData; + + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData, + }), + ); + + const $icon = instance.$element().find(`.${APPOINTMENT_CLASSES.RECURRENCE_ICON}`); + + expect($icon.length).toBe(isRecurring ? 1 : 0); + }); + }); + + describe('All Day', () => { + it.each([ + true, false, + ])('should have correct all day text visibility for allDay = %o', async (isAllDay) => { + const appointmentData = { ...defaultAppointmentData, allDay: isAllDay }; + + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData, + }), + ); + + const $allDayText = instance.$element().find(`.${APPOINTMENT_CLASSES.ALL_DAY_TEXT}`); + + expect($allDayText.length).toBe(isAllDay ? 1 : 0); + }); + }); + + describe('Resources', () => { + it('should have marker with color from resource', async () => { + const resourceColor = 'rgb(255, 0, 0)'; + const resources = [ + { + field: 'roomId', + dataSource: [{ id: 1, text: 'Room 1', color: resourceColor }], + }, + ]; + + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, roomId: 1 }, + resources, + }), + ); + + const $marker = instance.$element().find(`.${AGENDA_APPOINTMENT_CLASSES.MARKER}`); + + expect($marker.css('backgroundColor')).toBe(resourceColor); + }); + + it('should render resource list', async () => { + const resources = [ + { + label: 'roomId', + fieldExpr: 'roomId', + dataSource: [{ id: 1, text: 'Room 1' }], + }, + { + label: 'ownerId', + fieldExpr: 'ownerId', + dataSource: [{ id: 2, text: 'Owner 1' }], + }, + ]; + + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, roomId: 1, ownerId: 2 }, + resources, + }), + ); + + const $resourceItems = instance.$element().find(`.${AGENDA_APPOINTMENT_CLASSES.RESOURCE_ITEM}`); + + expect($resourceItems.length).toBe(2); + expect($resourceItems.eq(0).text()).toBe('roomId:Room 1'); + expect($resourceItems.eq(1).text()).toBe('ownerId:Owner 1'); + }); + + it('should not render resource list if there are no resources', async () => { + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData: defaultAppointmentData, + }), + ); + + const $resourceItems = instance.$element().find(`.${AGENDA_APPOINTMENT_CLASSES.RESOURCE_ITEM}`); + + expect($resourceItems.length).toBe(0); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.test.ts new file mode 100644 index 000000000000..7df604c35c0e --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.test.ts @@ -0,0 +1,122 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import $ from '@js/core/renderer'; +import type { SafeAppointment } from '@ts/scheduler/types'; +import type { ResourceConfig } from '@ts/scheduler/utils/loader/types'; +import type { AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; + +import fx from '../../../common/core/animation/fx'; +import { APPOINTMENT_CLASSES, APPOINTMENT_TYPE_CLASSES } from '../const'; +import type { BaseAppointmentProperties } from './base_appointment'; +import { BaseAppointment } from './base_appointment'; +import { getBaseMockedAppointmentProperties, mockGridViewModel } from './utils.test'; + +const getBaseAppointmentProperties = (options: { + appointmentData: SafeAppointment; + partialViewModel?: Partial; + resources?: ResourceConfig[] +}): BaseAppointmentProperties => { + const viewModel = mockGridViewModel(options.appointmentData, options.partialViewModel); + const result = getBaseMockedAppointmentProperties({ ...options, viewModel }); + + return { + ...result, + viewModel: result.viewModel, + }; +}; + +const createBaseAppointment = async ( + properties: BaseAppointmentProperties, +): Promise => { + const $element = $('.root'); + + // @ts-expect-error + const instance = new BaseAppointment($element, properties); + + // Await for resources + await new Promise(process.nextTick); + + return instance; +}; + +const defaultAppointmentData = { + title: 'Test appointment', + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 1, 10, 0), +}; + +describe('BaseAppointment', () => { + beforeEach(() => { + fx.off = true; + + const $container = $('
') + .addClass('container') + .appendTo(document.body); + + $('
') + .addClass('root') + .appendTo($container); + }); + + afterEach(() => { + $('.container').remove(); + fx.off = false; + jest.useRealTimers(); + }); + + describe('Classes', () => { + it('should have container class', async () => { + const instance = await createBaseAppointment( + getBaseAppointmentProperties({ + appointmentData: defaultAppointmentData, + }), + ); + + expect(instance.$element().hasClass(APPOINTMENT_CLASSES.CONTAINER)).toBe(true); + }); + + it.each([ + true, false, + ])('should have correct class for viewModel.isRecurring = %o', async (isRecurring) => { + const instance = await createBaseAppointment( + getBaseAppointmentProperties({ + appointmentData: { + ...defaultAppointmentData, + recurrenceRule: isRecurring ? 'FREQ=DAILY;COUNT=5' : undefined, + }, + }), + ); + + expect( + instance.$element().hasClass(APPOINTMENT_TYPE_CLASSES.RECURRING), + ).toBe(isRecurring); + }); + + it.each([ + true, false, + ])('should have correct class for viewModel.allDay = %o', async (allDay) => { + const instance = await createBaseAppointment( + getBaseAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, allDay }, + }), + ); + + expect( + instance.$element().hasClass(APPOINTMENT_TYPE_CLASSES.ALL_DAY), + ).toBe(allDay); + }); + }); + + describe('Aria', () => { + it('should have role button', async () => { + const instance = await createBaseAppointment( + getBaseAppointmentProperties({ + appointmentData: defaultAppointmentData, + }), + ); + + expect(instance.$element().attr('role')).toBe('button'); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts new file mode 100644 index 000000000000..7e04a6cefa21 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts @@ -0,0 +1,250 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import $ from '@js/core/renderer'; +import type { SafeAppointment } from '@ts/scheduler/types'; +import type { ResourceConfig } from '@ts/scheduler/utils/loader/types'; +import type { AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; + +import fx from '../../../common/core/animation/fx'; +import { APPOINTMENT_CLASSES, APPOINTMENT_TYPE_CLASSES } from '../const'; +import type { GridAppointmentProperties } from './grid_appointment'; +import { GridAppointment } from './grid_appointment'; +import { getBaseMockedAppointmentProperties, mockGridViewModel } from './utils.test'; + +const getGridAppointmentProperties = (options: { + appointmentData: SafeAppointment; + partialViewModel?: Partial; + resources?: ResourceConfig[] +}): GridAppointmentProperties => { + const viewModel = mockGridViewModel(options.appointmentData, options.partialViewModel); + const result = getBaseMockedAppointmentProperties({ ...options, viewModel }); + + return { + ...result, + viewModel: result.viewModel as AppointmentItemViewModel, + }; +}; + +const createGridAppointment = async ( + properties: GridAppointmentProperties, +): Promise => { + const $element = $('.root'); + + // @ts-expect-error + const instance = new GridAppointment($element, properties); + + // Await for resources + await new Promise(process.nextTick); + + return instance; +}; + +const defaultAppointmentData = { + title: 'Test appointment', + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 1, 10, 0), +}; + +describe('GridAppointment', () => { + beforeEach(() => { + fx.off = true; + + const $container = $('
') + .addClass('container') + .appendTo(document.body); + + $('
') + .addClass('root') + .appendTo($container); + }); + + afterEach(() => { + $('.container').remove(); + document.body.innerHTML = ''; + fx.off = false; + jest.useRealTimers(); + }); + + describe('Classes', () => { + it('should have container class', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: defaultAppointmentData, + }), + ); + + expect(instance.$element().hasClass(APPOINTMENT_CLASSES.CONTAINER)).toBe(true); + }); + + it.each([ + true, false, + ])('should have correct empty class for viewModel.empty = %o', async (empty) => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { empty }, + }), + ); + + expect(instance.$element().hasClass(APPOINTMENT_TYPE_CLASSES.EMPTY)).toBe(empty); + }); + }); + + describe('Title', () => { + it('should have correct title text', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, text: 'Test title' }, + }), + ); + + const $title = instance.$element().find(`.${APPOINTMENT_CLASSES.TITLE}`); + + expect($title.text()).toBe('Test title'); + }); + + it('should have emptry title text if appointment has no text', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, text: undefined }, + }), + ); + + const $title = instance.$element().find(`.${APPOINTMENT_CLASSES.TITLE}`); + expect($title.text()).toBe('(No subject)'); + }); + }); + + describe('Date text', () => { + it('should have correct date text', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: { + ...defaultAppointmentData, + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 1, 10, 0), + }, + }), + ); + + const $date = instance.$element().find(`.${APPOINTMENT_CLASSES.DATE}`); + + expect($date.text()).toBe('9:00 AM - 10:00 AM'); + }); + }); + + describe('Geometry', () => { + it('should apply viewModel geometry on init', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { + top: 10, left: 15, width: 100, height: 50, + }, + }), + ); + + const $element = instance.$element(); + + expect($element.css('top')).toBe('10px'); + expect($element.css('left')).toBe('15px'); + expect($element.css('width')).toBe('100px'); + expect($element.css('height')).toBe('50px'); + }); + + it('should apply new viewModel geometry when resize() is called', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { + top: 10, left: 15, width: 100, height: 50, + }, + }), + ); + + instance.option('viewModel', { + ...instance.option().viewModel, + top: 20, + left: 25, + width: 150, + height: 70, + }); + + instance.resize(); + + const $element = instance.$element(); + + expect($element.css('top')).toBe('20px'); + expect($element.css('left')).toBe('25px'); + expect($element.css('width')).toBe('150px'); + expect($element.css('height')).toBe('70px'); + }); + }); + + describe('Recurrence', () => { + it.each([ + true, false, + ])('should have correct recurrence icon visibility for isRecurring = %o', async (isRecurring) => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: { + ...defaultAppointmentData, + recurrenceRule: isRecurring ? 'FREQ=DAILY' : undefined, + }, + }), + ); + + const $icon = instance.$element().find(`.${APPOINTMENT_CLASSES.RECURRENCE_ICON}`); + + expect($icon.length).toBe(isRecurring ? 1 : 0); + }); + }); + + describe('All day', () => { + it.each([ + true, false, + ])('should have correct all day text visibility for allDay = %o', async (isAllDay) => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, allDay: isAllDay }, + }), + ); + + const $allDayText = instance.$element().find(`.${APPOINTMENT_CLASSES.ALL_DAY_TEXT}`); + + expect($allDayText.length).toBe(isAllDay ? 1 : 0); + }); + }); + + describe('Resources', () => { + it('should correct background-color when resource has color', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: { + ...defaultAppointmentData, + roomId: 1, + }, + resources: [ + { + fieldExpr: 'roomId', + dataSource: [{ id: 1, text: 'Room 1', color: 'red' }], + }, + ], + }), + ); + + expect(instance.$element().css('backgroundColor')).toBe('red'); + }); + + it('should not have background-color css when no resource', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: defaultAppointmentData, + }), + ); + + expect(instance.$element().css('backgroundColor')).toBe(''); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/utils.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/utils.test.ts new file mode 100644 index 000000000000..95755033e005 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/utils.test.ts @@ -0,0 +1,126 @@ +import { mockAppointmentDataAccessor } from '@ts/scheduler/__mock__/appointment_data_accessor.mock'; +import { getResourceManagerMock } from '@ts/scheduler/__mock__/resource_manager.mock'; +import type { SafeAppointment } from '@ts/scheduler/types'; +import type { AppointmentDataAccessor } from '@ts/scheduler/utils/data_accessor/appointment_data_accessor'; +import type { ResourceConfig } from '@ts/scheduler/utils/loader/types'; +import type { AppointmentAgendaViewModel, AppointmentCollectorViewModel, AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; + +import { getTargetedAppointment } from '../utils/get_targeted_appointment'; +import type { BaseAppointmentProperties } from './base_appointment'; + +export const mockGridViewModel = ( + appointmentData: SafeAppointment, + partialViewModel?: Partial, +): AppointmentItemViewModel => { + const sourceAppointment = { + allDay: appointmentData.allDay, + startDate: appointmentData.startDate as Date, + endDate: appointmentData.endDate as Date, + }; + + const viewModel: AppointmentItemViewModel = { + itemData: appointmentData, + allDay: appointmentData.allDay ?? false, + groupIndex: appointmentData.groupIndex ?? 0, + sortedIndex: appointmentData.sortedIndex ?? 0, + info: { + sourceAppointment, + appointment: { ...sourceAppointment }, + }, + direction: 'horizontal', + skipResizing: false, + level: 0, + maxLevel: 0, + empty: false, + left: 0, + top: 0, + height: 0, + width: 0, + reduced: undefined, + partIndex: 0, + partTotalCount: 0, + rowIndex: 0, + columnIndex: 0, + }; + + return { + ...viewModel, + ...partialViewModel, + }; +}; + +export const mockAgendaViewModel = ( + appointmentData: SafeAppointment, + partialViewModel?: Partial, +): AppointmentAgendaViewModel => { + const sourceAppointment = { + allDay: appointmentData.allDay, + startDate: appointmentData.startDate as Date, + endDate: appointmentData.endDate as Date, + }; + + const viewModel: AppointmentAgendaViewModel = { + itemData: appointmentData, + allDay: appointmentData.allDay ?? false, + groupIndex: appointmentData.groupIndex ?? 0, + sortedIndex: appointmentData.sortedIndex ?? 0, + isAgendaModel: true, + height: 50, + width: '100', + isLastInGroup: appointmentData.isLastInGroup ?? false, + info: { + sourceAppointment, + appointment: { ...sourceAppointment }, + partialDates: { ...sourceAppointment }, + }, + }; + + return { + ...viewModel, + ...partialViewModel, + }; +}; + +export const mockAppointmentCollectorViewModel = ( + appointmentData: SafeAppointment, + partialViewModel?: Partial, +): AppointmentCollectorViewModel => ({ + itemData: appointmentData, + allDay: appointmentData.allDay ?? false, + groupIndex: appointmentData.groupIndex ?? 0, + sortedIndex: appointmentData.sortedIndex ?? 0, + top: 0, + left: 0, + height: 0, + width: 0, + isCompact: false, + items: [mockGridViewModel(appointmentData)], + ...partialViewModel, +}); + +export const getBaseMockedAppointmentProperties = < + TViewModel extends AppointmentItemViewModel | AppointmentAgendaViewModel, +>(options: { + appointmentData: SafeAppointment; + viewModel: TViewModel; + resources?: ResourceConfig[] +}): BaseAppointmentProperties => { + const resourceManager = getResourceManagerMock(options.resources ?? []); + const dataAccessor = mockAppointmentDataAccessor; + const targetedAppointmentData = getTargetedAppointment( + options.viewModel, + dataAccessor, + resourceManager, + ); + + const config: BaseAppointmentProperties = { + viewModel: options.viewModel, + targetedAppointmentData, + appointmentTemplate: 'appointment', + onAppointmentRendered: () => {}, + getResourceManager: () => resourceManager, + getDataAccessor: (): AppointmentDataAccessor => mockAppointmentDataAccessor, + }; + + return config; +}; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.test.ts new file mode 100644 index 000000000000..5eea32e4488b --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.test.ts @@ -0,0 +1,196 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import $ from '@js/core/renderer'; + +import fx from '../../../common/core/animation/fx'; +import { mockAppointmentDataAccessor } from '../__mock__/appointment_data_accessor.mock'; +import { getResourceManagerMock } from '../__mock__/resource_manager.mock'; +import type { SafeAppointment } from '../types'; +import type { ResourceConfig } from '../utils/loader/types'; +import type { AppointmentCollectorViewModel } from '../view_model/types'; +import { mockAppointmentCollectorViewModel } from './appointment/utils.test'; +import type { AppointmentCollectorProperties } from './appointment_collector'; +import { AppointmentCollector } from './appointment_collector'; +import { APPOINTMENT_COLLECTOR_CLASSES } from './const'; +import { getTargetedAppointment } from './utils/get_targeted_appointment'; + +const getAppointmentCollectorProperties = (options: { + appointmentData: SafeAppointment, + partialViewModel?: Partial, + resources?: ResourceConfig[] +}): AppointmentCollectorProperties => { + const viewModel = mockAppointmentCollectorViewModel( + options.appointmentData, + options.partialViewModel, + ); + const resourceManager = getResourceManagerMock(options.resources ?? []); + const dataAccessor = mockAppointmentDataAccessor; + const targetedAppointmentData = getTargetedAppointment( + viewModel.items[0], + dataAccessor, + resourceManager, + ); + + return { + viewModel, + targetedAppointmentData, + appointmentCollectorTemplate: 'appointmentCollector', + }; +}; + +const createAppointmentCollector = ( + properties: AppointmentCollectorProperties, +): AppointmentCollector => { + const $element = $('.root'); + + // @ts-expect-error + return new AppointmentCollector($element, properties); +}; + +const defaultAppointmentData = { + title: 'Test appointment', + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 1, 10, 0), +}; + +describe('AppointmentCollector', () => { + beforeEach(() => { + fx.off = true; + + const $container = $('
') + .addClass('container') + .appendTo(document.body); + + $('
') + .addClass('root') + .appendTo($container); + }); + + afterEach(() => { + $('.container').remove(); + fx.off = false; + jest.useRealTimers(); + }); + + describe('Classes', () => { + it('should have correct container class', () => { + const properties = getAppointmentCollectorProperties({ + appointmentData: defaultAppointmentData, + }); + const instance = createAppointmentCollector(properties); + + expect(instance.$element().hasClass('dx-scheduler-appointment-collector')).toBe(true); + expect(instance.$element().hasClass('dx-button')).toBe(true); + }); + + it('should have correct content class', () => { + const properties = getAppointmentCollectorProperties({ + appointmentData: defaultAppointmentData, + }); + const instance = createAppointmentCollector(properties); + const $buttonContent = instance.$element().find('.dx-button-content'); + + expect($buttonContent.hasClass(APPOINTMENT_COLLECTOR_CLASSES.CONTENT)).toBe(true); + }); + + it.each([ + true, false, + ])('should have correct compact class for viewModel.isCompact = %o', (isCompact) => { + const properties = getAppointmentCollectorProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { isCompact }, + }); + const instance = createAppointmentCollector(properties); + + expect(instance.$element().hasClass(APPOINTMENT_COLLECTOR_CLASSES.COMPACT)).toBe(isCompact); + }); + }); + + describe('Aria', () => { + it('should have correct aria-roledescription when appointment is in the same date', () => { + const properties = getAppointmentCollectorProperties({ + appointmentData: { + text: 'test', + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 1, 10, 0), + }, + }); + const instance = createAppointmentCollector(properties); + + expect(instance.$element().attr('aria-roledescription')).toBe('January 1, 2024'); + }); + + it('should have correct aria-roledescription when appointment is in different dates', () => { + const properties = getAppointmentCollectorProperties({ + appointmentData: { + text: 'test', + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 2, 10, 0), + }, + }); + const instance = createAppointmentCollector(properties); + + expect(instance.$element().attr('aria-roledescription')).toBe('January 1, 2024 - January 2, 2024'); + }); + }); + + describe('Resize', () => { + it('should have correct top and left on init', () => { + const properties = getAppointmentCollectorProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { + top: 100, left: 200, height: 30, width: 40, + }, + }); + const instance = createAppointmentCollector(properties); + + expect(instance.$element().css('top')).toBe('100px'); + expect(instance.$element().css('left')).toBe('200px'); + expect(instance.$element().css('height')).toBe('30px'); + expect(instance.$element().css('width')).toBe('40px'); + }); + + it('should have correct top and left after view model is updated and resize is called', () => { + const properties = getAppointmentCollectorProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { top: 100, left: 200 }, + }); + const instance = createAppointmentCollector(properties); + + instance.option('viewModel', { + ...properties.viewModel, + top: 150, + left: 250, + }); + instance.resize(); + + expect(instance.$element().css('top')).toBe('150px'); + expect(instance.$element().css('left')).toBe('250px'); + }); + }); + + describe('Text', () => { + it('should have correct text according to items length when viewModel.isCompact is true', () => { + const properties = getAppointmentCollectorProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { isCompact: true }, + }); + const instance = createAppointmentCollector(properties); + const $buttonContent = instance.$element().find('.dx-button-content'); + + expect($buttonContent.text()).toBe('1'); + }); + + it('should have correct text according to items length when viewModel.isCompact is false', () => { + const properties = getAppointmentCollectorProperties({ + appointmentData: defaultAppointmentData, + partialViewModel: { isCompact: false }, + }); + const instance = createAppointmentCollector(properties); + const $buttonContent = instance.$element().find('.dx-button-content'); + + expect($buttonContent.text()).toBe('1 more'); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts index b9637e1363b9..35695f8efc2e 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.ts @@ -42,10 +42,6 @@ export class AppointmentCollector this.renderContentTemplate(); } - override dispose(): void { - super.dispose(); - } - public resize(): void { this.$element().css({ top: this.option().viewModel.top, @@ -79,7 +75,6 @@ export class AppointmentCollector private renderContentTemplate(): void { const template = this._getTemplateByOption('appointmentCollectorTemplate'); - this._createComponent(this.$element(), Button, { type: 'default', width: this.option().viewModel.width, diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts new file mode 100644 index 000000000000..0944117a430f --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts @@ -0,0 +1,348 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import $ from '@js/core/renderer'; + +import fx from '../../../common/core/animation/fx'; +import { mockAppointmentDataAccessor } from '../__mock__/appointment_data_accessor.mock'; +import { getResourceManagerMock } from '../__mock__/resource_manager.mock'; +import type { AppointmentDataSource } from '../view_model/m_appointment_data_source'; +import { + mockAgendaViewModel, + mockAppointmentCollectorViewModel, + mockGridViewModel, +} from './appointment/utils.test'; +import type { AppointmentsProperties } from './appointments'; +import { Appointments } from './appointments'; +import { + APPOINTMENT_CLASSES, + APPOINTMENT_COLLECTOR_CLASSES, + APPOINTMENTS_CONTAINER_CLASS, +} from './const'; + +const mockAppointmentDataSource = (): AppointmentDataSource => ({ + getUpdatedAppointment: () => null, + getUpdatedAppointmentKeys: () => [], +} as unknown as AppointmentDataSource); + +const getAppointmentsProperties = ( + options: Partial = {}, +): AppointmentsProperties => ({ + getAppointmentDataSource: mockAppointmentDataSource, + getResourceManager: () => getResourceManagerMock([]), + getDataAccessor: () => mockAppointmentDataAccessor, + ...options, +} as AppointmentsProperties); + +const createAppointments = ( + properties?: AppointmentsProperties, +): Appointments => { + const $element = $('.root'); + + // @ts-expect-error + return new Appointments($element, properties); +}; + +const defaultAppointmentData = { + text: 'Test appointment', + startDate: new Date(2024, 0, 1, 9, 0), + endDate: new Date(2024, 0, 1, 10, 0), +}; + +describe('Appointments', () => { + beforeEach(() => { + fx.off = true; + + const $container = $('
') + .addClass('container') + .appendTo(document.body); + + $('
') + .addClass('root') + .appendTo($container); + + $('
') + .addClass('allday-container') + .appendTo($container); + }); + + afterEach(() => { + $('.container').remove(); + fx.off = false; + jest.useRealTimers(); + }); + + describe('Classes', () => { + it('should have correct container class', () => { + const instance = createAppointments(getAppointmentsProperties()); + + expect(instance.$element().hasClass(APPOINTMENTS_CONTAINER_CLASS)).toBe(true); + }); + }); + + describe('Rendering', () => { + it('should render view model with grid appointments', () => { + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [ + mockGridViewModel(defaultAppointmentData, { sortedIndex: 0 }), + ]); + + expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1); + }); + + it('should render view model with agenda appointments', () => { + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [ + mockAgendaViewModel(defaultAppointmentData, { sortedIndex: 0 }), + ]); + + expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1); + }); + + it('should render view model with appointment collectors', () => { + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [ + mockAppointmentCollectorViewModel(defaultAppointmentData, { sortedIndex: 0 }), + ]); + + expect(instance.$element().find(`.${APPOINTMENT_COLLECTOR_CLASSES.CONTAINER}`).length).toBe(1); + }); + + it('should rerender all appointments when view model is completely changed', () => { + const data1 = { ...defaultAppointmentData }; + const data2 = { ...defaultAppointmentData, text: 'Appointment 2' }; + + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [ + mockGridViewModel(data1, { sortedIndex: 0 }), + mockGridViewModel(data2, { sortedIndex: 1 }), + ]); + + const elementsBefore = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(elementsBefore.length).toBe(2); + + const data3 = { ...defaultAppointmentData, text: 'Appointment 3' }; + const data4 = { ...defaultAppointmentData, text: 'Appointment 4' }; + instance.option('viewModel', [ + mockGridViewModel(data3, { sortedIndex: 0 }), + mockGridViewModel(data4, { sortedIndex: 1 }), + ]); + + const elementsAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(elementsAfter.length).toBe(2); + expect(elementsAfter[0]).not.toBe(elementsBefore[0]); + expect(elementsAfter[1]).not.toBe(elementsBefore[1]); + }); + + it('should render allDay appointment to the allDay container', () => { + const allDayData = { ...defaultAppointmentData, allDay: true }; + const $allDayContainer = $('.allday-container'); + + const instance = createAppointments(getAppointmentsProperties({ $allDayContainer })); + instance.option('viewModel', [ + mockGridViewModel(allDayData, { sortedIndex: 0, allDay: true }), + ]); + + expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(0); + expect($allDayContainer.find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1); + }); + }); + + describe('Partial rendering', () => { + it('should render only changed appointments if appointment is added', () => { + const data1 = { ...defaultAppointmentData }; + const data2 = { ...defaultAppointmentData, text: 'Appointment 2' }; + const item1 = mockGridViewModel(data1, { sortedIndex: 0 }); + const item2 = mockGridViewModel(data2, { sortedIndex: 1 }); + + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [item1, item2]); + + const appointmentsBefore = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(appointmentsBefore.length).toBe(2); + + const data3 = { ...defaultAppointmentData, text: 'Appointment 3' }; + const item3 = mockGridViewModel(data3, { sortedIndex: 2 }); + instance.option('viewModel', [item1, item2, item3]); + + const appointmentsAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(appointmentsAfter.length).toBe(3); + expect(appointmentsAfter[0]).toBe(appointmentsBefore[0]); + expect(appointmentsAfter[1]).toBe(appointmentsBefore[1]); + }); + + it('should render only changed appointments if appointment is removed', () => { + const data1 = { ...defaultAppointmentData }; + const data2 = { ...defaultAppointmentData, text: 'Appointment 2' }; + const data3 = { ...defaultAppointmentData, text: 'Appointment 3' }; + const item1 = mockGridViewModel(data1, { sortedIndex: 0 }); + const item2 = mockGridViewModel(data2, { sortedIndex: 1 }); + const item3 = mockGridViewModel(data3, { sortedIndex: 2 }); + + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [item1, item2, item3]); + + const appointmentsBefore = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(appointmentsBefore.length).toBe(3); + + instance.option('viewModel', [item1, item3]); + + const appointmentsAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(appointmentsAfter.length).toBe(2); + expect(appointmentsAfter[0]).toBe(appointmentsBefore[0]); + expect(appointmentsAfter[1]).toBe(appointmentsBefore[2]); + }); + + it('should rerender one appointment when its view model changed', () => { + const data1 = { ...defaultAppointmentData }; + const data2 = { ...defaultAppointmentData, text: 'Appointment 2' }; + const item1 = mockGridViewModel(data1, { sortedIndex: 0 }); + const item2 = mockGridViewModel(data2, { sortedIndex: 1 }); + + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [item1, item2]); + + const appointmentsBefore = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(appointmentsBefore.length).toBe(2); + + const item2Changed = mockGridViewModel(data2, { sortedIndex: 1, groupIndex: 1 }); + instance.option('viewModel', [item1, item2Changed]); + + const appointmentsAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(appointmentsAfter.length).toBe(2); + expect(appointmentsAfter[0]).toBe(appointmentsBefore[0]); + expect(appointmentsAfter[1]).not.toBe(appointmentsBefore[1]); + }); + + it('should rerender several appointments when their view models changed', () => { + const data0 = { ...defaultAppointmentData }; + const data1 = { ...defaultAppointmentData, text: 'Appointment 1' }; + const data2 = { ...defaultAppointmentData, text: 'Appointment 2' }; + const item0 = mockGridViewModel(data0, { sortedIndex: 0 }); + const item1 = mockGridViewModel(data1, { sortedIndex: 1 }); + const item2 = mockGridViewModel(data2, { sortedIndex: 2 }); + + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [item0, item1, item2]); + + const appointmentsBefore = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(appointmentsBefore.length).toBe(3); + + const item1Changed = mockGridViewModel(data1, { sortedIndex: 1, groupIndex: 1 }); + const item2Changed = mockGridViewModel(data2, { sortedIndex: 2, groupIndex: 1 }); + instance.option('viewModel', [item0, item1Changed, item2Changed]); + + const appointmentsAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray(); + expect(appointmentsAfter.length).toBe(3); + expect(appointmentsAfter[0]).toBe(appointmentsBefore[0]); + expect(appointmentsAfter[1]).not.toBe(appointmentsBefore[1]); + expect(appointmentsAfter[2]).not.toBe(appointmentsBefore[2]); + }); + + it('should resize appointment if its size changed', () => { + const data = { ...defaultAppointmentData }; + const item = mockGridViewModel(data, { + sortedIndex: 0, top: 10, left: 10, height: 50, width: 100, + }); + + const instance = createAppointments(getAppointmentsProperties()); + instance.option('viewModel', [item]); + + const elementBefore = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).get(0); + expect($(elementBefore).css('top')).toBe('10px'); + + const itemResized = mockGridViewModel(data, { + sortedIndex: 0, top: 20, left: 20, height: 50, width: 100, + }); + instance.option('viewModel', [itemResized]); + + const elementAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).get(0); + expect(elementAfter).toBe(elementBefore); + expect($(elementAfter).css('top')).toBe('20px'); + expect($(elementAfter).css('left')).toBe('20px'); + }); + }); + + describe('onAppointmentRendered', () => { + it('should be called with correct arguments when grid appointment is rendered', async () => { + const onAppointmentRendered = jest.fn(); + const instance = createAppointments(getAppointmentsProperties({ onAppointmentRendered })); + instance.option('viewModel', [ + mockGridViewModel(defaultAppointmentData, { sortedIndex: 0 }), + ]); + + await new Promise(process.nextTick); + + expect(onAppointmentRendered).toHaveBeenCalledTimes(1); + expect(onAppointmentRendered).toHaveBeenCalledWith( + expect.objectContaining({ + appointmentData: defaultAppointmentData, + targetedAppointmentData: expect.objectContaining({ + text: defaultAppointmentData.text, + }), + }), + ); + }); + + it('should be called with correct arguments when agenda appointment is rendered', async () => { + const onAppointmentRendered = jest.fn(); + const instance = createAppointments(getAppointmentsProperties({ onAppointmentRendered })); + instance.option('viewModel', [ + mockAgendaViewModel(defaultAppointmentData, { sortedIndex: 0 }), + ]); + + await new Promise(process.nextTick); + + expect(onAppointmentRendered).toHaveBeenCalledTimes(1); + expect(onAppointmentRendered).toHaveBeenCalledWith( + expect.objectContaining({ + appointmentData: defaultAppointmentData, + targetedAppointmentData: expect.objectContaining({ + text: defaultAppointmentData.text, + }), + }), + ); + }); + + it('should not be called when appointment collector is rendered', async () => { + const onAppointmentRendered = jest.fn(); + const instance = createAppointments(getAppointmentsProperties({ onAppointmentRendered })); + instance.option('viewModel', [ + mockAppointmentCollectorViewModel(defaultAppointmentData, { sortedIndex: 0 }), + ]); + + await new Promise(process.nextTick); + + expect(onAppointmentRendered).not.toHaveBeenCalled(); + }); + + it('should be called several times when several appointments are rendered', async () => { + const onAppointmentRendered = jest.fn(); + const instance = createAppointments(getAppointmentsProperties({ onAppointmentRendered })); + instance.option('viewModel', [ + mockGridViewModel({ ...defaultAppointmentData, text: 'Appointment 1' }, { sortedIndex: 0 }), + mockGridViewModel({ ...defaultAppointmentData, text: 'Appointment 2' }, { sortedIndex: 1 }), + ]); + + await new Promise(process.nextTick); + + expect(onAppointmentRendered).toHaveBeenCalledTimes(2); + expect(onAppointmentRendered).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + appointmentData: expect.objectContaining({ + text: 'Appointment 1', + }), + }), + ); + expect(onAppointmentRendered).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + appointmentData: expect.objectContaining({ + text: 'Appointment 2', + }), + }), + ); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts index 2f052eb73d9d..5e3658abd659 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts @@ -17,26 +17,20 @@ import type { BaseAppointmentProperties } from './appointment/base_appointment'; import { GridAppointment } from './appointment/grid_appointment'; import { AppointmentCollector } from './appointment_collector'; import { APPOINTMENTS_CONTAINER_CLASS } from './const'; -import type { DiffItem } from './get_view_model_diff'; -import { getViewModelDiff } from './get_view_model_diff'; import { getTargetedAppointment } from './utils/get_targeted_appointment'; +import type { DiffItem } from './utils/get_view_model_diff'; +import { getViewModelDiff } from './utils/get_view_model_diff'; import { isCollectorViewModel as isAppointmentCollectorViewModel, isGridAppointmentViewModel } from './utils/type_helpers'; export interface AppointmentsProperties extends DOMComponentProperties { - tabIndex: number; viewModel: AppointmentViewModelPlain[]; items: AppointmentViewModelPlain[]; // TODO: legacy compatibility - allowDrag: boolean; - allowResize: boolean; $allDayContainer: dxElementWrapper | null; appointmentTemplate: SchedulerProperties['appointmentTemplate']; appointmentCollectorTemplate: SchedulerProperties['appointmentCollectorTemplate']; onAppointmentRendered: BaseAppointmentProperties['onAppointmentRendered']; - onAppointmentClick: () => void; - onAppointmentDblClick: () => void; - onAppointmentContextMenu: () => void; getAppointmentDataSource: () => AppointmentDataSource; getResourceManager: () => ResourceManager; @@ -65,17 +59,11 @@ export class Appointments extends DOMComponent {}, - onAppointmentClick: (): void => {}, - onAppointmentDblClick: (): void => {}, - onAppointmentContextMenu: (): void => {}, }; } diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.test.ts new file mode 100644 index 000000000000..66518914bc48 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.test.ts @@ -0,0 +1,130 @@ +import { + describe, expect, it, +} from '@jest/globals'; +import { mockAppointmentDataAccessor } from '@ts/scheduler/__mock__/appointment_data_accessor.mock'; +import { getResourceManagerMock } from '@ts/scheduler/__mock__/resource_manager.mock'; + +import { getTargetedAppointment } from './get_targeted_appointment'; + +const appointment = { + startDate: new Date(200, 0, 0), + endDate: new Date(200, 0, 1), +}; + +const recurringAppointment = { + ...appointment, + recurrenceRule: 'FREQ=DAILY', +}; + +const info = { + sourceAppointment: { + startDate: new Date(200, 0, 5), + endDate: new Date(200, 0, 6), + }, + appointment: { + startDate: new Date(200, 0, 5, 10), + endDate: new Date(200, 0, 6, 11), + }, +}; + +describe('getTargetedAppointment', () => { + it('should return grid item targeted appointment', () => { + expect(getTargetedAppointment( + { + itemData: recurringAppointment, + info, + groupIndex: 0, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + mockAppointmentDataAccessor, + getResourceManagerMock([]), + )).toEqual({ + ...recurringAppointment, + startDate: new Date(200, 0, 5), + endDate: new Date(200, 0, 6), + displayStartDate: new Date(200, 0, 5, 10), + displayEndDate: new Date(200, 0, 6, 11), + }); + }); + + it('should return grid item targeted appointment with resources', async () => { + const resourceManager = getResourceManagerMock(); + await resourceManager.loadGroupResources(['roomId', 'assigneeId']); + + expect(getTargetedAppointment( + { + itemData: recurringAppointment, + info, + groupIndex: 5, // 0,1; 0,2; 0,3; 0,4; 1,1; 1,2; <- 5 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + mockAppointmentDataAccessor, + resourceManager, + )).toEqual({ + ...recurringAppointment, + assigneeId: [2], + roomId: 1, + startDate: new Date(200, 0, 5), + endDate: new Date(200, 0, 6), + displayStartDate: new Date(200, 0, 5, 10), + displayEndDate: new Date(200, 0, 6, 11), + }); + }); + + it('should return agenda item targeted partial dates', () => { + expect(getTargetedAppointment( + { + isAgendaModel: true, + itemData: recurringAppointment, + info: { + ...info, + partialDates: { + startDate: new Date(200, 0, 5, 3), + endDate: new Date(200, 0, 7), + }, + }, + groupIndex: 0, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + mockAppointmentDataAccessor, + getResourceManagerMock([]), + )).toEqual({ + ...recurringAppointment, + startDate: new Date(200, 0, 5), + endDate: new Date(200, 0, 6), + displayStartDate: new Date(200, 0, 5, 3), + displayEndDate: new Date(200, 0, 7), + }); + }); + + it('should return agenda item targeted partial dates with resources', async () => { + const resourceManager = getResourceManagerMock(); + await resourceManager.loadGroupResources(['roomId', 'assigneeId']); + + expect(getTargetedAppointment( + { + isAgendaModel: true, + itemData: appointment, + info: { + ...info, + partialDates: { + startDate: new Date(200, 0, 5, 3), + endDate: new Date(200, 0, 7), + }, + }, + groupIndex: 3, // 0,1; 0,2; 0,3; 0,4; <- 3 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + mockAppointmentDataAccessor, + resourceManager, + )).toEqual({ + ...appointment, + assigneeId: [4], + roomId: 0, + startDate: new Date(200, 0, 5), + endDate: new Date(200, 0, 6), + displayStartDate: new Date(200, 0, 5, 3), + displayEndDate: new Date(200, 0, 7), + }); + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.test.ts new file mode 100644 index 000000000000..4f40610c55fd --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.test.ts @@ -0,0 +1,281 @@ +import { + describe, expect, it, +} from '@jest/globals'; +import type { SafeAppointment } from '@ts/scheduler/types'; + +import type { AppointmentDataSource } from '../../view_model/m_appointment_data_source'; +import type { AppointmentItemViewModel } from '../../view_model/types'; +import { getViewModelDiff } from './get_view_model_diff'; + +type ItemData = Record; + +const createMockDataSource = ( + updatedAppointment: ItemData | null = null, + updatedKeys: { key: string; value: unknown }[] = [], +): AppointmentDataSource => ({ + getUpdatedAppointment: () => updatedAppointment, + getUpdatedAppointmentKeys: () => updatedKeys, +} as unknown as AppointmentDataSource); + +const defaultDataSource = createMockDataSource(); + +const makeItem = ( + itemData: SafeAppointment, + overrides: Partial = {}, +): AppointmentItemViewModel => ({ + itemData, + allDay: false, + groupIndex: 0, + sortedIndex: 0, + direction: 'vertical', + skipResizing: false, + level: 0, + maxLevel: 0, + empty: false, + left: 0, + top: 0, + height: 100, + width: 200, + reduced: undefined, + partIndex: 0, + partTotalCount: 1, + rowIndex: 0, + columnIndex: 0, + info: { + sourceAppointment: { startDate: new Date(), endDate: new Date() }, + appointment: { startDate: new Date(), endDate: new Date() }, + }, + ...overrides, +} as AppointmentItemViewModel); + +const getOperations = (items: ReturnType): string => items + .map((item) => { + if (item.needToAdd) return '+'; + if (item.needToRemove) return '-'; + if (item.needToResize) return 'r'; + return '='; + }) + .join(''); + +describe('getViewModelDiff', () => { + it('should return empty array for both empty inputs', () => { + expect(getViewModelDiff([], [], defaultDataSource)).toEqual([]); + }); + + it('should mark no changes for identical items', () => { + const data1: ItemData = {}; + const data2: ItemData = {}; + const data3: ItemData = {}; + const a = [makeItem(data1), makeItem(data2), makeItem(data3)]; + const b = [makeItem(data1), makeItem(data2), makeItem(data3)]; + + const diff = getViewModelDiff(a, b, defaultDataSource); + + expect(getOperations(diff)).toBe('==='); + expect(diff).toEqual([{ item: b[0] }, { item: b[1] }, { item: b[2] }]); + }); + + it('should mark all as needToAdd when old list is empty', () => { + const data1: ItemData = {}; + const data2: ItemData = {}; + const b = [makeItem(data1), makeItem(data2)]; + + const diff = getViewModelDiff([], b, defaultDataSource); + + expect(getOperations(diff)).toBe('++'); + expect(diff).toEqual([ + { item: b[0], needToAdd: true }, + { item: b[1], needToAdd: true }, + ]); + }); + + it('should mark all as needToRemove when new list is empty', () => { + const data1: ItemData = {}; + const data2: ItemData = {}; + const a = [makeItem(data1), makeItem(data2)]; + + const diff = getViewModelDiff(a, [], defaultDataSource); + + expect(getOperations(diff)).toBe('--'); + expect(diff).toEqual([ + { item: a[0], needToRemove: true }, + { item: a[1], needToRemove: true }, + ]); + }); + + it('should mark remove and add for one item replacement (different itemData)', () => { + const data1: ItemData = {}; + const data2: ItemData = {}; + const data3: ItemData = {}; + const data4: ItemData = {}; + const a = [makeItem(data1), makeItem(data2), makeItem(data4)]; + const b = [makeItem(data1), makeItem(data3), makeItem(data4)]; + + const diff = getViewModelDiff(a, b, defaultDataSource); + + expect(getOperations(diff)).toBe('=+-='); + expect(diff).toEqual([ + { item: b[0] }, + { item: b[1], needToAdd: true }, + { item: a[1], needToRemove: true }, + { item: b[2] }, + ]); + }); + + it('should mark remove and add for same itemData with changed non-dimension properties', () => { + const data1: ItemData = {}; + const data2: ItemData = {}; + const data4: ItemData = {}; + const a = [makeItem(data1), makeItem(data2), makeItem(data4)]; + const b = [makeItem(data1), makeItem(data2, { rowIndex: 1 }), makeItem(data4)]; + + const diff = getViewModelDiff(a, b, defaultDataSource); + + expect(getOperations(diff)).toBe('=+-='); + expect(diff).toEqual([ + { item: b[0] }, + { item: b[1], needToAdd: true }, + { item: a[1], needToRemove: true }, + { item: b[2] }, + ]); + }); + + it('should choose optimum operations for reordering', () => { + const data1: ItemData = {}; + const data2: ItemData = {}; + const data3: ItemData = {}; + const data4: ItemData = {}; + const a = [makeItem(data1), makeItem(data2), makeItem(data3), makeItem(data4)]; + const b = [makeItem(data4), makeItem(data1), makeItem(data2), makeItem(data3)]; + + const diff = getViewModelDiff(a, b, defaultDataSource); + + expect(getOperations(diff)).toBe('+===-'); + expect(diff).toEqual([ + { item: b[0], needToAdd: true }, + { item: b[1] }, + { item: b[2] }, + { item: b[3] }, + { item: a[3], needToRemove: true }, + ]); + }); + + it('should choose optimum operations for reordering, insertion, and removal', () => { + const data1: ItemData = {}; + const data2: ItemData = {}; + const data3: ItemData = {}; + const data4: ItemData = {}; + const data5: ItemData = {}; + const a = [makeItem(data1), makeItem(data2), makeItem(data3), makeItem(data4)]; + const b = [makeItem(data4), makeItem(data1), makeItem(data5), makeItem(data3)]; + + const diff = getViewModelDiff(a, b, defaultDataSource); + + expect(getOperations(diff)).toBe('+=+-=-'); + expect(diff).toEqual([ + { item: b[0], needToAdd: true }, + { item: b[1] }, + { item: b[2], needToAdd: true }, + { item: a[1], needToRemove: true }, + { item: b[3] }, + { item: a[3], needToRemove: true }, + ]); + }); + + it('should use the new item (from new list) in no-change cases', () => { + const data1: ItemData = { myId: 0 }; + const data2: ItemData = { myId: 1 }; + const data3: ItemData = { myId: 2 }; + const data4: ItemData = { myId: 3 }; + const data5: ItemData = { myId: 4 }; + const a = [makeItem(data1), makeItem(data2), makeItem(data3), makeItem(data4)]; + // bItem1 uses the same data2 ref as a[1] but with a different sortedIndex, + // which is not part of the comparison object — items are still considered equal. + const bItem1 = makeItem(data2, { sortedIndex: 99 }); + const b = [makeItem(data4), bItem1, makeItem(data5), makeItem(data3)]; + + const diff = getViewModelDiff(a, b, defaultDataSource); + + expect(getOperations(diff)).toBe('+-=+=-'); + expect(diff[2]).toEqual({ item: bItem1 }); + }); + + describe('needToResize', () => { + it('should mark needToResize when only dimensions change for the same item', () => { + const data1: ItemData = {}; + const a = [makeItem(data1, { + left: 0, top: 0, height: 100, width: 200, + })]; + const b = [makeItem(data1, { + left: 10, top: 20, height: 50, width: 150, + })]; + + const diff = getViewModelDiff(a, b, defaultDataSource); + + expect(getOperations(diff)).toBe('r'); + expect(diff).toEqual([{ item: b[0], needToResize: true }]); + }); + + it('should mix needToResize with other operations', () => { + const data1: ItemData = {}; + const data2: ItemData = {}; + const data3: ItemData = {}; + const a = [makeItem(data1), makeItem(data2), makeItem(data3)]; + const b = [ + makeItem(data1), + makeItem(data2, { left: 50, top: 50 }), + makeItem(data3), + ]; + + const diff = getViewModelDiff(a, b, defaultDataSource); + + expect(getOperations(diff)).toBe('=r='); + expect(diff).toEqual([ + { item: b[0] }, + { item: b[1], needToResize: true }, + { item: b[2] }, + ]); + }); + }); + + describe('updatedAppointment', () => { + it('should treat item as changed when itemData matches getUpdatedAppointment reference', () => { + const data1: ItemData = {}; + const a = [makeItem(data1)]; + const b = [makeItem(data1)]; + const dataSource = createMockDataSource(data1); + + const diff = getViewModelDiff(a, b, dataSource); + + expect(getOperations(diff)).toBe('+-'); + expect(diff).toEqual([ + { item: b[0], needToAdd: true }, + { item: a[0], needToRemove: true }, + ]); + }); + + it('should treat item as changed when its data matches an updatedAppointmentKey', () => { + const data1: ItemData = { id: 1 }; + const a = [makeItem(data1)]; + const b = [makeItem(data1)]; + const dataSource = createMockDataSource(null, [{ key: 'id', value: 1 }]); + + const diff = getViewModelDiff(a, b, dataSource); + + expect(getOperations(diff)).toBe('+-'); + }); + + it('should not affect items whose data has not changed', () => { + const data1: ItemData = { id: 1 }; + const data2: ItemData = { id: 2 }; + const updatedData: ItemData = { id: 3 }; + const a = [makeItem(data1), makeItem(data2)]; + const b = [makeItem(data1), makeItem(data2)]; + const dataSource = createMockDataSource(updatedData); + + const diff = getViewModelDiff(a, b, dataSource); + + expect(getOperations(diff)).toBe('=='); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/get_view_model_diff.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.ts similarity index 93% rename from packages/devextreme/js/__internal/scheduler/appointments_new/get_view_model_diff.ts rename to packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.ts index 7a5938d908f3..c55a6f872158 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/get_view_model_diff.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.ts @@ -1,12 +1,19 @@ import { equalByValue } from '@js/core/utils/common'; -import type { SafeAppointment } from '../types'; -import type { AppointmentDataSource } from '../view_model/m_appointment_data_source'; -import type { AppointmentViewModelPlain } from '../view_model/types'; -import { isAgendaAppointmentViewModel, isCollectorViewModel } from './utils/type_helpers'; +import type { SafeAppointment } from '../../types'; +import type { AppointmentDataSource } from '../../view_model/m_appointment_data_source'; +import type { AppointmentViewModelPlain } from '../../view_model/types'; +import { isAgendaAppointmentViewModel, isCollectorViewModel } from './type_helpers'; type Item = AppointmentViewModelPlain; +export interface DiffItem { + needToAdd?: boolean; + needToRemove?: boolean; + needToResize?: boolean + item: Item; +} + const getObjectToCompare = (item: Item, includeDimensions: boolean): object => { if (isAgendaAppointmentViewModel(item)) { return {}; @@ -55,14 +62,7 @@ const isAppointmentDataChanged = ( return updateAppointmentKeys.some((item) => appointmentData[item.key] === item.value); }; -export interface DiffItem { - needToAdd?: boolean; - needToRemove?: boolean; - needToResize?: boolean - item: Item; -} - -export function getArraysDiff(options: { +function getArraysDiff(options: { a: Item[], b: Item[], match: (x: Item, y: Item) => boolean, diff --git a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts index 5cc0d7265177..65a2a86515b7 100644 --- a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts @@ -1041,7 +1041,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { if (this.option('_newAppointments')) { const appointmentsConfig: Partial = { - // TODO: set custom templates + // TODO: set custom templates appointmentTemplate: 'appointment', appointmentCollectorTemplate: 'appointmentCollector', onAppointmentRendered: (e) => { From dde31682402ebe48ca5dc099f0cf8dffd0213227 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 25 Mar 2026 22:53:33 +0800 Subject: [PATCH 6/8] move to __mock__ --- .../appointment_properties.ts} | 4 ++-- .../appointment/agenda_appointment.test.ts | 4 ++-- .../appointment/base_appointment.test.ts | 4 ++-- .../appointment/grid_appointment.test.ts | 4 ++-- .../appointments_new/appointment_collector.test.ts | 2 +- .../scheduler/appointments_new/appointments.test.ts | 2 +- .../scheduler/appointments_new/appointments.ts | 12 ++++++++++++ 7 files changed, 22 insertions(+), 10 deletions(-) rename packages/devextreme/js/__internal/scheduler/appointments_new/{appointment/utils.test.ts => __mock__/appointment_properties.ts} (96%) diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/utils.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/__mock__/appointment_properties.ts similarity index 96% rename from packages/devextreme/js/__internal/scheduler/appointments_new/appointment/utils.test.ts rename to packages/devextreme/js/__internal/scheduler/appointments_new/__mock__/appointment_properties.ts index 95755033e005..777ca9236f51 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/utils.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/__mock__/appointment_properties.ts @@ -5,8 +5,8 @@ import type { AppointmentDataAccessor } from '@ts/scheduler/utils/data_accessor/ import type { ResourceConfig } from '@ts/scheduler/utils/loader/types'; import type { AppointmentAgendaViewModel, AppointmentCollectorViewModel, AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; +import type { BaseAppointmentProperties } from '../appointment/base_appointment'; import { getTargetedAppointment } from '../utils/get_targeted_appointment'; -import type { BaseAppointmentProperties } from './base_appointment'; export const mockGridViewModel = ( appointmentData: SafeAppointment, @@ -98,7 +98,7 @@ export const mockAppointmentCollectorViewModel = ( ...partialViewModel, }); -export const getBaseMockedAppointmentProperties = < +export const getMockedBaseAppointmentProperties = < TViewModel extends AppointmentItemViewModel | AppointmentAgendaViewModel, >(options: { appointmentData: SafeAppointment; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts index d07249012c3a..a91e0135d45f 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts @@ -7,10 +7,10 @@ import type { ResourceConfig } from '@ts/scheduler/utils/loader/types'; import type { AppointmentAgendaViewModel } from '@ts/scheduler/view_model/types'; import fx from '../../../common/core/animation/fx'; +import { getMockedBaseAppointmentProperties, mockAgendaViewModel } from '../__mock__/appointment_properties'; import { AGENDA_APPOINTMENT_CLASSES, APPOINTMENT_CLASSES } from '../const'; import type { AgendaAppointmentProperties } from './agenda_appointment'; import { AgendaAppointment } from './agenda_appointment'; -import { getBaseMockedAppointmentProperties, mockAgendaViewModel } from './utils.test'; const getAgendaAppointmentProperties = (options: { appointmentData: SafeAppointment; @@ -18,7 +18,7 @@ const getAgendaAppointmentProperties = (options: { resources?: ResourceConfig[] }): AgendaAppointmentProperties => { const viewModel = mockAgendaViewModel(options.appointmentData, options.partialViewModel); - const result = getBaseMockedAppointmentProperties({ ...options, viewModel }); + const result = getMockedBaseAppointmentProperties({ ...options, viewModel }); return { ...result, diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.test.ts index 7df604c35c0e..339b02eecea5 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.test.ts @@ -7,10 +7,10 @@ import type { ResourceConfig } from '@ts/scheduler/utils/loader/types'; import type { AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; import fx from '../../../common/core/animation/fx'; +import { getMockedBaseAppointmentProperties, mockGridViewModel } from '../__mock__/appointment_properties'; import { APPOINTMENT_CLASSES, APPOINTMENT_TYPE_CLASSES } from '../const'; import type { BaseAppointmentProperties } from './base_appointment'; import { BaseAppointment } from './base_appointment'; -import { getBaseMockedAppointmentProperties, mockGridViewModel } from './utils.test'; const getBaseAppointmentProperties = (options: { appointmentData: SafeAppointment; @@ -18,7 +18,7 @@ const getBaseAppointmentProperties = (options: { resources?: ResourceConfig[] }): BaseAppointmentProperties => { const viewModel = mockGridViewModel(options.appointmentData, options.partialViewModel); - const result = getBaseMockedAppointmentProperties({ ...options, viewModel }); + const result = getMockedBaseAppointmentProperties({ ...options, viewModel }); return { ...result, diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts index 7e04a6cefa21..c0822d935d46 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts @@ -7,10 +7,10 @@ import type { ResourceConfig } from '@ts/scheduler/utils/loader/types'; import type { AppointmentItemViewModel } from '@ts/scheduler/view_model/types'; import fx from '../../../common/core/animation/fx'; +import { getMockedBaseAppointmentProperties, mockGridViewModel } from '../__mock__/appointment_properties'; import { APPOINTMENT_CLASSES, APPOINTMENT_TYPE_CLASSES } from '../const'; import type { GridAppointmentProperties } from './grid_appointment'; import { GridAppointment } from './grid_appointment'; -import { getBaseMockedAppointmentProperties, mockGridViewModel } from './utils.test'; const getGridAppointmentProperties = (options: { appointmentData: SafeAppointment; @@ -18,7 +18,7 @@ const getGridAppointmentProperties = (options: { resources?: ResourceConfig[] }): GridAppointmentProperties => { const viewModel = mockGridViewModel(options.appointmentData, options.partialViewModel); - const result = getBaseMockedAppointmentProperties({ ...options, viewModel }); + const result = getMockedBaseAppointmentProperties({ ...options, viewModel }); return { ...result, diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.test.ts index 5eea32e4488b..0dada998e25e 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment_collector.test.ts @@ -9,7 +9,7 @@ import { getResourceManagerMock } from '../__mock__/resource_manager.mock'; import type { SafeAppointment } from '../types'; import type { ResourceConfig } from '../utils/loader/types'; import type { AppointmentCollectorViewModel } from '../view_model/types'; -import { mockAppointmentCollectorViewModel } from './appointment/utils.test'; +import { mockAppointmentCollectorViewModel } from './__mock__/appointment_properties'; import type { AppointmentCollectorProperties } from './appointment_collector'; import { AppointmentCollector } from './appointment_collector'; import { APPOINTMENT_COLLECTOR_CLASSES } from './const'; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts index 0944117a430f..a7ecbba0c5a5 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts @@ -11,7 +11,7 @@ import { mockAgendaViewModel, mockAppointmentCollectorViewModel, mockGridViewModel, -} from './appointment/utils.test'; +} from './__mock__/appointment_properties'; import type { AppointmentsProperties } from './appointments'; import { Appointments } from './appointments'; import { diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts index 5e3658abd659..7180560a63a1 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts @@ -39,6 +39,18 @@ export interface AppointmentsProperties extends DOMComponentProperties { private appointmentBySortIndex: Record = {}; From 25f9e8ca037db30a089ca5354cfc32d7eefecc44 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 25 Mar 2026 22:57:12 +0800 Subject: [PATCH 7/8] remove my comments --- .../scheduler/appointments_new/appointments.ts | 12 ------------ .../utils/get_targeted_appointment.ts | 1 - 2 files changed, 13 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts index 7180560a63a1..5e3658abd659 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts @@ -39,18 +39,6 @@ export interface AppointmentsProperties extends DOMComponentProperties { private appointmentBySortIndex: Record = {}; diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.ts index 3f535cc32b5a..2682b4ba2efd 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_targeted_appointment.ts @@ -21,7 +21,6 @@ const setTargetedAppointmentResources = ( } }; -// what's the diff between info.appointment and info.sourceAppointment export const getTargetedAppointment = ( appointmentViewModel: AppointmentItemViewModel | AppointmentAgendaViewModel, dataAccessor: AppointmentDataAccessor, From 69ef155fc7b9ceb6a0535d53ee24720897352f2a Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 25 Mar 2026 23:32:09 +0800 Subject: [PATCH 8/8] apply copilot review --- .../appointment/agenda_appointment.test.ts | 13 ++++++++++++- .../appointment/base_appointment.ts | 7 +++++-- .../appointment/grid_appointment.test.ts | 13 ++++++++++++- .../scheduler/appointments_new/appointments.ts | 4 +++- .../appointments_new/utils/get_date_text.ts | 2 +- .../appointments_new/utils/get_view_model_diff.ts | 2 +- 6 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts index a91e0135d45f..7adb04d2ecd8 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.test.ts @@ -95,7 +95,7 @@ describe('AgendaAppointment', () => { expect($title.text()).toBe('Test title'); }); - it('should have emptry title text if appointment has no text', async () => { + it('should have (No subject) title text if appointment text is undefined', async () => { const instance = await createAgendaAppointment( getAgendaAppointmentProperties({ appointmentData: { ...defaultAppointmentData, text: undefined }, @@ -105,6 +105,17 @@ describe('AgendaAppointment', () => { const $title = instance.$element().find(`.${APPOINTMENT_CLASSES.TITLE}`); expect($title.text()).toBe('(No subject)'); }); + + it('should have (No subject) title text if appointment text is empty', async () => { + const instance = await createAgendaAppointment( + getAgendaAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, text: '' }, + }), + ); + + const $title = instance.$element().find(`.${APPOINTMENT_CLASSES.TITLE}`); + expect($title.text()).toBe('(No subject)'); + }); }); describe('Date text', () => { diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts index db64012ae368..048a88eebd99 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/base_appointment.ts @@ -100,8 +100,11 @@ export class BaseAppointment< protected getTitleText(): string { const dataAccessor = this.option().getDataAccessor(); - const titleText = dataAccessor.get('text', this.appointmentData) - ?? messageLocalization.format('dxScheduler-noSubject'); + const titleText = dataAccessor.get('text', this.appointmentData); + + if (!titleText) { + return messageLocalization.format('dxScheduler-noSubject'); + } return titleText; } diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts index c0822d935d46..496868362bd8 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointment/grid_appointment.test.ts @@ -104,7 +104,7 @@ describe('GridAppointment', () => { expect($title.text()).toBe('Test title'); }); - it('should have emptry title text if appointment has no text', async () => { + it('should have (No subject) title text if appointment text is undefined', async () => { const instance = await createGridAppointment( getGridAppointmentProperties({ appointmentData: { ...defaultAppointmentData, text: undefined }, @@ -114,6 +114,17 @@ describe('GridAppointment', () => { const $title = instance.$element().find(`.${APPOINTMENT_CLASSES.TITLE}`); expect($title.text()).toBe('(No subject)'); }); + + it('should have (No subject) title text if appointment text is empty', async () => { + const instance = await createGridAppointment( + getGridAppointmentProperties({ + appointmentData: { ...defaultAppointmentData, text: '' }, + }), + ); + + const $title = instance.$element().find(`.${APPOINTMENT_CLASSES.TITLE}`); + expect($title.text()).toBe('(No subject)'); + }); }); describe('Date text', () => { diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts index 5e3658abd659..73de5964fc27 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts @@ -147,7 +147,9 @@ export class Appointments extends DOMComponent { const dateFormat = 'monthandday'; const timeFormat = 'shorttime'; - const isSameDate = startDate.getDate() === endDate.getDate(); + const isSameDate = dateUtils.sameDate(startDate, endDate); switch (formatType) { case DateFormatType.DATETIME: diff --git a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.ts b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.ts index c55a6f872158..945a197144fd 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments_new/utils/get_view_model_diff.ts @@ -10,7 +10,7 @@ type Item = AppointmentViewModelPlain; export interface DiffItem { needToAdd?: boolean; needToRemove?: boolean; - needToResize?: boolean + needToResize?: boolean; item: Item; }