From 11b0aa13f10942c524a8e065785c36ba86286b45 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 26 Sep 2023 13:16:47 +0530 Subject: [PATCH 01/11] dev: calendar view init --- .../filters/filter-selection.tsx | 71 ------------------- .../filters/filters-selection.tsx | 2 +- web/components/issues/index.ts | 1 + .../issue-layouts/calendar/calendar.tsx | 33 +++++++++ .../issues/issue-layouts/calendar/data.ts | 33 +++++++++ .../issues/issue-layouts/calendar/index.ts | 2 + web/components/issues/issue-layouts/index.ts | 1 + web/pages/calendar.tsx | 19 +++++ 8 files changed, 90 insertions(+), 72 deletions(-) delete mode 100644 web/components/issue-layouts/filters/filter-selection.tsx create mode 100644 web/components/issues/issue-layouts/calendar/calendar.tsx create mode 100644 web/components/issues/issue-layouts/calendar/data.ts create mode 100644 web/components/issues/issue-layouts/calendar/index.ts create mode 100644 web/components/issues/issue-layouts/index.ts create mode 100644 web/pages/calendar.tsx diff --git a/web/components/issue-layouts/filters/filter-selection.tsx b/web/components/issue-layouts/filters/filter-selection.tsx deleted file mode 100644 index 5e20d384aec..00000000000 --- a/web/components/issue-layouts/filters/filter-selection.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from "react"; - -// components -import { - FilterAssignees, - FilterCreatedBy, - FilterLabels, - FilterPriority, - FilterState, - FilterStateGroup, -} from "components/issue-layouts"; - -type Props = { - workspaceSlug: string; - projectId: string; -}; - -export const FilterSelection: React.FC = (props) => { - const { workspaceSlug, projectId } = props; - - return ( -
- {/*
Search container
*/} -
- {/* priority */} -
- -
- - {/* state group */} -
- -
- - {/* state */} -
- -
- - {/* assignees */} -
- -
- - {/* created_by */} -
- -
- - {/* labels */} -
- -
- - {/* start_date */} - {/* {handleFilterSectionVisibility("start_date") && ( -
- -
- )} */} - - {/* due_date */} - {/* {handleFilterSectionVisibility("due_date") && ( -
- -
- )} */} -
-
- ); -}; diff --git a/web/components/issue-layouts/filters/filters-selection.tsx b/web/components/issue-layouts/filters/filters-selection.tsx index c0416e356e3..71aa144bf7b 100644 --- a/web/components/issue-layouts/filters/filters-selection.tsx +++ b/web/components/issue-layouts/filters/filters-selection.tsx @@ -90,7 +90,7 @@ export const FilterSelection: React.FC = observer((props) => { return (
-
+
= (props) => { + const {} = props; + + const currentDate = new Date(); + const CALENDAR_PAYLOAD = generateCalendarData(currentDate.getFullYear(), currentDate.getMonth(), 5); + + console.log("calendar payload", CALENDAR_PAYLOAD); + + return ( +
+ {Object.keys(CALENDAR_PAYLOAD).map((year) => ( +
+ {Object.keys(CALENDAR_PAYLOAD[year]).map((month) => ( +
+ {Object.keys(CALENDAR_PAYLOAD[year][month]).map((week) => ( +
+ {CALENDAR_PAYLOAD[year][month][week].map((day) => ( +
{day ? renderLongDateFormat(day) : "No date"}
+ ))} +
+ ))} +
+ ))} +
+ ))} +
+ ); +}; diff --git a/web/components/issues/issue-layouts/calendar/data.ts b/web/components/issues/issue-layouts/calendar/data.ts new file mode 100644 index 00000000000..b8274983d67 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/data.ts @@ -0,0 +1,33 @@ +export interface CalendarPayload { + [year: number]: { + [month: number]: { + [week: number]: (Date | null)[]; + }; + }; +} + +export const generateCalendarData = (startYear: number, startMonth: number, numMonths: number): CalendarPayload => { + const calendarData: CalendarPayload = {}; + + for (let i = 0; i < numMonths; i++) { + const currentDate = new Date(startYear, startMonth + i, 1); + const year = currentDate.getFullYear(); + const month = currentDate.getMonth(); + const numDaysInMonth = new Date(year, month + 1, 0).getDate(); + const firstDayOfWeek = (new Date(year, month, 1).getDay() + 6) % 7; // Convert Sunday to 0-based index + + calendarData[year] ||= {}; + calendarData[year][month] ||= {}; + + const numWeeks = Math.ceil((numDaysInMonth + firstDayOfWeek) / 7); + + for (let week = 0; week < numWeeks; week++) { + calendarData[year][month][week] = Array.from({ length: 7 }, (_, day) => { + const dayNumber = week * 7 + day - firstDayOfWeek + 1; + return dayNumber >= 1 && dayNumber <= numDaysInMonth ? new Date(year, month, dayNumber) : null; + }); + } + } + + return calendarData; +}; diff --git a/web/components/issues/issue-layouts/calendar/index.ts b/web/components/issues/issue-layouts/calendar/index.ts new file mode 100644 index 00000000000..d95efa8487a --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/index.ts @@ -0,0 +1,2 @@ +export * from "./calendar"; +export * from "./data"; diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts new file mode 100644 index 00000000000..90fe453b29c --- /dev/null +++ b/web/components/issues/issue-layouts/index.ts @@ -0,0 +1 @@ +export * from "./calendar"; diff --git a/web/pages/calendar.tsx b/web/pages/calendar.tsx new file mode 100644 index 00000000000..bddca1f6f1c --- /dev/null +++ b/web/pages/calendar.tsx @@ -0,0 +1,19 @@ +import React from "react"; + +// layouts +import DefaultLayout from "layouts/default-layout"; +import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper"; +// components +import { CalendarView } from "components/issues"; +// types +import type { NextPage } from "next"; + +const OnBoard: NextPage = () => ( + + + + + +); + +export default OnBoard; From c932c9f87fb0c3482c658dfcf66a9bfa4ae85d63 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 26 Sep 2023 14:20:29 +0530 Subject: [PATCH 02/11] chore: new render logic --- .../issue-layouts/calendar/calendar.tsx | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/web/components/issues/issue-layouts/calendar/calendar.tsx b/web/components/issues/issue-layouts/calendar/calendar.tsx index d597e9007e2..094a1d8929f 100644 --- a/web/components/issues/issue-layouts/calendar/calendar.tsx +++ b/web/components/issues/issue-layouts/calendar/calendar.tsx @@ -12,22 +12,38 @@ export const CalendarView: React.FC = (props) => { console.log("calendar payload", CALENDAR_PAYLOAD); return ( -
- {Object.keys(CALENDAR_PAYLOAD).map((year) => ( -
- {Object.keys(CALENDAR_PAYLOAD[year]).map((month) => ( -
- {Object.keys(CALENDAR_PAYLOAD[year][month]).map((week) => ( -
- {CALENDAR_PAYLOAD[year][month][week].map((day) => ( -
{day ? renderLongDateFormat(day) : "No date"}
+
+ {Object.entries(CALENDAR_PAYLOAD).map(([year, months]) => + Object.entries(months).map(([month, weeks]) => ( +
+

{`${year}-${Number(month) + 1}`}

+
+
Sun
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+
+
+ {Object.values(weeks).map((week: Date[], weekIndex) => ( +
+ {week.map((date, dayIndex) => ( +
+ {date && ( +
+ {date.getDate()} {/* Assuming you want to display the day of the month */} +
+ )} +
))}
))}
- ))} -
- ))} +
+ )) + )}
); }; From 871ced4808baca0ef013a20e9ae8f18f992f4fcc Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 26 Sep 2023 15:46:18 +0530 Subject: [PATCH 03/11] chore: implement calendar view --- .../scope-and-demand/year-wise-issues.tsx | 13 +- web/components/core/views/all-views.tsx | 5 +- .../issue-layouts/calendar/calendar.tsx | 49 -------- .../issues/issue-layouts/calendar/data.ts | 20 ++-- .../issues/issue-layouts/calendar/index.ts | 2 +- .../issues/issue-layouts/calendar/root.tsx | 70 +++++++++++ web/constants/calendar.ts | 111 ++++++++++++++---- 7 files changed, 181 insertions(+), 89 deletions(-) delete mode 100644 web/components/issues/issue-layouts/calendar/calendar.tsx create mode 100644 web/components/issues/issue-layouts/calendar/root.tsx diff --git a/web/components/analytics/scope-and-demand/year-wise-issues.tsx b/web/components/analytics/scope-and-demand/year-wise-issues.tsx index 87127ed6086..7dedee31cfa 100644 --- a/web/components/analytics/scope-and-demand/year-wise-issues.tsx +++ b/web/components/analytics/scope-and-demand/year-wise-issues.tsx @@ -20,18 +20,15 @@ export const AnalyticsYearWiseIssues: React.FC = ({ defaultAnalytics }) = { id: "issues_closed", color: "rgb(var(--color-primary-100))", - data: MONTHS_LIST.map((month) => ({ - x: month.label.substring(0, 3), + data: Object.entries(MONTHS_LIST).map(([index, month]) => ({ + x: month.shortTitle, y: - defaultAnalytics.issue_completed_month_wise.find( - (data) => data.month === month.value - )?.count || 0, + defaultAnalytics.issue_completed_month_wise.find((data) => data.month === parseInt(index, 10))?.count || + 0, })), }, ]} - customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map( - (data) => data.count - )} + customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map((data) => data.count)} height="300px" colors={(datum) => datum.color} curve="monotoneX" diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx index a75ac67d4c4..6c97a556c39 100644 --- a/web/components/core/views/all-views.tsx +++ b/web/components/core/views/all-views.tsx @@ -14,7 +14,7 @@ import useUser from "hooks/use-user"; import { useProjectMyMembership } from "contexts/project-member.context"; // components import { AllLists, AllBoards, CalendarView, SpreadsheetView, GanttChartView } from "components/core"; -import { KanBanLayout } from "components/issues/issue-layouts"; +import { CalendarLayout, KanBanLayout } from "components/issues"; // ui import { EmptyState, Spinner } from "components/ui"; // icons @@ -234,7 +234,8 @@ export const AllViews: React.FC = ({ // )} //
- + {/* */} +
); }; diff --git a/web/components/issues/issue-layouts/calendar/calendar.tsx b/web/components/issues/issue-layouts/calendar/calendar.tsx deleted file mode 100644 index 094a1d8929f..00000000000 --- a/web/components/issues/issue-layouts/calendar/calendar.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { renderLongDateFormat } from "helpers/date-time.helper"; -import { generateCalendarData } from "./data"; - -type Props = {}; - -export const CalendarView: React.FC = (props) => { - const {} = props; - - const currentDate = new Date(); - const CALENDAR_PAYLOAD = generateCalendarData(currentDate.getFullYear(), currentDate.getMonth(), 5); - - console.log("calendar payload", CALENDAR_PAYLOAD); - - return ( -
- {Object.entries(CALENDAR_PAYLOAD).map(([year, months]) => - Object.entries(months).map(([month, weeks]) => ( -
-

{`${year}-${Number(month) + 1}`}

-
-
Sun
-
Mon
-
Tue
-
Wed
-
Thu
-
Fri
-
Sat
-
-
- {Object.values(weeks).map((week: Date[], weekIndex) => ( -
- {week.map((date, dayIndex) => ( -
- {date && ( -
- {date.getDate()} {/* Assuming you want to display the day of the month */} -
- )} -
- ))} -
- ))} -
-
- )) - )} -
- ); -}; diff --git a/web/components/issues/issue-layouts/calendar/data.ts b/web/components/issues/issue-layouts/calendar/data.ts index b8274983d67..4b8fecd1b3a 100644 --- a/web/components/issues/issue-layouts/calendar/data.ts +++ b/web/components/issues/issue-layouts/calendar/data.ts @@ -1,13 +1,17 @@ -export interface CalendarPayload { - [year: number]: { - [month: number]: { - [week: number]: (Date | null)[]; - }; - }; +export interface ICalendarWeek { + [weekNumber: number]: (Date | null)[]; } -export const generateCalendarData = (startYear: number, startMonth: number, numMonths: number): CalendarPayload => { - const calendarData: CalendarPayload = {}; +export interface ICalendarMonth { + [monthNumber: number]: ICalendarWeek; +} + +export interface ICalendarPayload { + [year: number]: ICalendarMonth; +} + +export const generateCalendarData = (startYear: number, startMonth: number, numMonths: number): ICalendarPayload => { + const calendarData: ICalendarPayload = {}; for (let i = 0; i < numMonths; i++) { const currentDate = new Date(startYear, startMonth + i, 1); diff --git a/web/components/issues/issue-layouts/calendar/index.ts b/web/components/issues/issue-layouts/calendar/index.ts index d95efa8487a..0280a744324 100644 --- a/web/components/issues/issue-layouts/calendar/index.ts +++ b/web/components/issues/issue-layouts/calendar/index.ts @@ -1,2 +1,2 @@ -export * from "./calendar"; +export * from "./root"; export * from "./data"; diff --git a/web/components/issues/issue-layouts/calendar/root.tsx b/web/components/issues/issue-layouts/calendar/root.tsx new file mode 100644 index 00000000000..b42e5c5567b --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/root.tsx @@ -0,0 +1,70 @@ +import { useState } from "react"; + +// icons +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { ICalendarPayload, ICalendarWeek, generateCalendarData } from "./data"; +// constants +import { DAYS_LIST, MONTHS_LIST } from "constants/calendar"; + +type Props = {}; + +export const CalendarLayout: React.FC = (props) => { + const {} = props; + + const [activeMonth, setActiveMonth] = useState(null); + const [showWeekends, setShowWeekends] = useState(true); + + const currentDate = new Date(); + const CALENDAR_PAYLOAD: ICalendarPayload = generateCalendarData(currentDate.getFullYear(), currentDate.getMonth(), 1); + + console.log("calendar payload", CALENDAR_PAYLOAD); + + return ( +
+ {Object.entries(CALENDAR_PAYLOAD).map(([year, months]) => + Object.entries(months).map(([month, weeks]) => ( +
+
+ +

{MONTHS_LIST[parseInt(month, 10) + 1].title}

+ +
+
+ {Object.values(DAYS_LIST).map((day) => { + if (!showWeekends && (day.shortTitle === "Sat" || day.shortTitle === "Sun")) return null; + + return ( +
+ {day.shortTitle} +
+ ); + })} +
+
+ {Object.values(weeks as ICalendarWeek).map((week, weekIndex) => ( +
+ {week.map((date, dayIndex) => { + if (!showWeekends && (dayIndex === 5 || dayIndex === 6)) return null; + + return ( +
+ {date &&
{date.getDate()}
} +
+ ); + })} +
+ ))} +
+
+ )) + )} +
+ ); +}; diff --git a/web/constants/calendar.ts b/web/constants/calendar.ts index 5f1f742542e..9b23b3d3fc7 100644 --- a/web/constants/calendar.ts +++ b/web/constants/calendar.ts @@ -1,22 +1,91 @@ -export const MONTHS_LIST = [ - { value: 1, label: "January" }, - { value: 2, label: "February" }, - { value: 3, label: "March" }, - { value: 4, label: "April" }, - { value: 5, label: "May" }, - { value: 6, label: "June" }, - { value: 7, label: "July" }, - { value: 8, label: "August" }, - { value: 9, label: "September" }, - { value: 10, label: "October" }, - { value: 11, label: "November" }, - { value: 12, label: "December" }, -]; +export const MONTHS_LIST: { + [monthNumber: number]: { + shortTitle: string; + title: string; + }; +} = { + 1: { + shortTitle: "Jan", + title: "January", + }, + 2: { + shortTitle: "Feb", + title: "February", + }, + 3: { + shortTitle: "Mar", + title: "March", + }, + 4: { + shortTitle: "Apr", + title: "April", + }, + 5: { + shortTitle: "May", + title: "May", + }, + 6: { + shortTitle: "Jun", + title: "June", + }, + 7: { + shortTitle: "Jul", + title: "July", + }, + 8: { + shortTitle: "Aug", + title: "August", + }, + 9: { + shortTitle: "Sep", + title: "September", + }, + 10: { + shortTitle: "Oct", + title: "October", + }, + 11: { + shortTitle: "Nov", + title: "November", + }, + 12: { + shortTitle: "Dec", + title: "December", + }, +}; -export const YEARS_LIST = [ - { value: "2021", label: "2021" }, - { value: "2022", label: "2022" }, - { value: "2023", label: "2023" }, - { value: "2024", label: "2024" }, - { value: "2025", label: "2025" }, -]; +export const DAYS_LIST: { + [dayIndex: number]: { + shortTitle: string; + title: string; + }; +} = { + 1: { + shortTitle: "Mon", + title: "Monday", + }, + 2: { + shortTitle: "Tue", + title: "Tuesday", + }, + 3: { + shortTitle: "Wed", + title: "Wednesday", + }, + 4: { + shortTitle: "Thu", + title: "Thursday", + }, + 5: { + shortTitle: "Fri", + title: "Friday", + }, + 6: { + shortTitle: "Sat", + title: "Saturday", + }, + 7: { + shortTitle: "Sun", + title: "Sunday", + }, +}; From ce3f0e66d80c73820087a2aa966c439415b398df Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 26 Sep 2023 20:26:39 +0530 Subject: [PATCH 04/11] chore: calendar view --- web/components/core/views/all-views.tsx | 144 +----------------- .../issue-layouts/calendar/calendar.tsx | 69 +++++++++ .../issues/issue-layouts/calendar/data.ts | 56 ++++++- .../issue-layouts/calendar/day-tile.tsx | 45 ++++++ .../issues/issue-layouts/calendar/header.tsx | 26 ++++ .../issues/issue-layouts/calendar/index.ts | 7 +- .../issue-layouts/calendar/issue-blocks.tsx | 37 +++++ .../issues/issue-layouts/calendar/root.tsx | 71 ++------- .../issue-layouts/calendar/week-header.tsx | 28 ++++ web/store/issue.ts | 10 +- 10 files changed, 291 insertions(+), 202 deletions(-) create mode 100644 web/components/issues/issue-layouts/calendar/calendar.tsx create mode 100644 web/components/issues/issue-layouts/calendar/day-tile.tsx create mode 100644 web/components/issues/issue-layouts/calendar/header.tsx create mode 100644 web/components/issues/issue-layouts/calendar/issue-blocks.tsx create mode 100644 web/components/issues/issue-layouts/calendar/week-header.tsx diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx index 6c97a556c39..5f35a64f7bb 100644 --- a/web/components/core/views/all-views.tsx +++ b/web/components/core/views/all-views.tsx @@ -31,6 +31,7 @@ import { STATES_LIST } from "constants/fetch-keys"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; +import { observer } from "mobx-react-lite"; type Props = { addIssueToDate: (date: string) => void; @@ -58,22 +59,7 @@ type Props = { viewProps: IIssueViewProps; }; -export const AllViews: React.FC = ({ - addIssueToDate, - addIssueToGroup, - disableUserActions, - dragDisabled = false, - emptyState, - handleIssueAction, - handleDraftIssueAction, - handleOnDragEnd, - openIssuesListModal, - removeIssue, - disableAddIssueOption = false, - trashBox, - setTrashBox, - viewProps, -}) => { +export const AllViews: React.FC = observer(({ trashBox, setTrashBox }) => { const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query as { workspaceSlug: string; @@ -84,10 +70,7 @@ export const AllViews: React.FC = ({ const [myIssueProjectId, setMyIssueProjectId] = useState(null); - const { user } = useUser(); - const { memberRole } = useProjectMyMembership(); - - const { groupedIssues, isEmpty, displayFilters } = viewProps; + const { issue: issueStore, project: projectStore, issueFilter: issueFilterStore } = useMobxStore(); const { data: stateGroups } = useSWR( workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, @@ -106,8 +89,6 @@ export const AllViews: React.FC = ({ [trashBox, setTrashBox] ); - const { issue: issueStore, project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); - useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES` : null, async () => { if (workspaceSlug && projectId) { await issueFilterStore.fetchUserProjectFilters(workspaceSlug, projectId); @@ -120,122 +101,11 @@ export const AllViews: React.FC = ({ } }); + const activeLayout = issueFilterStore.userDisplayFilters.layout; + return ( - // - // - // {(provided, snapshot) => ( - //
- // - // Drop here to delete the issue. - //
- // )} - //
- // {groupedIssues ? ( - // !isEmpty || - // displayFilters?.layout === "kanban" || - // displayFilters?.layout === "calendar" || - // displayFilters?.layout === "gantt_chart" ? ( - // <> - // {displayFilters?.layout === "list" ? ( - // - // ) : displayFilters?.layout === "kanban" ? ( - // - // ) : displayFilters?.layout === "calendar" ? ( - // - // ) : displayFilters?.layout === "spreadsheet" ? ( - // - // ) : ( - // displayFilters?.layout === "gantt_chart" && - // )} - // - // ) : router.pathname.includes("archived-issues") ? ( - // { - // router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`); - // }, - // }} - // /> - // ) : ( - // - // ) - // ) : ( - //
- // - //
- // )} - //
- {/* */} - + {activeLayout === "kanban" ? : activeLayout === "calendar" ? : null}
); -}; +}); diff --git a/web/components/issues/issue-layouts/calendar/calendar.tsx b/web/components/issues/issue-layouts/calendar/calendar.tsx new file mode 100644 index 00000000000..e53279fe963 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/calendar.tsx @@ -0,0 +1,69 @@ +import { useEffect, useState } from "react"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { CalendarDayTile, CalendarHeader, CalendarWeekHeader } from "components/issues"; +// ui +import { Spinner } from "components/ui"; +// icons +import { ICalendarDate, ICalendarPayload, ICalendarWeek, generateCalendarData } from "./data"; +// constants +import { renderDateFormat } from "helpers/date-time.helper"; + +type Props = {}; + +export const CalendarChart: React.FC = observer((props) => { + const {} = props; + + const [activeMonthDate, setActiveMonthDate] = useState(null); + const [showWeekends, setShowWeekends] = useState(false); + const [calendarPayload, setCalendarPayload] = useState(null); + + const { issue: issueStore } = useMobxStore(); + + useEffect(() => { + if (activeMonthDate !== null) return; + + setActiveMonthDate(new Date()); + + setCalendarPayload(generateCalendarData(new Date().getFullYear(), new Date().getMonth(), 1)); + }, []); + + if (!activeMonthDate || !calendarPayload || !issueStore.getIssues) + return ( +
+ +
+ ); + + console.log("calendarPayload", calendarPayload); + + return ( + <> + { +
+ setActiveMonthDate(monthDate)} + /> + +
+ {Object.values(calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()]).map( + (week: ICalendarWeek, weekIndex) => ( +
+ {Object.values(week).map((date, index: number) => { + if (!showWeekends && (index === 5 || index === 6)) return <>; + + return ; + })} +
+ ) + )} +
+
+ } + + ); +}); diff --git a/web/components/issues/issue-layouts/calendar/data.ts b/web/components/issues/issue-layouts/calendar/data.ts index 4b8fecd1b3a..9f26608a36a 100644 --- a/web/components/issues/issue-layouts/calendar/data.ts +++ b/web/components/issues/issue-layouts/calendar/data.ts @@ -1,5 +1,20 @@ +import { renderDateFormat } from "helpers/date-time.helper"; + +export interface ICalendarDate { + date: Date; + year: number; + month: number; + day: number; + week: number; // week number wrt year, eg- 51, 52 + is_current_month: boolean; + is_current_week: boolean; + is_today: boolean; +} + export interface ICalendarWeek { - [weekNumber: number]: (Date | null)[]; + [weekNumber: number]: { + [date: string]: ICalendarDate | null; + }; } export interface ICalendarMonth { @@ -10,6 +25,18 @@ export interface ICalendarPayload { [year: number]: ICalendarMonth; } +// get week number wrt year +export const getWeekNumber = (date: Date): number => { + const d = new Date(date); + + d.setHours(0, 0, 0, 0); + d.setDate(d.getDate() + 3 - ((d.getDay() + 6) % 7)); + + const week1 = new Date(d.getFullYear(), 0, 4); + + return 1 + Math.round(((d.valueOf() - week1.valueOf()) / 86400000 - 3 + ((week1.getDay() + 6) % 7)) / 7); +}; + export const generateCalendarData = (startYear: number, startMonth: number, numMonths: number): ICalendarPayload => { const calendarData: ICalendarPayload = {}; @@ -26,10 +53,29 @@ export const generateCalendarData = (startYear: number, startMonth: number, numM const numWeeks = Math.ceil((numDaysInMonth + firstDayOfWeek) / 7); for (let week = 0; week < numWeeks; week++) { - calendarData[year][month][week] = Array.from({ length: 7 }, (_, day) => { - const dayNumber = week * 7 + day - firstDayOfWeek + 1; - return dayNumber >= 1 && dayNumber <= numDaysInMonth ? new Date(year, month, dayNumber) : null; - }); + const currentWeekObject: { [date: string]: ICalendarDate | null } = {}; + + for (let i = 0; i < 7; i++) { + const dayNumber = week * 7 + i - firstDayOfWeek + 1; + + const date = new Date(year, month, dayNumber); + + currentWeekObject[renderDateFormat(date)] = + dayNumber >= 1 && dayNumber <= numDaysInMonth + ? { + date, + year, + month, + day: dayNumber, + week: getWeekNumber(date), + is_current_month: true, + is_current_week: getWeekNumber(date) === getWeekNumber(new Date()), + is_today: date.toDateString() === new Date().toDateString(), + } + : null; + } + + calendarData[year][month][week] = currentWeekObject; } } diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx new file mode 100644 index 00000000000..ed026123021 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx @@ -0,0 +1,45 @@ +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; +import { Droppable } from "@hello-pangea/dnd"; +// components +import { CalendarIssueBlocks, ICalendarDate } from "components/issues"; +// helpers +import { renderDateFormat } from "helpers/date-time.helper"; +// types +import { IIssueGroupedStructure } from "store/issue"; + +type Props = { date: ICalendarDate | null }; + +export const CalendarDayTile: React.FC = observer((props) => { + const { date } = props; + + const { issue: issueStore } = useMobxStore(); + + if (!date) return
; + + const issues = issueStore.getIssues + ? (issueStore.getIssues as IIssueGroupedStructure)[renderDateFormat(date.date)] + : null; + + return ( + + {(provided, snapshot) => ( +
+ <> + {date &&
{date.date.getDate()}
} +
+ +
+ {provided.placeholder} + +
+ )} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/calendar/header.tsx b/web/components/issues/issue-layouts/calendar/header.tsx new file mode 100644 index 00000000000..48b04f1f5aa --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/header.tsx @@ -0,0 +1,26 @@ +// icons +import { ChevronLeft, ChevronRight } from "lucide-react"; +// constants +import { MONTHS_LIST } from "constants/calendar"; + +type Props = { activeMonthDate: Date; handleMonthChange: (monthDate: Date) => void }; + +export const CalendarHeader: React.FC = (props) => { + const { activeMonthDate, handleMonthChange } = props; + + const handlePreviousMonth = () => {}; + + const handleNextMonth = () => {}; + + return ( +
+ +

{MONTHS_LIST[activeMonthDate.getMonth() + 1].title}

+ +
+ ); +}; diff --git a/web/components/issues/issue-layouts/calendar/index.ts b/web/components/issues/issue-layouts/calendar/index.ts index 0280a744324..ea201412c45 100644 --- a/web/components/issues/issue-layouts/calendar/index.ts +++ b/web/components/issues/issue-layouts/calendar/index.ts @@ -1,2 +1,7 @@ -export * from "./root"; +export * from "./calendar"; export * from "./data"; +export * from "./day-tile"; +export * from "./header"; +export * from "./issue-blocks"; +export * from "./root"; +export * from "./week-header"; diff --git a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx new file mode 100644 index 00000000000..34c8d7a8792 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -0,0 +1,37 @@ +import { Draggable } from "@hello-pangea/dnd"; +// types +import { IIssue } from "types"; + +type Props = { issues: IIssue[] | null }; + +export const CalendarIssueBlocks: React.FC = (props) => { + const { issues } = props; + + return ( +
+ {issues?.map((issue, index) => ( + + {(provided, snapshot) => ( +
+ +
+ {issue.project_detail.identifier}-{issue.sequence_id} +
+
{issue.name}
+
+ )} +
+ ))} +
+ ); +}; diff --git a/web/components/issues/issue-layouts/calendar/root.tsx b/web/components/issues/issue-layouts/calendar/root.tsx index b42e5c5567b..c80be7e745d 100644 --- a/web/components/issues/issue-layouts/calendar/root.tsx +++ b/web/components/issues/issue-layouts/calendar/root.tsx @@ -1,70 +1,29 @@ -import { useState } from "react"; - -// icons -import { ChevronLeft, ChevronRight } from "lucide-react"; -import { ICalendarPayload, ICalendarWeek, generateCalendarData } from "./data"; -// constants -import { DAYS_LIST, MONTHS_LIST } from "constants/calendar"; +import { DragDropContext, DropResult } from "@hello-pangea/dnd"; +// components +import { CalendarChart } from "components/issues"; type Props = {}; export const CalendarLayout: React.FC = (props) => { const {} = props; - const [activeMonth, setActiveMonth] = useState(null); - const [showWeekends, setShowWeekends] = useState(true); + const onDragEnd = (result: DropResult) => { + if (!result) return; + + // return if not dropped on the correct place + if (!result.destination) return; - const currentDate = new Date(); - const CALENDAR_PAYLOAD: ICalendarPayload = generateCalendarData(currentDate.getFullYear(), currentDate.getMonth(), 1); + // return if dropped on the same date + if (result.destination.droppableId === result.source.droppableId) return; - console.log("calendar payload", CALENDAR_PAYLOAD); + // issueKanBanViewStore?.handleDragDrop(result.source, result.destination); + }; return (
- {Object.entries(CALENDAR_PAYLOAD).map(([year, months]) => - Object.entries(months).map(([month, weeks]) => ( -
-
- -

{MONTHS_LIST[parseInt(month, 10) + 1].title}

- -
-
- {Object.values(DAYS_LIST).map((day) => { - if (!showWeekends && (day.shortTitle === "Sat" || day.shortTitle === "Sun")) return null; - - return ( -
- {day.shortTitle} -
- ); - })} -
-
- {Object.values(weeks as ICalendarWeek).map((week, weekIndex) => ( -
- {week.map((date, dayIndex) => { - if (!showWeekends && (dayIndex === 5 || dayIndex === 6)) return null; - - return ( -
- {date &&
{date.getDate()}
} -
- ); - })} -
- ))} -
-
- )) - )} + + +
); }; diff --git a/web/components/issues/issue-layouts/calendar/week-header.tsx b/web/components/issues/issue-layouts/calendar/week-header.tsx new file mode 100644 index 00000000000..13bda10bcac --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/week-header.tsx @@ -0,0 +1,28 @@ +// constants +import { DAYS_LIST } from "constants/calendar"; + +type Props = { + showWeekends: boolean; +}; + +export const CalendarWeekHeader: React.FC = (props) => { + const { showWeekends } = props; + + return ( +
+ {Object.values(DAYS_LIST).map((day) => { + if (!showWeekends && (day.shortTitle === "Sat" || day.shortTitle === "Sun")) return null; + + return ( +
+ {day.shortTitle} +
+ ); + })} +
+ ); +}; diff --git a/web/store/issue.ts b/web/store/issue.ts index 00c3fa46abb..a8577ebc9f3 100644 --- a/web/store/issue.ts +++ b/web/store/issue.ts @@ -72,8 +72,8 @@ class IssueStore implements IIssueStore { } get getIssueType() { - const groupedLayouts = ["kanban", "list"]; - const ungroupedLayouts = ["calendar", "spreadsheet", "gantt_chart"]; + const groupedLayouts = ["kanban", "list", "calendar"]; + const ungroupedLayouts = ["spreadsheet", "gantt_chart"]; const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null; @@ -143,7 +143,11 @@ class IssueStore implements IIssueStore { this.rootStore.project.setProjectId(projectId); // TODO: replace this once the issue filter is completed - const params = { group_by: "state", order_by: "-created_at" }; + const params = { + group_by: "target_date", + order_by: "-created_at", + target_date: "2023-09-01;after,2023-09-30;before", + }; const issueResponse = await this.issueService.getIssuesWithParams(workspaceSlug, projectId, params); const issueType = this.getIssueType; From d1939f7699d4b48d1e1eb2204eb8a686a34db55d Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 27 Sep 2023 11:50:29 +0530 Subject: [PATCH 05/11] refactor: calendar payload --- .../issue-layouts/calendar/calendar.tsx | 81 +++++++--- .../issues/issue-layouts/calendar/data.ts | 13 +- .../issue-layouts/calendar/day-tile.tsx | 7 +- .../issues/issue-layouts/calendar/header.tsx | 138 ++++++++++++++++-- .../issues/issue-layouts/calendar/index.ts | 1 + .../calendar/options-dropdown.tsx | 53 +++++++ .../issue-layouts/calendar/week-header.tsx | 14 +- web/constants/issue.ts | 1 - web/contexts/issue-view.context.tsx | 44 +----- web/helpers/issue.helper.ts | 1 - web/hooks/use-calendar-issues-view.tsx | 1 - web/store/issue_filters.ts | 7 +- web/types/view-props.d.ts | 7 +- 13 files changed, 273 insertions(+), 95 deletions(-) create mode 100644 web/components/issues/issue-layouts/calendar/options-dropdown.tsx diff --git a/web/components/issues/issue-layouts/calendar/calendar.tsx b/web/components/issues/issue-layouts/calendar/calendar.tsx index e53279fe963..45a9ca7e802 100644 --- a/web/components/issues/issue-layouts/calendar/calendar.tsx +++ b/web/components/issues/issue-layouts/calendar/calendar.tsx @@ -1,14 +1,14 @@ import { useEffect, useState } from "react"; - -// mobx +import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; + // components import { CalendarDayTile, CalendarHeader, CalendarWeekHeader } from "components/issues"; // ui import { Spinner } from "components/ui"; // icons -import { ICalendarDate, ICalendarPayload, ICalendarWeek, generateCalendarData } from "./data"; +import { ICalendarPayload, ICalendarWeek, generateCalendarData } from "./data"; // constants import { renderDateFormat } from "helpers/date-time.helper"; @@ -17,44 +17,77 @@ type Props = {}; export const CalendarChart: React.FC = observer((props) => { const {} = props; - const [activeMonthDate, setActiveMonthDate] = useState(null); - const [showWeekends, setShowWeekends] = useState(false); const [calendarPayload, setCalendarPayload] = useState(null); - const { issue: issueStore } = useMobxStore(); + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore(); + + const activeMonth = issueFilterStore.userDisplayFilters.calendar?.active_month; + const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; + + // generate calendar payload + useEffect(() => { + console.log("Active month changed"); + + let activeMonthDate = new Date(); + if (activeMonth) activeMonthDate = new Date(activeMonth); + + setCalendarPayload( + generateCalendarData(calendarPayload, activeMonthDate.getFullYear(), activeMonthDate.getMonth(), 5) + ); + }, [activeMonth, calendarPayload]); + + // set active month if not set useEffect(() => { - if (activeMonthDate !== null) return; + if (!activeMonth) { + const today = new Date(); + const firstDayOfCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 1); - setActiveMonthDate(new Date()); + if (!workspaceSlug || !projectId) return; - setCalendarPayload(generateCalendarData(new Date().getFullYear(), new Date().getMonth(), 1)); - }, []); + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + calendar: { + active_month: renderDateFormat(firstDayOfCurrentMonth), + }, + }, + }); + } + }, [activeMonth, issueFilterStore, projectId, workspaceSlug]); - if (!activeMonthDate || !calendarPayload || !issueStore.getIssues) + if (!calendarPayload || !issueStore.getIssues || !activeMonth) return (
); + const activeMonthDate = new Date(activeMonth); + console.log("calendarPayload", calendarPayload); + console.log("activeMonthPayload", calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()]); + return ( <> - { -
- setActiveMonthDate(monthDate)} - /> - +
+ + + {calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()] ? (
{Object.values(calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()]).map( (week: ICalendarWeek, weekIndex) => ( -
+
{Object.values(week).map((date, index: number) => { - if (!showWeekends && (index === 5 || index === 6)) return <>; + if (!showWeekends && (index === 5 || index === 6)) return null; return ; })} @@ -62,8 +95,12 @@ export const CalendarChart: React.FC = observer((props) => { ) )}
-
- } + ) : ( +
+ +
+ )} +
); }); diff --git a/web/components/issues/issue-layouts/calendar/data.ts b/web/components/issues/issue-layouts/calendar/data.ts index 9f26608a36a..abe9d2a7d0e 100644 --- a/web/components/issues/issue-layouts/calendar/data.ts +++ b/web/components/issues/issue-layouts/calendar/data.ts @@ -37,10 +37,15 @@ export const getWeekNumber = (date: Date): number => { return 1 + Math.round(((d.valueOf() - week1.valueOf()) / 86400000 - 3 + ((week1.getDay() + 6) % 7)) / 7); }; -export const generateCalendarData = (startYear: number, startMonth: number, numMonths: number): ICalendarPayload => { - const calendarData: ICalendarPayload = {}; - - for (let i = 0; i < numMonths; i++) { +export const generateCalendarData = ( + currentStructure: ICalendarPayload | null, + startYear: number, + startMonth: number, + numMonths: number +): ICalendarPayload => { + const calendarData: ICalendarPayload = currentStructure ?? {}; + + for (let i = -Math.floor(numMonths / 2); i <= Math.floor(numMonths / 2); i++) { const currentDate = new Date(startYear, startMonth + i, 1); const year = currentDate.getFullYear(); const month = currentDate.getMonth(); diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx index ed026123021..2802bf6e390 100644 --- a/web/components/issues/issue-layouts/calendar/day-tile.tsx +++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx @@ -15,7 +15,7 @@ export const CalendarDayTile: React.FC = observer((props) => { const { issue: issueStore } = useMobxStore(); - if (!date) return
; + if (!date) return
; const issues = issueStore.getIssues ? (issueStore.getIssues as IIssueGroupedStructure)[renderDateFormat(date.date)] @@ -25,14 +25,15 @@ export const CalendarDayTile: React.FC = observer((props) => { {(provided, snapshot) => (
<> - {date &&
{date.date.getDate()}
} + {date &&
{date.date.getDate()}
}
diff --git a/web/components/issues/issue-layouts/calendar/header.tsx b/web/components/issues/issue-layouts/calendar/header.tsx index 48b04f1f5aa..2711290e192 100644 --- a/web/components/issues/issue-layouts/calendar/header.tsx +++ b/web/components/issues/issue-layouts/calendar/header.tsx @@ -1,26 +1,136 @@ +import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; + +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { CalendarOptionsDropdown } from "components/issues"; // icons import { ChevronLeft, ChevronRight } from "lucide-react"; +// helpers +import { renderDateFormat } from "helpers/date-time.helper"; // constants import { MONTHS_LIST } from "constants/calendar"; -type Props = { activeMonthDate: Date; handleMonthChange: (monthDate: Date) => void }; +type Props = {}; + +export const CalendarHeader: React.FC = observer((props) => { + const {} = props; + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { issueFilter: issueFilterStore } = useMobxStore(); + + const handlePreviousMonth = () => { + const activeMonth = issueFilterStore.userDisplayFilters.calendar?.active_month; + + if (!workspaceSlug || !projectId || !activeMonth) return; + + const activeMonthDate = new Date(activeMonth); + + const previousMonthYear = + activeMonthDate.getMonth() === 0 ? activeMonthDate.getFullYear() - 1 : activeMonthDate.getFullYear(); + const previousMonthMonth = activeMonthDate.getMonth() === 0 ? 11 : activeMonthDate.getMonth() - 1; + + const previousMonthFirstDate = new Date(previousMonthYear, previousMonthMonth, 1); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + calendar: { + ...issueFilterStore.userDisplayFilters.calendar, + active_month: renderDateFormat(previousMonthFirstDate), + }, + }, + }); + }; + + const handleNextMonth = () => { + const activeMonth = issueFilterStore.userDisplayFilters.calendar?.active_month; + + if (!workspaceSlug || !projectId || !activeMonth) return; + + const activeMonthDate = new Date(activeMonth); + + const nextMonthYear = + activeMonthDate.getMonth() === 11 ? activeMonthDate.getFullYear() + 1 : activeMonthDate.getFullYear(); + const nextMonthMonth = (activeMonthDate.getMonth() + 1) % 12; + + const nextMonthFirstDate = new Date(nextMonthYear, nextMonthMonth, 1); + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + calendar: { + ...issueFilterStore.userDisplayFilters.calendar, + active_month: renderDateFormat(nextMonthFirstDate), + }, + }, + }); + }; + + const handleToday = () => { + const today = new Date(); + const firstDayOfCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 1); + + if (!workspaceSlug || !projectId) return; + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + calendar: { + ...issueFilterStore.userDisplayFilters.calendar, + active_month: renderDateFormat(firstDayOfCurrentMonth), + }, + }, + }); + }; + + const handleToggleWeekends = () => { + const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; -export const CalendarHeader: React.FC = (props) => { - const { activeMonthDate, handleMonthChange } = props; + if (!workspaceSlug || !projectId) return; - const handlePreviousMonth = () => {}; + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + calendar: { + ...issueFilterStore.userDisplayFilters.calendar, + show_weekends: !showWeekends, + }, + }, + }); + }; - const handleNextMonth = () => {}; + const activeMonthDate = new Date(issueFilterStore.userDisplayFilters.calendar?.active_month || new Date()); return ( -
- -

{MONTHS_LIST[activeMonthDate.getMonth() + 1].title}

- +
+
+ +

+ {MONTHS_LIST[activeMonthDate.getMonth() + 1].title} {activeMonthDate.getFullYear()} +

+ +
+
+ + + +
); -}; +}); diff --git a/web/components/issues/issue-layouts/calendar/index.ts b/web/components/issues/issue-layouts/calendar/index.ts index ea201412c45..665860c9a72 100644 --- a/web/components/issues/issue-layouts/calendar/index.ts +++ b/web/components/issues/issue-layouts/calendar/index.ts @@ -3,5 +3,6 @@ export * from "./data"; export * from "./day-tile"; export * from "./header"; export * from "./issue-blocks"; +export * from "./options-dropdown"; export * from "./root"; export * from "./week-header"; diff --git a/web/components/issues/issue-layouts/calendar/options-dropdown.tsx b/web/components/issues/issue-layouts/calendar/options-dropdown.tsx new file mode 100644 index 00000000000..e0bc317b8fa --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/options-dropdown.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { Popover, Transition } from "@headlessui/react"; +import { observer } from "mobx-react-lite"; + +// icons +import { ChevronUp } from "lucide-react"; +import { useMobxStore } from "lib/mobx/store-provider"; + +type Props = {}; + +export const CalendarOptionsDropdown: React.FC = observer((props) => { + const {} = props; + + const { issueFilter: issueFilterStore } = useMobxStore(); + + return ( + + {({ open }) => { + if (open) { + } + return ( + <> + +
Options
+
+ +
+
+ + + Hi + + + + ); + }} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/calendar/week-header.tsx b/web/components/issues/issue-layouts/calendar/week-header.tsx index 13bda10bcac..a19c295d1d5 100644 --- a/web/components/issues/issue-layouts/calendar/week-header.tsx +++ b/web/components/issues/issue-layouts/calendar/week-header.tsx @@ -1,12 +1,14 @@ +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // constants import { DAYS_LIST } from "constants/calendar"; -type Props = { - showWeekends: boolean; -}; +type Props = {}; -export const CalendarWeekHeader: React.FC = (props) => { - const { showWeekends } = props; +export const CalendarWeekHeader: React.FC = observer((props) => { + const { issueFilter: issueFilterStore } = useMobxStore(); + + const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; return (
= (props) => { })}
); -}; +}); diff --git a/web/constants/issue.ts b/web/constants/issue.ts index eaec04999f2..52fc6c4dba8 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -112,7 +112,6 @@ export const ISSUE_EXTRA_OPTIONS: { }[] = [ { key: "sub_issue", title: "Show sub-issues" }, // in spreadsheet its always false { key: "show_empty_groups", title: "Show empty states" }, // filter on front-end - { key: "calendar_date_range", title: "Calendar Date Range" }, // calendar date range yyyy-mm-dd;before range yyyy-mm-dd;after { key: "start_target_date", title: "Start target Date" }, // gantt always be true ]; diff --git a/web/contexts/issue-view.context.tsx b/web/contexts/issue-view.context.tsx index 786d6041346..9ac698a6afd 100644 --- a/web/contexts/issue-view.context.tsx +++ b/web/contexts/issue-view.context.tsx @@ -22,12 +22,7 @@ import { IProjectViewProps, } from "types"; // fetch-keys -import { - CYCLE_DETAILS, - MODULE_DETAILS, - USER_PROJECT_VIEW, - VIEW_DETAILS, -} from "constants/fetch-keys"; +import { CYCLE_DETAILS, MODULE_DETAILS, USER_PROJECT_VIEW, VIEW_DETAILS } from "constants/fetch-keys"; export const issueViewContext = createContext({} as ContextType); @@ -48,7 +43,6 @@ type ReducerFunctionType = (state: StateType, action: ReducerActionType) => Stat export const initialState: StateType = { display_filters: { - calendar_date_range: "", group_by: null, layout: "list", order_by: "-created_at", @@ -123,11 +117,7 @@ export const reducer: ReducerFunctionType = (state, action) => { } }; -const saveDataToServer = async ( - workspaceSlug: string, - projectId: string, - state: IProjectViewProps -) => { +const saveDataToServer = async (workspaceSlug: string, projectId: string, state: IProjectViewProps) => { mutate( workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null, (prevData) => { @@ -238,36 +228,21 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = const { data: viewDetails, mutate: mutateViewDetails } = useSWR( workspaceSlug && projectId && viewId ? VIEW_DETAILS(viewId as string) : null, workspaceSlug && projectId && viewId - ? () => - viewsService.getViewDetails( - workspaceSlug as string, - projectId as string, - viewId as string - ) + ? () => viewsService.getViewDetails(workspaceSlug as string, projectId as string, viewId as string) : null ); const { data: cycleDetails, mutate: mutateCycleDetails } = useSWR( workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId as string) : null, workspaceSlug && projectId && cycleId - ? () => - cyclesService.getCycleDetails( - workspaceSlug.toString(), - projectId.toString(), - cycleId.toString() - ) + ? () => cyclesService.getCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString()) : null ); const { data: moduleDetails, mutate: mutateModuleDetails } = useSWR( workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null, workspaceSlug && projectId && moduleId - ? () => - modulesService.getModuleDetails( - workspaceSlug.toString(), - projectId.toString(), - moduleId.toString() - ) + ? () => modulesService.getModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString()) : null ); @@ -288,11 +263,7 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = order_by: displayFilter.order_by ?? state.display_filters?.order_by, }; - if ( - displayFilter.layout && - displayFilter.layout === "kanban" && - state.display_filters?.group_by === null - ) { + if (displayFilter.layout && displayFilter.layout === "kanban" && state.display_filters?.group_by === null) { additionalProperties.group_by = "state"; dispatch({ type: "SET_DISPLAY_FILTERS", @@ -343,8 +314,7 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = const setFilters = useCallback( (property: Partial, saveToServer = true) => { Object.keys(property).forEach((key) => { - if (property[key as keyof typeof property]?.length === 0) - property[key as keyof typeof property] = null; + if (property[key as keyof typeof property]?.length === 0) property[key as keyof typeof property] = null; }); dispatch({ diff --git a/web/helpers/issue.helper.ts b/web/helpers/issue.helper.ts index eab6f8a4797..5cb78bdc861 100644 --- a/web/helpers/issue.helper.ts +++ b/web/helpers/issue.helper.ts @@ -125,7 +125,6 @@ export const handleIssueQueryParamsByLayout = (_layout: TIssueLayouts | undefine "start_date", "target_date", "type", - "calendar_date_range", ]; if (_layout === "spreadsheet") return [ diff --git a/web/hooks/use-calendar-issues-view.tsx b/web/hooks/use-calendar-issues-view.tsx index 21f8a41998c..b32dcfc3c8b 100644 --- a/web/hooks/use-calendar-issues-view.tsx +++ b/web/hooks/use-calendar-issues-view.tsx @@ -41,7 +41,6 @@ const useCalendarIssuesView = () => { labels: filters?.labels ? filters?.labels.join(",") : undefined, created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, start_date: filters?.start_date ? filters?.start_date.join(",") : undefined, - target_date: displayFilters?.calendar_date_range, }; const { data: projectCalendarIssues, mutate: mutateProjectCalendarIssues } = useSWR( diff --git a/web/store/issue_filters.ts b/web/store/issue_filters.ts index c8407a1ecfd..46cf8ae211e 100644 --- a/web/store/issue_filters.ts +++ b/web/store/issue_filters.ts @@ -25,15 +25,15 @@ export interface IIssueFilterStore { filtersSearchQuery: string; // action - fetchUserProjectFilters: (workspaceSlug: string, projectSlug: string) => Promise; + fetchUserProjectFilters: (workspaceSlug: string, projectId: string) => Promise; updateUserFilters: ( workspaceSlug: string, - projectSlug: string, + projectId: string, filterToUpdate: Partial ) => Promise; updateDisplayProperties: ( workspaceSlug: string, - projectSlug: string, + projectId: string, properties: Partial ) => Promise; updateFiltersSearchQuery: (query: string) => void; @@ -140,7 +140,6 @@ class IssueFilterStore implements IIssueFilterStore { type: this.userDisplayFilters?.type || undefined, sub_issue: this.userDisplayFilters?.sub_issue || true, show_empty_groups: this.userDisplayFilters?.show_empty_groups || true, - calendar_date_range: this.userDisplayFilters?.calendar_date_range || undefined, start_target_date: this.userDisplayFilters?.start_target_date || true, }; diff --git a/web/types/view-props.d.ts b/web/types/view-props.d.ts index 6630d527e6a..9be4404dea1 100644 --- a/web/types/view-props.d.ts +++ b/web/types/view-props.d.ts @@ -45,7 +45,6 @@ export type TIssueParams = | "type" | "sub_issue" | "show_empty_groups" - | "calendar_date_range" | "start_target_date"; export interface IIssueFilterOptions { @@ -61,7 +60,11 @@ export interface IIssueFilterOptions { } export interface IIssueDisplayFilterOptions { - calendar_date_range?: string; + calendar?: { + active_month?: string; + show_weekends?: boolean; + layout?: "month" | "week"; + }; group_by?: TIssueGroupByOptions; sub_group_by?: TIssueGroupByOptions; layout?: TIssueLayouts; From 293b488e0d453545e4969e6aa980763bf1c96eb6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 27 Sep 2023 16:25:53 +0530 Subject: [PATCH 06/11] chore: remove active month logic from backend --- .../issue-layouts/calendar/calendar.tsx | 73 ++++------------- .../issues/issue-layouts/calendar/data.ts | 27 +++---- .../issue-layouts/calendar/day-tile.tsx | 30 ++++--- .../issues/issue-layouts/calendar/header.tsx | 80 ++++--------------- .../issues/issue-layouts/calendar/index.ts | 1 + .../issue-layouts/calendar/issue-blocks.tsx | 4 +- .../calendar/options-dropdown.tsx | 66 ++++++++++++++- .../issue-layouts/calendar/week-days.tsx | 37 +++++++++ web/constants/calendar.ts | 18 +++++ web/types/view-props.d.ts | 5 +- 10 files changed, 189 insertions(+), 152 deletions(-) create mode 100644 web/components/issues/issue-layouts/calendar/week-days.tsx diff --git a/web/components/issues/issue-layouts/calendar/calendar.tsx b/web/components/issues/issue-layouts/calendar/calendar.tsx index 45a9ca7e802..8da6de8e43c 100644 --- a/web/components/issues/issue-layouts/calendar/calendar.tsx +++ b/web/components/issues/issue-layouts/calendar/calendar.tsx @@ -1,99 +1,60 @@ import { useEffect, useState } from "react"; -import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -import { useMobxStore } from "lib/mobx/store-provider"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // components -import { CalendarDayTile, CalendarHeader, CalendarWeekHeader } from "components/issues"; +import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components/issues"; // ui import { Spinner } from "components/ui"; // icons import { ICalendarPayload, ICalendarWeek, generateCalendarData } from "./data"; -// constants -import { renderDateFormat } from "helpers/date-time.helper"; type Props = {}; export const CalendarChart: React.FC = observer((props) => { const {} = props; + const [activeMonthDate, setActiveMonthDate] = useState(new Date()); + const [activeWeekNumber, setActiveWeekNumber] = useState(null); const [calendarPayload, setCalendarPayload] = useState(null); - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore(); - const activeMonth = issueFilterStore.userDisplayFilters.calendar?.active_month; - const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; - // generate calendar payload useEffect(() => { - console.log("Active month changed"); - - let activeMonthDate = new Date(); - - if (activeMonth) activeMonthDate = new Date(activeMonth); + console.log("Generating payload..."); setCalendarPayload( generateCalendarData(calendarPayload, activeMonthDate.getFullYear(), activeMonthDate.getMonth(), 5) ); - }, [activeMonth, calendarPayload]); + }, [activeMonthDate, calendarPayload]); - // set active month if not set - useEffect(() => { - if (!activeMonth) { - const today = new Date(); - const firstDayOfCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 1); - - if (!workspaceSlug || !projectId) return; - - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - calendar: { - active_month: renderDateFormat(firstDayOfCurrentMonth), - }, - }, - }); - } - }, [activeMonth, issueFilterStore, projectId, workspaceSlug]); - - if (!calendarPayload || !issueStore.getIssues || !activeMonth) + if (!calendarPayload || !issueStore.getIssues) return (
); - const activeMonthDate = new Date(activeMonth); + const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; console.log("calendarPayload", calendarPayload); - console.log("activeMonthPayload", calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()]); + const activeMonthAllWeeks = calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()]; return ( <>
- + setActiveMonthDate(date)} /> - {calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()] ? ( + {activeMonthAllWeeks ? (
- {Object.values(calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()]).map( - (week: ICalendarWeek, weekIndex) => ( -
- {Object.values(week).map((date, index: number) => { - if (!showWeekends && (index === 5 || index === 6)) return null; - - return ; - })} -
- ) - )} + {calendarLayout === "month" + ? Object.values(activeMonthAllWeeks).map((week: ICalendarWeek, weekIndex) => ( + + )) + : "Week view, boys"}
) : (
diff --git a/web/components/issues/issue-layouts/calendar/data.ts b/web/components/issues/issue-layouts/calendar/data.ts index abe9d2a7d0e..117cdb710de 100644 --- a/web/components/issues/issue-layouts/calendar/data.ts +++ b/web/components/issues/issue-layouts/calendar/data.ts @@ -13,7 +13,7 @@ export interface ICalendarDate { export interface ICalendarWeek { [weekNumber: number]: { - [date: string]: ICalendarDate | null; + [date: string]: ICalendarDate; }; } @@ -58,26 +58,23 @@ export const generateCalendarData = ( const numWeeks = Math.ceil((numDaysInMonth + firstDayOfWeek) / 7); for (let week = 0; week < numWeeks; week++) { - const currentWeekObject: { [date: string]: ICalendarDate | null } = {}; + const currentWeekObject: { [date: string]: ICalendarDate } = {}; for (let i = 0; i < 7; i++) { const dayNumber = week * 7 + i - firstDayOfWeek + 1; const date = new Date(year, month, dayNumber); - currentWeekObject[renderDateFormat(date)] = - dayNumber >= 1 && dayNumber <= numDaysInMonth - ? { - date, - year, - month, - day: dayNumber, - week: getWeekNumber(date), - is_current_month: true, - is_current_week: getWeekNumber(date) === getWeekNumber(new Date()), - is_today: date.toDateString() === new Date().toDateString(), - } - : null; + currentWeekObject[renderDateFormat(date)] = { + date, + year, + month, + day: dayNumber, + week: getWeekNumber(date), + is_current_month: date.getMonth() === month, + is_current_week: getWeekNumber(date) === getWeekNumber(new Date()), + is_today: date.toDateString() === new Date().toDateString(), + }; } calendarData[year][month][week] = currentWeekObject; diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx index 2802bf6e390..5e807ef1998 100644 --- a/web/components/issues/issue-layouts/calendar/day-tile.tsx +++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx @@ -1,42 +1,50 @@ import { observer } from "mobx-react-lite"; -import { useMobxStore } from "lib/mobx/store-provider"; import { Droppable } from "@hello-pangea/dnd"; + +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // components import { CalendarIssueBlocks, ICalendarDate } from "components/issues"; // helpers import { renderDateFormat } from "helpers/date-time.helper"; // types import { IIssueGroupedStructure } from "store/issue"; +// constants +import { MONTHS_LIST } from "constants/calendar"; -type Props = { date: ICalendarDate | null }; +type Props = { activeMonthDate: Date; date: ICalendarDate }; export const CalendarDayTile: React.FC = observer((props) => { - const { date } = props; + const { activeMonthDate, date } = props; const { issue: issueStore } = useMobxStore(); - if (!date) return
; - const issues = issueStore.getIssues ? (issueStore.getIssues as IIssueGroupedStructure)[renderDateFormat(date.date)] : null; return ( - + {(provided, snapshot) => (
<> - {date &&
{date.date.getDate()}
} -
- -
+ {date && ( +
+ {date.date.getDate() === 1 && MONTHS_LIST[date.date.getMonth() + 1].shortTitle + " "} + {date.date.getDate()} +
+ )} + {provided.placeholder}
diff --git a/web/components/issues/issue-layouts/calendar/header.tsx b/web/components/issues/issue-layouts/calendar/header.tsx index 2711290e192..65b4f0880f9 100644 --- a/web/components/issues/issue-layouts/calendar/header.tsx +++ b/web/components/issues/issue-layouts/calendar/header.tsx @@ -7,15 +7,16 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { CalendarOptionsDropdown } from "components/issues"; // icons import { ChevronLeft, ChevronRight } from "lucide-react"; -// helpers -import { renderDateFormat } from "helpers/date-time.helper"; // constants import { MONTHS_LIST } from "constants/calendar"; -type Props = {}; +type Props = { + activeMonthDate: Date; + setActiveMonthDate: (date: Date) => void; +}; export const CalendarHeader: React.FC = observer((props) => { - const {} = props; + const { activeMonthDate, setActiveMonthDate } = props; const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -23,11 +24,7 @@ export const CalendarHeader: React.FC = observer((props) => { const { issueFilter: issueFilterStore } = useMobxStore(); const handlePreviousMonth = () => { - const activeMonth = issueFilterStore.userDisplayFilters.calendar?.active_month; - - if (!workspaceSlug || !projectId || !activeMonth) return; - - const activeMonthDate = new Date(activeMonth); + if (!workspaceSlug || !projectId) return; const previousMonthYear = activeMonthDate.getMonth() === 0 ? activeMonthDate.getFullYear() - 1 : activeMonthDate.getFullYear(); @@ -35,22 +32,11 @@ export const CalendarHeader: React.FC = observer((props) => { const previousMonthFirstDate = new Date(previousMonthYear, previousMonthMonth, 1); - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - calendar: { - ...issueFilterStore.userDisplayFilters.calendar, - active_month: renderDateFormat(previousMonthFirstDate), - }, - }, - }); + setActiveMonthDate(previousMonthFirstDate); }; const handleNextMonth = () => { - const activeMonth = issueFilterStore.userDisplayFilters.calendar?.active_month; - - if (!workspaceSlug || !projectId || !activeMonth) return; - - const activeMonthDate = new Date(activeMonth); + if (!workspaceSlug || !projectId) return; const nextMonthYear = activeMonthDate.getMonth() === 11 ? activeMonthDate.getFullYear() + 1 : activeMonthDate.getFullYear(); @@ -58,14 +44,7 @@ export const CalendarHeader: React.FC = observer((props) => { const nextMonthFirstDate = new Date(nextMonthYear, nextMonthMonth, 1); - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - calendar: { - ...issueFilterStore.userDisplayFilters.calendar, - active_month: renderDateFormat(nextMonthFirstDate), - }, - }, - }); + setActiveMonthDate(nextMonthFirstDate); }; const handleToday = () => { @@ -74,32 +53,10 @@ export const CalendarHeader: React.FC = observer((props) => { if (!workspaceSlug || !projectId) return; - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - calendar: { - ...issueFilterStore.userDisplayFilters.calendar, - active_month: renderDateFormat(firstDayOfCurrentMonth), - }, - }, - }); - }; - - const handleToggleWeekends = () => { - const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; - - if (!workspaceSlug || !projectId) return; - - issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { - display_filters: { - calendar: { - ...issueFilterStore.userDisplayFilters.calendar, - show_weekends: !showWeekends, - }, - }, - }); + setActiveMonthDate(firstDayOfCurrentMonth); }; - const activeMonthDate = new Date(issueFilterStore.userDisplayFilters.calendar?.active_month || new Date()); + const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; return (
@@ -108,28 +65,23 @@ export const CalendarHeader: React.FC = observer((props) => {

- {MONTHS_LIST[activeMonthDate.getMonth() + 1].title} {activeMonthDate.getFullYear()} + {calendarLayout === "month" + ? `${MONTHS_LIST[activeMonthDate.getMonth() + 1].title} ${activeMonthDate.getFullYear()}` + : "Week view"}

-
+
-
); diff --git a/web/components/issues/issue-layouts/calendar/index.ts b/web/components/issues/issue-layouts/calendar/index.ts index 665860c9a72..c62addb855b 100644 --- a/web/components/issues/issue-layouts/calendar/index.ts +++ b/web/components/issues/issue-layouts/calendar/index.ts @@ -5,4 +5,5 @@ export * from "./header"; export * from "./issue-blocks"; export * from "./options-dropdown"; export * from "./root"; +export * from "./week-days"; export * from "./week-header"; diff --git a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx index 34c8d7a8792..2b5485341ec 100644 --- a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx +++ b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -13,7 +13,9 @@ export const CalendarIssueBlocks: React.FC = (props) => { {(provided, snapshot) => (
= observer((props) => { const {} = props; + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + const { issueFilter: issueFilterStore } = useMobxStore(); + const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; + const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; + + const handleLayoutChange = (layout: TCalendarLayouts) => { + if (!workspaceSlug || !projectId) return; + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + calendar: { + ...issueFilterStore.userDisplayFilters.calendar, + layout, + }, + }, + }); + }; + + const handleToggleWeekends = () => { + const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; + + if (!workspaceSlug || !projectId) return; + + issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), { + display_filters: { + calendar: { + ...issueFilterStore.userDisplayFilters.calendar, + show_weekends: !showWeekends, + }, + }, + }); + }; + return ( {({ open }) => { @@ -41,8 +79,30 @@ export const CalendarOptionsDropdown: React.FC = observer((props) => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - - Hi + +
+
+ {Object.entries(CALENDAR_LAYOUTS).map(([layout, layoutDetails]) => ( + + ))} + +
+
diff --git a/web/components/issues/issue-layouts/calendar/week-days.tsx b/web/components/issues/issue-layouts/calendar/week-days.tsx new file mode 100644 index 00000000000..a883e29e0fe --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/week-days.tsx @@ -0,0 +1,37 @@ +import { observer } from "mobx-react-lite"; + +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { CalendarDayTile } from "components/issues"; +// helpers +import { renderDateFormat } from "helpers/date-time.helper"; +// types +import { ICalendarDate, ICalendarWeek } from "./data"; + +type Props = { + activeMonthDate: Date; + week: ICalendarWeek; +}; + +export const CalendarWeekDays: React.FC = observer((props) => { + const { activeMonthDate, week } = props; + + const { issueFilter: issueFilterStore } = useMobxStore(); + + const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; + + return ( +
+ {Object.values(week).map((date: ICalendarDate, index) => { + if (!showWeekends && (index === 5 || index === 6)) return null; + + return ; + })} +
+ ); +}); diff --git a/web/constants/calendar.ts b/web/constants/calendar.ts index 9b23b3d3fc7..5bedf1f2169 100644 --- a/web/constants/calendar.ts +++ b/web/constants/calendar.ts @@ -1,3 +1,5 @@ +import { TCalendarLayouts } from "types"; + export const MONTHS_LIST: { [monthNumber: number]: { shortTitle: string; @@ -89,3 +91,19 @@ export const DAYS_LIST: { title: "Sunday", }, }; + +export const CALENDAR_LAYOUTS: { + [layout in TCalendarLayouts]: { + key: TCalendarLayouts; + title: string; + }; +} = { + month: { + key: "month", + title: "Month layout", + }, + week: { + key: "week", + title: "Week layout", + }, +}; diff --git a/web/types/view-props.d.ts b/web/types/view-props.d.ts index 9be4404dea1..c142ec0c6fb 100644 --- a/web/types/view-props.d.ts +++ b/web/types/view-props.d.ts @@ -47,6 +47,8 @@ export type TIssueParams = | "show_empty_groups" | "start_target_date"; +export type TCalendarLayouts = "month" | "week"; + export interface IIssueFilterOptions { assignees?: string[] | null; created_by?: string[] | null; @@ -61,9 +63,8 @@ export interface IIssueFilterOptions { export interface IIssueDisplayFilterOptions { calendar?: { - active_month?: string; show_weekends?: boolean; - layout?: "month" | "week"; + layout?: TCalendarLayouts; }; group_by?: TIssueGroupByOptions; sub_group_by?: TIssueGroupByOptions; From a780b5023e71009274ccbffca3c7b40b2c3e4a36 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 28 Sep 2023 11:36:43 +0530 Subject: [PATCH 07/11] chore: setup new store for calendar --- .../issue-layouts/layout-selection.tsx | 3 +- .../issue-layouts/calendar/calendar.tsx | 56 +++--- .../issues/issue-layouts/calendar/data.ts | 85 --------- .../issue-layouts/calendar/day-tile.tsx | 30 ++- .../issues/issue-layouts/calendar/header.tsx | 95 ++++++---- .../issues/issue-layouts/calendar/index.ts | 2 +- .../calendar/options-dropdown.tsx | 6 +- .../issues/issue-layouts/calendar/types.d.ts | 24 +++ .../issue-layouts/calendar/week-days.tsx | 14 +- .../issue-layouts/calendar/week-header.tsx | 8 +- web/constants/calendar.ts | 18 +- web/helpers/calendar.helper.ts | 171 ++++++------------ web/helpers/date-time.helper.ts | 90 ++++----- web/store/calendar.ts | 82 +++++++++ web/store/root.ts | 3 + 15 files changed, 333 insertions(+), 354 deletions(-) delete mode 100644 web/components/issues/issue-layouts/calendar/data.ts create mode 100644 web/components/issues/issue-layouts/calendar/types.d.ts create mode 100644 web/store/calendar.ts diff --git a/web/components/issue-layouts/layout-selection.tsx b/web/components/issue-layouts/layout-selection.tsx index e2863f08809..416b37d6f9a 100644 --- a/web/components/issue-layouts/layout-selection.tsx +++ b/web/components/issue-layouts/layout-selection.tsx @@ -19,9 +19,8 @@ export const LayoutSelection: React.FC = (props) => { return (
{ISSUE_LAYOUTS.filter((l) => layouts.includes(l.key)).map((layout) => ( - +
); - const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; - console.log("calendarPayload", calendarPayload); + // console.log("activeMonthDate", activeMonthDate); + // console.log("activeWeekDate", activeWeekDate); + + const activeWeekNumber = getWeekNumberOfDate(activeWeekDate); + + // console.log("activeWeekNumber", activeWeekNumber); + const activeMonthAllWeeks = calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()]; + const activeWeekAllDays = calendarPayload[activeWeekDate.getFullYear()][activeWeekDate.getMonth()][activeWeekNumber]; return ( <>
- setActiveMonthDate(date)} /> + {activeMonthAllWeeks ? (
- {calendarLayout === "month" - ? Object.values(activeMonthAllWeeks).map((week: ICalendarWeek, weekIndex) => ( - - )) - : "Week view, boys"} + {calendarLayout === "month" ? ( +
+ {Object.values(activeMonthAllWeeks).map((week: ICalendarWeek, weekIndex) => ( + + ))} +
+ ) : ( + + )}
) : (
diff --git a/web/components/issues/issue-layouts/calendar/data.ts b/web/components/issues/issue-layouts/calendar/data.ts deleted file mode 100644 index 117cdb710de..00000000000 --- a/web/components/issues/issue-layouts/calendar/data.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { renderDateFormat } from "helpers/date-time.helper"; - -export interface ICalendarDate { - date: Date; - year: number; - month: number; - day: number; - week: number; // week number wrt year, eg- 51, 52 - is_current_month: boolean; - is_current_week: boolean; - is_today: boolean; -} - -export interface ICalendarWeek { - [weekNumber: number]: { - [date: string]: ICalendarDate; - }; -} - -export interface ICalendarMonth { - [monthNumber: number]: ICalendarWeek; -} - -export interface ICalendarPayload { - [year: number]: ICalendarMonth; -} - -// get week number wrt year -export const getWeekNumber = (date: Date): number => { - const d = new Date(date); - - d.setHours(0, 0, 0, 0); - d.setDate(d.getDate() + 3 - ((d.getDay() + 6) % 7)); - - const week1 = new Date(d.getFullYear(), 0, 4); - - return 1 + Math.round(((d.valueOf() - week1.valueOf()) / 86400000 - 3 + ((week1.getDay() + 6) % 7)) / 7); -}; - -export const generateCalendarData = ( - currentStructure: ICalendarPayload | null, - startYear: number, - startMonth: number, - numMonths: number -): ICalendarPayload => { - const calendarData: ICalendarPayload = currentStructure ?? {}; - - for (let i = -Math.floor(numMonths / 2); i <= Math.floor(numMonths / 2); i++) { - const currentDate = new Date(startYear, startMonth + i, 1); - const year = currentDate.getFullYear(); - const month = currentDate.getMonth(); - const numDaysInMonth = new Date(year, month + 1, 0).getDate(); - const firstDayOfWeek = (new Date(year, month, 1).getDay() + 6) % 7; // Convert Sunday to 0-based index - - calendarData[year] ||= {}; - calendarData[year][month] ||= {}; - - const numWeeks = Math.ceil((numDaysInMonth + firstDayOfWeek) / 7); - - for (let week = 0; week < numWeeks; week++) { - const currentWeekObject: { [date: string]: ICalendarDate } = {}; - - for (let i = 0; i < 7; i++) { - const dayNumber = week * 7 + i - firstDayOfWeek + 1; - - const date = new Date(year, month, dayNumber); - - currentWeekObject[renderDateFormat(date)] = { - date, - year, - month, - day: dayNumber, - week: getWeekNumber(date), - is_current_month: date.getMonth() === month, - is_current_week: getWeekNumber(date) === getWeekNumber(new Date()), - is_today: date.toDateString() === new Date().toDateString(), - }; - } - - calendarData[year][month][week] = currentWeekObject; - } - } - - return calendarData; -}; diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx index 5e807ef1998..e4a2390803c 100644 --- a/web/components/issues/issue-layouts/calendar/day-tile.tsx +++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx @@ -12,12 +12,14 @@ import { IIssueGroupedStructure } from "store/issue"; // constants import { MONTHS_LIST } from "constants/calendar"; -type Props = { activeMonthDate: Date; date: ICalendarDate }; +type Props = { date: ICalendarDate }; export const CalendarDayTile: React.FC = observer((props) => { - const { activeMonthDate, date } = props; + const { date } = props; - const { issue: issueStore } = useMobxStore(); + const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore(); + + const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; const issues = issueStore.getIssues ? (issueStore.getIssues as IIssueGroupedStructure)[renderDateFormat(date.date)] @@ -27,23 +29,19 @@ export const CalendarDayTile: React.FC = observer((props) => { {(provided, snapshot) => (
<> - {date && ( -
- {date.date.getDate() === 1 && MONTHS_LIST[date.date.getMonth() + 1].shortTitle + " "} - {date.date.getDate()} -
- )} +
+ {date.date.getDate() === 1 && MONTHS_LIST[date.date.getMonth() + 1].shortTitle + " "} + {date.date.getDate()} +
{provided.placeholder} diff --git a/web/components/issues/issue-layouts/calendar/header.tsx b/web/components/issues/issue-layouts/calendar/header.tsx index 65b4f0880f9..c42090ca006 100644 --- a/web/components/issues/issue-layouts/calendar/header.tsx +++ b/web/components/issues/issue-layouts/calendar/header.tsx @@ -1,4 +1,3 @@ -import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // mobx store @@ -10,58 +9,74 @@ import { ChevronLeft, ChevronRight } from "lucide-react"; // constants import { MONTHS_LIST } from "constants/calendar"; -type Props = { - activeMonthDate: Date; - setActiveMonthDate: (date: Date) => void; -}; +export const CalendarHeader: React.FC = observer(() => { + const { issueFilter: issueFilterStore, calendar: calendarStore } = useMobxStore(); -export const CalendarHeader: React.FC = observer((props) => { - const { activeMonthDate, setActiveMonthDate } = props; - - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const { issueFilter: issueFilterStore } = useMobxStore(); - - const handlePreviousMonth = () => { - if (!workspaceSlug || !projectId) return; - - const previousMonthYear = - activeMonthDate.getMonth() === 0 ? activeMonthDate.getFullYear() - 1 : activeMonthDate.getFullYear(); - const previousMonthMonth = activeMonthDate.getMonth() === 0 ? 11 : activeMonthDate.getMonth() - 1; - - const previousMonthFirstDate = new Date(previousMonthYear, previousMonthMonth, 1); - - setActiveMonthDate(previousMonthFirstDate); + const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; + const activeMonthDate = calendarStore.calendarFilters.activeMonthDate; + const activeWeekDate = calendarStore.calendarFilters.activeWeekDate; + + const handlePrevious = () => { + if (calendarLayout === "month") { + const previousMonthYear = + activeMonthDate.getMonth() === 0 ? activeMonthDate.getFullYear() - 1 : activeMonthDate.getFullYear(); + const previousMonthMonth = activeMonthDate.getMonth() === 0 ? 11 : activeMonthDate.getMonth() - 1; + + const previousMonthFirstDate = new Date(previousMonthYear, previousMonthMonth, 1); + + calendarStore.updateCalendarFilters({ + activeMonthDate: previousMonthFirstDate, + }); + } else { + const previousWeekDate = new Date( + activeWeekDate.getFullYear(), + activeWeekDate.getMonth(), + activeWeekDate.getDate() - 7 + ); + + calendarStore.updateCalendarFilters({ + activeWeekDate: previousWeekDate, + }); + } }; - const handleNextMonth = () => { - if (!workspaceSlug || !projectId) return; - - const nextMonthYear = - activeMonthDate.getMonth() === 11 ? activeMonthDate.getFullYear() + 1 : activeMonthDate.getFullYear(); - const nextMonthMonth = (activeMonthDate.getMonth() + 1) % 12; - - const nextMonthFirstDate = new Date(nextMonthYear, nextMonthMonth, 1); - - setActiveMonthDate(nextMonthFirstDate); + const handleNext = () => { + if (calendarLayout === "month") { + const nextMonthYear = + activeMonthDate.getMonth() === 11 ? activeMonthDate.getFullYear() + 1 : activeMonthDate.getFullYear(); + const nextMonthMonth = (activeMonthDate.getMonth() + 1) % 12; + + const nextMonthFirstDate = new Date(nextMonthYear, nextMonthMonth, 1); + + calendarStore.updateCalendarFilters({ + activeMonthDate: nextMonthFirstDate, + }); + } else { + const nextWeekDate = new Date( + activeWeekDate.getFullYear(), + activeWeekDate.getMonth(), + activeWeekDate.getDate() + 7 + ); + + calendarStore.updateCalendarFilters({ + activeWeekDate: nextWeekDate, + }); + } }; const handleToday = () => { const today = new Date(); const firstDayOfCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 1); - if (!workspaceSlug || !projectId) return; - - setActiveMonthDate(firstDayOfCurrentMonth); + calendarStore.updateCalendarFilters({ + activeMonthDate: firstDayOfCurrentMonth, + }); }; - const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; - return (
-

@@ -69,7 +84,7 @@ export const CalendarHeader: React.FC = observer((props) => { ? `${MONTHS_LIST[activeMonthDate.getMonth() + 1].title} ${activeMonthDate.getFullYear()}` : "Week view"}

-
diff --git a/web/components/issues/issue-layouts/calendar/index.ts b/web/components/issues/issue-layouts/calendar/index.ts index c62addb855b..57c52888c9d 100644 --- a/web/components/issues/issue-layouts/calendar/index.ts +++ b/web/components/issues/issue-layouts/calendar/index.ts @@ -1,5 +1,5 @@ export * from "./calendar"; -export * from "./data"; +export * from "./types"; export * from "./day-tile"; export * from "./header"; export * from "./issue-blocks"; diff --git a/web/components/issues/issue-layouts/calendar/options-dropdown.tsx b/web/components/issues/issue-layouts/calendar/options-dropdown.tsx index 0c33ba35497..b27aa12424f 100644 --- a/web/components/issues/issue-layouts/calendar/options-dropdown.tsx +++ b/web/components/issues/issue-layouts/calendar/options-dropdown.tsx @@ -18,7 +18,7 @@ export const CalendarOptionsDropdown: React.FC = observer((props) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; - const { issueFilter: issueFilterStore } = useMobxStore(); + const { issueFilter: issueFilterStore, calendar: calendarStore } = useMobxStore(); const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; @@ -34,6 +34,10 @@ export const CalendarOptionsDropdown: React.FC = observer((props) => { }, }, }); + + calendarStore.updateCalendarPayload( + layout === "month" ? calendarStore.calendarFilters.activeMonthDate : calendarStore.calendarFilters.activeWeekDate + ); }; const handleToggleWeekends = () => { diff --git a/web/components/issues/issue-layouts/calendar/types.d.ts b/web/components/issues/issue-layouts/calendar/types.d.ts new file mode 100644 index 00000000000..e7864c0c84d --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/types.d.ts @@ -0,0 +1,24 @@ +export interface ICalendarDate { + date: Date; + year: number; + month: number; + day: number; + week: number; // week number wrt year, eg- 51, 52 + is_current_month: boolean; + is_current_week: boolean; + is_today: boolean; +} + +export interface ICalendarWeek { + [date: string]: ICalendarDate; +} + +export interface ICalendarMonth { + [monthNumber: number]: { + [weekNumber: string]: ICalendarWeek; + }; +} + +export interface ICalendarPayload { + [year: number]: ICalendarMonth; +} diff --git a/web/components/issues/issue-layouts/calendar/week-days.tsx b/web/components/issues/issue-layouts/calendar/week-days.tsx index a883e29e0fe..bf2bd5dc8e4 100644 --- a/web/components/issues/issue-layouts/calendar/week-days.tsx +++ b/web/components/issues/issue-layouts/calendar/week-days.tsx @@ -7,30 +7,30 @@ import { CalendarDayTile } from "components/issues"; // helpers import { renderDateFormat } from "helpers/date-time.helper"; // types -import { ICalendarDate, ICalendarWeek } from "./data"; +import { ICalendarDate, ICalendarWeek } from "./types"; type Props = { - activeMonthDate: Date; week: ICalendarWeek; }; export const CalendarWeekDays: React.FC = observer((props) => { - const { activeMonthDate, week } = props; + const { week } = props; const { issueFilter: issueFilterStore } = useMobxStore(); + const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; return (
- {Object.values(week).map((date: ICalendarDate, index) => { - if (!showWeekends && (index === 5 || index === 6)) return null; + {Object.values(week).map((date: ICalendarDate) => { + if (!showWeekends && (date.date.getDay() === 0 || date.date.getDay() === 6)) return null; - return ; + return ; })}
); diff --git a/web/components/issues/issue-layouts/calendar/week-header.tsx b/web/components/issues/issue-layouts/calendar/week-header.tsx index a19c295d1d5..eb6b9ae46c8 100644 --- a/web/components/issues/issue-layouts/calendar/week-header.tsx +++ b/web/components/issues/issue-layouts/calendar/week-header.tsx @@ -1,11 +1,11 @@ import { observer } from "mobx-react-lite"; + +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; // constants import { DAYS_LIST } from "constants/calendar"; -type Props = {}; - -export const CalendarWeekHeader: React.FC = observer((props) => { +export const CalendarWeekHeader: React.FC = observer(() => { const { issueFilter: issueFilterStore } = useMobxStore(); const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; @@ -20,7 +20,7 @@ export const CalendarWeekHeader: React.FC = observer((props) => { if (!showWeekends && (day.shortTitle === "Sat" || day.shortTitle === "Sun")) return null; return ( -
+
{day.shortTitle}
); diff --git a/web/constants/calendar.ts b/web/constants/calendar.ts index 5bedf1f2169..4c4070ec145 100644 --- a/web/constants/calendar.ts +++ b/web/constants/calendar.ts @@ -63,33 +63,33 @@ export const DAYS_LIST: { }; } = { 1: { + shortTitle: "Sun", + title: "Sunday", + }, + 2: { shortTitle: "Mon", title: "Monday", }, - 2: { + 3: { shortTitle: "Tue", title: "Tuesday", }, - 3: { + 4: { shortTitle: "Wed", title: "Wednesday", }, - 4: { + 5: { shortTitle: "Thu", title: "Thursday", }, - 5: { + 6: { shortTitle: "Fri", title: "Friday", }, - 6: { + 7: { shortTitle: "Sat", title: "Saturday", }, - 7: { - shortTitle: "Sun", - title: "Sunday", - }, }; export const CALENDAR_LAYOUTS: { diff --git a/web/helpers/calendar.helper.ts b/web/helpers/calendar.helper.ts index ea553e170be..4b291d541ab 100644 --- a/web/helpers/calendar.helper.ts +++ b/web/helpers/calendar.helper.ts @@ -1,79 +1,7 @@ -export const startOfWeek = (date: Date) => { - const startOfMonthDate = new Date(date.getFullYear(), date.getMonth(), 1); - const dayOfWeek = startOfMonthDate.getDay() % 7; - const startOfWeekDate = new Date( - startOfMonthDate.getFullYear(), - startOfMonthDate.getMonth(), - startOfMonthDate.getDate() - dayOfWeek - ); - const timezoneOffset = startOfMonthDate.getTimezoneOffset(); - const timezoneOffsetMilliseconds = timezoneOffset * 60 * 1000; - const startOfWeekAdjusted = new Date(startOfWeekDate.getTime() - timezoneOffsetMilliseconds); - return startOfWeekAdjusted; -}; - -export const lastDayOfWeek = (date: Date) => { - const lastDayOfPreviousMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0); - const dayOfWeek = lastDayOfPreviousMonth.getDay() % 7; - const daysUntilEndOfWeek = 6 - dayOfWeek; - const lastDayOfWeekDate = new Date( - lastDayOfPreviousMonth.getFullYear(), - lastDayOfPreviousMonth.getMonth(), - lastDayOfPreviousMonth.getDate() + daysUntilEndOfWeek - ); - const timezoneOffset = lastDayOfPreviousMonth.getTimezoneOffset(); - const timezoneOffsetMilliseconds = timezoneOffset * 60 * 1000; - const lastDayOfWeekAdjusted = new Date(lastDayOfWeekDate.getTime() - timezoneOffsetMilliseconds); - return lastDayOfWeekAdjusted; -}; - -export const getCurrentWeekStartDate = (date: Date) => { - const today = new Date(date); - const dayOfWeek = today.getDay(); - const startOfWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() - dayOfWeek); - const timezoneOffset = startOfWeek.getTimezoneOffset(); - const timezoneOffsetMilliseconds = timezoneOffset * 60 * 1000; - const startOfWeekAdjusted = new Date(startOfWeek.getTime() - timezoneOffsetMilliseconds); - return startOfWeekAdjusted; -}; - -export const getCurrentWeekEndDate = (date: Date) => { - const today = new Date(date); - const dayOfWeek = today.getDay(); - const endOfWeek = new Date( - today.getFullYear(), - today.getMonth(), - today.getDate() + (6 - dayOfWeek) - ); - const timezoneOffset = endOfWeek.getTimezoneOffset(); - const timezoneOffsetMilliseconds = timezoneOffset * 60 * 1000; - const endOfWeekAdjusted = new Date(endOfWeek.getTime() - timezoneOffsetMilliseconds); - return endOfWeekAdjusted; -}; - -export const eachDayOfInterval = ({ start, end }: { start: Date; end: Date }) => { - const days = []; - const current = new Date(start); - while (current <= end) { - days.push(new Date(current)); - current.setDate(current.getDate() + 1); - } - return days; -}; - -export const weekDayInterval = ({ start, end }: { start: Date; end: Date }) => { - const dates = []; - const currentDate = new Date(start); - const endDate = new Date(end); - while (currentDate <= endDate) { - const dayOfWeek = currentDate.getDay(); - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - dates.push(new Date(currentDate)); - } - currentDate.setDate(currentDate.getDate() + 1); - } - return dates; -}; +// helpers +import { getWeekNumberOfDate, renderDateFormat } from "helpers/date-time.helper"; +// types +import { ICalendarDate, ICalendarPayload } from "components/issues"; export const formatDate = (date: Date, format: string): string => { const day = date.getDate(); @@ -112,50 +40,53 @@ export const formatDate = (date: Date, format: string): string => { return formattedDate; }; -export const subtractMonths = (date: Date, numMonths: number) => { - const result = new Date(date); - result.setMonth(result.getMonth() - numMonths); - return result; -}; - -export const addMonths = (date: Date, numMonths: number) => { - const result = new Date(date); - result.setMonth(result.getMonth() + numMonths); - return result; -}; - -export const updateDateWithYear = (yearString: string, date: Date) => { - const year = parseInt(yearString); - const month = date.getMonth(); - const day = date.getDate(); - return new Date(year, month, day); -}; - -export const updateDateWithMonth = (monthString: string, date: Date) => { - const month = parseInt(monthString) - 1; - const year = date.getFullYear(); - const day = date.getDate(); - return new Date(year, month, day); -}; - -export const isSameMonth = (monthString: string, date: Date) => { - const month = parseInt(monthString) - 1; - return month === date.getMonth(); -}; - -export const isSameYear = (yearString: string, date: Date) => { - const year = parseInt(yearString); - return year === date.getFullYear(); -}; +/** + * @returns {ICalendarPayload} calendar payload to render the calendar + * @param {ICalendarPayload | null} currentStructure current calendar payload + * @param {Date} startDate date of the month to render + * @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 => { + const calendarData: ICalendarPayload = currentStructure ?? {}; + + const startMonth = startDate.getMonth(); + const startYear = startDate.getFullYear(); + + const currentDate = new Date(startYear, startMonth, 1); + const year = currentDate.getFullYear(); + const month = currentDate.getMonth(); + const numDaysInMonth = new Date(year, month + 1, 0).getDate(); + const firstDayOfWeek = new Date(year, month, 1).getDay(); // Sunday is 0, Monday is 1, ..., Saturday is 6 + + calendarData[year] ||= {}; + calendarData[year][month] ||= {}; + + const numWeeks = Math.ceil((numDaysInMonth + firstDayOfWeek) / 7); + + for (let week = 0; week < numWeeks; week++) { + const currentWeekObject: { [date: string]: ICalendarDate } = {}; + + const weekNumber = getWeekNumberOfDate(new Date(year, month, week * 7 - firstDayOfWeek + 1)); + + for (let i = 0; i < 7; i++) { + const dayNumber = week * 7 + i - firstDayOfWeek; + + const date = new Date(year, month, dayNumber + 1); + + currentWeekObject[renderDateFormat(date)] = { + date, + year, + month, + day: dayNumber + 1, + week: weekNumber, + is_current_month: date.getMonth() === month, + is_current_week: getWeekNumberOfDate(date) === getWeekNumberOfDate(new Date()), + is_today: date.toDateString() === new Date().toDateString(), + }; + } -export const addSevenDaysToDate = (date: Date) => { - const currentDate = new Date(date); - const newDate = new Date(currentDate.setDate(currentDate.getDate() + 7)); - return newDate; -}; + calendarData[year][month][weekNumber] = currentWeekObject; + } -export const subtract7DaysToDate = (date: Date) => { - const currentDate = new Date(date); - const newDate = new Date(currentDate.getTime() - 7 * 24 * 60 * 60 * 1000); - return newDate; + return calendarData; }; diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index 6784a9aa638..09aa511dfee 100644 --- a/web/helpers/date-time.helper.ts +++ b/web/helpers/date-time.helper.ts @@ -130,10 +130,7 @@ export const formatDateDistance = (date: string | Date) => { } }; -export const getDateRangeStatus = ( - startDate: string | null | undefined, - endDate: string | null | undefined -) => { +export const getDateRangeStatus = (startDate: string | null | undefined, endDate: string | null | undefined) => { if (!startDate || !endDate) return "draft"; const today = renderDateFormat(new Date()); @@ -155,20 +152,7 @@ export const renderShortDateWithYearFormat = (date: string | Date, placeholder?: date = new Date(date); - const months = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; + const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; const day = date.getDate(); const month = months[date.getMonth()]; const year = date.getFullYear(); @@ -181,20 +165,7 @@ export const renderShortDate = (date: string | Date, placeholder?: string) => { date = new Date(date); - const months = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; + const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; const day = date.getDate(); const month = months[date.getMonth()]; @@ -234,8 +205,7 @@ export const render24HourFormatTime = (date: string | Date): string => { return hours + ":" + minutes; }; -export const isDateRangeValid = (startDate: string, endDate: string) => - new Date(startDate) < new Date(endDate); +export const isDateRangeValid = (startDate: string, endDate: string) => new Date(startDate) < new Date(endDate); export const isDateGreaterThanToday = (dateStr: string) => { const date = new Date(dateStr); @@ -331,8 +301,7 @@ export const getDatesAfterCurrentDate = (): Array<{ * @example checkIfStringIsDate("2021-01-32") // false */ -export const checkIfStringIsDate = (date: string): boolean => - new Date(date).toString() !== "Invalid Date"; +export const checkIfStringIsDate = (date: string): boolean => new Date(date).toString() !== "Invalid Date"; // return an array of dates starting from 12:00 to 23:30 with 30 minutes interval as dates export const getDatesWith30MinutesInterval = (): Array => { @@ -384,11 +353,7 @@ export const getAllTimeIn30MinutesInterval = (): Array<{ * @example checkIfStringIsDate("2021-01-01", "2021-01-08") // 8 */ -export const findTotalDaysInRange = ( - startDate: Date | string, - endDate: Date | string, - inclusive: boolean -): number => { +export const findTotalDaysInRange = (startDate: Date | string, endDate: Date | string, inclusive: boolean): number => { if (!startDate || !endDate) return 0; startDate = new Date(startDate); @@ -405,3 +370,46 @@ export const findTotalDaysInRange = ( }; export const getUserTimeZoneFromWindow = () => Intl.DateTimeFormat().resolvedOptions().timeZone; + +/** + * @returns {number} week number of date + * @description Returns week number of date + * @param {Date} date + * @example getWeekNumber(new Date("2023-09-01")) // 35 + */ +export const getWeekNumberOfDate = (date: Date): number => { + const d = new Date(date); + + // Set the first day of the week to Sunday (0) and calculate the day of the week + d.setHours(0, 0, 0, 0); + const dayOfWeek: number = d.getDay(); + + // Calculate the week number based on Sunday as the first day of the week + const weekStart: Date = new Date(d.getFullYear(), 0, 1); + const daysUntilDate: number = Math.ceil((d.getTime() - weekStart.getTime()) / (24 * 60 * 60 * 1000)); + const weekNumber: number = Math.ceil((daysUntilDate + 1 + dayOfWeek) / 7); + + return weekNumber; +}; + +/** + * @returns {Date} first date of week + * @description Returns week number of date + * @param {Date} date + * @example getFirstDateOfWeek(35, 2023) // 2023-08-27T00:00:00.000Z + */ +export const getFirstDateOfWeek = (date: Date): Date => { + const year = date.getFullYear(); + const weekNumber = getWeekNumberOfDate(date); + + const januaryFirst: Date = new Date(year, 0, 1); // January is month 0 + const daysToAdd: number = (weekNumber - 1) * 7; // Subtract 1 from the week number since weeks are 0-indexed + const firstDateOfWeek: Date = new Date(januaryFirst); + firstDateOfWeek.setDate(januaryFirst.getDate() + daysToAdd); + + // Adjust the date to Sunday (week start) + const dayOfWeek: number = firstDateOfWeek.getDay(); + firstDateOfWeek.setDate(firstDateOfWeek.getDate() - dayOfWeek); // Move back to Sunday + + return firstDateOfWeek; +}; diff --git a/web/store/calendar.ts b/web/store/calendar.ts new file mode 100644 index 00000000000..ed7430a1cca --- /dev/null +++ b/web/store/calendar.ts @@ -0,0 +1,82 @@ +import { observable, action, makeObservable, runInAction } from "mobx"; + +// types +import { RootStore } from "./root"; +import { ICalendarPayload, generateCalendarData } from "components/issues"; + +export interface ICalendarStore { + calendarFilters: { + activeMonthDate: Date; + activeWeekDate: Date; + }; + calendarPayload: ICalendarPayload | null; + + // action + updateCalendarFilters: (filters: Partial<{ activeMonthDate: Date; activeWeekDate: Date }>) => void; + updateCalendarPayload: (date: Date) => void; +} + +class CalendarStore implements ICalendarStore { + loader: boolean = false; + error: any | null = null; + + // observables + calendarFilters: { activeMonthDate: Date; activeWeekDate: Date } = { + activeMonthDate: new Date(), + activeWeekDate: new Date(), + }; + calendarPayload: ICalendarPayload | null = null; + + // root store + rootStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + loader: observable.ref, + error: observable.ref, + + // observables + calendarFilters: observable.ref, + calendarPayload: observable.ref, + + // actions + updateCalendarFilters: action, + updateCalendarPayload: action, + }); + + this.rootStore = _rootStore; + + this.initCalendar(); + } + + updateCalendarFilters = (filters: Partial<{ activeMonthDate: Date; activeWeekDate: Date }>) => { + this.updateCalendarPayload(filters.activeMonthDate || filters.activeWeekDate || new Date()); + + runInAction(() => { + this.calendarFilters = { + ...this.calendarFilters, + ...filters, + }; + }); + }; + + updateCalendarPayload = (date: Date) => { + if (!this.calendarPayload) return null; + + const nextDate = new Date(date); + + runInAction(() => { + this.calendarPayload = generateCalendarData(this.calendarPayload, nextDate); + }); + }; + + initCalendar = () => { + const newCalendarPayload = generateCalendarData(null, new Date()); + + runInAction(() => { + this.calendarPayload = newCalendarPayload; + }); + }; +} + +export default CalendarStore; diff --git a/web/store/root.ts b/web/store/root.ts index 2ec0099d3e9..8d40d55d6dd 100644 --- a/web/store/root.ts +++ b/web/store/root.ts @@ -14,6 +14,7 @@ import ViewStore, { IViewStore } from "./views"; import IssueFilterStore, { IIssueFilterStore } from "./issue_filters"; import IssueViewDetailStore from "./issue_detail"; import IssueKanBanViewStore from "./kanban_view"; +import CalendarStore, { ICalendarStore } from "./calendar"; enableStaticRendering(typeof window === "undefined"); @@ -31,6 +32,7 @@ export class RootStore { issueFilter: IIssueFilterStore; issueDetail: IssueViewDetailStore; issueKanBanView: IssueKanBanViewStore; + calendar: ICalendarStore; constructor() { this.user = new UserStore(this); @@ -46,5 +48,6 @@ export class RootStore { this.issueDetail = new IssueViewDetailStore(this); this.issueKanBanView = new IssueKanBanViewStore(this); this.draftIssuesStore = new DraftIssuesStore(this); + this.calendar = new CalendarStore(this); } } From e367104b44029e7afad2c15f4813e54ba012f78d Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 28 Sep 2023 13:29:10 +0530 Subject: [PATCH 08/11] refactor: issues fetching structure --- .../issue-layouts/calendar/calendar.tsx | 40 +++++++--------- .../issue-layouts/calendar/day-tile.tsx | 24 ++++++---- .../issues/issue-layouts/calendar/header.tsx | 30 ++++++++++-- .../issues/issue-layouts/calendar/index.ts | 2 +- .../issue-layouts/calendar/issue-blocks.tsx | 48 +++++++++++-------- .../issues/issue-layouts/calendar/root.tsx | 18 ++++--- .../issues/issue-layouts/calendar/types.d.ts | 4 +- .../issue-layouts/calendar/week-days.tsx | 10 ++-- web/helpers/calendar.helper.ts | 16 +++---- web/helpers/date-time.helper.ts | 16 +++---- web/store/calendar.ts | 43 ++++++++++++++++- 11 files changed, 166 insertions(+), 85 deletions(-) diff --git a/web/components/issues/issue-layouts/calendar/calendar.tsx b/web/components/issues/issue-layouts/calendar/calendar.tsx index ade3f7429c7..3d13455e2cc 100644 --- a/web/components/issues/issue-layouts/calendar/calendar.tsx +++ b/web/components/issues/issue-layouts/calendar/calendar.tsx @@ -6,54 +6,48 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components/issues"; // ui import { Spinner } from "components/ui"; -// helpers -import { getWeekNumberOfDate } from "helpers/date-time.helper"; // types import { ICalendarWeek } from "./types"; +import { IIssueGroupedStructure } from "store/issue"; -export const CalendarChart: React.FC = observer(() => { - const { issue: issueStore, issueFilter: issueFilterStore, calendar: calendarStore } = useMobxStore(); +type Props = { + issues: IIssueGroupedStructure | null; +}; - const activeMonthDate = calendarStore.calendarFilters.activeMonthDate; - const activeWeekDate = calendarStore.calendarFilters.activeWeekDate; +export const CalendarChart: React.FC = observer((props) => { + const { issues } = props; + + const { calendar: calendarStore, issueFilter: issueFilterStore } = useMobxStore(); const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; const calendarPayload = calendarStore.calendarPayload; - if (!calendarPayload || !issueStore.getIssues) + const allWeeksOfActiveMonth = calendarStore.allWeeksOfActiveMonth; + + console.log("calendarPayload", calendarPayload); + + if (!calendarPayload) return (
); - console.log("calendarPayload", calendarPayload); - - // console.log("activeMonthDate", activeMonthDate); - // console.log("activeWeekDate", activeWeekDate); - - const activeWeekNumber = getWeekNumberOfDate(activeWeekDate); - - // console.log("activeWeekNumber", activeWeekNumber); - - const activeMonthAllWeeks = calendarPayload[activeMonthDate.getFullYear()][activeMonthDate.getMonth()]; - const activeWeekAllDays = calendarPayload[activeWeekDate.getFullYear()][activeWeekDate.getMonth()][activeWeekNumber]; - return ( <>
- {activeMonthAllWeeks ? ( + {allWeeksOfActiveMonth ? (
{calendarLayout === "month" ? (
- {Object.values(activeMonthAllWeeks).map((week: ICalendarWeek, weekIndex) => ( - + {Object.values(allWeeksOfActiveMonth).map((week: ICalendarWeek, weekIndex) => ( + ))}
) : ( - + )}
) : ( diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx index e4a2390803c..7f49e843191 100644 --- a/web/components/issues/issue-layouts/calendar/day-tile.tsx +++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx @@ -12,24 +12,22 @@ import { IIssueGroupedStructure } from "store/issue"; // constants import { MONTHS_LIST } from "constants/calendar"; -type Props = { date: ICalendarDate }; +type Props = { date: ICalendarDate; issues: IIssueGroupedStructure | null }; export const CalendarDayTile: React.FC = observer((props) => { - const { date } = props; + const { date, issues } = props; - const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore(); + const { issueFilter: issueFilterStore } = useMobxStore(); const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; - const issues = issueStore.getIssues - ? (issueStore.getIssues as IIssueGroupedStructure)[renderDateFormat(date.date)] - : null; + const issuesList = issues ? (issues as IIssueGroupedStructure)[renderDateFormat(date.date)] : null; return ( {(provided, snapshot) => (
= observer((props) => { ref={provided.innerRef} > <> -
+
{date.date.getDate() === 1 && MONTHS_LIST[date.date.getMonth() + 1].shortTitle + " "} {date.date.getDate()}
- + {provided.placeholder}
diff --git a/web/components/issues/issue-layouts/calendar/header.tsx b/web/components/issues/issue-layouts/calendar/header.tsx index c42090ca006..77c23013ae5 100644 --- a/web/components/issues/issue-layouts/calendar/header.tsx +++ b/web/components/issues/issue-layouts/calendar/header.tsx @@ -13,8 +13,31 @@ export const CalendarHeader: React.FC = observer(() => { const { issueFilter: issueFilterStore, calendar: calendarStore } = useMobxStore(); const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; - const activeMonthDate = calendarStore.calendarFilters.activeMonthDate; - const activeWeekDate = calendarStore.calendarFilters.activeWeekDate; + + const { activeMonthDate, activeWeekDate } = calendarStore.calendarFilters; + + const getWeekLayoutHeader = (): string => { + const allDaysOfActiveWeek = calendarStore.allDaysOfActiveWeek; + + if (!allDaysOfActiveWeek) return "Week view"; + + const daysList = Object.keys(allDaysOfActiveWeek); + + const firstDay = new Date(daysList[0]); + const lastDay = new Date(daysList[daysList.length - 1]); + + if (firstDay.getMonth() === lastDay.getMonth() && firstDay.getFullYear() === lastDay.getFullYear()) + return `${MONTHS_LIST[firstDay.getMonth() + 1].shortTitle} ${firstDay.getFullYear()}`; + + if (firstDay.getFullYear() !== lastDay.getFullYear()) { + return `${MONTHS_LIST[firstDay.getMonth() + 1].shortTitle} ${firstDay.getFullYear()} - ${ + MONTHS_LIST[lastDay.getMonth() + 1].shortTitle + } ${lastDay.getFullYear()}`; + } else + return `${MONTHS_LIST[firstDay.getMonth() + 1].shortTitle} - ${ + MONTHS_LIST[lastDay.getMonth() + 1].shortTitle + } ${lastDay.getFullYear()}`; + }; const handlePrevious = () => { if (calendarLayout === "month") { @@ -70,6 +93,7 @@ export const CalendarHeader: React.FC = observer(() => { calendarStore.updateCalendarFilters({ activeMonthDate: firstDayOfCurrentMonth, + activeWeekDate: today, }); }; @@ -82,7 +106,7 @@ export const CalendarHeader: React.FC = observer(() => {

{calendarLayout === "month" ? `${MONTHS_LIST[activeMonthDate.getMonth() + 1].title} ${activeMonthDate.getFullYear()}` - : "Week view"} + : getWeekLayoutHeader()}

+ {activeMonthDate.getFullYear()} + +
+
+ {Object.values(MONTHS_LIST).map((month, index) => ( + + ))} +
+
+ + + + )} + + ); +}); diff --git a/web/components/issues/issue-layouts/calendar/options-dropdown.tsx b/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx similarity index 97% rename from web/components/issues/issue-layouts/calendar/options-dropdown.tsx rename to web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx index b27aa12424f..fe14e32f63c 100644 --- a/web/components/issues/issue-layouts/calendar/options-dropdown.tsx +++ b/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx @@ -1,20 +1,20 @@ import React from "react"; +import { useRouter } from "next/router"; import { Popover, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// ui +import { ToggleSwitch } from "components/ui"; // icons import { Check, ChevronUp } from "lucide-react"; -import { useMobxStore } from "lib/mobx/store-provider"; -import { CALENDAR_LAYOUTS } from "constants/calendar"; +// types import { TCalendarLayouts } from "types"; -import { useRouter } from "next/router"; -import { ToggleSwitch } from "components/ui"; - -type Props = {}; - -export const CalendarOptionsDropdown: React.FC = observer((props) => { - const {} = props; +// constants +import { CALENDAR_LAYOUTS } from "constants/calendar"; +export const CalendarOptionsDropdown: React.FC = observer(() => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; diff --git a/web/components/issues/issue-layouts/calendar/header.tsx b/web/components/issues/issue-layouts/calendar/header.tsx index 77c23013ae5..363719920ca 100644 --- a/web/components/issues/issue-layouts/calendar/header.tsx +++ b/web/components/issues/issue-layouts/calendar/header.tsx @@ -3,11 +3,9 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { CalendarOptionsDropdown } from "components/issues"; +import { CalendarMonthsDropdown, CalendarOptionsDropdown } from "components/issues"; // icons import { ChevronLeft, ChevronRight } from "lucide-react"; -// constants -import { MONTHS_LIST } from "constants/calendar"; export const CalendarHeader: React.FC = observer(() => { const { issueFilter: issueFilterStore, calendar: calendarStore } = useMobxStore(); @@ -16,29 +14,6 @@ export const CalendarHeader: React.FC = observer(() => { const { activeMonthDate, activeWeekDate } = calendarStore.calendarFilters; - const getWeekLayoutHeader = (): string => { - const allDaysOfActiveWeek = calendarStore.allDaysOfActiveWeek; - - if (!allDaysOfActiveWeek) return "Week view"; - - const daysList = Object.keys(allDaysOfActiveWeek); - - const firstDay = new Date(daysList[0]); - const lastDay = new Date(daysList[daysList.length - 1]); - - if (firstDay.getMonth() === lastDay.getMonth() && firstDay.getFullYear() === lastDay.getFullYear()) - return `${MONTHS_LIST[firstDay.getMonth() + 1].shortTitle} ${firstDay.getFullYear()}`; - - if (firstDay.getFullYear() !== lastDay.getFullYear()) { - return `${MONTHS_LIST[firstDay.getMonth() + 1].shortTitle} ${firstDay.getFullYear()} - ${ - MONTHS_LIST[lastDay.getMonth() + 1].shortTitle - } ${lastDay.getFullYear()}`; - } else - return `${MONTHS_LIST[firstDay.getMonth() + 1].shortTitle} - ${ - MONTHS_LIST[lastDay.getMonth() + 1].shortTitle - } ${lastDay.getFullYear()}`; - }; - const handlePrevious = () => { if (calendarLayout === "month") { const previousMonthYear = @@ -103,14 +78,10 @@ export const CalendarHeader: React.FC = observer(() => { -

- {calendarLayout === "month" - ? `${MONTHS_LIST[activeMonthDate.getMonth() + 1].title} ${activeMonthDate.getFullYear()}` - : getWeekLayoutHeader()} -

+