diff --git a/apps/web/core/store/issue/issue_calendar_view.store.ts b/apps/web/core/store/issue/issue_calendar_view.store.ts index f9ee2c29d..9e00cbd7e 100644 --- a/apps/web/core/store/issue/issue_calendar_view.store.ts +++ b/apps/web/core/store/issue/issue_calendar_view.store.ts @@ -1,10 +1,13 @@ -import { observable, action, makeObservable, runInAction, computed } from "mobx"; +import { observable, action, makeObservable, runInAction, computed, reaction } from "mobx"; // helpers import { computedFn } from "mobx-utils"; import type { ICalendarPayload, ICalendarWeek } from "@plane/types"; +import { EStartOfTheWeek } from "@plane/types"; import { generateCalendarData, getWeekNumberOfDate } from "@plane/utils"; // types +import type { IIssueRootStore } from "./root.store"; + export interface ICalendarStore { calendarFilters: { activeMonthDate: Date; @@ -15,6 +18,7 @@ export interface ICalendarStore { // action updateCalendarFilters: (filters: Partial<{ activeMonthDate: Date; activeWeekDate: Date }>) => void; updateCalendarPayload: (date: Date) => void; + regenerateCalendar: () => void; // computed allWeeksOfActiveMonth: @@ -38,8 +42,10 @@ export class CalendarStore implements ICalendarStore { activeWeekDate: new Date(), }; calendarPayload: ICalendarPayload | null = null; + // root store + rootStore; - constructor() { + constructor(_rootStore: IIssueRootStore) { makeObservable(this, { loader: observable.ref, error: observable.ref, @@ -51,6 +57,7 @@ export class CalendarStore implements ICalendarStore { // actions updateCalendarFilters: action, updateCalendarPayload: action, + regenerateCalendar: action, //computed allWeeksOfActiveMonth: computed, @@ -58,7 +65,17 @@ export class CalendarStore implements ICalendarStore { allDaysOfActiveWeek: computed, }); + this.rootStore = _rootStore; this.initCalendar(); + + // Watch for changes in startOfWeek preference and regenerate calendar + reaction( + () => this.rootStore.rootStore.user.userProfile.data?.start_of_the_week, + () => { + // Regenerate calendar when startOfWeek preference changes + this.regenerateCalendar(); + } + ); } get allWeeksOfActiveMonth() { @@ -138,14 +155,32 @@ export class CalendarStore implements ICalendarStore { if (!this.calendarPayload) return null; const nextDate = new Date(date); + const startOfWeek = this.rootStore.rootStore.user.userProfile.data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; runInAction(() => { - this.calendarPayload = generateCalendarData(this.calendarPayload, nextDate); + this.calendarPayload = generateCalendarData(this.calendarPayload, nextDate, startOfWeek); }); }; initCalendar = () => { - const newCalendarPayload = generateCalendarData(null, new Date()); + const startOfWeek = this.rootStore.rootStore.user.userProfile.data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; + const newCalendarPayload = generateCalendarData(null, new Date(), startOfWeek); + + runInAction(() => { + this.calendarPayload = newCalendarPayload; + }); + }; + + /** + * Force complete regeneration of calendar data + * This should be called when startOfWeek preference changes + */ + regenerateCalendar = () => { + const startOfWeek = this.rootStore.rootStore.user.userProfile.data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; + const { activeMonthDate } = this.calendarFilters; + + // Force complete regeneration by passing null to clear all cached data + const newCalendarPayload = generateCalendarData(null, activeMonthDate, startOfWeek); runInAction(() => { this.calendarPayload = newCalendarPayload; diff --git a/apps/web/core/store/issue/root.store.ts b/apps/web/core/store/issue/root.store.ts index 02dcfe184..ad671a566 100644 --- a/apps/web/core/store/issue/root.store.ts +++ b/apps/web/core/store/issue/root.store.ts @@ -266,7 +266,7 @@ export class IssueRootStore implements IIssueRootStore { this.archivedIssues = new ArchivedIssues(this, this.archivedIssuesFilter); this.issueKanBanView = new IssueKanBanViewStore(this); - this.issueCalendarView = new CalendarStore(); + this.issueCalendarView = new CalendarStore(this); this.projectEpicsFilter = new ProjectEpicsFilter(this); this.projectEpics = new ProjectEpics(this, this.projectEpicsFilter); diff --git a/packages/utils/src/calendar.ts b/packages/utils/src/calendar.ts index a8038ec5e..6d1f1de57 100644 --- a/packages/utils/src/calendar.ts +++ b/packages/utils/src/calendar.ts @@ -7,9 +7,14 @@ import { getWeekNumberOfDate, renderFormattedPayloadDate } from "./datetime"; * @returns {ICalendarPayload} calendar payload to render the calendar * @param {ICalendarPayload | null} currentStructure current calendar payload * @param {Date} startDate date of the month to render + * @param {EStartOfTheWeek} startOfWeek the day to start the week on * @description Returns calendar payload to render the calendar, if currentStructure is null, it will generate the payload for the month of startDate, else it will construct the payload for the month of startDate and append it to the currentStructure */ -export const generateCalendarData = (currentStructure: ICalendarPayload | null, startDate: Date): ICalendarPayload => { +export const generateCalendarData = ( + currentStructure: ICalendarPayload | null, + startDate: Date, + startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY +): ICalendarPayload => { const calendarData: ICalendarPayload = currentStructure ?? {}; const startMonth = startDate.getMonth(); @@ -19,10 +24,15 @@ export const generateCalendarData = (currentStructure: ICalendarPayload | null, const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const totalDaysInMonth = new Date(year, month + 1, 0).getDate(); - const firstDayOfMonth = new Date(year, month, 1).getDay(); // Sunday is 0, Monday is 1, ..., Saturday is 6 + const firstDayOfMonthRaw = new Date(year, month, 1).getDay(); // Sunday is 0, Monday is 1, ..., Saturday is 6 + + // Adjust firstDayOfMonth based on startOfWeek preference + // This calculates how many empty cells we need at the start of the calendar + const firstDayOfMonth = (firstDayOfMonthRaw - startOfWeek + 7) % 7; calendarData[`y-${year}`] ||= {}; - calendarData[`y-${year}`][`m-${month}`] ||= {}; + // Always reset the month data to ensure clean regeneration with correct startOfWeek + calendarData[`y-${year}`][`m-${month}`] = {}; const numWeeks = Math.ceil((totalDaysInMonth + firstDayOfMonth) / 7); @@ -50,7 +60,9 @@ export const generateCalendarData = (currentStructure: ICalendarPayload | null, }; } - calendarData[`y-${year}`][`m-${month}`][`w-${weekNumber}`] = currentWeekObject; + // Use sequential week index instead of calculated week number for the key + // This ensures weeks are grouped correctly regardless of startOfWeek preference + calendarData[`y-${year}`][`m-${month}`][`w-${week}`] = currentWeekObject; } return calendarData;