From 97e06e69a6a792a114c58a730235c9d30560e4cb Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 28 Aug 2023 17:09:54 +0530 Subject: [PATCH 1/6] dev: mobx for issues store --- .../components/command-palette/command-k.tsx | 2 +- apps/app/components/core/views/all-views.tsx | 212 +++++++++--------- .../views/spreadsheet-view/single-issue.tsx | 24 +- .../peek-overview/full-screen-peek-view.tsx | 10 +- .../issues/peek-overview/header.tsx | 18 +- .../issues/peek-overview/issue-properties.tsx | 22 +- .../issues/peek-overview/layout.tsx | 205 ++++++++++------- .../issues/peek-overview/side-peek-view.tsx | 92 +++++--- apps/app/store/issues.ts | 177 +++++++++++++++ apps/app/store/root.ts | 3 + 10 files changed, 496 insertions(+), 269 deletions(-) create mode 100644 apps/app/store/issues.ts diff --git a/apps/app/components/command-palette/command-k.tsx b/apps/app/components/command-palette/command-k.tsx index a1525a34831..d20a4429093 100644 --- a/apps/app/components/command-palette/command-k.tsx +++ b/apps/app/components/command-palette/command-k.tsx @@ -665,7 +665,7 @@ export const CommandK: React.FC = ({ deleteIssue, isPaletteOpen, setIsPal className="focus:outline-none" >
- + Join our Discord
diff --git a/apps/app/components/core/views/all-views.tsx b/apps/app/components/core/views/all-views.tsx index 79d5d6b11f0..254bc277fc3 100644 --- a/apps/app/components/core/views/all-views.tsx +++ b/apps/app/components/core/views/all-views.tsx @@ -20,6 +20,7 @@ import { SpreadsheetView, GanttChartView, } from "components/core"; +import { IssuePeekOverview } from "components/issues"; // ui import { EmptyState, Spinner } from "components/ui"; // icons @@ -96,109 +97,116 @@ export const AllViews: React.FC = ({ ); return ( - - - {(provided, snapshot) => ( -
- - Drop here to delete the issue. + <> + + + + {(provided, snapshot) => ( +
+ + Drop here to delete the issue. +
+ )} +
+ {groupedIssues ? ( + !isEmpty || + issueView === "kanban" || + issueView === "calendar" || + issueView === "gantt_chart" ? ( + <> + {issueView === "list" ? ( + + ) : issueView === "kanban" ? ( + + ) : issueView === "calendar" ? ( + + ) : issueView === "spreadsheet" ? ( + + ) : ( + issueView === "gantt_chart" && + )} + + ) : router.pathname.includes("archived-issues") ? ( + { + router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`); + }, + }} + /> + ) : ( + + ) + ) : ( +
+
)} - - {groupedIssues ? ( - !isEmpty || - issueView === "kanban" || - issueView === "calendar" || - issueView === "gantt_chart" ? ( - <> - {issueView === "list" ? ( - - ) : issueView === "kanban" ? ( - - ) : issueView === "calendar" ? ( - - ) : issueView === "spreadsheet" ? ( - - ) : ( - issueView === "gantt_chart" && - )} - - ) : router.pathname.includes("archived-issues") ? ( - { - router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`); - }, - }} - /> - ) : ( - - ) - ) : ( -
- -
- )} -
+ + ); }; diff --git a/apps/app/components/core/views/spreadsheet-view/single-issue.tsx b/apps/app/components/core/views/spreadsheet-view/single-issue.tsx index 11a8c42c55e..53869a63834 100644 --- a/apps/app/components/core/views/spreadsheet-view/single-issue.tsx +++ b/apps/app/components/core/views/spreadsheet-view/single-issue.tsx @@ -6,7 +6,6 @@ import { mutate } from "swr"; // components import { - IssuePeekOverview, ViewAssigneeSelect, ViewDueDateSelect, ViewEstimateSelect, @@ -76,9 +75,6 @@ export const SingleSpreadsheetIssue: React.FC = ({ }) => { const [isOpen, setIsOpen] = useState(false); - // issue peek overview - const [issuePeekOverview, setIssuePeekOverview] = useState(false); - const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; @@ -161,6 +157,15 @@ export const SingleSpreadsheetIssue: React.FC = ({ [workspaceSlug, projectId, cycleId, moduleId, viewId, params, user] ); + const openPeekOverview = () => { + const { query } = router; + + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: issue.id }, + }); + }; + const handleCopyText = () => { const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; @@ -183,15 +188,6 @@ export const SingleSpreadsheetIssue: React.FC = ({ return ( <> - handleDeleteIssue(issue)} - handleUpdateIssue={async (formData) => partialUpdateIssue(formData, issue)} - issue={issue} - isOpen={issuePeekOverview} - onClose={() => setIssuePeekOverview(false)} - workspaceSlug={workspaceSlug?.toString() ?? ""} - readOnly={isNotAllowed} - />
= ({ diff --git a/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx b/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx index d470f491088..90230124e41 100644 --- a/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx +++ b/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx @@ -9,9 +9,8 @@ import { IIssue } from "types"; type Props = { handleClose: () => void; - handleDeleteIssue: () => void; - handleUpdateIssue: (issue: Partial) => Promise; - issue: IIssue; + handleUpdateIssue: (formData: Partial) => Promise; + issue: IIssue | undefined; mode: TPeekOverviewModes; readOnly: boolean; setMode: (mode: TPeekOverviewModes) => void; @@ -20,7 +19,6 @@ type Props = { export const FullScreenPeekView: React.FC = ({ handleClose, - handleDeleteIssue, handleUpdateIssue, issue, mode, @@ -33,7 +31,6 @@ export const FullScreenPeekView: React.FC = ({
= ({ {/* issue properties */}
diff --git a/apps/app/components/issues/peek-overview/header.tsx b/apps/app/components/issues/peek-overview/header.tsx index 29e23a262af..a7208ce7b1c 100644 --- a/apps/app/components/issues/peek-overview/header.tsx +++ b/apps/app/components/issues/peek-overview/header.tsx @@ -2,17 +2,17 @@ import useToast from "hooks/use-toast"; // ui import { CustomSelect, Icon } from "components/ui"; +// icons +import { CloseFullscreen, East, OpenInFull } from "@mui/icons-material"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; // types import { IIssue } from "types"; import { TPeekOverviewModes } from "./layout"; -import { ArrowRightAlt, CloseFullscreen, East, OpenInFull } from "@mui/icons-material"; type Props = { handleClose: () => void; - handleDeleteIssue: () => void; - issue: IIssue; + issue: IIssue | undefined; mode: TPeekOverviewModes; setMode: (mode: TPeekOverviewModes) => void; workspaceSlug: string; @@ -39,7 +39,6 @@ const peekModes: { export const PeekOverviewHeader: React.FC = ({ issue, handleClose, - handleDeleteIssue, mode, setMode, workspaceSlug, @@ -47,12 +46,9 @@ export const PeekOverviewHeader: React.FC = ({ const { setToastAlert } = useToast(); const handleCopyLink = () => { - const originURL = - typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + const urlToCopy = window.location.href; - copyTextToClipboard( - `${originURL}/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}` - ).then(() => { + copyTextToClipboard(urlToCopy).then(() => { setToastAlert({ type: "success", title: "Link copied!", @@ -123,9 +119,9 @@ export const PeekOverviewHeader: React.FC = ({ - + */}
)}
diff --git a/apps/app/components/issues/peek-overview/issue-properties.tsx b/apps/app/components/issues/peek-overview/issue-properties.tsx index bf1ecefd993..340241936e9 100644 --- a/apps/app/components/issues/peek-overview/issue-properties.tsx +++ b/apps/app/components/issues/peek-overview/issue-properties.tsx @@ -17,19 +17,17 @@ import useToast from "hooks/use-toast"; import { IIssue } from "types"; type Props = { - handleDeleteIssue: () => void; + handleUpdateIssue: (formData: Partial) => Promise; issue: IIssue; mode: TPeekOverviewModes; - onChange: (issueProperty: Partial) => void; readOnly: boolean; workspaceSlug: string; }; export const PeekOverviewIssueProperties: React.FC = ({ - handleDeleteIssue, + handleUpdateIssue, issue, mode, - onChange, readOnly, workspaceSlug, }) => { @@ -71,9 +69,9 @@ export const PeekOverviewIssueProperties: React.FC = ({ - + */}
)} @@ -86,7 +84,7 @@ export const PeekOverviewIssueProperties: React.FC = ({
onChange({ state: val })} + onChange={(val: string) => handleUpdateIssue({ state: val })} disabled={readOnly} />
@@ -99,7 +97,7 @@ export const PeekOverviewIssueProperties: React.FC = ({
onChange({ assignees_list: val })} + onChange={(val: string[]) => handleUpdateIssue({ assignees_list: val })} disabled={readOnly} />
@@ -112,7 +110,7 @@ export const PeekOverviewIssueProperties: React.FC = ({
onChange({ priority: val })} + onChange={(val: string) => handleUpdateIssue({ priority: val })} disabled={readOnly} />
@@ -128,7 +126,7 @@ export const PeekOverviewIssueProperties: React.FC = ({ placeholder="Start date" value={issue.start_date} onChange={(val) => - onChange({ + handleUpdateIssue({ start_date: val, }) } @@ -153,7 +151,7 @@ export const PeekOverviewIssueProperties: React.FC = ({ placeholder="Due date" value={issue.target_date} onChange={(val) => - onChange({ + handleUpdateIssue({ target_date: val, }) } @@ -175,7 +173,7 @@ export const PeekOverviewIssueProperties: React.FC = ({
onChange({ estimate_point: val })} + onChange={(val: number | null) =>handleUpdateIssue({ estimate_point: val })} disabled={readOnly} />
diff --git a/apps/app/components/issues/peek-overview/layout.tsx b/apps/app/components/issues/peek-overview/layout.tsx index 7196052f8db..32a8481187e 100644 --- a/apps/app/components/issues/peek-overview/layout.tsx +++ b/apps/app/components/issues/peek-overview/layout.tsx @@ -1,107 +1,140 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; + +import { useRouter } from "next/router"; + +import useSWR from "swr"; // headless ui import { Dialog, Transition } from "@headlessui/react"; +// components import { FullScreenPeekView, SidePeekView } from "components/issues"; +// services +import issuesService from "services/issues.service"; // types import { IIssue } from "types"; +// fetch-keys +import { PROJECT_ISSUES_DETAILS } from "constants/fetch-keys"; +import useUser from "hooks/use-user"; +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; type Props = { - handleDeleteIssue: () => void; - handleUpdateIssue: (issue: Partial) => Promise; - issue: IIssue | null; - isOpen: boolean; - onClose: () => void; + projectId: string; workspaceSlug: string; readOnly: boolean; }; export type TPeekOverviewModes = "side" | "modal" | "full"; -export const IssuePeekOverview: React.FC = ({ - handleDeleteIssue, - handleUpdateIssue, - issue, - isOpen, - onClose, - workspaceSlug, - readOnly, -}) => { - const [peekOverviewMode, setPeekOverviewMode] = useState("side"); - - const handleClose = () => { - onClose(); - setPeekOverviewMode("side"); - }; - - if (!issue || !isOpen) return null; - - return ( - - - {/* add backdrop conditionally */} - {(peekOverviewMode === "modal" || peekOverviewMode === "full") && ( - -
- - )} -
-
+export const IssuePeekOverview: React.FC = observer( + ({ projectId, workspaceSlug, readOnly }) => { + const [isOpen, setIsOpen] = useState(false); + const [peekOverviewMode, setPeekOverviewMode] = useState("side"); + + const router = useRouter(); + const { peekIssue } = router.query; + + const { issues: issuesStore } = useMobxStore(); + const { getIssueById, issues, updateIssue } = issuesStore; + + const issue = issues[peekIssue?.toString() ?? ""]; + + const { user } = useUser(); + + const handleClose = () => { + const { query } = router; + delete query.peekIssue; + + router.push({ + pathname: router.pathname, + query: { ...query }, + }); + + setPeekOverviewMode("side"); + }; + + const handleUpdateIssue = async (formData: Partial) => { + if (!issue || !user) return; + + await updateIssue(workspaceSlug, projectId, issue.id, formData, user); + }; + + useEffect(() => { + if (!peekIssue) return; + + getIssueById(workspaceSlug, projectId, peekIssue.toString()); + }, [getIssueById, peekIssue, projectId, workspaceSlug]); + + useEffect(() => { + if (peekIssue) setIsOpen(true); + else setIsOpen(false); + }, [peekIssue]); + + return ( + + + {/* add backdrop conditionally */} + {(peekOverviewMode === "modal" || peekOverviewMode === "full") && ( - - {(peekOverviewMode === "side" || peekOverviewMode === "modal") && ( - setPeekOverviewMode(mode)} - workspaceSlug={workspaceSlug} - /> - )} - {peekOverviewMode === "full" && ( - setPeekOverviewMode(mode)} - workspaceSlug={workspaceSlug} - /> - )} - +
+ )} +
+
+ + + {(peekOverviewMode === "side" || peekOverviewMode === "modal") && ( + setPeekOverviewMode(mode)} + workspaceSlug={workspaceSlug} + /> + )} + {peekOverviewMode === "full" && ( + setPeekOverviewMode(mode)} + workspaceSlug={workspaceSlug} + /> + )} + + +
-
-
-
- ); -}; +
+
+ ); + } +); diff --git a/apps/app/components/issues/peek-overview/side-peek-view.tsx b/apps/app/components/issues/peek-overview/side-peek-view.tsx index f938c38056f..1b2da7264c2 100644 --- a/apps/app/components/issues/peek-overview/side-peek-view.tsx +++ b/apps/app/components/issues/peek-overview/side-peek-view.tsx @@ -1,3 +1,4 @@ +// components import { PeekOverviewHeader, PeekOverviewIssueActivity, @@ -5,13 +6,15 @@ import { PeekOverviewIssueProperties, TPeekOverviewModes, } from "components/issues"; +// ui +import { Loader } from "components/ui"; +// types import { IIssue } from "types"; type Props = { handleClose: () => void; - handleDeleteIssue: () => void; - handleUpdateIssue: (issue: Partial) => Promise; - issue: IIssue; + handleUpdateIssue: (formData: Partial) => Promise; + issue: IIssue | undefined; mode: TPeekOverviewModes; readOnly: boolean; setMode: (mode: TPeekOverviewModes) => void; @@ -20,7 +23,6 @@ type Props = { export const SidePeekView: React.FC = ({ handleClose, - handleDeleteIssue, handleUpdateIssue, issue, mode, @@ -32,44 +34,62 @@ export const SidePeekView: React.FC = ({
-
- {/* issue title and description */} -
- + {issue ? ( +
+ {/* issue title and description */} +
+ +
+ {/* issue properties */} +
+ +
+ {/* divider */} +
+ {/* issue activity/comments */} +
+ {issue && ( + + )} +
- {/* issue properties */} -
- -
- {/* divider */} -
- {/* issue activity/comments */} -
- -
-
+ ) : ( + + +
+ + +
+
+ +
+
+ + + +
+
+ )}
); diff --git a/apps/app/store/issues.ts b/apps/app/store/issues.ts new file mode 100644 index 00000000000..16a64329d8e --- /dev/null +++ b/apps/app/store/issues.ts @@ -0,0 +1,177 @@ +// mobx +import { action, observable, runInAction, makeAutoObservable } from "mobx"; + +// services +import issueService from "services/issues.service"; + +// types +import type { ICurrentUserResponse, IIssue } from "types"; + +class IssuesStore { + issues: { [key: string]: IIssue } = {}; + isIssuesLoading: boolean = false; + rootStore: any | null = null; + + constructor(_rootStore: any | null = null) { + makeAutoObservable(this, { + issues: observable.ref, + loadIssues: action, + getIssueById: action, + isIssuesLoading: observable, + createIssue: action, + updateIssue: action, + deleteIssue: action, + }); + + this.rootStore = _rootStore; + } + + /** + * @description Fetch all issues of a project and hydrate issues field + */ + + loadIssues = async (workspaceSlug: string, projectId: string) => { + this.isIssuesLoading = true; + try { + const issuesResponse: IIssue[] = (await issueService.getIssuesWithParams( + workspaceSlug, + projectId + )) as IIssue[]; + + const issues: { [kye: string]: IIssue } = {}; + issuesResponse.forEach((issue) => { + issues[issue.id] = issue; + }); + + runInAction(() => { + this.issues = issues; + this.isIssuesLoading = false; + }); + } catch (error) { + this.isIssuesLoading = false; + console.error("Fetching issues error", error); + } + }; + + getIssueById = async ( + workspaceSlug: string, + projectId: string, + issueId: string + ): Promise => { + if (this.issues[issueId]) return this.issues[issueId]; + + try { + const issueResponse: IIssue = await issueService.retrieve(workspaceSlug, projectId, issueId); + + const issues = { + ...this.issues, + [issueResponse.id]: { ...issueResponse }, + }; + + runInAction(() => { + this.issues = issues; + }); + + return issueResponse; + } catch (error) { + throw error; + } + }; + + /** + * For provided query, this function returns all issues that contain query in their name from the issues store. + * @param query - query string + * @returns {IIssue[]} array of issues that contain query in their name + * @example + * getFilteredIssues("issue") // [{ id: "", name: "issue", description: "", parent: null }] + */ + // getFilteredIssues = (query: string): IIssue[] => + // this.issues.filter((i) => i.name.includes(query)); + + createIssue = async ( + workspaceSlug: string, + projectId: string, + issueForm: IIssue, + user: ICurrentUserResponse + ): Promise => { + try { + const issueResponse = await issueService.createIssues( + workspaceSlug, + projectId, + issueForm, + user + ); + + const issues = { + ...this.issues, + [issueResponse.id]: { ...issueResponse }, + }; + + runInAction(() => { + this.issues = issues; + }); + return issueResponse; + } catch (error) { + console.error("Creating issue error", error); + throw error; + } + }; + + updateIssue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + issueForm: Partial, + user: ICurrentUserResponse + ) => { + // immediately update the issue in the store + const issues = { ...this.issues }; + issues[issueId] = { ...this.issues[issueId], ...issueForm }; + + runInAction(() => { + this.issues = issues; + }); + + try { + // make a patch request to update the issue + const issueResponse: IIssue = await issueService.patchIssue( + workspaceSlug, + projectId, + issueId, + issueForm, + user + ); + + const updatedIssues = { ...this.issues }; + updatedIssues[issueId] = { ...issueResponse }; + + runInAction(() => { + this.issues = updatedIssues; + }); + } catch (error) { + return error; + } + }; + + deleteIssue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + user: ICurrentUserResponse + ) => { + try { + issueService.deleteIssue(workspaceSlug, projectId, issueId, user); + + const issues = { ...this.issues }; + delete issues[issueId]; + + runInAction(() => { + this.issues = issues; + }); + } catch (error) { + console.error("Deleting issue error", error); + } + }; +} + +export default IssuesStore; diff --git a/apps/app/store/root.ts b/apps/app/store/root.ts index 5895637a89f..40dd62fe6d2 100644 --- a/apps/app/store/root.ts +++ b/apps/app/store/root.ts @@ -3,6 +3,7 @@ import { enableStaticRendering } from "mobx-react-lite"; // store imports import UserStore from "./user"; import ThemeStore from "./theme"; +import IssuesStore from "./issues"; import ProjectPublishStore, { IProjectPublishStore } from "./project-publish"; enableStaticRendering(typeof window === "undefined"); @@ -11,10 +12,12 @@ export class RootStore { user; theme; projectPublish: IProjectPublishStore; + issues: IssuesStore; constructor() { this.user = new UserStore(this); this.theme = new ThemeStore(this); this.projectPublish = new ProjectPublishStore(this); + this.issues = new IssuesStore(this); } } From 1e53461463cfd073e39843efabe6b5be832df0c5 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 29 Aug 2023 13:23:50 +0530 Subject: [PATCH 2/6] refactor: peek overview component --- .../components/issues/gantt-chart/blocks.tsx | 30 +- .../peek-overview/full-screen-peek-view.tsx | 78 +++-- .../issues/peek-overview/header.tsx | 187 ++++++------ .../issues/peek-overview/issue-properties.tsx | 286 ++++++++++-------- .../issues/peek-overview/layout.tsx | 162 ++++++---- .../issues/peek-overview/side-peek-view.tsx | 11 +- apps/app/store/issues.ts | 44 ++- 7 files changed, 449 insertions(+), 349 deletions(-) diff --git a/apps/app/components/issues/gantt-chart/blocks.tsx b/apps/app/components/issues/gantt-chart/blocks.tsx index 2ad21c49969..a79f16712ce 100644 --- a/apps/app/components/issues/gantt-chart/blocks.tsx +++ b/apps/app/components/issues/gantt-chart/blocks.tsx @@ -11,13 +11,23 @@ import { IIssue } from "types"; export const IssueGanttBlock = ({ data }: { data: IIssue }) => { const router = useRouter(); - const { workspaceSlug } = router.query; + + const openPeekOverview = () => { + if (!data) return; + + const { query } = router; + + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: data.id }, + }); + }; return (
router.push(`/${workspaceSlug}/projects/${data?.project}/issues/${data?.id}`)} + onClick={openPeekOverview} >
{ // rendering issues on gantt sidebar export const IssueGanttSidebarBlock = ({ data }: { data: IIssue }) => { const router = useRouter(); - const { workspaceSlug } = router.query; + + const openPeekOverview = () => { + if (!data) return; + + const { query } = router; + + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: data.id }, + }); + }; const duration = findTotalDaysInRange(data?.start_date ?? "", data?.target_date ?? "", true); return (
router.push(`/${workspaceSlug}/projects/${data?.project}/issues/${data?.id}`)} + className="relative w-full flex items-center gap-2 h-full cursor-pointer" + onClick={openPeekOverview} > {getStateGroupIcon(data?.state_detail?.group, "14", "14", data?.state_detail?.color)}
diff --git a/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx b/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx index 90230124e41..ad36b1f195b 100644 --- a/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx +++ b/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx @@ -1,3 +1,4 @@ +// components import { PeekOverviewHeader, PeekOverviewIssueActivity, @@ -5,6 +6,9 @@ import { PeekOverviewIssueProperties, TPeekOverviewModes, } from "components/issues"; +// ui +import { Loader } from "components/ui"; +// types import { IIssue } from "types"; type Props = { @@ -37,38 +41,58 @@ export const FullScreenPeekView: React.FC = ({ workspaceSlug={workspaceSlug} />
-
- {/* issue title and description */} -
- -
- {/* divider */} -
- {/* issue activity/comments */} -
- + {issue ? ( +
+ {/* issue title and description */} +
+ +
+ {/* divider */} +
+ {/* issue activity/comments */} +
+ +
-
+ ) : ( + + +
+ + + +
+
+ )}
{/* issue properties */}
- + {issue ? ( + + ) : ( + + + + + + + )}
diff --git a/apps/app/components/issues/peek-overview/header.tsx b/apps/app/components/issues/peek-overview/header.tsx index a7208ce7b1c..2ca89b8780e 100644 --- a/apps/app/components/issues/peek-overview/header.tsx +++ b/apps/app/components/issues/peek-overview/header.tsx @@ -1,5 +1,9 @@ +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // hooks import useToast from "hooks/use-toast"; +import useUser from "hooks/use-user"; // ui import { CustomSelect, Icon } from "components/ui"; // icons @@ -9,6 +13,7 @@ import { copyTextToClipboard } from "helpers/string.helper"; // types import { IIssue } from "types"; import { TPeekOverviewModes } from "./layout"; +import Link from "next/link"; type Props = { handleClose: () => void; @@ -36,94 +41,106 @@ const peekModes: { }, ]; -export const PeekOverviewHeader: React.FC = ({ - issue, - handleClose, - mode, - setMode, - workspaceSlug, -}) => { - const { setToastAlert } = useToast(); +export const PeekOverviewHeader: React.FC = observer( + ({ issue, handleClose, mode, setMode, workspaceSlug }) => { + const { setToastAlert } = useToast(); - const handleCopyLink = () => { - const urlToCopy = window.location.href; + const { user } = useUser(); - copyTextToClipboard(urlToCopy).then(() => { - setToastAlert({ - type: "success", - title: "Link copied!", - message: "Issue link copied to clipboard", + const { issues: issuesStore } = useMobxStore(); + const { deleteIssue } = issuesStore; + + const handleDeleteIssue = async () => { + if (!issue || !user) return; + + await deleteIssue(workspaceSlug, issue.project, issue.id, user); + }; + + const handleCopyLink = () => { + const urlToCopy = window.location.href; + + copyTextToClipboard(urlToCopy).then(() => { + setToastAlert({ + type: "success", + title: "Link copied!", + message: "Issue link copied to clipboard", + }); }); - }); - }; + }; - return ( -
-
- {mode === "side" && ( - - )} - {mode === "modal" || mode === "full" ? ( - - ) : ( - - )} - setMode(val)} - customButton={ - - } - position="left" - > - {peekModes.map((mode) => ( - -
- - {mode.label} -
-
- ))} -
-
- {(mode === "side" || mode === "modal") && ( -
- - {/* */} + )} + {mode === "modal" || mode === "full" ? ( + + ) : ( + + )} + setMode(val)} + customButton={ + + } + position="left" + > + {peekModes.map((mode) => ( + +
+ + {mode.label} +
+
+ ))} +
- )} -
- ); -}; + {(mode === "side" || mode === "modal") && ( +
+ + + Open issue + + + + +
+ )} +
+ ); + } +); diff --git a/apps/app/components/issues/peek-overview/issue-properties.tsx b/apps/app/components/issues/peek-overview/issue-properties.tsx index 340241936e9..ea1bd7ee277 100644 --- a/apps/app/components/issues/peek-overview/issue-properties.tsx +++ b/apps/app/components/issues/peek-overview/issue-properties.tsx @@ -1,6 +1,14 @@ +import Link from "next/link"; + +// mobx +import { useMobxStore } from "lib/mobx/store-provider"; +import { observer } from "mobx-react-lite"; // headless ui import { Disclosure } from "@headlessui/react"; import { getStateGroupIcon } from "components/icons"; +// hooks +import useToast from "hooks/use-toast"; +import useUser from "hooks/use-user"; // components import { SidebarAssigneeSelect, @@ -9,10 +17,10 @@ import { SidebarStateSelect, TPeekOverviewModes, } from "components/issues"; -// icons +// ui import { CustomDatePicker, Icon } from "components/ui"; +// helpers import { copyTextToClipboard } from "helpers/string.helper"; -import useToast from "hooks/use-toast"; // types import { IIssue } from "types"; @@ -24,148 +32,159 @@ type Props = { workspaceSlug: string; }; -export const PeekOverviewIssueProperties: React.FC = ({ - handleUpdateIssue, - issue, - mode, - readOnly, - workspaceSlug, -}) => { - const { setToastAlert } = useToast(); +export const PeekOverviewIssueProperties: React.FC = observer( + ({ handleUpdateIssue, issue, mode, readOnly, workspaceSlug }) => { + const { setToastAlert } = useToast(); + + const { user } = useUser(); + + const startDate = issue.start_date; + const targetDate = issue.target_date; - const startDate = issue.start_date; - const targetDate = issue.target_date; + const minDate = startDate ? new Date(startDate) : null; + minDate?.setDate(minDate.getDate()); - const minDate = startDate ? new Date(startDate) : null; - minDate?.setDate(minDate.getDate()); + const maxDate = targetDate ? new Date(targetDate) : null; + maxDate?.setDate(maxDate.getDate()); - const maxDate = targetDate ? new Date(targetDate) : null; - maxDate?.setDate(maxDate.getDate()); + const { issues: issuesStore } = useMobxStore(); + const { deleteIssue } = issuesStore; - const handleCopyLink = () => { - const originURL = - typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + const handleDeleteIssue = async () => { + if (!issue || !user) return; - copyTextToClipboard( - `${originURL}/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}` - ).then(() => { - setToastAlert({ - type: "success", - title: "Link copied!", - message: "Issue link copied to clipboard", + await deleteIssue(workspaceSlug, issue.project, issue.id, user); + }; + + const handleCopyLink = () => { + const originURL = + typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + + copyTextToClipboard( + `${originURL}/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}` + ).then(() => { + setToastAlert({ + type: "success", + title: "Link copied!", + message: "Issue link copied to clipboard", + }); }); - }); - }; + }; - return ( -
- {mode === "full" && ( -
-
- {getStateGroupIcon(issue.state_detail.group, "16", "16", issue.state_detail.color)} - {issue.project_detail.identifier}-{issue.sequence_id} -
-
- - {/* */} -
-
- )} -
-
-
- - State -
-
- handleUpdateIssue({ state: val })} - disabled={readOnly} - /> -
-
-
-
- - Assignees -
-
- handleUpdateIssue({ assignees_list: val })} - disabled={readOnly} - /> -
-
-
-
- - Priority -
-
- handleUpdateIssue({ priority: val })} - disabled={readOnly} - /> -
-
-
-
- - Start date + return ( +
+ {mode === "full" && ( +
+
+ {getStateGroupIcon(issue.state_detail.group, "16", "16", issue.state_detail.color)} + {issue.project_detail.identifier}-{issue.sequence_id} +
+
+ + + Open issue + + + + +
-
- {issue.start_date ? ( - - handleUpdateIssue({ - start_date: val, - }) - } - className="bg-custom-background-100" - wrapperClassName="w-full" - maxDate={maxDate ?? undefined} + )} +
+
+
+ + State +
+
+ handleUpdateIssue({ state: val })} disabled={readOnly} /> - ) : ( - Empty - )} +
-
-
-
- - Due date +
+
+ + Assignees +
+
+ handleUpdateIssue({ assignees_list: val })} + disabled={readOnly} + /> +
-
- {issue.target_date ? ( - - handleUpdateIssue({ - target_date: val, - }) - } - className="bg-custom-background-100" - wrapperClassName="w-full" - minDate={minDate ?? undefined} +
+
+ + Priority +
+
+ handleUpdateIssue({ priority: val })} disabled={readOnly} /> - ) : ( - Empty - )} +
-
- {/*
+
+
+ + Start date +
+
+ {issue.start_date ? ( + + handleUpdateIssue({ + start_date: val, + }) + } + className="bg-custom-background-100" + wrapperClassName="w-full" + maxDate={maxDate ?? undefined} + disabled={readOnly} + /> + ) : ( + Empty + )} +
+
+
+
+ + Due date +
+
+ {issue.target_date ? ( + + handleUpdateIssue({ + target_date: val, + }) + } + className="bg-custom-background-100" + wrapperClassName="w-full" + minDate={minDate ?? undefined} + disabled={readOnly} + /> + ) : ( + Empty + )} +
+
+ {/*
Estimate @@ -178,7 +197,7 @@ export const PeekOverviewIssueProperties: React.FC = ({ />
*/} - {/* + {/* {({ open }) => ( <> = ({ )} */} +
-
- ); -}; + ); + } +); diff --git a/apps/app/components/issues/peek-overview/layout.tsx b/apps/app/components/issues/peek-overview/layout.tsx index 32a8481187e..49cd06f3557 100644 --- a/apps/app/components/issues/peek-overview/layout.tsx +++ b/apps/app/components/issues/peek-overview/layout.tsx @@ -2,21 +2,17 @@ import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; -import useSWR from "swr"; - +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; // headless ui import { Dialog, Transition } from "@headlessui/react"; +// hooks +import useUser from "hooks/use-user"; // components import { FullScreenPeekView, SidePeekView } from "components/issues"; -// services -import issuesService from "services/issues.service"; // types import { IIssue } from "types"; -// fetch-keys -import { PROJECT_ISSUES_DETAILS } from "constants/fetch-keys"; -import useUser from "hooks/use-user"; -import { observer } from "mobx-react-lite"; -import { useMobxStore } from "lib/mobx/store-provider"; type Props = { projectId: string; @@ -28,7 +24,8 @@ export type TPeekOverviewModes = "side" | "modal" | "full"; export const IssuePeekOverview: React.FC = observer( ({ projectId, workspaceSlug, readOnly }) => { - const [isOpen, setIsOpen] = useState(false); + const [isSidePeekOpen, setIsSidePeekOpen] = useState(false); + const [isModalPeekOpen, setIsModalPeekOpen] = useState(false); const [peekOverviewMode, setPeekOverviewMode] = useState("side"); const router = useRouter(); @@ -49,8 +46,6 @@ export const IssuePeekOverview: React.FC = observer( pathname: router.pathname, query: { ...query }, }); - - setPeekOverviewMode("side"); }; const handleUpdateIssue = async (formData: Partial) => { @@ -66,15 +61,54 @@ export const IssuePeekOverview: React.FC = observer( }, [getIssueById, peekIssue, projectId, workspaceSlug]); useEffect(() => { - if (peekIssue) setIsOpen(true); - else setIsOpen(false); - }, [peekIssue]); + if (peekIssue) { + if (peekOverviewMode === "side") { + setIsSidePeekOpen(true); + setIsModalPeekOpen(false); + } else { + setIsModalPeekOpen(true); + setIsSidePeekOpen(false); + } + } else { + console.log("Triggered"); + setIsSidePeekOpen(false); + setIsModalPeekOpen(false); + } + }, [peekIssue, peekOverviewMode]); return ( - - - {/* add backdrop conditionally */} - {(peekOverviewMode === "modal" || peekOverviewMode === "full") && ( + <> + + +
+
+ + + setPeekOverviewMode(mode)} + workspaceSlug={workspaceSlug} + /> + + +
+
+
+
+ + = observer( >
- )} -
-
- - +
+ - {(peekOverviewMode === "side" || peekOverviewMode === "modal") && ( - setPeekOverviewMode(mode)} - workspaceSlug={workspaceSlug} - /> - )} - {peekOverviewMode === "full" && ( - setPeekOverviewMode(mode)} - workspaceSlug={workspaceSlug} - /> - )} - - + + {peekOverviewMode === "modal" && ( + setPeekOverviewMode(mode)} + workspaceSlug={workspaceSlug} + /> + )} + {peekOverviewMode === "full" && ( + setPeekOverviewMode(mode)} + workspaceSlug={workspaceSlug} + /> + )} + + +
-
-
-
+
+
+ ); } ); diff --git a/apps/app/components/issues/peek-overview/side-peek-view.tsx b/apps/app/components/issues/peek-overview/side-peek-view.tsx index 1b2da7264c2..ed3b700de7d 100644 --- a/apps/app/components/issues/peek-overview/side-peek-view.tsx +++ b/apps/app/components/issues/peek-overview/side-peek-view.tsx @@ -79,15 +79,8 @@ export const SidePeekView: React.FC = ({
- -
-
- -
-
- - - + +
)} diff --git a/apps/app/store/issues.ts b/apps/app/store/issues.ts index 16a64329d8e..644b6029a17 100644 --- a/apps/app/store/issues.ts +++ b/apps/app/store/issues.ts @@ -1,9 +1,7 @@ // mobx import { action, observable, runInAction, makeAutoObservable } from "mobx"; - // services import issueService from "services/issues.service"; - // types import type { ICurrentUserResponse, IIssue } from "types"; @@ -65,7 +63,7 @@ class IssuesStore { const issues = { ...this.issues, - [issueResponse.id]: { ...issueResponse }, + [issueId]: { ...issueResponse }, }; runInAction(() => { @@ -78,16 +76,6 @@ class IssuesStore { } }; - /** - * For provided query, this function returns all issues that contain query in their name from the issues store. - * @param query - query string - * @returns {IIssue[]} array of issues that contain query in their name - * @example - * getFilteredIssues("issue") // [{ id: "", name: "issue", description: "", parent: null }] - */ - // getFilteredIssues = (query: string): IIssue[] => - // this.issues.filter((i) => i.name.includes(query)); - createIssue = async ( workspaceSlug: string, projectId: string, @@ -124,15 +112,18 @@ class IssuesStore { issueForm: Partial, user: ICurrentUserResponse ) => { - // immediately update the issue in the store - const issues = { ...this.issues }; - issues[issueId] = { ...this.issues[issueId], ...issueForm }; + // keep a copy of the issue in the store + const originalIssue = { ...this.issues[issueId] }; - runInAction(() => { - this.issues = issues; - }); + // immediately update the issue in the store + let updatedIssue = { ...this.issues[issueId] }; + updatedIssue = { ...updatedIssue, ...issueForm }; try { + runInAction(() => { + this.issues[issueId] = updatedIssue; + }); + // make a patch request to update the issue const issueResponse: IIssue = await issueService.patchIssue( workspaceSlug, @@ -149,6 +140,11 @@ class IssuesStore { this.issues = updatedIssues; }); } catch (error) { + // if there is an error, revert the changes + runInAction(() => { + this.issues[issueId] = originalIssue; + }); + return error; } }; @@ -159,15 +155,15 @@ class IssuesStore { issueId: string, user: ICurrentUserResponse ) => { - try { - issueService.deleteIssue(workspaceSlug, projectId, issueId, user); - - const issues = { ...this.issues }; - delete issues[issueId]; + const issues = { ...this.issues }; + delete issues[issueId]; + try { runInAction(() => { this.issues = issues; }); + + issueService.deleteIssue(workspaceSlug, projectId, issueId, user); } catch (error) { console.error("Deleting issue error", error); } From 8b0941b48b92764cced93c53b9f8a286cc20b660 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 29 Aug 2023 15:42:07 +0530 Subject: [PATCH 3/6] chore: update open issue button --- .../issues/peek-overview/header.tsx | 26 +++++-------------- .../issues/peek-overview/issue-properties.tsx | 7 ----- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/apps/app/components/issues/peek-overview/header.tsx b/apps/app/components/issues/peek-overview/header.tsx index 2ca89b8780e..cdaeb65eb2e 100644 --- a/apps/app/components/issues/peek-overview/header.tsx +++ b/apps/app/components/issues/peek-overview/header.tsx @@ -1,3 +1,5 @@ +import Link from "next/link"; + // mobx import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; @@ -7,13 +9,12 @@ import useUser from "hooks/use-user"; // ui import { CustomSelect, Icon } from "components/ui"; // icons -import { CloseFullscreen, East, OpenInFull } from "@mui/icons-material"; +import { East, OpenInFull } from "@mui/icons-material"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; // types import { IIssue } from "types"; import { TPeekOverviewModes } from "./layout"; -import Link from "next/link"; type Props = { handleClose: () => void; @@ -80,23 +81,15 @@ export const PeekOverviewHeader: React.FC = observer( /> )} - {mode === "modal" || mode === "full" ? ( - - ) : ( - - )} + + setMode(val)} @@ -127,11 +120,6 @@ export const PeekOverviewHeader: React.FC = observer(
{(mode === "side" || mode === "modal") && (
- - - Open issue - - diff --git a/apps/app/components/issues/peek-overview/issue-properties.tsx b/apps/app/components/issues/peek-overview/issue-properties.tsx index ea1bd7ee277..b823112329a 100644 --- a/apps/app/components/issues/peek-overview/issue-properties.tsx +++ b/apps/app/components/issues/peek-overview/issue-properties.tsx @@ -1,5 +1,3 @@ -import Link from "next/link"; - // mobx import { useMobxStore } from "lib/mobx/store-provider"; import { observer } from "mobx-react-lite"; @@ -80,11 +78,6 @@ export const PeekOverviewIssueProperties: React.FC = observer( {issue.project_detail.identifier}-{issue.sequence_id}
- - - Open issue - - From 3de60b0773ee69c3030200897e5f23eef3eccfc3 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 30 Aug 2023 12:52:46 +0530 Subject: [PATCH 4/6] fix: issue mutation after any crud action --- apps/app/components/core/views/all-views.tsx | 6 - .../spreadsheet-view/spreadsheet-view.tsx | 153 +++++----- .../peek-overview/full-screen-peek-view.tsx | 4 + .../issues/peek-overview/header.tsx | 167 +++++------ .../issues/peek-overview/issue-properties.tsx | 272 +++++++++--------- .../issues/peek-overview/layout.tsx | 20 +- .../issues/peek-overview/side-peek-view.tsx | 4 + .../app/hooks/use-spreadsheet-issues-view.tsx | 15 +- apps/app/store/issues.ts | 3 +- 9 files changed, 330 insertions(+), 314 deletions(-) diff --git a/apps/app/components/core/views/all-views.tsx b/apps/app/components/core/views/all-views.tsx index 254bc277fc3..52f47de3190 100644 --- a/apps/app/components/core/views/all-views.tsx +++ b/apps/app/components/core/views/all-views.tsx @@ -20,7 +20,6 @@ import { SpreadsheetView, GanttChartView, } from "components/core"; -import { IssuePeekOverview } from "components/issues"; // ui import { EmptyState, Spinner } from "components/ui"; // icons @@ -98,11 +97,6 @@ export const AllViews: React.FC = ({ return ( <> - {(provided, snapshot) => ( diff --git a/apps/app/components/core/views/spreadsheet-view/spreadsheet-view.tsx b/apps/app/components/core/views/spreadsheet-view/spreadsheet-view.tsx index 0b2e785d68f..1076f30d002 100644 --- a/apps/app/components/core/views/spreadsheet-view/spreadsheet-view.tsx +++ b/apps/app/components/core/views/spreadsheet-view/spreadsheet-view.tsx @@ -6,6 +6,7 @@ import { useRouter } from "next/router"; // components import { SpreadsheetColumns, SpreadsheetIssues } from "components/core"; import { CustomMenu, Spinner } from "components/ui"; +import { IssuePeekOverview } from "components/issues"; // hooks import useIssuesProperties from "hooks/use-issue-properties"; import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; @@ -38,7 +39,7 @@ export const SpreadsheetView: React.FC = ({ const type = cycleId ? "cycle" : moduleId ? "module" : "issue"; - const { spreadsheetIssues } = useSpreadsheetIssuesView(); + const { spreadsheetIssues, mutateIssues } = useSpreadsheetIssuesView(); const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); @@ -59,80 +60,88 @@ export const SpreadsheetView: React.FC = ({ .join(" "); return ( -
-
- -
- {spreadsheetIssues ? ( -
- {spreadsheetIssues.map((issue: IIssue, index) => ( - - ))} -
- {type === "issue" ? ( - - ) : ( - !disableUserActions && ( - - - Add Issue - - } - position="left" - optionsClassName="left-5 !w-36" - noBorder + <> + mutateIssues()} + projectId={projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} + readOnly={disableUserActions} + /> +
+
+ +
+ {spreadsheetIssues ? ( +
+ {spreadsheetIssues.map((issue: IIssue, index) => ( + + ))} +
+ {type === "issue" ? ( + + ) : ( + !disableUserActions && ( + + + Add Issue + + } + position="left" + optionsClassName="left-5 !w-36" + noBorder > - Create new - - {openIssuesListModal && ( - - Add an existing issue + { + const e = new KeyboardEvent("keydown", { key: "c" }); + document.dispatchEvent(e); + }} + > + Create new - )} - - ) - )} + {openIssuesListModal && ( + + Add an existing issue + + )} + + ) + )} +
-
- ) : ( - - )} -
+ ) : ( + + )} +
+ ); }; diff --git a/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx b/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx index ad36b1f195b..9c04e0b6ae5 100644 --- a/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx +++ b/apps/app/components/issues/peek-overview/full-screen-peek-view.tsx @@ -13,6 +13,7 @@ import { IIssue } from "types"; type Props = { handleClose: () => void; + handleDeleteIssue: () => void; handleUpdateIssue: (formData: Partial) => Promise; issue: IIssue | undefined; mode: TPeekOverviewModes; @@ -23,6 +24,7 @@ type Props = { export const FullScreenPeekView: React.FC = ({ handleClose, + handleDeleteIssue, handleUpdateIssue, issue, mode, @@ -35,6 +37,7 @@ export const FullScreenPeekView: React.FC = ({
= ({
{issue ? ( void; + handleDeleteIssue: () => void; issue: IIssue | undefined; mode: TPeekOverviewModes; setMode: (mode: TPeekOverviewModes) => void; @@ -42,93 +39,87 @@ const peekModes: { }, ]; -export const PeekOverviewHeader: React.FC = observer( - ({ issue, handleClose, mode, setMode, workspaceSlug }) => { - const { setToastAlert } = useToast(); +export const PeekOverviewHeader: React.FC = ({ + issue, + handleClose, + handleDeleteIssue, + mode, + setMode, + workspaceSlug, +}) => { + const { setToastAlert } = useToast(); - const { user } = useUser(); + const handleCopyLink = () => { + const urlToCopy = window.location.href; - const { issues: issuesStore } = useMobxStore(); - const { deleteIssue } = issuesStore; - - const handleDeleteIssue = async () => { - if (!issue || !user) return; - - await deleteIssue(workspaceSlug, issue.project, issue.id, user); - }; - - const handleCopyLink = () => { - const urlToCopy = window.location.href; - - copyTextToClipboard(urlToCopy).then(() => { - setToastAlert({ - type: "success", - title: "Link copied!", - message: "Issue link copied to clipboard", - }); + copyTextToClipboard(urlToCopy).then(() => { + setToastAlert({ + type: "success", + title: "Link copied!", + message: "Issue link copied to clipboard", }); - }; + }); + }; - return ( -
-
- {mode === "side" && ( - - )} - - - - - - setMode(val)} - customButton={ - - } - position="left" - > - {peekModes.map((mode) => ( - -
- - {mode.label} -
-
- ))} -
-
- {(mode === "side" || mode === "modal") && ( -
- - -
+ return ( +
+
+ {mode === "side" && ( + )} + + + + + + setMode(val)} + customButton={ + + } + position="left" + > + {peekModes.map((mode) => ( + +
+ + {mode.label} +
+
+ ))} +
- ); - } -); + {(mode === "side" || mode === "modal") && ( +
+ + +
+ )} +
+ ); +}; diff --git a/apps/app/components/issues/peek-overview/issue-properties.tsx b/apps/app/components/issues/peek-overview/issue-properties.tsx index b823112329a..2c8b4d57275 100644 --- a/apps/app/components/issues/peek-overview/issue-properties.tsx +++ b/apps/app/components/issues/peek-overview/issue-properties.tsx @@ -1,5 +1,4 @@ // mobx -import { useMobxStore } from "lib/mobx/store-provider"; import { observer } from "mobx-react-lite"; // headless ui import { Disclosure } from "@headlessui/react"; @@ -23,6 +22,7 @@ import { copyTextToClipboard } from "helpers/string.helper"; import { IIssue } from "types"; type Props = { + handleDeleteIssue: () => void; handleUpdateIssue: (formData: Partial) => Promise; issue: IIssue; mode: TPeekOverviewModes; @@ -30,154 +30,149 @@ type Props = { workspaceSlug: string; }; -export const PeekOverviewIssueProperties: React.FC = observer( - ({ handleUpdateIssue, issue, mode, readOnly, workspaceSlug }) => { - const { setToastAlert } = useToast(); +export const PeekOverviewIssueProperties: React.FC = ({ + handleDeleteIssue, + handleUpdateIssue, + issue, + mode, + readOnly, + workspaceSlug, +}) => { + const { setToastAlert } = useToast(); - const { user } = useUser(); + const startDate = issue.start_date; + const targetDate = issue.target_date; - const startDate = issue.start_date; - const targetDate = issue.target_date; + const minDate = startDate ? new Date(startDate) : null; + minDate?.setDate(minDate.getDate()); - const minDate = startDate ? new Date(startDate) : null; - minDate?.setDate(minDate.getDate()); + const maxDate = targetDate ? new Date(targetDate) : null; + maxDate?.setDate(maxDate.getDate()); - const maxDate = targetDate ? new Date(targetDate) : null; - maxDate?.setDate(maxDate.getDate()); + const handleCopyLink = () => { + const originURL = + typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; - const { issues: issuesStore } = useMobxStore(); - const { deleteIssue } = issuesStore; - - const handleDeleteIssue = async () => { - if (!issue || !user) return; - - await deleteIssue(workspaceSlug, issue.project, issue.id, user); - }; - - const handleCopyLink = () => { - const originURL = - typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; - - copyTextToClipboard( - `${originURL}/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}` - ).then(() => { - setToastAlert({ - type: "success", - title: "Link copied!", - message: "Issue link copied to clipboard", - }); + copyTextToClipboard( + `${originURL}/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}` + ).then(() => { + setToastAlert({ + type: "success", + title: "Link copied!", + message: "Issue link copied to clipboard", }); - }; + }); + }; - return ( -
- {mode === "full" && ( -
-
- {getStateGroupIcon(issue.state_detail.group, "16", "16", issue.state_detail.color)} - {issue.project_detail.identifier}-{issue.sequence_id} -
-
- - -
+ return ( +
+ {mode === "full" && ( +
+
+ {getStateGroupIcon(issue.state_detail.group, "16", "16", issue.state_detail.color)} + {issue.project_detail.identifier}-{issue.sequence_id} +
+
+ +
- )} -
-
-
- - State -
-
- handleUpdateIssue({ state: val })} - disabled={readOnly} - /> -
+
+ )} +
+
+
+ + State
-
-
- - Assignees -
-
- handleUpdateIssue({ assignees_list: val })} - disabled={readOnly} - /> -
+
+ handleUpdateIssue({ state: val })} + disabled={readOnly} + />
-
-
- - Priority -
-
- handleUpdateIssue({ priority: val })} +
+
+
+ + Assignees +
+
+ handleUpdateIssue({ assignees_list: val })} + disabled={readOnly} + /> +
+
+
+
+ + Priority +
+
+ handleUpdateIssue({ priority: val })} + disabled={readOnly} + /> +
+
+
+
+ + Start date +
+
+ {issue.start_date ? ( + + handleUpdateIssue({ + start_date: val, + }) + } + className="bg-custom-background-100" + wrapperClassName="w-full" + maxDate={maxDate ?? undefined} disabled={readOnly} /> -
+ ) : ( + Empty + )}
-
-
- - Start date -
-
- {issue.start_date ? ( - - handleUpdateIssue({ - start_date: val, - }) - } - className="bg-custom-background-100" - wrapperClassName="w-full" - maxDate={maxDate ?? undefined} - disabled={readOnly} - /> - ) : ( - Empty - )} -
+
+
+
+ + Due date
-
-
- - Due date -
-
- {issue.target_date ? ( - - handleUpdateIssue({ - target_date: val, - }) - } - className="bg-custom-background-100" - wrapperClassName="w-full" - minDate={minDate ?? undefined} - disabled={readOnly} - /> - ) : ( - Empty - )} -
+
+ {issue.target_date ? ( + + handleUpdateIssue({ + target_date: val, + }) + } + className="bg-custom-background-100" + wrapperClassName="w-full" + minDate={minDate ?? undefined} + disabled={readOnly} + /> + ) : ( + Empty + )}
- {/*
+
+ {/*
Estimate @@ -190,7 +185,7 @@ export const PeekOverviewIssueProperties: React.FC = observer( />
*/} - {/* + {/* {({ open }) => ( <> = observer( )} */} -
- ); - } -); +
+ ); +}; diff --git a/apps/app/components/issues/peek-overview/layout.tsx b/apps/app/components/issues/peek-overview/layout.tsx index 49cd06f3557..50fa5df6829 100644 --- a/apps/app/components/issues/peek-overview/layout.tsx +++ b/apps/app/components/issues/peek-overview/layout.tsx @@ -15,15 +15,16 @@ import { FullScreenPeekView, SidePeekView } from "components/issues"; import { IIssue } from "types"; type Props = { + handleMutation: () => void; projectId: string; - workspaceSlug: string; readOnly: boolean; + workspaceSlug: string; }; export type TPeekOverviewModes = "side" | "modal" | "full"; export const IssuePeekOverview: React.FC = observer( - ({ projectId, workspaceSlug, readOnly }) => { + ({ handleMutation, projectId, readOnly, workspaceSlug }) => { const [isSidePeekOpen, setIsSidePeekOpen] = useState(false); const [isModalPeekOpen, setIsModalPeekOpen] = useState(false); const [peekOverviewMode, setPeekOverviewMode] = useState("side"); @@ -32,7 +33,7 @@ export const IssuePeekOverview: React.FC = observer( const { peekIssue } = router.query; const { issues: issuesStore } = useMobxStore(); - const { getIssueById, issues, updateIssue } = issuesStore; + const { deleteIssue, getIssueById, issues, updateIssue } = issuesStore; const issue = issues[peekIssue?.toString() ?? ""]; @@ -52,6 +53,16 @@ export const IssuePeekOverview: React.FC = observer( if (!issue || !user) return; await updateIssue(workspaceSlug, projectId, issue.id, formData, user); + handleMutation(); + }; + + const handleDeleteIssue = async () => { + if (!issue || !user) return; + + await deleteIssue(workspaceSlug, projectId, issue.id, user); + handleMutation(); + + handleClose(); }; useEffect(() => { @@ -94,6 +105,7 @@ export const IssuePeekOverview: React.FC = observer( = observer( {peekOverviewMode === "modal" && ( = observer( {peekOverviewMode === "full" && ( void; + handleDeleteIssue: () => void; handleUpdateIssue: (formData: Partial) => Promise; issue: IIssue | undefined; mode: TPeekOverviewModes; @@ -23,6 +24,7 @@ type Props = { export const SidePeekView: React.FC = ({ handleClose, + handleDeleteIssue, handleUpdateIssue, issue, mode, @@ -34,6 +36,7 @@ export const SidePeekView: React.FC = ({
= ({ {/* issue properties */}
{ sub_issue: "false", }; - const { data: projectSpreadsheetIssues } = useSWR( + const { data: projectSpreadsheetIssues, mutate: mutateProjectSpreadsheetIssues } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params) : null, @@ -58,7 +58,7 @@ const useSpreadsheetIssuesView = () => { : null ); - const { data: cycleSpreadsheetIssues } = useSWR( + const { data: cycleSpreadsheetIssues, mutate: mutateCycleSpreadsheetIssues } = useSWR( workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) : null, @@ -73,7 +73,7 @@ const useSpreadsheetIssuesView = () => { : null ); - const { data: moduleSpreadsheetIssues } = useSWR( + const { data: moduleSpreadsheetIssues, mutate: mutateModuleSpreadsheetIssues } = useSWR( workspaceSlug && projectId && moduleId ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) : null, @@ -88,7 +88,7 @@ const useSpreadsheetIssuesView = () => { : null ); - const { data: viewSpreadsheetIssues } = useSWR( + const { data: viewSpreadsheetIssues, mutate: mutateViewSpreadsheetIssues } = useSWR( workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null, workspaceSlug && projectId && viewId && params ? () => @@ -106,6 +106,13 @@ const useSpreadsheetIssuesView = () => { return { issueView, + mutateIssues: cycleId + ? mutateCycleSpreadsheetIssues + : moduleId + ? mutateModuleSpreadsheetIssues + : viewId + ? mutateViewSpreadsheetIssues + : mutateProjectSpreadsheetIssues, spreadsheetIssues: spreadsheetIssues ?? [], orderBy, setOrderBy, diff --git a/apps/app/store/issues.ts b/apps/app/store/issues.ts index 644b6029a17..538c5e2a9a2 100644 --- a/apps/app/store/issues.ts +++ b/apps/app/store/issues.ts @@ -116,8 +116,7 @@ class IssuesStore { const originalIssue = { ...this.issues[issueId] }; // immediately update the issue in the store - let updatedIssue = { ...this.issues[issueId] }; - updatedIssue = { ...updatedIssue, ...issueForm }; + const updatedIssue = { ...originalIssue, ...issueForm }; try { runInAction(() => { From bf1c5919b14cb9fc8fc1ea67aaf395afa2048589 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 30 Aug 2023 12:56:02 +0530 Subject: [PATCH 5/6] chore: remove peek overview from gantt --- .../components/issues/gantt-chart/blocks.tsx | 30 ++++--------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/apps/app/components/issues/gantt-chart/blocks.tsx b/apps/app/components/issues/gantt-chart/blocks.tsx index a79f16712ce..3ab7ea90bb6 100644 --- a/apps/app/components/issues/gantt-chart/blocks.tsx +++ b/apps/app/components/issues/gantt-chart/blocks.tsx @@ -11,23 +11,13 @@ import { IIssue } from "types"; export const IssueGanttBlock = ({ data }: { data: IIssue }) => { const router = useRouter(); - - const openPeekOverview = () => { - if (!data) return; - - const { query } = router; - - router.push({ - pathname: router.pathname, - query: { ...query, peekIssue: data.id }, - }); - }; + const { workspaceSlug } = router.query; return (
router.push(`/${workspaceSlug}/projects/${data?.project}/issues/${data?.id}`)} >
{ // rendering issues on gantt sidebar export const IssueGanttSidebarBlock = ({ data }: { data: IIssue }) => { const router = useRouter(); - - const openPeekOverview = () => { - if (!data) return; - - const { query } = router; - - router.push({ - pathname: router.pathname, - query: { ...query, peekIssue: data.id }, - }); - }; + const { workspaceSlug } = router.query; const duration = findTotalDaysInRange(data?.start_date ?? "", data?.target_date ?? "", true); return (
router.push(`/${workspaceSlug}/projects/${data?.project}/issues/${data?.id}`)} > {getStateGroupIcon(data?.state_detail?.group, "14", "14", data?.state_detail?.color)}
From b5646a38cd1f4d35a361c7740438ebbb99815349 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 30 Aug 2023 13:01:43 +0530 Subject: [PATCH 6/6] chore: refactor code --- apps/app/components/core/views/all-views.tsx | 206 +++++++++---------- 1 file changed, 102 insertions(+), 104 deletions(-) diff --git a/apps/app/components/core/views/all-views.tsx b/apps/app/components/core/views/all-views.tsx index 52f47de3190..79d5d6b11f0 100644 --- a/apps/app/components/core/views/all-views.tsx +++ b/apps/app/components/core/views/all-views.tsx @@ -96,111 +96,109 @@ export const AllViews: React.FC = ({ ); return ( - <> - - - {(provided, snapshot) => ( -
- - Drop here to delete the issue. -
- )} -
- {groupedIssues ? ( - !isEmpty || - issueView === "kanban" || - issueView === "calendar" || - issueView === "gantt_chart" ? ( - <> - {issueView === "list" ? ( - - ) : issueView === "kanban" ? ( - - ) : issueView === "calendar" ? ( - - ) : issueView === "spreadsheet" ? ( - - ) : ( - issueView === "gantt_chart" && - )} - - ) : router.pathname.includes("archived-issues") ? ( - { - router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`); - }, - }} - /> - ) : ( - - ) - ) : ( -
- + + + {(provided, snapshot) => ( +
+ + Drop here to delete the issue.
)} -
- + + {groupedIssues ? ( + !isEmpty || + issueView === "kanban" || + issueView === "calendar" || + issueView === "gantt_chart" ? ( + <> + {issueView === "list" ? ( + + ) : issueView === "kanban" ? ( + + ) : issueView === "calendar" ? ( + + ) : issueView === "spreadsheet" ? ( + + ) : ( + issueView === "gantt_chart" && + )} + + ) : router.pathname.includes("archived-issues") ? ( + { + router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`); + }, + }} + /> + ) : ( + + ) + ) : ( +
+ +
+ )} + ); };