From 3bf590b67edaa45e8f742039523d7828fee4826f Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:16:24 +0530 Subject: [PATCH] dev: calendar view layout revamp (#2293) * dev: calendar view init * chore: new render logic * chore: implement calendar view * chore: calendar view * refactor: calendar payload * chore: remove active month logic from backend * chore: setup new store for calendar * refactor: issues fetching structure * chore: months dropdown * chore: modify request query params for calendar layout * refactor: remove console logs and add comments --- .../scope-and-demand/year-wise-issues.tsx | 13 +- web/components/core/views/all-views.tsx | 145 +--------------- .../filters/filter-selection.tsx | 71 -------- .../filters/filters-selection.tsx | 2 +- .../issue-layouts/layout-selection.tsx | 3 +- web/components/issues/index.ts | 1 + .../issues/issue-layouts/calandar/index.ts | 1 - .../issues/issue-layouts/calandar/root.tsx | 17 -- .../issue-layouts/calendar/calendar.tsx | 54 ++++++ .../issue-layouts/calendar/day-tile.tsx | 58 +++++++ .../issue-layouts/calendar/dropdowns/index.ts | 2 + .../calendar/dropdowns/months-dropdown.tsx | 120 +++++++++++++ .../calendar/dropdowns/options-dropdown.tsx | 117 +++++++++++++ .../issues/issue-layouts/calendar/header.tsx | 98 +++++++++++ .../issues/issue-layouts/calendar/index.ts | 9 + .../issue-layouts/calendar/issue-blocks.tsx | 48 ++++++ .../issues/issue-layouts/calendar/root.tsx | 36 ++++ .../issues/issue-layouts/calendar/types.d.ts | 24 +++ .../issue-layouts/calendar/week-days.tsx | 41 +++++ .../issue-layouts/calendar/week-header.tsx | 30 ++++ web/components/issues/issue-layouts/index.ts | 2 +- web/constants/calendar.ts | 129 +++++++++++--- web/constants/issue.ts | 1 - web/contexts/issue-view.context.tsx | 44 +---- web/helpers/calendar.helper.ts | 161 +++++------------- web/helpers/date-time.helper.ts | 90 +++++----- web/helpers/issue.helper.ts | 1 - web/hooks/use-calendar-issues-view.tsx | 1 - web/pages/calendar.tsx | 19 +++ web/store/calendar.ts | 121 +++++++++++++ web/store/issue.ts | 10 +- web/store/issue_filters.ts | 26 ++- web/store/root.ts | 3 + web/types/view-props.d.ts | 8 +- 34 files changed, 1042 insertions(+), 464 deletions(-) delete mode 100644 web/components/issue-layouts/filters/filter-selection.tsx delete mode 100644 web/components/issues/issue-layouts/calandar/index.ts delete mode 100644 web/components/issues/issue-layouts/calandar/root.tsx 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/dropdowns/index.ts create mode 100644 web/components/issues/issue-layouts/calendar/dropdowns/months-dropdown.tsx create mode 100644 web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx create mode 100644 web/components/issues/issue-layouts/calendar/header.tsx create mode 100644 web/components/issues/issue-layouts/calendar/index.ts create mode 100644 web/components/issues/issue-layouts/calendar/issue-blocks.tsx create mode 100644 web/components/issues/issue-layouts/calendar/root.tsx create mode 100644 web/components/issues/issue-layouts/calendar/types.d.ts create mode 100644 web/components/issues/issue-layouts/calendar/week-days.tsx create mode 100644 web/components/issues/issue-layouts/calendar/week-header.tsx create mode 100644 web/pages/calendar.tsx create mode 100644 web/store/calendar.ts 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 87127ed60..7dedee31c 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 a75ac67d4..5f35a64f7 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 @@ -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,121 +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/issue-layouts/filters/filter-selection.tsx b/web/components/issue-layouts/filters/filter-selection.tsx deleted file mode 100644 index 5e20d384a..000000000 --- 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 c0416e356..71aa144bf 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) => { return (
{ISSUE_LAYOUTS.filter((l) => layouts.includes(l.key)).map((layout) => ( - + + {activeMonthDate.getFullYear()} + +
+
+ {Object.values(MONTHS_LIST).map((month, index) => ( + + ))} +
+
+ + + + )} + + ); +}); diff --git a/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx b/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx new file mode 100644 index 000000000..fe14e32f6 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx @@ -0,0 +1,117 @@ +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"; +// types +import { TCalendarLayouts } from "types"; +// constants +import { CALENDAR_LAYOUTS } from "constants/calendar"; + +export const CalendarOptionsDropdown: React.FC = observer(() => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { issueFilter: issueFilterStore, calendar: calendarStore } = 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, + }, + }, + }); + + calendarStore.updateCalendarPayload( + layout === "month" ? calendarStore.calendarFilters.activeMonthDate : calendarStore.calendarFilters.activeWeekDate + ); + }; + + 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 }) => { + if (open) { + } + return ( + <> + +
Options
+
+ +
+
+ + +
+
+ {Object.entries(CALENDAR_LAYOUTS).map(([layout, layoutDetails]) => ( + + ))} + +
+
+
+
+ + ); + }} +
+ ); +}); 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 000000000..363719920 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/header.tsx @@ -0,0 +1,98 @@ +import { observer } from "mobx-react-lite"; + +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { CalendarMonthsDropdown, CalendarOptionsDropdown } from "components/issues"; +// icons +import { ChevronLeft, ChevronRight } from "lucide-react"; + +export const CalendarHeader: React.FC = observer(() => { + const { issueFilter: issueFilterStore, calendar: calendarStore } = useMobxStore(); + + const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; + + const { activeMonthDate, activeWeekDate } = calendarStore.calendarFilters; + + 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 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); + + calendarStore.updateCalendarFilters({ + activeMonthDate: firstDayOfCurrentMonth, + activeWeekDate: today, + }); + }; + + return ( +
+
+ + + +
+
+ + +
+
+ ); +}); 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 000000000..0ff8fe1d1 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/index.ts @@ -0,0 +1,9 @@ +export * from "./dropdowns"; +export * from "./calendar"; +export * from "./types.d"; +export * from "./day-tile"; +export * from "./header"; +export * from "./issue-blocks"; +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 new file mode 100644 index 000000000..2205ce8d8 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -0,0 +1,48 @@ +import Link from "next/link"; +import { useRouter } from "next/router"; +import { Draggable } from "@hello-pangea/dnd"; +import { observer } from "mobx-react-lite"; + +// types +import { IIssue } from "types"; + +type Props = { issues: IIssue[] | null }; + +export const CalendarIssueBlocks: React.FC = observer((props) => { + const { issues } = props; + + const router = useRouter(); + const { workspaceSlug } = router.query; + + 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 new file mode 100644 index 000000000..537836f89 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/root.tsx @@ -0,0 +1,36 @@ +import { observer } from "mobx-react-lite"; +import { DragDropContext, DropResult } from "@hello-pangea/dnd"; + +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { CalendarChart } from "components/issues"; +// types +import { IIssueGroupedStructure } from "store/issue"; + +export const CalendarLayout: React.FC = observer(() => { + const { issue: issueStore } = useMobxStore(); + + // TODO: add drag and drop functionality + const onDragEnd = (result: DropResult) => { + if (!result) return; + + // return if not dropped on the correct place + if (!result.destination) return; + + // return if dropped on the same date + if (result.destination.droppableId === result.source.droppableId) return; + + // issueKanBanViewStore?.handleDragDrop(result.source, result.destination); + }; + + const issues = issueStore.getIssues; + + return ( +
+ + + +
+ ); +}); 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 000000000..3855aeb86 --- /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 { + [monthIndex: string]: { + [weekNumber: string]: ICalendarWeek; + }; +} + +export interface ICalendarPayload { + [year: string]: ICalendarMonth; +} 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 000000000..9041ee91b --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/week-days.tsx @@ -0,0 +1,41 @@ +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 "./types"; +import { IIssueGroupedStructure } from "store/issue"; + +type Props = { + issues: IIssueGroupedStructure | null; + week: ICalendarWeek | undefined; +}; + +export const CalendarWeekDays: React.FC = observer((props) => { + const { issues, week } = props; + + const { issueFilter: issueFilterStore } = useMobxStore(); + + const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month"; + const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; + + if (!week) return null; + + return ( +
+ {Object.values(week).map((date: ICalendarDate) => { + if (!showWeekends && (date.date.getDay() === 0 || date.date.getDay() === 6)) return null; + + return ; + })} +
+ ); +}); 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 000000000..eb6b9ae46 --- /dev/null +++ b/web/components/issues/issue-layouts/calendar/week-header.tsx @@ -0,0 +1,30 @@ +import { observer } from "mobx-react-lite"; + +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// constants +import { DAYS_LIST } from "constants/calendar"; + +export const CalendarWeekHeader: React.FC = observer(() => { + const { issueFilter: issueFilterStore } = useMobxStore(); + + const showWeekends = issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false; + + return ( +
+ {Object.values(DAYS_LIST).map((day) => { + if (!showWeekends && (day.shortTitle === "Sat" || day.shortTitle === "Sun")) return null; + + return ( +
+ {day.shortTitle} +
+ ); + })} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts index f8337cc77..30249be79 100644 --- a/web/components/issues/issue-layouts/index.ts +++ b/web/components/issues/issue-layouts/index.ts @@ -1,2 +1,2 @@ +export * from "./calendar"; export * from "./kanban"; -export * from "./calandar"; diff --git a/web/constants/calendar.ts b/web/constants/calendar.ts index 5f1f74254..4c4070ec1 100644 --- a/web/constants/calendar.ts +++ b/web/constants/calendar.ts @@ -1,22 +1,109 @@ -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" }, -]; +import { TCalendarLayouts } from "types"; -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 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 DAYS_LIST: { + [dayIndex: number]: { + shortTitle: string; + title: string; + }; +} = { + 1: { + shortTitle: "Sun", + title: "Sunday", + }, + 2: { + shortTitle: "Mon", + title: "Monday", + }, + 3: { + shortTitle: "Tue", + title: "Tuesday", + }, + 4: { + shortTitle: "Wed", + title: "Wednesday", + }, + 5: { + shortTitle: "Thu", + title: "Thursday", + }, + 6: { + shortTitle: "Fri", + title: "Friday", + }, + 7: { + shortTitle: "Sat", + title: "Saturday", + }, +}; + +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/constants/issue.ts b/web/constants/issue.ts index eaec04999..52fc6c4db 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 786d60413..9ac698a6a 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/calendar.helper.ts b/web/helpers/calendar.helper.ts index ea553e170..b00668894 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; -}; +/** + * @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 ?? {}; -export const addMonths = (date: Date, numMonths: number) => { - const result = new Date(date); - result.setMonth(result.getMonth() + numMonths); - return result; -}; + const startMonth = startDate.getMonth(); + const startYear = startDate.getFullYear(); -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); -}; + const currentDate = new Date(startYear, startMonth, 1); + const year = currentDate.getFullYear(); + const month = currentDate.getMonth(); + const totalDaysInMonth = new Date(year, month + 1, 0).getDate(); + const firstDayOfMonth = new Date(year, month, 1).getDay(); // Sunday is 0, Monday is 1, ..., Saturday is 6 -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); -}; + calendarData[`y-${year}`] ||= {}; + calendarData[`y-${year}`][`m-${month}`] ||= {}; -export const isSameMonth = (monthString: string, date: Date) => { - const month = parseInt(monthString) - 1; - return month === date.getMonth(); -}; + const numWeeks = Math.ceil((totalDaysInMonth + firstDayOfMonth) / 7); -export const isSameYear = (yearString: string, date: Date) => { - const year = parseInt(yearString); - return year === date.getFullYear(); -}; + for (let week = 0; week < numWeeks; week++) { + const currentWeekObject: { [date: string]: ICalendarDate } = {}; -export const addSevenDaysToDate = (date: Date) => { - const currentDate = new Date(date); - const newDate = new Date(currentDate.setDate(currentDate.getDate() + 7)); - return newDate; -}; + const weekNumber = getWeekNumberOfDate(new Date(year, month, week * 7 - firstDayOfMonth + 1)); -export const subtract7DaysToDate = (date: Date) => { - const currentDate = new Date(date); - const newDate = new Date(currentDate.getTime() - 7 * 24 * 60 * 60 * 1000); - return newDate; + for (let i = 0; i < 7; i++) { + const dayNumber = week * 7 + i - firstDayOfMonth; + + 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(), + }; + } + + calendarData[`y-${year}`][`m-${month}`][`w-${weekNumber}`] = currentWeekObject; + } + + return calendarData; }; diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index 6784a9aa6..08dff4a18 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 currentDate = new Date(date); + + // Adjust the starting day to Sunday (0) instead of Monday (1) + const startDate = new Date(currentDate.getFullYear(), 0, 1); + + // Calculate the number of days between currentDate and startDate + const days = Math.floor((currentDate.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1000)); + + // Adjust the calculation for weekNumber + const weekNumber = Math.ceil((days + 1) / 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/helpers/issue.helper.ts b/web/helpers/issue.helper.ts index eab6f8a47..5cb78bdc8 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 21f8a4199..b32dcfc3c 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/pages/calendar.tsx b/web/pages/calendar.tsx new file mode 100644 index 000000000..bddca1f6f --- /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; diff --git a/web/store/calendar.ts b/web/store/calendar.ts new file mode 100644 index 000000000..3d4f5cfdd --- /dev/null +++ b/web/store/calendar.ts @@ -0,0 +1,121 @@ +import { observable, action, makeObservable, runInAction, computed } from "mobx"; + +// helpers +import { generateCalendarData } from "helpers/calendar.helper"; +// types +import { RootStore } from "./root"; +import { ICalendarPayload, ICalendarWeek } from "components/issues"; +import { getWeekNumberOfDate } from "helpers/date-time.helper"; + +export interface ICalendarStore { + calendarFilters: { + activeMonthDate: Date; + activeWeekDate: Date; + }; + calendarPayload: ICalendarPayload | null; + + // action + updateCalendarFilters: (filters: Partial<{ activeMonthDate: Date; activeWeekDate: Date }>) => void; + updateCalendarPayload: (date: Date) => void; + + // computed + allWeeksOfActiveMonth: + | { + [weekNumber: string]: ICalendarWeek; + } + | undefined; + activeWeekNumber: number; + allDaysOfActiveWeek: ICalendarWeek | undefined; +} + +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, + + //computed + allWeeksOfActiveMonth: computed, + activeWeekNumber: computed, + allDaysOfActiveWeek: computed, + }); + + this.rootStore = _rootStore; + + this.initCalendar(); + } + + get allWeeksOfActiveMonth() { + if (!this.calendarPayload) return undefined; + + const { activeMonthDate } = this.calendarFilters; + + return this.calendarPayload[`y-${activeMonthDate.getFullYear()}`][`m-${activeMonthDate.getMonth()}`]; + } + + get activeWeekNumber() { + return getWeekNumberOfDate(this.calendarFilters.activeWeekDate); + } + + get allDaysOfActiveWeek() { + if (!this.calendarPayload) return undefined; + + const { activeWeekDate } = this.calendarFilters; + + return this.calendarPayload[`y-${activeWeekDate.getFullYear()}`][`m-${activeWeekDate.getMonth()}`][ + `w-${this.activeWeekNumber}` + ]; + } + + 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/issue.ts b/web/store/issue.ts index 00c3fa46a..a8577ebc9 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; diff --git a/web/store/issue_filters.ts b/web/store/issue_filters.ts index c8407a1ec..cfecd674f 100644 --- a/web/store/issue_filters.ts +++ b/web/store/issue_filters.ts @@ -4,6 +4,7 @@ import { ProjectService } from "services/project.service"; import { IssueService } from "services/issue.service"; // helpers import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +import { renderDateFormat } from "helpers/date-time.helper"; // types import { RootStore } from "./root"; import { @@ -25,15 +26,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; @@ -116,6 +117,22 @@ class IssueFilterStore implements IIssueFilterStore { return computedFilters; }; + calendarLayoutDateRange = () => { + const { activeMonthDate, activeWeekDate } = this.rootStore.calendar.calendarFilters; + + const calendarLayout = this.userDisplayFilters.calendar?.layout ?? "month"; + + let filterDate = new Date(); + + if (calendarLayout === "month") filterDate = activeMonthDate; + else filterDate = activeWeekDate; + + const startOfMonth = renderDateFormat(new Date(filterDate.getFullYear(), filterDate.getMonth(), 1)); + const endOfMonth = renderDateFormat(new Date(filterDate.getFullYear(), filterDate.getMonth() + 1, 0)); + + return [`${startOfMonth};after`, `${endOfMonth};before`]; + }; + get appliedFilters(): TIssueParams[] | null { if ( !this.userFilters || @@ -140,10 +157,11 @@ 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, }; + if (this.userDisplayFilters.layout === "calendar") filteredRouteParams.target_date = this.calendarLayoutDateRange(); + const filteredParams = handleIssueQueryParamsByLayout(this.userDisplayFilters.layout); if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); diff --git a/web/store/root.ts b/web/store/root.ts index 2ec0099d3..8d40d55d6 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); } } diff --git a/web/types/view-props.d.ts b/web/types/view-props.d.ts index 6630d527e..c142ec0c6 100644 --- a/web/types/view-props.d.ts +++ b/web/types/view-props.d.ts @@ -45,9 +45,10 @@ export type TIssueParams = | "type" | "sub_issue" | "show_empty_groups" - | "calendar_date_range" | "start_target_date"; +export type TCalendarLayouts = "month" | "week"; + export interface IIssueFilterOptions { assignees?: string[] | null; created_by?: string[] | null; @@ -61,7 +62,10 @@ export interface IIssueFilterOptions { } export interface IIssueDisplayFilterOptions { - calendar_date_range?: string; + calendar?: { + show_weekends?: boolean; + layout?: TCalendarLayouts; + }; group_by?: TIssueGroupByOptions; sub_group_by?: TIssueGroupByOptions; layout?: TIssueLayouts;