[WEB-3523] feat: start of week preference (#7033)
* chore: startOfWeek constant and types updated * chore: startOfWeek updated in profile store * chore: StartOfWeekPreference added to profile appearance settings * chore: calendar layout startOfWeek implementation * chore: date picker startOfWeek implementation * chore: gantt layout startOfWeek implementation * chore: code refactor * chore: code refactor * chore: code refactor
This commit is contained in:
parent
dc16f2862e
commit
8613a80b16
17 changed files with 251 additions and 36 deletions
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
11
packages/types/src/users.d.ts
vendored
11
packages/types/src/users.d.ts
vendored
|
|
@ -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<IUser, "avatar_url" | "cover_image_url" | "display_name" | "first_name" | "last_name"> & {
|
||||
date_joined: Date;
|
||||
user_timezone: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const Calendar = ({ className, classNames, showOutsideDays = true, ...pro
|
|||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
weekStartsOn={props.weekStartsOn}
|
||||
// classNames={{
|
||||
// months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
// month: "space-y-4",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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, CustomThemeSelector } 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();
|
||||
|
|
@ -75,6 +75,7 @@ const ProfileAppearancePage = observer(() => {
|
|||
</div>
|
||||
</div>
|
||||
{userProfile?.theme?.theme === "custom" && <CustomThemeSelector applyThemeChange={applyThemeChange} />}
|
||||
<StartOfWeekPreference />
|
||||
</ProfileSettingContentWrapper>
|
||||
) : (
|
||||
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Matcher } from "react-day-picker";
|
||||
import { createPortal } from "react-dom";
|
||||
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";
|
||||
import { renderFormattedDate, getDate } from "@/helpers/date-time.helper";
|
||||
// hooks
|
||||
import { useUserProfile } from "@/hooks/store";
|
||||
import { useDropdown } from "@/hooks/use-dropdown";
|
||||
// components
|
||||
import { DropdownButton } from "./buttons";
|
||||
|
|
@ -33,7 +36,7 @@ type Props = TDropdownProps & {
|
|||
renderByDefault?: boolean;
|
||||
};
|
||||
|
||||
export const DateDropdown: React.FC<Props> = (props) => {
|
||||
export const DateDropdown: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
buttonClassName = "",
|
||||
buttonContainerClassName,
|
||||
|
|
@ -62,6 +65,9 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
|||
const [isOpen, setIsOpen] = useState(false);
|
||||
// refs
|
||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||
// hooks
|
||||
const { data } = useUserProfile();
|
||||
const startOfWeek = data?.start_of_the_week;
|
||||
// popper-js refs
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
|
@ -186,6 +192,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
|||
disabled={disabledDays}
|
||||
mode="single"
|
||||
fixedWeeks
|
||||
weekStartsOn={startOfWeek}
|
||||
/>
|
||||
</div>
|
||||
</Combobox.Options>,
|
||||
|
|
@ -193,4 +200,4 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
|||
)}
|
||||
</ComboDropDown>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
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
|
||||
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 +90,8 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||
updateRenderView,
|
||||
updateAllBlocksOnChartChangeWhileDragging,
|
||||
} = useTimeLineChartStore();
|
||||
const { data } = useUserProfile();
|
||||
const startOfWeek = data?.start_of_the_week;
|
||||
|
||||
const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews, targetDate?: Date) => {
|
||||
const selectedCurrentView: TGanttViews = view;
|
||||
|
|
@ -98,7 +103,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = 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[]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
// types
|
||||
import { EStartOfTheWeek } from "@plane/constants";
|
||||
import { WeekMonthDataType, ChartDataType, TGanttViews } from "../types";
|
||||
|
||||
// constants
|
||||
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" },
|
||||
{ key: 1, shortTitle: "mon", title: "monday", abbreviation: "M" },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { EStartOfTheWeek } from "@plane/constants";
|
||||
import { TGroupedIssues, TIssue, TIssueMap, TPaginationData } from "@plane/types";
|
||||
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";
|
||||
// types
|
||||
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
|
||||
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||
|
|
@ -65,20 +70,33 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
|||
canEditProperties,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// hooks
|
||||
const { data } = useUserProfile();
|
||||
const startOfWeek = data?.start_of_the_week;
|
||||
|
||||
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 = getOrderedDays(Object.values(week), (item) => item.date.getDay(), startOfWeek);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`grid divide-custom-border-200 md:divide-x-[0.5px] ${showWeekends ? "grid-cols-7" : "grid-cols-5"} ${
|
||||
calendarLayout === "month" ? "" : "h-full"
|
||||
}`}
|
||||
className={cn("grid divide-custom-border-200 md:divide-x-[0.5px]", {
|
||||
"grid-cols-7": showWeekends,
|
||||
"grid-cols-5": !showWeekends,
|
||||
"h-full": calendarLayout !== "month",
|
||||
})}
|
||||
>
|
||||
{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 (
|
||||
<CalendarDayTile
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { observer } from "mobx-react";
|
||||
|
||||
// constants
|
||||
import { EStartOfTheWeek } from "@plane/constants";
|
||||
import { DAYS_LIST } from "@/constants/calendar";
|
||||
// helpers
|
||||
import { getOrderedDays } from "@/helpers/calendar.helper";
|
||||
// hooks
|
||||
import { useUserProfile } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
isLoading: boolean;
|
||||
|
|
@ -10,6 +13,12 @@ type Props = {
|
|||
|
||||
export const CalendarWeekHeader: React.FC<Props> = observer((props) => {
|
||||
const { isLoading, showWeekends } = props;
|
||||
// hooks
|
||||
const { data } = useUserProfile();
|
||||
const startOfWeek = data?.start_of_the_week;
|
||||
|
||||
// derived
|
||||
const orderedDays = getOrderedDays(Object.values(DAYS_LIST), (item) => item.value, startOfWeek);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -20,8 +29,9 @@ export const CalendarWeekHeader: React.FC<Props> = observer((props) => {
|
|||
{isLoading && (
|
||||
<div className="absolute h-[1.5px] w-3/4 animate-[bar-loader_2s_linear_infinite] bg-custom-primary-100" />
|
||||
)}
|
||||
{Object.values(DAYS_LIST).map((day) => {
|
||||
if (!showWeekends && (day.shortTitle === "Sat" || day.shortTitle === "Sun")) return null;
|
||||
{orderedDays.map((day) => {
|
||||
if (!showWeekends && (day.value === EStartOfTheWeek.SUNDAY || day.value === EStartOfTheWeek.SATURDAY))
|
||||
return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
55
web/core/components/profile/start-of-week-preference.tsx
Normal file
55
web/core/components/profile/start-of-week-preference.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"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 (
|
||||
<div className="grid grid-cols-12 gap-4 py-6 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold text-custom-text-100">First day of the week</h4>
|
||||
<p className="text-sm text-custom-text-200">This will change how all calendars in your app look.</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<CustomSelect
|
||||
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",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToast({ type: TOAST_TYPE.ERROR, title: "Update failed", message: "Please try again later." });
|
||||
});
|
||||
}}
|
||||
input
|
||||
maxHeight="lg"
|
||||
>
|
||||
<>
|
||||
{START_OF_THE_WEEK_OPTIONS.map((day) => (
|
||||
<CustomSelect.Option key={day.value} value={day.value}>
|
||||
{day.label}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</>
|
||||
</CustomSelect>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { EStartOfTheWeek } from "@plane/constants";
|
||||
import { TCalendarLayouts } from "@plane/types";
|
||||
|
||||
export const MONTHS_LIST: {
|
||||
|
|
@ -60,35 +61,43 @@ export const DAYS_LIST: {
|
|||
[dayIndex: number]: {
|
||||
shortTitle: string;
|
||||
title: string;
|
||||
value: EStartOfTheWeek;
|
||||
};
|
||||
} = {
|
||||
1: {
|
||||
shortTitle: "Sun",
|
||||
title: "Sunday",
|
||||
value: EStartOfTheWeek.SUNDAY,
|
||||
},
|
||||
2: {
|
||||
shortTitle: "Mon",
|
||||
title: "Monday",
|
||||
value: EStartOfTheWeek.MONDAY,
|
||||
},
|
||||
3: {
|
||||
shortTitle: "Tue",
|
||||
title: "Tuesday",
|
||||
value: EStartOfTheWeek.TUESDAY,
|
||||
},
|
||||
4: {
|
||||
shortTitle: "Wed",
|
||||
title: "Wednesday",
|
||||
value: EStartOfTheWeek.WEDNESDAY,
|
||||
},
|
||||
5: {
|
||||
shortTitle: "Thu",
|
||||
title: "Thursday",
|
||||
value: EStartOfTheWeek.THURSDAY,
|
||||
},
|
||||
6: {
|
||||
shortTitle: "Fri",
|
||||
title: "Friday",
|
||||
value: EStartOfTheWeek.FRIDAY,
|
||||
},
|
||||
7: {
|
||||
shortTitle: "Sat",
|
||||
title: "Saturday",
|
||||
value: EStartOfTheWeek.SATURDAY,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,29 @@ export class CalendarStore implements ICalendarStore {
|
|||
|
||||
const { activeMonthDate } = this.calendarFilters;
|
||||
|
||||
return this.calendarPayload[`y-${activeMonthDate.getFullYear()}`][`m-${activeMonthDate.getMonth()}`];
|
||||
const year = activeMonthDate.getFullYear();
|
||||
const month = activeMonthDate.getMonth();
|
||||
|
||||
// Get the weeks for the current month
|
||||
const weeks = this.calendarPayload[`y-${year}`][`m-${month}`];
|
||||
|
||||
// If no weeks exist, return undefined
|
||||
if (!weeks) return undefined;
|
||||
|
||||
// Create a new object to store the reordered weeks
|
||||
const reorderedWeeks: { [weekNumber: string]: ICalendarWeek } = {};
|
||||
|
||||
// Get all week numbers and sort them
|
||||
const weekNumbers = Object.keys(weeks).map((key) => 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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,21 @@ export const generateCalendarData = (currentStructure: ICalendarPayload | null,
|
|||
|
||||
return calendarData;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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<T>(
|
||||
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;
|
||||
return dayA - dayB;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue