From 0711ceeea826685fa74f742b27dbc6cfa4a11b12 Mon Sep 17 00:00:00 2001 From: Sergei Burkatskii Date: Wed, 17 Jun 2026 09:23:40 +0200 Subject: [PATCH] feat: create spike --- .../base/scheduler/views/day/_index.scss | 9 +++ .../base/scheduler/views/month/_index.scss | 27 +++++++ .../js/__internal/scheduler/m_scheduler.ts | 11 +++ .../r1/components/base/date_header.tsx | 74 +++++++++++-------- .../r1/components/base/date_table.tsx | 6 ++ .../r1/components/base/date_table_body.tsx | 72 ++++++++++++------ .../r1/components/base/header_panel.tsx | 3 + .../r1/components/base/layout_props.ts | 3 + .../r1/components/month/date_table_month.tsx | 6 ++ .../components/wrappers/date_table_month.ts | 3 + .../r1/components/wrappers/header_panel.ts | 1 + .../options/option_manager.ts | 4 +- .../get_appointment_abstract_geometry.ts | 3 +- .../get_appointment_collector_geometry.ts | 6 +- .../add_geometry/get_appointment_geometry.ts | 4 +- .../steps/add_geometry/types.ts | 1 + .../scheduler/workspaces/m_work_space.ts | 7 ++ .../scheduler/workspaces/work_space_month.ts | 22 +++++- .../workspaces/work_space_vertical.ts | 33 +++++++++ 19 files changed, 235 insertions(+), 60 deletions(-) diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/day/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/day/_index.scss index 4d1205c67e90..70efb563202d 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/day/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/day/_index.scss @@ -1,6 +1,15 @@ $scheduler-base-border: null !default; $scheduler-base-border-color: null !default; +.dx-scheduler-week-number-label { + pointer-events: none; + user-select: none; + text-align: center; + opacity: 0.56; + font-size: 0.85em; + padding: 2px 0; +} + .dx-scheduler-work-space-day { &:not(.dx-scheduler-work-space-count) { &:not(.dx-scheduler-work-space-grouped) { diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/month/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/month/_index.scss index f19bd0a0e64a..138cda12bac0 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/month/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/month/_index.scss @@ -7,6 +7,33 @@ $scheduler-workspace-month-text-color: null !default; $scheduler-month-date-text-padding: null !default; $scheduler-first-month-cell-background-color: null !default; +.dx-scheduler-week-number-cell, +.dx-scheduler-week-number-header-cell { + pointer-events: none; + user-select: none; + text-align: center; + width: 28px; + min-width: 28px; + max-width: 28px; + overflow: hidden; + opacity: 0.56; + font-size: 0.85em; +} + +.dx-scheduler-week-number-cell { + vertical-align: middle; + padding: 0 2px; +} + +.dx-scheduler-week-number-header-cell { + font-weight: normal; + + div { + text-transform: uppercase; + letter-spacing: 0.02em; + } +} + .dx-scheduler-work-space-month { .dx-scheduler-all-day-title { display: none; diff --git a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts index 9f6c5c7d607c..343e8ddfc323 100644 --- a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts @@ -270,6 +270,13 @@ class Scheduler extends SchedulerOptionsBaseWidget { _optionChanged(args: OptionChanged): void { this.schedulerOptionChanged(args); + const optionName = args.name as string; + if (optionName === 'showWeekNumbers' || optionName === 'weekNumberRule') { + this.updateOption('workSpace', optionName, args.value); + this.repaint(); + return; + } + const { value, name } = args; switch (args.name) { @@ -1519,6 +1526,10 @@ class Scheduler extends SchedulerOptionsBaseWidget { focusStateEnabled: this.option('focusStateEnabled'), cellDuration: this.option('cellDuration'), showAllDayPanel: this.option('showAllDayPanel'), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + showWeekNumbers: (this as any).option('showWeekNumbers') ?? false, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + weekNumberRule: (this as any).option('weekNumberRule') ?? 'auto', showCurrentTimeIndicator: this.option('showCurrentTimeIndicator'), indicatorTime: this.option('indicatorTime'), indicatorUpdateInterval: this.option('indicatorUpdateInterval'), diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/date_header.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/date_header.tsx index c070984db28b..00ae636ec491 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/date_header.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/date_header.tsx @@ -19,6 +19,7 @@ export interface DateHeaderProps extends PropsWithViewContext { groups: Group[]; dateCellTemplate?: JSXTemplate; timeCellTemplate?: JSXTemplate; + showWeekNumbers?: boolean; } export const DateHeaderDefaultProps = { @@ -42,9 +43,11 @@ export class DateHeader extends BaseInfernoComponent { groupByDate, groupOrientation, groups, + showWeekNumbers, } = this.props; const isHorizontalGrouping = isHorizontalGroupingApplied(groups.length, groupOrientation) && !groupByDate; + const isMonthView = viewContext.view.type === 'month'; return ( <> @@ -59,38 +62,45 @@ export class DateHeader extends BaseInfernoComponent { rightVirtualCellCount={rightVirtualCellCount} isHeaderRow={true} > - { - dateHeaderRow.map(({ - colSpan, - groupIndex, - groups: cellGroups, - index, - isFirstGroupCell, - isLastGroupCell, - key, - startDate, - text, - today, - }) => ( - - )) - } + <> + {showWeekNumbers && isMonthView && rowIndex === 0 && ( + +
Wk
+ + )} + { + dateHeaderRow.map(({ + colSpan, + groupIndex, + groups: cellGroups, + index, + isFirstGroupCell, + isLastGroupCell, + key, + startDate, + text, + today, + }) => ( + + )) + } + )) } diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/date_table.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/date_table.tsx index ee24981fbff7..d36ff8e03c19 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/date_table.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/date_table.tsx @@ -35,6 +35,9 @@ export class DateTable extends InfernoWrapperComponent { dataCellTemplate, groupOrientation, addVerticalSizesClassToRows, + showWeekNumbers, + weekNumberRule, + firstDayOfWeek, ...restProps } = this.props; const classes = addDateTableClass ? 'dx-scheduler-date-table' : undefined; @@ -70,6 +73,9 @@ export class DateTable extends InfernoWrapperComponent { topVirtualRowHeight={DateTableBodyDefaultProps.topVirtualRowHeight} bottomVirtualRowHeight={DateTableBodyDefaultProps.bottomVirtualRowHeight} addDateTableClass={DateTableBodyDefaultProps.addDateTableClass} + showWeekNumbers={showWeekNumbers} + weekNumberRule={weekNumberRule} + firstDayOfWeek={firstDayOfWeek} /> ); diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/date_table_body.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/date_table_body.tsx index e2c540d16fcb..af27a139c8c3 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/date_table_body.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/date_table_body.tsx @@ -1,3 +1,5 @@ +import dateLocalization from '@js/common/core/localization/date'; +import dateUtils from '@js/core/utils/date'; import { BaseInfernoComponent } from '@ts/core/r1/runtime/inferno/index'; import type { JSXTemplate } from '@ts/core/r1/types'; import { PublicTemplate } from '@ts/scheduler/r1/components/templates/index'; @@ -12,6 +14,19 @@ import type { LayoutProps } from './layout_props'; import { LayoutDefaultProps } from './layout_props'; import { Row, RowDefaultProps } from './row'; +function getLocaleBasedRule(): string { + return dateLocalization.firstDayOfWeekIndex() === 1 ? 'firstFourDays' : 'firstDay'; +} + +function resolveWeekNumber( + date: Date, + firstDayOfWeek: number | undefined, + rule: string | undefined, +): number { + const effectiveRule = !rule || rule === 'auto' ? getLocaleBasedRule() : rule; + return dateUtils.getWeekNumber(date, firstDayOfWeek ?? 0, effectiveRule); +} + export interface DateTableBodyProps extends LayoutProps { cellTemplate: JSXTemplate; } @@ -29,7 +44,11 @@ export class DateTableBody extends BaseInfernoComponent { addVerticalSizesClassToRows, cellTemplate, dataCellTemplate, + showWeekNumbers, + weekNumberRule, + firstDayOfWeek, } = this.props; + const isMonthView = viewContext.view.type === 'month'; const rowClasses = combineClasses({ [DATE_TABLE_ROW_CLASS]: true, 'dx-scheduler-cell-sizes-vertical': addVerticalSizesClassToRows, @@ -74,8 +93,16 @@ export class DateTableBody extends BaseInfernoComponent { leftVirtualCellCount={viewData.leftVirtualCellCount} rightVirtualCellCount={viewData.rightVirtualCellCount} > - { - cells.map(({ + <> + {showWeekNumbers && isMonthView && cells.length > 0 && ( + + {resolveWeekNumber(cells[0].startDate, firstDayOfWeek, weekNumberRule)} + + )} + {cells.map(({ key: cellKey, endDate, isFirstDayMonthHighlighting, @@ -91,26 +118,27 @@ export class DateTableBody extends BaseInfernoComponent { text, today, }) => ) - } + template={cellTemplate} + templateProps={{ + key: cellKey, + viewContext, + isFirstGroupCell, + isLastGroupCell, + startDate, + endDate, + groups, + groupIndex: cellGroupIndex, + index: cellIndex, + dataCellTemplate, + text, + today, + otherMonth, + isFirstDayMonthHighlighting, + isSelected, + isFocused, + } as CellTemplateProps} />) + } + )) } diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/header_panel.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/header_panel.tsx index f4b7591f34fa..a4fc0c71e24d 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/header_panel.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/header_panel.tsx @@ -17,6 +17,7 @@ export interface HeaderPanelProps extends GroupPanelProps { dateCellTemplate?: JSXTemplate; timeCellTemplate?: JSXTemplate; dateHeaderTemplate: JSXTemplate; + showWeekNumbers?: boolean; } export const HeaderPanelDefaultProps = { @@ -43,6 +44,7 @@ export class HeaderPanel extends InfernoWrapperComponent { dateHeaderTemplate, resourceCellTemplate, timeCellTemplate, + showWeekNumbers, } = this.props; const isHorizontalGrouping = isHorizontalGroupingApplied(groups.length, groupOrientation); @@ -72,6 +74,7 @@ export class HeaderPanel extends InfernoWrapperComponent { groups, dateCellTemplate, timeCellTemplate, + showWeekNumbers, }} /> } diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/layout_props.ts b/packages/devextreme/js/__internal/scheduler/r1/components/base/layout_props.ts index 5817b77be299..43bffdfadea3 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/layout_props.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/layout_props.ts @@ -16,6 +16,9 @@ export interface LayoutProps extends PropsWithViewContext { addVerticalSizesClassToRows: boolean; width?: number; dataCellTemplate?: JSXTemplate; + showWeekNumbers?: boolean; + weekNumberRule?: string; + firstDayOfWeek?: number; } export const LayoutDefaultProps: DefaultProps = { diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/month/date_table_month.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/month/date_table_month.tsx index 34045f379fac..ec43acb2f90d 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/month/date_table_month.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/month/date_table_month.tsx @@ -20,6 +20,9 @@ export class DateTableMonth extends InfernoWrapperComponent { groupOrientation, tableRef, width, + showWeekNumbers, + weekNumberRule, + firstDayOfWeek, ...restProps } = this.props; @@ -35,6 +38,9 @@ export class DateTableMonth extends InfernoWrapperComponent { tableRef={tableRef} addVerticalSizesClassToRows={addVerticalSizesClassToRows} width={width} + showWeekNumbers={showWeekNumbers} + weekNumberRule={weekNumberRule} + firstDayOfWeek={firstDayOfWeek} /> ); } diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/wrappers/date_table_month.ts b/packages/devextreme/js/__internal/scheduler/r1/components/wrappers/date_table_month.ts index 3001c310b5a3..e9757666dae8 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/wrappers/date_table_month.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/components/wrappers/date_table_month.ts @@ -25,6 +25,9 @@ export class DateTableMonthComponent extends DateTableComponent { 'addVerticalSizesClassToRows', 'width', 'dataCellTemplate', + 'showWeekNumbers', + 'weekNumberRule', + 'firstDayOfWeek', ], }; } diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/wrappers/header_panel.ts b/packages/devextreme/js/__internal/scheduler/r1/components/wrappers/header_panel.ts index 1116337c8781..ce4e9e1f27d1 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/wrappers/header_panel.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/components/wrappers/header_panel.ts @@ -43,6 +43,7 @@ export class HeaderPanelComponent extends ComponentWrapper { 'height', 'className', 'resourceCellTemplate', + 'showWeekNumbers', ], }; } diff --git a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/options/option_manager.ts b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/options/option_manager.ts index b830eae445bb..bde71e69917e 100644 --- a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/options/option_manager.ts +++ b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/options/option_manager.ts @@ -80,6 +80,7 @@ export class OptionManager { const viewOrientation = panelName === 'allDayPanel' ? 'horizontal' : nativeViewOrientation; const isCompactCollector = isAdaptivityEnabled || viewOrientation === 'vertical'; const collectorCSS = workspace.getCollectorDimension(isCompactCollector, panelName); + const DOMMetaData = workspace.getDOMElementsMetaData(); const { allDayPanelCellSize, cellSize, @@ -92,7 +93,7 @@ export class OptionManager { viewOrientation, isAdaptivityEnabled, collectorCSS, - DOMMetaData: workspace.getDOMElementsMetaData(), + DOMMetaData, panelName, }); @@ -137,6 +138,7 @@ export class OptionManager { isAllDayPanel: panelName === 'allDayPanel', }), panelSize: panelDOMSize, + panelLeftOffset: DOMMetaData.dateTableCellsMeta[0]?.[0]?.left ?? 0, }; const collectorOptions: CollectorOptions = { cells, diff --git a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_abstract_geometry.ts b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_abstract_geometry.ts index 6d63427e4277..95be14b38a45 100644 --- a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_abstract_geometry.ts +++ b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_abstract_geometry.ts @@ -18,10 +18,11 @@ export const getAppointmentX = ( entity: Pick, cellSize: AbstractSize, cells: CellInterval[], + panelLeftOffset = 0, ): X => { const startX = getInsideCellX(entity.startDateUTC, cells[entity.cellIndex], cellSize.sizeX); const endX = getInsideCellX(entity.endDateUTC, cells[entity.endCellIndex], cellSize.sizeX); - const offsetX = entity.columnIndex * cellSize.sizeX + startX; + const offsetX = panelLeftOffset + entity.columnIndex * cellSize.sizeX + startX; const sizeX = (entity.endCellIndex - entity.cellIndex) * cellSize.sizeX + endX - startX; return { offsetX, sizeX }; diff --git a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_collector_geometry.ts b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_collector_geometry.ts index 160a5a972577..9872880bb9ab 100644 --- a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_collector_geometry.ts +++ b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_collector_geometry.ts @@ -16,12 +16,14 @@ export const getAppointmentCollectorGeometry = ( collectorSize, collectorWithMarginsSize, viewOrientation, - }: Pick, + panelLeftOffset, + }: Pick, ): Geometry => { const collectorAbstractSize = getAbstractSizeByViewOrientation(collectorSize, viewOrientation); const cellAbstractSize = getAbstractSizeByViewOrientation(cellSize, viewOrientation); + const abstractLeftOffset = viewOrientation === 'horizontal' ? (panelLeftOffset ?? 0) : 0; const abstractGeometry = { - offsetX: entity.columnIndex * cellAbstractSize.sizeX, + offsetX: abstractLeftOffset + entity.columnIndex * cellAbstractSize.sizeX, offsetY: collectorPosition === 'start' ? 0 : cellAbstractSize.sizeY - getAbstractSizeByViewOrientation( collectorWithMarginsSize, viewOrientation, diff --git a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_geometry.ts b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_geometry.ts index fdea4ef82c3e..6c5d2e7744ca 100644 --- a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_geometry.ts +++ b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/get_appointment_geometry.ts @@ -17,6 +17,7 @@ export const getAppointmentGeometry = ( collectorWithMarginsSize, viewOrientation, cells, + panelLeftOffset, }: GeometryOptions, ): Geometry => { const cellAbstractSize = getAbstractSizeByViewOrientation(cellSize, viewOrientation); @@ -24,8 +25,9 @@ export const getAppointmentGeometry = ( collectorWithMarginsSize, viewOrientation, ); + const abstractLeftOffset = viewOrientation === 'horizontal' ? (panelLeftOffset ?? 0) : 0; const abstractGeometry = { - ...getAppointmentX(entity, cellAbstractSize, cells), + ...getAppointmentX(entity, cellAbstractSize, cells, abstractLeftOffset), ...getAppointmentY( entity, cellAbstractSize, diff --git a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/types.ts b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/types.ts index a1c75e4c8ef8..79938ef6d0ff 100644 --- a/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/types.ts +++ b/packages/devextreme/js/__internal/scheduler/view_model/generate_view_model/steps/add_geometry/types.ts @@ -69,6 +69,7 @@ export interface GeometryOptions { collectorWithMarginsSize: RealSize; groupSize: RealSize; panelSize: RealSize; + panelLeftOffset?: number; } export interface VirtualCropOptions { diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts index 247c2c94862d..c583ea2aac86 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts @@ -277,6 +277,8 @@ export interface WorkspaceOptionsInternal { width?: number | string | undefined; rtlEnabled: boolean; + showWeekNumbers?: boolean; + weekNumberRule?: string; } class SchedulerWorkSpace extends Widget { @@ -2260,6 +2262,7 @@ class SchedulerWorkSpace extends Widget { groupOrientation: this.option('groupOrientation'), resourceCellTemplate: this.option('resourceCellTemplate'), isRenderDateHeader, + showWeekNumbers: this.option().showWeekNumbers, }, ); } @@ -2417,6 +2420,8 @@ class SchedulerWorkSpace extends Widget { selectedCellData: [], groupByDate: false, skippedDays: undefined, + showWeekNumbers: false, + weekNumberRule: 'auto', scrolling: { mode: 'standard', }, @@ -2449,6 +2454,8 @@ class SchedulerWorkSpace extends Widget { case 'firstDayOfWeek': case 'currentDate': case 'startDate': + case 'showWeekNumbers': + case 'weekNumberRule': this.cleanWorkSpace(); break; case 'groups': diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_month.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_month.ts index ecfd0b78bf23..338617ecf643 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_month.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_month.ts @@ -120,7 +120,14 @@ class SchedulerWorkSpaceMonth extends SchedulerWorkSpace { } getWorkSpaceLeftOffset(): number { - return 0; + let result = 0; + if (this.option().showWeekNumbers && hasWindow()) { + const $weekCell = this.$element().find('.dx-scheduler-week-number-cell').first(); + if ($weekCell.length) { + result = Math.round(getBoundingRect($weekCell.get(0)).width); + } + } + return result; } needApplyCollectorOffset(): boolean { @@ -135,6 +142,19 @@ class SchedulerWorkSpaceMonth extends SchedulerWorkSpace { renderRTimeTable(): void {} + protected override getRDateTableProps(): ReturnType & { + showWeekNumbers: boolean | undefined; + weekNumberRule: string | undefined; + firstDayOfWeek: number | undefined; + } { + return { + ...super.getRDateTableProps(), + showWeekNumbers: this.option().showWeekNumbers, + weekNumberRule: this.option().weekNumberRule, + firstDayOfWeek: this.option().firstDayOfWeek, + }; + } + renderRDateTable(): void { utils.renovation.renderComponent( this, diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_vertical.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_vertical.ts index 6b024e490c8a..686bba167324 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_vertical.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_vertical.ts @@ -1,8 +1,18 @@ +import dateLocalization from '@js/common/core/localization/date'; +import $ from '@js/core/renderer'; +import dateUtils from '@js/core/utils/date'; import { formatWeekdayAndDay } from '@ts/scheduler/r1/utils/index'; import type { ViewDataProviderOptions } from './view_model/types'; import SchedulerWorkSpaceIndicator from './work_space_indicator'; +const WEEK_NUMBER_LABEL_CLASS = 'dx-scheduler-week-number-label'; +const HEADER_PANEL_EMPTY_CELL_CLASS = 'dx-scheduler-header-panel-empty-cell'; + +function getLocaleBasedWeekRule(): string { + return dateLocalization.firstDayOfWeekIndex() === 1 ? 'firstFourDays' : 'firstDay'; +} + class SchedulerWorkspaceVertical extends SchedulerWorkSpaceIndicator { protected override getFormat(): (date: Date) => string { return formatWeekdayAndDay; @@ -20,6 +30,29 @@ class SchedulerWorkspaceVertical extends SchedulerWorkSpaceIndicator { protected override isRenderHeaderPanelEmptyCell(): boolean { return true; } + + renderRHeaderPanel(isRenderDateHeader = true): void { + super.renderRHeaderPanel(isRenderDateHeader); + this.renderWeekNumberInCornerCell(); + } + + private renderWeekNumberInCornerCell(): void { + if (!this.option().showWeekNumbers) { + this.$element().find(`.${WEEK_NUMBER_LABEL_CLASS}`).remove(); + return; + } + + const startDate = this.getStartViewDate(); + const firstDayOfWeek = this.option().firstDayOfWeek + ?? dateLocalization.firstDayOfWeekIndex(); + const weekNumberRule = this.option().weekNumberRule ?? 'auto'; + const resolvedRule = weekNumberRule === 'auto' ? getLocaleBasedWeekRule() : weekNumberRule; + const weekNumber = dateUtils.getWeekNumber(startDate, firstDayOfWeek, resolvedRule); + + const $emptyCell = this.$element().find(`.${HEADER_PANEL_EMPTY_CELL_CLASS}`); + $emptyCell.find(`.${WEEK_NUMBER_LABEL_CLASS}`).remove(); + $('
').addClass(WEEK_NUMBER_LABEL_CLASS).text(weekNumber).prependTo($emptyCell); + } } export default SchedulerWorkspaceVertical;