From 2fffdad4c03f4b8578ec701587155512454bf6bb Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 May 2025 18:29:25 +0530 Subject: [PATCH 1/9] chore: startOfWeek constant and types updated --- packages/constants/src/profile.ts | 50 +++++++++++++++++++++++++++++++ packages/types/src/users.d.ts | 11 ++----- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/packages/constants/src/profile.ts b/packages/constants/src/profile.ts index f7765a0cfc0..032e4526a84 100644 --- a/packages/constants/src/profile.ts +++ b/packages/constants/src/profile.ts @@ -71,3 +71,53 @@ export const PROFILE_ADMINS_TAB = [ selected: "/activity/", }, ]; + +/** + * @description The start of the week for the user + * @enum {number} + */ +export enum EStartOfTheWeek { + SUNDAY = 0, + MONDAY = 1, + TUESDAY = 2, + WEDNESDAY = 3, + THURSDAY = 4, + FRIDAY = 5, + SATURDAY = 6, +} + +/** + * @description The options for the start of the week + * @type {Array<{value: EStartOfTheWeek, label: string}>} + * @constant + */ +export const START_OF_THE_WEEK_OPTIONS = [ + { + value: EStartOfTheWeek.SUNDAY, + label: "Sunday", + }, + { + value: EStartOfTheWeek.MONDAY, + label: "Monday", + }, + { + value: EStartOfTheWeek.TUESDAY, + label: "Tuesday", + }, + { + value: EStartOfTheWeek.WEDNESDAY, + label: "Wednesday", + }, + { + value: EStartOfTheWeek.THURSDAY, + label: "Thursday", + }, + { + value: EStartOfTheWeek.FRIDAY, + label: "Friday", + }, + { + value: EStartOfTheWeek.SATURDAY, + label: "Saturday", + }, +]; diff --git a/packages/types/src/users.d.ts b/packages/types/src/users.d.ts index e5140fdef10..9f6ac490559 100644 --- a/packages/types/src/users.d.ts +++ b/packages/types/src/users.d.ts @@ -1,3 +1,4 @@ +import { EStartOfTheWeek } from "@plane/constants"; import { IIssueActivity, TIssuePriorities, TStateGroups } from "."; import { TUserPermissions } from "./enums"; @@ -64,6 +65,7 @@ export type TUserProfile = { language: string; created_at: Date | string; updated_at: Date | string; + start_of_the_week: EStartOfTheWeek; }; export interface IInstanceAdminStatus { @@ -155,14 +157,7 @@ export interface IUserProfileProjectSegregation { id: string; pending_issues: number; }[]; - user_data: Pick< - IUser, - | "avatar_url" - | "cover_image_url" - | "display_name" - | "first_name" - | "last_name" - > & { + user_data: Pick & { date_joined: Date; user_timezone: string; }; From 0fc6e88554b76dc528a1a24b04db1ff1e55e00cd Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 May 2025 18:30:18 +0530 Subject: [PATCH 2/9] chore: startOfWeek updated in profile store --- web/core/store/user/profile.store.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/core/store/user/profile.store.ts b/web/core/store/user/profile.store.ts index 08cbe1fc70f..d5e796be668 100644 --- a/web/core/store/user/profile.store.ts +++ b/web/core/store/user/profile.store.ts @@ -2,6 +2,7 @@ import cloneDeep from "lodash/cloneDeep"; import set from "lodash/set"; import { action, makeObservable, observable, runInAction } from "mobx"; // types +import { EStartOfTheWeek } from "@plane/constants"; import { IUserTheme, TUserProfile } from "@plane/types"; // services import { UserService } from "@/services/user.service"; @@ -58,7 +59,8 @@ export class ProfileStore implements IUserProfileStore { has_billing_address: false, created_at: "", updated_at: "", - language: "" + language: "", + start_of_the_week: EStartOfTheWeek.SUNDAY, }; // services From ba6d4ee088369043ab30f34c495e46b9296effd1 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 May 2025 18:38:18 +0530 Subject: [PATCH 3/9] chore: StartOfWeekPreference added to profile appearance settings --- web/app/profile/appearance/page.tsx | 8 +-- web/core/components/profile/index.ts | 1 + .../profile/start-of-week-preference.tsx | 51 +++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 web/core/components/profile/start-of-week-preference.tsx diff --git a/web/app/profile/appearance/page.tsx b/web/app/profile/appearance/page.tsx index c89bcdf3c33..c8541b6c8ad 100644 --- a/web/app/profile/appearance/page.tsx +++ b/web/app/profile/appearance/page.tsx @@ -10,13 +10,13 @@ import { IUserTheme } from "@plane/types"; import { setPromiseToast } from "@plane/ui"; // components import { LogoSpinner } from "@/components/common"; -import { CustomThemeSelector, ThemeSwitch, PageHead } from "@/components/core"; -import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/components/profile"; -// constants +import { ThemeSwitch, PageHead } from "@/components/core"; +import { ProfileSettingContentHeader, ProfileSettingContentWrapper, StartOfWeekPreference } from "@/components/profile"; // helpers import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; // hooks import { useUserProfile } from "@/hooks/store"; + const ProfileAppearancePage = observer(() => { const { t } = useTranslation(); const { setTheme } = useTheme(); @@ -74,7 +74,7 @@ const ProfileAppearancePage = observer(() => { - {userProfile?.theme?.theme === "custom" && } + ) : (
diff --git a/web/core/components/profile/index.ts b/web/core/components/profile/index.ts index e5495aba1f5..5ccaa341645 100644 --- a/web/core/components/profile/index.ts +++ b/web/core/components/profile/index.ts @@ -6,3 +6,4 @@ export * from "./time"; export * from "./profile-setting-content-wrapper"; export * from "./profile-setting-content-header"; export * from "./form"; +export * from "./start-of-week-preference"; diff --git a/web/core/components/profile/start-of-week-preference.tsx b/web/core/components/profile/start-of-week-preference.tsx new file mode 100644 index 00000000000..50ed50533bd --- /dev/null +++ b/web/core/components/profile/start-of-week-preference.tsx @@ -0,0 +1,51 @@ +"use client"; + +import React from "react"; +import { observer } from "mobx-react"; +// plane imports +import { EStartOfTheWeek, START_OF_THE_WEEK_OPTIONS } from "@plane/constants"; +import { CustomSelect, setToast, TOAST_TYPE } from "@plane/ui"; +// hooks +import { useUserProfile } from "@/hooks/store"; + +const getStartOfWeekLabel = (startOfWeek: EStartOfTheWeek) => + START_OF_THE_WEEK_OPTIONS.find((option) => option.value === startOfWeek)?.label; + +export const StartOfWeekPreference = observer(() => { + // hooks + const { data: userProfile, updateUserProfile } = useUserProfile(); + + return ( +
+
+

First day of the week

+

This will change how all calendars in your app look.

+
+
+ { + updateUserProfile({ start_of_the_week: val }).then(() => { + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success", + message: "First day of the week updated successfully", + }); + }); + }} + input + maxHeight="lg" + > + <> + {START_OF_THE_WEEK_OPTIONS.map((day) => ( + + {day.label} + + ))} + + +
+
+ ); +}); From c700f5b265992f26155a363ebc66933b3c82103b Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 May 2025 18:45:41 +0530 Subject: [PATCH 4/9] chore: calendar layout startOfWeek implementation --- .../issue-layouts/calendar/week-days.tsx | 31 ++++++++++++++++--- .../issue-layouts/calendar/week-header.tsx | 18 ++++++++--- web/core/constants/calendar.ts | 9 ++++++ .../store/issue/issue_calendar_view.store.ts | 24 +++++++++++++- web/helpers/calendar.helper.ts | 9 ++++++ 5 files changed, 80 insertions(+), 11 deletions(-) diff --git a/web/core/components/issues/issue-layouts/calendar/week-days.tsx b/web/core/components/issues/issue-layouts/calendar/week-days.tsx index 9aaa132294c..8d3eac13492 100644 --- a/web/core/components/issues/issue-layouts/calendar/week-days.tsx +++ b/web/core/components/issues/issue-layouts/calendar/week-days.tsx @@ -1,9 +1,12 @@ import { observer } from "mobx-react"; import { TGroupedIssues, TIssue, TIssueMap, TPaginationData } from "@plane/types"; +import { cn } from "@plane/utils"; // components import { CalendarDayTile } from "@/components/issues"; // helpers import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; +// hooks +import { useUserProfile } from "@/hooks/store"; // types import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic"; import { ICycleIssuesFilter } from "@/store/issue/cycle"; @@ -65,20 +68,38 @@ export const CalendarWeekDays: React.FC = observer((props) => { canEditProperties, isEpic = false, } = props; + // hooks + const { + data: { start_of_the_week: startOfWeek }, + } = useUserProfile(); const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month"; const showWeekends = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.show_weekends ?? false; if (!week) return null; + const shouldShowDay = (dayDate: Date) => { + if (showWeekends) return true; + const day = dayDate.getDay(); + return !(day === 0 || day === 6); + }; + + const sortedWeekDays = Object.values(week).sort((a, b) => { + const dayA = (7 + a.date.getDay() - startOfWeek) % 7; + const dayB = (7 + b.date.getDay() - startOfWeek) % 7; + return dayA - dayB; + }); + return (
- {Object.values(week).map((date: ICalendarDate) => { - if (!showWeekends && (date.date.getDay() === 0 || date.date.getDay() === 6)) return null; + {sortedWeekDays.map((date: ICalendarDate) => { + if (!shouldShowDay(date.date)) return null; return ( = observer((props) => { const { isLoading, showWeekends } = props; + // hooks + const { + data: { start_of_the_week: startOfWeek }, + } = useUserProfile(); + + // derived + const orderedDays = getOrderedDays(startOfWeek); return (
= observer((props) => { {isLoading && (
)} - {Object.values(DAYS_LIST).map((day) => { - if (!showWeekends && (day.shortTitle === "Sat" || day.shortTitle === "Sun")) return null; + {orderedDays.map((day) => { + if (!showWeekends && (day.value === 0 || day.value === 6)) return null; return (
parseInt(key.replace("w-", ""))); + weekNumbers.sort((a, b) => a - b); + + // Reorder weeks based on start_of_week + weekNumbers.forEach((weekNumber) => { + const weekKey = `w-${weekNumber}`; + reorderedWeeks[weekKey] = weeks[weekKey]; + }); + + return reorderedWeeks; } get activeWeekNumber() { diff --git a/web/helpers/calendar.helper.ts b/web/helpers/calendar.helper.ts index 5b89d8625f8..e363c08f3a7 100644 --- a/web/helpers/calendar.helper.ts +++ b/web/helpers/calendar.helper.ts @@ -1,5 +1,7 @@ +import { EStartOfTheWeek } from "@plane/constants"; // helpers import { ICalendarDate, ICalendarPayload } from "@/components/issues"; +import { DAYS_LIST } from "@/constants/calendar"; import { getWeekNumberOfDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; // types @@ -92,3 +94,10 @@ export const generateCalendarData = (currentStructure: ICalendarPayload | null, return calendarData; }; + +export const getOrderedDays = (startOfWeek: EStartOfTheWeek) => + Object.values(DAYS_LIST).sort((a, b) => { + const dayA = (7 + a.value - startOfWeek) % 7; + const dayB = (7 + b.value - startOfWeek) % 7; + return dayA - dayB; + }); From 7ac9eaefc13e784eccc342e33f2e331a59837336 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 May 2025 18:46:19 +0530 Subject: [PATCH 5/9] chore: date picker startOfWeek implementation --- packages/ui/src/calendar.tsx | 1 + web/core/components/dropdowns/date.tsx | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/calendar.tsx b/packages/ui/src/calendar.tsx index 9fdbab17662..80b160cc1b4 100644 --- a/packages/ui/src/calendar.tsx +++ b/packages/ui/src/calendar.tsx @@ -17,6 +17,7 @@ export const Calendar = ({ className, classNames, showOutsideDays = true, ...pro = (props) => { +export const DateDropdown: React.FC = observer((props) => { const { buttonClassName = "", buttonContainerClassName, @@ -62,6 +64,10 @@ export const DateDropdown: React.FC = (props) => { const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + // hooks + const { + data: { start_of_the_week: startOfWeek }, + } = useUserProfile(); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -186,6 +192,7 @@ export const DateDropdown: React.FC = (props) => { disabled={disabledDays} mode="single" fixedWeeks + weekStartsOn={startOfWeek} />
, @@ -193,4 +200,4 @@ export const DateDropdown: React.FC = (props) => { )} ); -}; +}); From 18e5a4c74b492d63733b0b251edeb74052ac0551 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 May 2025 18:54:48 +0530 Subject: [PATCH 6/9] chore: gantt layout startOfWeek implementation --- .../components/gantt-chart/chart/root.tsx | 6 +++- web/core/components/gantt-chart/data/index.ts | 16 +++++++++ .../components/gantt-chart/views/week-view.ts | 33 ++++++++++++------- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/web/core/components/gantt-chart/chart/root.tsx b/web/core/components/gantt-chart/chart/root.tsx index 03e63e7a5bf..5944c9a113f 100644 --- a/web/core/components/gantt-chart/chart/root.tsx +++ b/web/core/components/gantt-chart/chart/root.tsx @@ -5,6 +5,7 @@ import { GanttChartHeader, GanttChartMainContent } from "@/components/gantt-char // helpers import { cn } from "@/helpers/common.helper"; // hooks +import { useUserProfile } from "@/hooks/store"; import { useTimeLineChartStore } from "@/hooks/use-timeline-chart"; // import { SIDEBAR_WIDTH } from "../constants"; @@ -87,6 +88,9 @@ export const ChartViewRoot: FC = observer((props) => { updateRenderView, updateAllBlocksOnChartChangeWhileDragging, } = useTimeLineChartStore(); + const { + data: { start_of_the_week: startOfWeek }, + } = useUserProfile(); const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews, targetDate?: Date) => { const selectedCurrentView: TGanttViews = view; @@ -98,7 +102,7 @@ export const ChartViewRoot: FC = observer((props) => { if (selectedCurrentViewData === undefined) return; const currentViewHelpers = timelineViewHelpers[selectedCurrentView]; - const currentRender = currentViewHelpers.generateChart(selectedCurrentViewData, side, targetDate); + const currentRender = currentViewHelpers.generateChart(selectedCurrentViewData, side, targetDate, startOfWeek); const mergeRenderPayloads = currentViewHelpers.mergeRenderPayloads as ( a: IWeekBlock[] | IMonthView | IMonthBlock[], b: IWeekBlock[] | IMonthView | IMonthBlock[] diff --git a/web/core/components/gantt-chart/data/index.ts b/web/core/components/gantt-chart/data/index.ts index 6db8dda659f..53293fb2cbc 100644 --- a/web/core/components/gantt-chart/data/index.ts +++ b/web/core/components/gantt-chart/data/index.ts @@ -1,7 +1,23 @@ // types +import { EStartOfTheWeek } from "@plane/constants"; import { WeekMonthDataType, ChartDataType, TGanttViews } from "../types"; // constants +export const generateWeeks = (startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY): WeekMonthDataType[] => { + const allDays: WeekMonthDataType[] = [ + { key: 0, shortTitle: "sun", title: "sunday", abbreviation: "Su" }, + { key: 1, shortTitle: "mon", title: "monday", abbreviation: "M" }, + { key: 2, shortTitle: "tue", title: "tuesday", abbreviation: "T" }, + { key: 3, shortTitle: "wed", title: "wednesday", abbreviation: "W" }, + { key: 4, shortTitle: "thurs", title: "thursday", abbreviation: "Th" }, + { key: 5, shortTitle: "fri", title: "friday", abbreviation: "F" }, + { key: 6, shortTitle: "sat", title: "saturday", abbreviation: "Sa" }, + ]; + + // Reorder the array based on startOfWeek + return [...allDays.slice(startOfWeek), ...allDays.slice(0, startOfWeek)]; +}; + export const weeks: WeekMonthDataType[] = [ { key: 0, shortTitle: "sun", title: "sunday", abbreviation: "Su" }, { key: 1, shortTitle: "mon", title: "monday", abbreviation: "M" }, diff --git a/web/core/components/gantt-chart/views/week-view.ts b/web/core/components/gantt-chart/views/week-view.ts index 65915274c53..ea3d75f91b5 100644 --- a/web/core/components/gantt-chart/views/week-view.ts +++ b/web/core/components/gantt-chart/views/week-view.ts @@ -1,5 +1,6 @@ // -import { weeks, months } from "../data"; +import { EStartOfTheWeek } from "@plane/constants"; +import { months, generateWeeks } from "../data"; import { ChartDataType } from "../types"; import { getNumberOfDaysBetweenTwoDates, getWeekNumberByDate } from "./helpers"; export interface IDayBlock { @@ -38,7 +39,12 @@ export interface IWeekBlock { * @param side * @returns */ -const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "right", targetDate?: Date) => { +const generateWeekChart = ( + weekPayload: ChartDataType, + side: null | "left" | "right", + targetDate?: Date, + startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY +) => { let renderState = weekPayload; const range: number = renderState.data.approxFilterRange || 6; @@ -56,7 +62,7 @@ const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "ri minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate()); plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate()); - if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate); + if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate, true, startOfWeek); startDate = filteredDates[0].startDate; endDate = filteredDates[filteredDates.length - 1].endDate; @@ -77,7 +83,7 @@ const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "ri minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1); plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1); - if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate); + if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate, true, startOfWeek); startDate = filteredDates[0].startDate; endDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1); @@ -94,7 +100,7 @@ const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "ri minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1); plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 1); - if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate); + if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate, true, startOfWeek); startDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1); endDate = filteredDates[filteredDates.length - 1].endDate; @@ -120,14 +126,18 @@ const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "ri export const getWeeksBetweenTwoDates = ( startDate: Date, endDate: Date, - shouldPopulateDaysForWeek: boolean = true + shouldPopulateDaysForWeek: boolean = true, + startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY ): IWeekBlock[] => { const weeks: IWeekBlock[] = []; const currentDate = new Date(startDate.getTime()); const today = new Date(); - currentDate.setDate(currentDate.getDate() - currentDate.getDay()); + // Adjust the current date to the start of the week + const day = currentDate.getDay(); + const diff = (day + 7 - startOfWeek) % 7; // Calculate days to subtract to get to startOfWeek + currentDate.setDate(currentDate.getDate() - diff); while (currentDate <= endDate) { const weekStartDate = new Date(currentDate.getTime()); @@ -141,7 +151,7 @@ export const getWeeksBetweenTwoDates = ( const weekNumber = getWeekNumberByDate(currentDate); weeks.push({ - children: shouldPopulateDaysForWeek ? populateDaysForWeek(weekStartDate) : undefined, + children: shouldPopulateDaysForWeek ? populateDaysForWeek(weekStartDate, startOfWeek) : undefined, weekNumber, weekData: { shortTitle: `w${weekNumber}`, @@ -171,17 +181,18 @@ export const getWeeksBetweenTwoDates = ( * @param startDate * @returns */ -const populateDaysForWeek = (startDate: Date): IDayBlock[] => { +const populateDaysForWeek = (startDate: Date, startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY): IDayBlock[] => { const currentDate = new Date(startDate); const days: IDayBlock[] = []; const today = new Date(); + const weekDays = generateWeeks(startOfWeek); for (let i = 0; i < 7; i++) { days.push({ date: new Date(currentDate), day: currentDate.getDay(), - dayData: weeks[currentDate.getDay()], - title: `${weeks[currentDate.getDay()].abbreviation} ${currentDate.getDate()}`, + dayData: weekDays[i], + title: `${weekDays[i].abbreviation} ${currentDate.getDate()}`, today: today.setHours(0, 0, 0, 0) == currentDate.setHours(0, 0, 0, 0), }); currentDate.setDate(currentDate.getDate() + 1); From 8f1c350bb9250cc76096b1aac8346baafbc3bbbd Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Fri, 9 May 2025 13:37:47 +0530 Subject: [PATCH 7/9] chore: code refactor --- space/core/store/profile.store.ts | 2 ++ web/core/components/dropdowns/date.tsx | 6 +++--- web/core/components/gantt-chart/chart/root.tsx | 7 ++++--- .../issues/issue-layouts/calendar/week-days.tsx | 6 +++--- .../issue-layouts/calendar/week-header.tsx | 6 +++--- .../profile/start-of-week-preference.tsx | 16 ++++++++++------ 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/space/core/store/profile.store.ts b/space/core/store/profile.store.ts index 5523e8daddb..c032efba998 100644 --- a/space/core/store/profile.store.ts +++ b/space/core/store/profile.store.ts @@ -1,6 +1,7 @@ import set from "lodash/set"; import { action, makeObservable, observable, runInAction } from "mobx"; // plane imports +import { EStartOfTheWeek } from "@plane/constants"; import { UserService } from "@plane/services"; import { TUserProfile } from "@plane/types"; // store @@ -54,6 +55,7 @@ export class ProfileStore implements IProfileStore { created_at: "", updated_at: "", language: "", + start_of_the_week: EStartOfTheWeek.SUNDAY, }; // services diff --git a/web/core/components/dropdowns/date.tsx b/web/core/components/dropdowns/date.tsx index e1fe7d28879..9f75e8d3388 100644 --- a/web/core/components/dropdowns/date.tsx +++ b/web/core/components/dropdowns/date.tsx @@ -6,6 +6,7 @@ import { usePopper } from "react-popper"; import { CalendarDays, X } from "lucide-react"; import { Combobox } from "@headlessui/react"; // ui +import { EStartOfTheWeek } from "@plane/constants"; import { ComboDropDown, Calendar } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; @@ -65,9 +66,8 @@ export const DateDropdown: React.FC = observer((props) => { // refs const dropdownRef = useRef(null); // hooks - const { - data: { start_of_the_week: startOfWeek }, - } = useUserProfile(); + const { data } = useUserProfile(); + const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); diff --git a/web/core/components/gantt-chart/chart/root.tsx b/web/core/components/gantt-chart/chart/root.tsx index 5944c9a113f..6f9d3294715 100644 --- a/web/core/components/gantt-chart/chart/root.tsx +++ b/web/core/components/gantt-chart/chart/root.tsx @@ -1,5 +1,7 @@ import { FC, useEffect, useState } from "react"; import { observer } from "mobx-react"; +// plane imports +import { EStartOfTheWeek } from "@plane/constants"; // components import { GanttChartHeader, GanttChartMainContent } from "@/components/gantt-chart"; // helpers @@ -88,9 +90,8 @@ export const ChartViewRoot: FC = observer((props) => { updateRenderView, updateAllBlocksOnChartChangeWhileDragging, } = useTimeLineChartStore(); - const { - data: { start_of_the_week: startOfWeek }, - } = useUserProfile(); + const { data } = useUserProfile(); + const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews, targetDate?: Date) => { const selectedCurrentView: TGanttViews = view; diff --git a/web/core/components/issues/issue-layouts/calendar/week-days.tsx b/web/core/components/issues/issue-layouts/calendar/week-days.tsx index 8d3eac13492..5b000ee9758 100644 --- a/web/core/components/issues/issue-layouts/calendar/week-days.tsx +++ b/web/core/components/issues/issue-layouts/calendar/week-days.tsx @@ -1,4 +1,5 @@ import { observer } from "mobx-react"; +import { EStartOfTheWeek } from "@plane/constants"; import { TGroupedIssues, TIssue, TIssueMap, TPaginationData } from "@plane/types"; import { cn } from "@plane/utils"; // components @@ -69,9 +70,8 @@ export const CalendarWeekDays: React.FC = observer((props) => { isEpic = false, } = props; // hooks - const { - data: { start_of_the_week: startOfWeek }, - } = useUserProfile(); + const { data } = useUserProfile(); + const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month"; const showWeekends = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.show_weekends ?? false; diff --git a/web/core/components/issues/issue-layouts/calendar/week-header.tsx b/web/core/components/issues/issue-layouts/calendar/week-header.tsx index aab03e6f668..43e9b0b7bc8 100644 --- a/web/core/components/issues/issue-layouts/calendar/week-header.tsx +++ b/web/core/components/issues/issue-layouts/calendar/week-header.tsx @@ -1,4 +1,5 @@ import { observer } from "mobx-react"; +import { EStartOfTheWeek } from "@plane/constants"; // helpers import { getOrderedDays } from "@/helpers/calendar.helper"; // hooks @@ -12,9 +13,8 @@ type Props = { export const CalendarWeekHeader: React.FC = observer((props) => { const { isLoading, showWeekends } = props; // hooks - const { - data: { start_of_the_week: startOfWeek }, - } = useUserProfile(); + const { data } = useUserProfile(); + const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; // derived const orderedDays = getOrderedDays(startOfWeek); diff --git a/web/core/components/profile/start-of-week-preference.tsx b/web/core/components/profile/start-of-week-preference.tsx index 50ed50533bd..91049b8099b 100644 --- a/web/core/components/profile/start-of-week-preference.tsx +++ b/web/core/components/profile/start-of-week-preference.tsx @@ -26,13 +26,17 @@ export const StartOfWeekPreference = observer(() => { value={userProfile.start_of_the_week} label={getStartOfWeekLabel(userProfile.start_of_the_week)} onChange={(val: number) => { - updateUserProfile({ start_of_the_week: val }).then(() => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success", - message: "First day of the week updated successfully", + updateUserProfile({ start_of_the_week: val }) + .then(() => { + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success", + message: "First day of the week updated successfully", + }); + }) + .catch(() => { + setToast({ type: TOAST_TYPE.ERROR, title: "Update failed", message: "Please try again later." }); }); - }); }} input maxHeight="lg" From 6b8bd0945e3b4d1d9296bcc377773150ab1c5422 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Fri, 9 May 2025 17:57:47 +0530 Subject: [PATCH 8/9] chore: code refactor --- web/app/profile/appearance/page.tsx | 3 ++- web/core/components/gantt-chart/data/index.ts | 18 ++++-------------- .../issue-layouts/calendar/week-days.tsx | 7 ++----- .../issue-layouts/calendar/week-header.tsx | 3 ++- web/helpers/calendar.helper.ts | 15 +++++++++++---- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/web/app/profile/appearance/page.tsx b/web/app/profile/appearance/page.tsx index c8541b6c8ad..db367e49a7c 100644 --- a/web/app/profile/appearance/page.tsx +++ b/web/app/profile/appearance/page.tsx @@ -10,7 +10,7 @@ import { IUserTheme } from "@plane/types"; import { setPromiseToast } from "@plane/ui"; // components import { LogoSpinner } from "@/components/common"; -import { ThemeSwitch, PageHead } from "@/components/core"; +import { ThemeSwitch, PageHead, CustomThemeSelector } from "@/components/core"; import { ProfileSettingContentHeader, ProfileSettingContentWrapper, StartOfWeekPreference } from "@/components/profile"; // helpers import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; @@ -74,6 +74,7 @@ const ProfileAppearancePage = observer(() => {
+ {userProfile?.theme?.theme === "custom" && } ) : ( diff --git a/web/core/components/gantt-chart/data/index.ts b/web/core/components/gantt-chart/data/index.ts index 53293fb2cbc..2e72810d87b 100644 --- a/web/core/components/gantt-chart/data/index.ts +++ b/web/core/components/gantt-chart/data/index.ts @@ -3,20 +3,10 @@ import { EStartOfTheWeek } from "@plane/constants"; import { WeekMonthDataType, ChartDataType, TGanttViews } from "../types"; // constants -export const generateWeeks = (startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY): WeekMonthDataType[] => { - const allDays: WeekMonthDataType[] = [ - { key: 0, shortTitle: "sun", title: "sunday", abbreviation: "Su" }, - { key: 1, shortTitle: "mon", title: "monday", abbreviation: "M" }, - { key: 2, shortTitle: "tue", title: "tuesday", abbreviation: "T" }, - { key: 3, shortTitle: "wed", title: "wednesday", abbreviation: "W" }, - { key: 4, shortTitle: "thurs", title: "thursday", abbreviation: "Th" }, - { key: 5, shortTitle: "fri", title: "friday", abbreviation: "F" }, - { key: 6, shortTitle: "sat", title: "saturday", abbreviation: "Sa" }, - ]; - - // Reorder the array based on startOfWeek - return [...allDays.slice(startOfWeek), ...allDays.slice(0, startOfWeek)]; -}; +export const generateWeeks = (startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY): WeekMonthDataType[] => [ + ...weeks.slice(startOfWeek), + ...weeks.slice(0, startOfWeek), +]; export const weeks: WeekMonthDataType[] = [ { key: 0, shortTitle: "sun", title: "sunday", abbreviation: "Su" }, diff --git a/web/core/components/issues/issue-layouts/calendar/week-days.tsx b/web/core/components/issues/issue-layouts/calendar/week-days.tsx index 5b000ee9758..430caf42fbb 100644 --- a/web/core/components/issues/issue-layouts/calendar/week-days.tsx +++ b/web/core/components/issues/issue-layouts/calendar/week-days.tsx @@ -5,6 +5,7 @@ import { cn } from "@plane/utils"; // components import { CalendarDayTile } from "@/components/issues"; // helpers +import { getOrderedDays } from "@/helpers/calendar.helper"; import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; // hooks import { useUserProfile } from "@/hooks/store"; @@ -84,11 +85,7 @@ export const CalendarWeekDays: React.FC = observer((props) => { return !(day === 0 || day === 6); }; - const sortedWeekDays = Object.values(week).sort((a, b) => { - const dayA = (7 + a.date.getDay() - startOfWeek) % 7; - const dayB = (7 + b.date.getDay() - startOfWeek) % 7; - return dayA - dayB; - }); + const sortedWeekDays = getOrderedDays(Object.values(week), (item) => item.date.getDay(), startOfWeek); return (
= observer((props) => { const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; // derived - const orderedDays = getOrderedDays(startOfWeek); + const orderedDays = getOrderedDays(Object.values(DAYS_LIST), (item) => item.value, startOfWeek); return (
- Object.values(DAYS_LIST).sort((a, b) => { - const dayA = (7 + a.value - startOfWeek) % 7; - const dayB = (7 + b.value - startOfWeek) % 7; +/** + * Returns a new array sorted by the startOfWeek. + * @param items Array of items to sort. + * @param getDayIndex Function to get the day index (0-6) from an item. + * @param startOfWeek The day to start the week on. + */ +export function getOrderedDays(items: T[], getDayIndex: (item: T) => number, startOfWeek: EStartOfTheWeek): T[] { + return [...items].sort((a, b) => { + const dayA = (7 + getDayIndex(a) - startOfWeek) % 7; + const dayB = (7 + getDayIndex(b) - startOfWeek) % 7; return dayA - dayB; }); +} From 7dfa2a3813a0d10f9e56d92dadc89df38efad6de Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 12 May 2025 17:12:38 +0530 Subject: [PATCH 9/9] chore: code refactor --- web/core/components/dropdowns/date.tsx | 2 +- web/core/components/gantt-chart/chart/root.tsx | 2 +- .../components/issues/issue-layouts/calendar/week-days.tsx | 2 +- .../issues/issue-layouts/calendar/week-header.tsx | 5 +++-- web/helpers/calendar.helper.ts | 6 +++++- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/web/core/components/dropdowns/date.tsx b/web/core/components/dropdowns/date.tsx index 9f75e8d3388..684d6f3ef00 100644 --- a/web/core/components/dropdowns/date.tsx +++ b/web/core/components/dropdowns/date.tsx @@ -67,7 +67,7 @@ export const DateDropdown: React.FC = observer((props) => { const dropdownRef = useRef(null); // hooks const { data } = useUserProfile(); - const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; + const startOfWeek = data?.start_of_the_week; // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); diff --git a/web/core/components/gantt-chart/chart/root.tsx b/web/core/components/gantt-chart/chart/root.tsx index 6f9d3294715..16604d88170 100644 --- a/web/core/components/gantt-chart/chart/root.tsx +++ b/web/core/components/gantt-chart/chart/root.tsx @@ -91,7 +91,7 @@ export const ChartViewRoot: FC = observer((props) => { updateAllBlocksOnChartChangeWhileDragging, } = useTimeLineChartStore(); const { data } = useUserProfile(); - const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; + const startOfWeek = data?.start_of_the_week; const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews, targetDate?: Date) => { const selectedCurrentView: TGanttViews = view; diff --git a/web/core/components/issues/issue-layouts/calendar/week-days.tsx b/web/core/components/issues/issue-layouts/calendar/week-days.tsx index 430caf42fbb..c5ba104ee58 100644 --- a/web/core/components/issues/issue-layouts/calendar/week-days.tsx +++ b/web/core/components/issues/issue-layouts/calendar/week-days.tsx @@ -72,7 +72,7 @@ export const CalendarWeekDays: React.FC = observer((props) => { } = props; // hooks const { data } = useUserProfile(); - const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; + const startOfWeek = data?.start_of_the_week; const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month"; const showWeekends = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.show_weekends ?? false; diff --git a/web/core/components/issues/issue-layouts/calendar/week-header.tsx b/web/core/components/issues/issue-layouts/calendar/week-header.tsx index 65e007ad52c..d97a9340e72 100644 --- a/web/core/components/issues/issue-layouts/calendar/week-header.tsx +++ b/web/core/components/issues/issue-layouts/calendar/week-header.tsx @@ -15,7 +15,7 @@ export const CalendarWeekHeader: React.FC = observer((props) => { const { isLoading, showWeekends } = props; // hooks const { data } = useUserProfile(); - const startOfWeek = data?.start_of_the_week ?? EStartOfTheWeek.SUNDAY; + const startOfWeek = data?.start_of_the_week; // derived const orderedDays = getOrderedDays(Object.values(DAYS_LIST), (item) => item.value, startOfWeek); @@ -30,7 +30,8 @@ export const CalendarWeekHeader: React.FC = observer((props) => {
)} {orderedDays.map((day) => { - if (!showWeekends && (day.value === 0 || day.value === 6)) return null; + if (!showWeekends && (day.value === EStartOfTheWeek.SUNDAY || day.value === EStartOfTheWeek.SATURDAY)) + return null; return (
(items: T[], getDayIndex: (item: T) => number, startOfWeek: EStartOfTheWeek): T[] { +export function getOrderedDays( + items: T[], + getDayIndex: (item: T) => number, + startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY +): T[] { return [...items].sort((a, b) => { const dayA = (7 + getDayIndex(a) - startOfWeek) % 7; const dayB = (7 + getDayIndex(b) - startOfWeek) % 7;