From c7deb00f2ab2f580e6627f10516fa7e4bf45985a Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 11 May 2023 02:27:14 +0530 Subject: [PATCH] feat: calendar view display properties (#1024) * fix: calendar mutation fix, chore: calendar type added * feat: calendar view display property added * feat: calendar header, single date and single issue component added, chore: code refactor * chore: partialupdateissue function updated * fix: dropdown overflow fix, style: issue card styling * fix: calendar weekly view row height fix * feat: calendar issue card ellipsis added, fix: edit and delete mutation fix * style: plus icon , add issue button padding and onhover effect fix * style: calendar issue card * style: weekly view height --- .../core/board-view/single-issue.tsx | 17 +- .../core/calendar-view/calendar-header.tsx | 217 +++++++++ .../core/calendar-view/calendar.tsx | 438 ++++-------------- .../components/core/calendar-view/index.ts | 5 +- .../core/calendar-view/single-date.tsx | 107 +++++ .../core/calendar-view/single-issue.tsx | 262 +++++++++++ apps/app/components/core/index.ts | 1 + .../components/core/issues-view-filter.tsx | 45 +- apps/app/components/core/issues-view.tsx | 11 +- .../core/list-view/single-issue.tsx | 17 +- .../components/issues/delete-issue-modal.tsx | 26 +- apps/app/components/issues/modal.tsx | 36 +- .../components/issues/my-issues-list-item.tsx | 8 +- .../issues/view-select/assignee.tsx | 4 +- .../issues/view-select/due-date.tsx | 15 +- .../issues/view-select/estimate.tsx | 4 +- .../issues/view-select/priority.tsx | 4 +- .../components/issues/view-select/state.tsx | 15 +- apps/app/types/calendar.ts | 4 + apps/app/types/index.d.ts | 3 +- 20 files changed, 803 insertions(+), 436 deletions(-) create mode 100644 apps/app/components/core/calendar-view/calendar-header.tsx create mode 100644 apps/app/components/core/calendar-view/single-date.tsx create mode 100644 apps/app/components/core/calendar-view/single-issue.tsx create mode 100644 apps/app/types/calendar.ts diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index e82b9897c..926f0e81a 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -98,7 +98,7 @@ export const SingleBoardIssue: React.FC = ({ const { setToastAlert } = useToast(); const partialUpdateIssue = useCallback( - (formData: Partial) => { + (formData: Partial, issueId: string) => { if (!workspaceSlug || !projectId) return; if (cycleId) @@ -164,7 +164,7 @@ export const SingleBoardIssue: React.FC = ({ } issuesService - .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) + .patchIssue(workspaceSlug as string, projectId as string, issueId, formData) .then(() => { if (cycleId) { mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); @@ -178,18 +178,7 @@ export const SingleBoardIssue: React.FC = ({ console.log(error); }); }, - [ - workspaceSlug, - projectId, - cycleId, - moduleId, - issue, - groupTitle, - index, - selectedGroup, - orderBy, - params, - ] + [workspaceSlug, projectId, cycleId, moduleId, groupTitle, index, selectedGroup, orderBy, params] ); const getStyle = ( diff --git a/apps/app/components/core/calendar-view/calendar-header.tsx b/apps/app/components/core/calendar-view/calendar-header.tsx new file mode 100644 index 000000000..b25dffb90 --- /dev/null +++ b/apps/app/components/core/calendar-view/calendar-header.tsx @@ -0,0 +1,217 @@ +import { Popover, Transition } from "@headlessui/react"; +import { + addMonths, + addSevenDaysToDate, + formatDate, + getCurrentWeekEndDate, + getCurrentWeekStartDate, + isSameMonth, + isSameYear, + lastDayOfWeek, + startOfWeek, + subtract7DaysToDate, + subtractMonths, + updateDateWithMonth, + updateDateWithYear, +} from "helpers/calendar.helper"; +import React from "react"; +import { monthOptions, yearOptions } from "constants/calendar"; + +import { ICalendarRange } from "types"; +import { + CheckIcon, + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "@heroicons/react/24/outline"; +import { CustomMenu, ToggleSwitch } from "components/ui"; + +type Props = { + isMonthlyView: boolean; + setIsMonthlyView: React.Dispatch>; + currentDate: Date; + setCurrentDate: React.Dispatch>; + setCalendarDateRange: React.Dispatch>; + showWeekEnds: boolean; + setShowWeekEnds: React.Dispatch>; +}; + +export const CalendarHeader: React.FC = ({ + setIsMonthlyView, + isMonthlyView, + currentDate, + setCurrentDate, + setCalendarDateRange, + showWeekEnds, + setShowWeekEnds, +}) => { + const updateDate = (date: Date) => { + setCurrentDate(date); + + setCalendarDateRange({ + startDate: startOfWeek(date), + endDate: lastDayOfWeek(date), + }); + }; + return ( +
+
+ + {({ open }) => ( + <> + +
+ {formatDate(currentDate, "Month")}{" "} + {formatDate(currentDate, "yyyy")} +
+
+ + + +
+ {yearOptions.map((year) => ( + + ))} +
+
+ {monthOptions.map((month) => ( + + ))} +
+
+
+ + )} +
+ +
+ + +
+
+ +
+ + + + {isMonthlyView ? "Monthly" : "Weekly"} +
+ } + > + { + setIsMonthlyView(true); + setCalendarDateRange({ + startDate: startOfWeek(currentDate), + endDate: lastDayOfWeek(currentDate), + }); + }} + className="w-52 text-sm text-brand-secondary" + > +
+ Monthly View + +
+
+ { + setIsMonthlyView(false); + setCalendarDateRange({ + startDate: getCurrentWeekStartDate(currentDate), + endDate: getCurrentWeekEndDate(currentDate), + }); + }} + className="w-52 text-sm text-brand-secondary" + > +
+ Weekly View + +
+
+
+

Show weekends

+ setShowWeekEnds(!showWeekEnds)} /> +
+ +
+ + ); +}; + +export default CalendarHeader; diff --git a/apps/app/components/core/calendar-view/calendar.tsx b/apps/app/components/core/calendar-view/calendar.tsx index 9dfdac64e..bf32237d1 100644 --- a/apps/app/components/core/calendar-view/calendar.tsx +++ b/apps/app/components/core/calendar-view/calendar.tsx @@ -1,8 +1,27 @@ import React, { useState } from "react"; + +// swr import useSWR, { mutate } from "swr"; -import Link from "next/link"; + import { useRouter } from "next/router"; +// ui +import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd"; +import { SingleCalendarDate, CalendarHeader } from "components/core"; + +import { Spinner } from "components/ui"; +// hooks +import useIssuesView from "hooks/use-issues-view"; +// services +import issuesService from "services/issues.service"; +import cyclesService from "services/cycles.service"; +import modulesService from "services/modules.service"; +// fetch key +import { + CYCLE_CALENDAR_ISSUES, + MODULE_CALENDAR_ISSUES, + PROJECT_CALENDAR_ISSUES, +} from "constants/fetch-keys"; // helper import { renderDateFormat } from "helpers/date-time.helper"; import { @@ -11,62 +30,28 @@ import { eachDayOfInterval, weekDayInterval, formatDate, - getCurrentWeekStartDate, - getCurrentWeekEndDate, - subtractMonths, - addMonths, - updateDateWithYear, - updateDateWithMonth, - isSameMonth, - isSameYear, - subtract7DaysToDate, - addSevenDaysToDate, } from "helpers/calendar.helper"; -// ui -import { Popover, Transition } from "@headlessui/react"; -import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd"; -import StrictModeDroppable from "components/dnd/StrictModeDroppable"; -import { CustomMenu, Spinner, ToggleSwitch } from "components/ui"; -// icon -import { - CheckIcon, - ChevronDownIcon, - ChevronLeftIcon, - ChevronRightIcon, - PlusIcon, -} from "@heroicons/react/24/outline"; -// hooks -import useIssuesView from "hooks/use-issues-view"; -// services -import issuesService from "services/issues.service"; -import cyclesService from "services/cycles.service"; -// fetch key -import { - CYCLE_CALENDAR_ISSUES, - MODULE_CALENDAR_ISSUES, - PROJECT_CALENDAR_ISSUES, -} from "constants/fetch-keys"; // type -import { IIssue } from "types"; -// constant -import { monthOptions, yearOptions } from "constants/calendar"; -import modulesService from "services/modules.service"; -import { getStateGroupIcon } from "components/icons"; +import { ICalendarRange, IIssue, UserAuth } from "types"; type Props = { + handleEditIssue: (issue: IIssue) => void; + handleDeleteIssue: (issue: IIssue) => void; addIssueToDate: (date: string) => void; + isCompleted: boolean; + userAuth: UserAuth; }; -interface ICalendarRange { - startDate: Date; - endDate: Date; -} - -export const CalendarView: React.FC = ({ addIssueToDate }) => { +export const CalendarView: React.FC = ({ + handleEditIssue, + handleDeleteIssue, + addIssueToDate, + isCompleted = false, + userAuth, +}) => { const [showWeekEnds, setShowWeekEnds] = useState(false); const [currentDate, setCurrentDate] = useState(new Date()); const [isMonthlyView, setIsMonthlyView] = useState(true); - const [showAllIssues, setShowAllIssues] = useState(false); const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; @@ -78,12 +63,6 @@ export const CalendarView: React.FC = ({ addIssueToDate }) => { endDate: lastDayOfWeek(currentDate), }); - const targetDateFilter = { - target_date: `${renderDateFormat(calendarDateRange.startDate)};after,${renderDateFormat( - calendarDateRange.endDate - )};before`, - }; - const { data: projectCalendarIssues } = useSWR( workspaceSlug && projectId ? PROJECT_CALENDAR_ISSUES(projectId as string) : null, workspaceSlug && projectId @@ -218,304 +197,73 @@ export const CalendarView: React.FC = ({ addIssueToDate }) => { }); }; - const updateDate = (date: Date) => { - setCurrentDate(date); - - setCalendarDateRange({ - startDate: startOfWeek(date), - endDate: lastDayOfWeek(date), - }); - }; + const isNotAllowed = userAuth.isGuest || userAuth.isViewer || isCompleted; return calendarIssues ? ( - -
-
-
- - {({ open }) => ( - <> - -
- {formatDate(currentDate, "Month")}{" "} - {formatDate(currentDate, "yyyy")} -
-
+
+ +
+ - - -
- {yearOptions.map((year) => ( - - ))} -
-
- {monthOptions.map((month) => ( - - ))} -
-
-
- - )} - - -
- - -
-
- -
- - - - {isMonthlyView ? "Monthly" : "Weekly"} -
- } - > - { - setIsMonthlyView(true); - setCalendarDateRange({ - startDate: startOfWeek(currentDate), - endDate: lastDayOfWeek(currentDate), - }); - }} - className="w-52 text-sm text-brand-secondary" - > -
- Monthly View - -
-
- { - setIsMonthlyView(false); - setCalendarDateRange({ - startDate: getCurrentWeekStartDate(currentDate), - endDate: getCurrentWeekEndDate(currentDate), - }); - }} - className="w-52 text-sm text-brand-secondary" - > -
- Weekly View - -
-
-
-

Show weekends

- setShowWeekEnds(!showWeekEnds)} - /> -
- -
-
- -
- {weeks.map((date, index) => ( -
- - {isMonthlyView ? formatDate(date, "eee").substring(0, 3) : formatDate(date, "eee")} - - {!isMonthlyView && {formatDate(date, "d")}} -
- ))} -
- -
- {currentViewDaysData.map((date, index) => { - const totalIssues = date.issues.length; - - return ( - - {(provided) => ( -
+ {weeks.map((date, index) => ( +
- {isMonthlyView && {formatDate(new Date(date.date), "d")}} - {totalIssues > 0 && - date.issues - .slice(0, showAllIssues ? totalIssues : 4) - .map((issue: IIssue, index) => ( - - {(provided, snapshot) => ( - - )} - - ))} - {totalIssues > 4 && ( - - )} -
- -
- {provided.placeholder} -
- )} - - ); - })} + : (index + 1) % 5 === 0 + ? "" + : "border-r" + : "" + }`} + > + + {isMonthlyView + ? formatDate(date, "eee").substring(0, 3) + : formatDate(date, "eee")} + + {!isMonthlyView && {formatDate(date, "d")}} +
+ ))} +
+ +
+ {currentViewDaysData.map((date, index) => ( + + ))} +
-
-
+ + ) : (
diff --git a/apps/app/components/core/calendar-view/index.ts b/apps/app/components/core/calendar-view/index.ts index 55608c7e8..e3591e7b7 100644 --- a/apps/app/components/core/calendar-view/index.ts +++ b/apps/app/components/core/calendar-view/index.ts @@ -1 +1,4 @@ -export * from "./calendar" \ No newline at end of file +export * from "./calendar"; +export * from "./single-date"; +export * from "./single-issue"; +export * from "./calendar-header"; diff --git a/apps/app/components/core/calendar-view/single-date.tsx b/apps/app/components/core/calendar-view/single-date.tsx new file mode 100644 index 000000000..fd552188c --- /dev/null +++ b/apps/app/components/core/calendar-view/single-date.tsx @@ -0,0 +1,107 @@ +import React, { useState } from "react"; + +// react-beautiful-dnd +import { Draggable } from "react-beautiful-dnd"; +// component +import StrictModeDroppable from "components/dnd/StrictModeDroppable"; +import { SingleCalendarIssue } from "./single-issue"; +// icons +import { PlusSmallIcon } from "@heroicons/react/24/outline"; +// helper +import { formatDate } from "helpers/calendar.helper"; +// types +import { IIssue } from "types"; + +type Props = { + handleEditIssue: (issue: IIssue) => void; + handleDeleteIssue: (issue: IIssue) => void; + index: number; + date: { + date: string; + issues: IIssue[]; + }; + addIssueToDate: (date: string) => void; + isMonthlyView: boolean; + showWeekEnds: boolean; + isNotAllowed: boolean; +}; + +export const SingleCalendarDate: React.FC = ({ + handleEditIssue, + handleDeleteIssue, + date, + index, + addIssueToDate, + isMonthlyView, + showWeekEnds, + isNotAllowed, +}) => { + const [showAllIssues, setShowAllIssues] = useState(false); + + const totalIssues = date.issues.length; + + return ( + + {(provided) => ( +
+ {isMonthlyView && {formatDate(new Date(date.date), "d")}} + {totalIssues > 0 && + date.issues.slice(0, showAllIssues ? totalIssues : 4).map((issue: IIssue, index) => ( + + {(provided, snapshot) => ( + + )} + + ))} + {totalIssues > 4 && ( + + )} + +
+ +
+ + {provided.placeholder} +
+ )} +
+ ); +}; diff --git a/apps/app/components/core/calendar-view/single-issue.tsx b/apps/app/components/core/calendar-view/single-issue.tsx new file mode 100644 index 000000000..c9f8ecf57 --- /dev/null +++ b/apps/app/components/core/calendar-view/single-issue.tsx @@ -0,0 +1,262 @@ +import React, { useCallback } from "react"; + +import Link from "next/link"; +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + +// react-beautiful-dnd +import { DraggableProvided, DraggableStateSnapshot } from "react-beautiful-dnd"; +// services +import issuesService from "services/issues.service"; +// hooks +import useIssuesProperties from "hooks/use-issue-properties"; +import useToast from "hooks/use-toast"; +// components +import { CustomMenu, Tooltip } from "components/ui"; +import { + ViewAssigneeSelect, + ViewDueDateSelect, + ViewEstimateSelect, + ViewPrioritySelect, + ViewStateSelect, +} from "components/issues"; +// icons +import { LinkIcon, PaperClipIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; +// helper +import { copyTextToClipboard, truncateText } from "helpers/string.helper"; +// type +import { IIssue } from "types"; +// fetch-keys +import { + CYCLE_CALENDAR_ISSUES, + MODULE_CALENDAR_ISSUES, + PROJECT_CALENDAR_ISSUES, +} from "constants/fetch-keys"; + +type Props = { + handleEditIssue: (issue: IIssue) => void; + handleDeleteIssue: (issue: IIssue) => void; + index: number; + provided: DraggableProvided; + snapshot: DraggableStateSnapshot; + issue: IIssue; + isNotAllowed: boolean; +}; + +export const SingleCalendarIssue: React.FC = ({ + handleEditIssue, + handleDeleteIssue, + index, + provided, + snapshot, + issue, + isNotAllowed, +}) => { + const router = useRouter(); + const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + + const { setToastAlert } = useToast(); + + const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); + + const partialUpdateIssue = useCallback( + (formData: Partial, issueId: string) => { + if (!workspaceSlug || !projectId) return; + + const fetchKey = cycleId + ? CYCLE_CALENDAR_ISSUES(projectId as string, cycleId as string) + : moduleId + ? MODULE_CALENDAR_ISSUES(projectId as string, moduleId as string) + : PROJECT_CALENDAR_ISSUES(projectId as string); + + mutate( + fetchKey, + (prevData) => + (prevData ?? []).map((p) => { + if (p.id === issueId) + return { + ...p, + formData, + }; + return p; + }), + false + ); + + issuesService + .patchIssue(workspaceSlug as string, projectId as string, issueId as string, formData) + .then(() => { + mutate(fetchKey); + }) + .catch((error) => { + console.log(error); + }); + }, + [workspaceSlug, projectId, cycleId, moduleId] + ); + + const handleCopyText = () => { + const originURL = + typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + copyTextToClipboard( + `${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}` + ).then(() => { + setToastAlert({ + type: "success", + title: "Link Copied!", + message: "Issue link copied to clipboard.", + }); + }); + }; + + const displayProperties = properties + ? Object.values(properties).some((value) => value === true) + : false; + + return ( +
+
+ {!isNotAllowed && ( +
+ + handleEditIssue(issue)}> +
+ + Edit issue +
+
+ handleDeleteIssue(issue)}> +
+ + Delete issue +
+
+ +
+ + Copy issue Link +
+
+
+
+ )} + + + {properties.key && ( + + + {issue.project_detail?.identifier}-{issue.sequence_id} + + + )} + + {truncateText(issue.name, 25)} + + + + {displayProperties && ( +
+ {properties.priority && ( + + )} + {properties.state && ( + + )} + + {properties.due_date && ( + + )} + {properties.sub_issue_count && ( +
+ {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} +
+ )} + {properties.labels && issue.label_details.length > 0 ? ( +
+ {issue.label_details.map((label) => ( + + + {label.name} + + ))} +
+ ) : ( + "" + )} + {properties.assignee && ( + + )} + {properties.estimate && ( + + )} + + {properties.link && ( +
+ +
+ + {issue.link_count} +
+
+
+ )} + {properties.attachment_count && ( +
+ +
+ + {issue.attachment_count} +
+
+
+ )} +
+ )} +
+
+ ); +}; diff --git a/apps/app/components/core/index.ts b/apps/app/components/core/index.ts index 3ce807f98..f9c0cebdf 100644 --- a/apps/app/components/core/index.ts +++ b/apps/app/components/core/index.ts @@ -1,4 +1,5 @@ export * from "./board-view"; +export * from "./calendar-view"; export * from "./list-view"; export * from "./sidebar"; export * from "./bulk-delete-issues-modal"; diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 6868cf7b0..5910d85fa 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -233,31 +233,30 @@ export const IssuesFilterView: React.FC = () => { )}
- {issueView !== "calendar" && ( -
-

Display Properties

-
- {Object.keys(properties).map((key) => { - if (key === "estimate" && !isEstimateActive) return null; - return ( - - ); - })} -
+
+

Display Properties

+
+ {Object.keys(properties).map((key) => { + if (key === "estimate" && !isEstimateActive) return null; + + return ( + + ); + })}
- )} +
diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 9baa0318d..0557603f8 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -17,14 +17,13 @@ import { useProjectMyMembership } from "contexts/project-member.context"; import useToast from "hooks/use-toast"; import useIssuesView from "hooks/use-issues-view"; // components -import { AllLists, AllBoards, FilterList } from "components/core"; +import { AllLists, AllBoards, FilterList, CalendarView } from "components/core"; import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; import StrictModeDroppable from "components/dnd/StrictModeDroppable"; import { CreateUpdateViewModal } from "components/views"; import { TransferIssues, TransferIssuesModal } from "components/cycles"; // ui import { EmptySpace, EmptySpaceItem, EmptyState, PrimaryButton, Spinner } from "components/ui"; -import { CalendarView } from "./calendar-view"; // icons import { ListBulletIcon, @@ -533,7 +532,13 @@ export const IssuesView: React.FC = ({ userAuth={memberRole} /> ) : ( - + )} ) : type === "issue" ? ( diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index dbcb87451..49e11ca2f 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -84,7 +84,7 @@ export const SingleListIssue: React.FC = ({ const { groupByProperty: selectedGroup, orderBy, params } = useIssueView(); const partialUpdateIssue = useCallback( - (formData: Partial) => { + (formData: Partial, issueId: string) => { if (!workspaceSlug || !projectId) return; if (cycleId) @@ -140,7 +140,7 @@ export const SingleListIssue: React.FC = ({ ); issuesService - .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) + .patchIssue(workspaceSlug as string, projectId as string, issueId, formData) .then(() => { if (cycleId) { mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); @@ -151,18 +151,7 @@ export const SingleListIssue: React.FC = ({ } else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params)); }); }, - [ - workspaceSlug, - projectId, - cycleId, - moduleId, - issue, - groupTitle, - index, - selectedGroup, - orderBy, - params, - ] + [workspaceSlug, projectId, cycleId, moduleId, groupTitle, index, selectedGroup, orderBy, params] ); const handleCopyText = () => { diff --git a/apps/app/components/issues/delete-issue-modal.tsx b/apps/app/components/issues/delete-issue-modal.tsx index 903022584..5350ce21f 100644 --- a/apps/app/components/issues/delete-issue-modal.tsx +++ b/apps/app/components/issues/delete-issue-modal.tsx @@ -10,6 +10,7 @@ import { Dialog, Transition } from "@headlessui/react"; import issueServices from "services/issues.service"; // hooks import useToast from "hooks/use-toast"; +import useIssuesView from "hooks/use-issues-view"; // icons import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; // ui @@ -18,14 +19,13 @@ import { SecondaryButton, DangerButton } from "components/ui"; import type { CycleIssueResponse, IIssue, ModuleIssueResponse } from "types"; // fetch-keys import { - CYCLE_ISSUES, + CYCLE_CALENDAR_ISSUES, CYCLE_ISSUES_WITH_PARAMS, - MODULE_ISSUES, + MODULE_CALENDAR_ISSUES, MODULE_ISSUES_WITH_PARAMS, + PROJECT_CALENDAR_ISSUES, PROJECT_ISSUES_LIST_WITH_PARAMS, - USER_ISSUE, } from "constants/fetch-keys"; -import useIssuesView from "hooks/use-issues-view"; type Props = { isOpen: boolean; @@ -39,7 +39,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; - const { params } = useIssuesView(); + const { issueView, params } = useIssuesView(); const { setToastAlert } = useToast(); @@ -59,9 +59,19 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) await issueServices .deleteIssue(workspaceSlug as string, projectId as string, data.id) .then(() => { - if (cycleId) mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); - else if (moduleId) mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); - else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params)); + if (issueView === "calendar") { + const calendarFetchKey = cycleId + ? CYCLE_CALENDAR_ISSUES(projectId as string, cycleId as string) + : moduleId + ? MODULE_CALENDAR_ISSUES(projectId as string, moduleId as string) + : PROJECT_CALENDAR_ISSUES(projectId as string); + + mutate(calendarFetchKey); + } else { + if (cycleId) mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); + else if (moduleId) mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); + else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params)); + } handleClose(); setToastAlert({ diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index b2eb45e28..f8a39eda9 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -31,6 +31,9 @@ import { MODULE_ISSUES_WITH_PARAMS, CYCLE_DETAILS, MODULE_DETAILS, + PROJECT_CALENDAR_ISSUES, + CYCLE_CALENDAR_ISSUES, + MODULE_CALENDAR_ISSUES, } from "constants/fetch-keys"; export interface IssuesModalProps { @@ -55,7 +58,7 @@ export const CreateUpdateIssueModal: React.FC = ({ const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; - const { params } = useIssuesView(); + const { issueView, params } = useIssuesView(); if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string }; if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string }; @@ -134,6 +137,14 @@ export const CreateUpdateIssueModal: React.FC = ({ if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle); if (payload.module && payload.module !== "") addIssueToModule(res.id, payload.module); + const calendarFetchKey = cycleId + ? CYCLE_CALENDAR_ISSUES(projectId as string, cycleId as string) + : moduleId + ? MODULE_CALENDAR_ISSUES(projectId as string, moduleId as string) + : PROJECT_CALENDAR_ISSUES(projectId as string); + + mutate(calendarFetchKey); + if (!createMore) handleClose(); setToastAlert({ @@ -162,14 +173,29 @@ export const CreateUpdateIssueModal: React.FC = ({ if (isUpdatingSingleIssue) { mutate(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false); } else { - mutate( - PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params), - (prevData) => + if (issueView === "calendar") { + const calendarFetchKey = cycleId + ? CYCLE_CALENDAR_ISSUES(projectId as string, cycleId as string) + : moduleId + ? MODULE_CALENDAR_ISSUES(projectId as string, moduleId as string) + : PROJECT_CALENDAR_ISSUES(projectId as string); + + mutate(calendarFetchKey, (prevData) => (prevData ?? []).map((i) => { if (i.id === res.id) return { ...i, ...res }; return i; }) - ); + ); + } else { + mutate( + PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params), + (prevData) => + (prevData ?? []).map((i) => { + if (i.id === res.id) return { ...i, ...res }; + return i; + }) + ); + } } if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle); diff --git a/apps/app/components/issues/my-issues-list-item.tsx b/apps/app/components/issues/my-issues-list-item.tsx index 369cfc6e2..fc8e5c28f 100644 --- a/apps/app/components/issues/my-issues-list-item.tsx +++ b/apps/app/components/issues/my-issues-list-item.tsx @@ -39,14 +39,14 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId const { setToastAlert } = useToast(); const partialUpdateIssue = useCallback( - (formData: Partial) => { + (formData: Partial, issueId: string) => { if (!workspaceSlug) return; mutate( USER_ISSUE(workspaceSlug as string), (prevData) => prevData?.map((p) => { - if (p.id === issue.id) return { ...p, ...formData }; + if (p.id === issueId) return { ...p, ...formData }; return p; }), @@ -54,7 +54,7 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId ); issuesService - .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) + .patchIssue(workspaceSlug as string, projectId as string, issueId, formData) .then((res) => { mutate(USER_ISSUE(workspaceSlug as string)); }) @@ -62,7 +62,7 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId console.log(error); }); }, - [workspaceSlug, projectId, issue] + [workspaceSlug, projectId] ); const handleCopyText = () => { diff --git a/apps/app/components/issues/view-select/assignee.tsx b/apps/app/components/issues/view-select/assignee.tsx index 188543185..483f003e3 100644 --- a/apps/app/components/issues/view-select/assignee.tsx +++ b/apps/app/components/issues/view-select/assignee.tsx @@ -18,7 +18,7 @@ import { PROJECT_MEMBERS } from "constants/fetch-keys"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial) => void; + partialUpdateIssue: (formData: Partial, issueId: string) => void; position?: "left" | "right"; selfPositioned?: boolean; tooltipPosition?: "left" | "right"; @@ -71,7 +71,7 @@ export const ViewAssigneeSelect: React.FC = ({ if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); else newData.push(data); - partialUpdateIssue({ assignees_list: data }); + partialUpdateIssue({ assignees_list: data }, issue.id); trackEventServices.trackIssuePartialPropertyUpdateEvent( { diff --git a/apps/app/components/issues/view-select/due-date.tsx b/apps/app/components/issues/view-select/due-date.tsx index d3f16c5b4..0aa2ea816 100644 --- a/apps/app/components/issues/view-select/due-date.tsx +++ b/apps/app/components/issues/view-select/due-date.tsx @@ -11,7 +11,7 @@ import { IIssue } from "types"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial) => void; + partialUpdateIssue: (formData: Partial, issueId: string) => void; isNotAllowed: boolean; }; @@ -34,11 +34,14 @@ export const ViewDueDateSelect: React.FC = ({ issue, partialUpdateIssue, placeholder="Due date" value={issue?.target_date} onChange={(val) => { - partialUpdateIssue({ - target_date: val, - priority: issue.priority, - state: issue.state, - }); + partialUpdateIssue( + { + target_date: val, + priority: issue.priority, + state: issue.state, + }, + issue.id + ); trackEventServices.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, diff --git a/apps/app/components/issues/view-select/estimate.tsx b/apps/app/components/issues/view-select/estimate.tsx index 737896c4f..586f930bc 100644 --- a/apps/app/components/issues/view-select/estimate.tsx +++ b/apps/app/components/issues/view-select/estimate.tsx @@ -15,7 +15,7 @@ import { IIssue } from "types"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial) => void; + partialUpdateIssue: (formData: Partial, issueId: string) => void; position?: "left" | "right"; selfPositioned?: boolean; isNotAllowed: boolean; @@ -41,7 +41,7 @@ export const ViewEstimateSelect: React.FC = ({ { - partialUpdateIssue({ estimate_point: val }); + partialUpdateIssue({ estimate_point: val }, issue.id); trackEventServices.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, diff --git a/apps/app/components/issues/view-select/priority.tsx b/apps/app/components/issues/view-select/priority.tsx index eb20f7040..aa9a5306e 100644 --- a/apps/app/components/issues/view-select/priority.tsx +++ b/apps/app/components/issues/view-select/priority.tsx @@ -15,7 +15,7 @@ import trackEventServices from "services/track-event.service"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial) => void; + partialUpdateIssue: (formData: Partial, issueId: string) => void; position?: "left" | "right"; selfPositioned?: boolean; isNotAllowed: boolean; @@ -35,7 +35,7 @@ export const ViewPrioritySelect: React.FC = ({ { - partialUpdateIssue({ priority: data }); + partialUpdateIssue({ priority: data }, issue.id); trackEventServices.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, diff --git a/apps/app/components/issues/view-select/state.tsx b/apps/app/components/issues/view-select/state.tsx index 222b8085d..68a99ac72 100644 --- a/apps/app/components/issues/view-select/state.tsx +++ b/apps/app/components/issues/view-select/state.tsx @@ -19,7 +19,7 @@ import { STATES_LIST } from "constants/fetch-keys"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial) => void; + partialUpdateIssue: (formData: Partial, issueId: string) => void; position?: "left" | "right"; selfPositioned?: boolean; isNotAllowed: boolean; @@ -60,11 +60,14 @@ export const ViewStateSelect: React.FC = ({ { - partialUpdateIssue({ - state: data, - priority: issue.priority, - target_date: issue.target_date, - }); + partialUpdateIssue( + { + state: data, + priority: issue.priority, + target_date: issue.target_date, + }, + issue.id + ); trackEventServices.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, diff --git a/apps/app/types/calendar.ts b/apps/app/types/calendar.ts new file mode 100644 index 000000000..cb27e2d10 --- /dev/null +++ b/apps/app/types/calendar.ts @@ -0,0 +1,4 @@ +export interface ICalendarRange { + startDate: Date; + endDate: Date; +} diff --git a/apps/app/types/index.d.ts b/apps/app/types/index.d.ts index 259cb1d41..0a161932e 100644 --- a/apps/app/types/index.d.ts +++ b/apps/app/types/index.d.ts @@ -10,8 +10,9 @@ export * from "./views"; export * from "./integration"; export * from "./pages"; export * from "./ai"; -export * from "./estimate" +export * from "./estimate"; export * from "./importer"; +export * from "./calendar"; export type NestedKeyOf = { [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object