From 19a28ea9d5018b941eb117ec768d3895435b979c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 26 Sep 2023 17:28:16 +0530 Subject: [PATCH] style: spreadsheet view revamp and code refactor --- .../assignee-column/assignee-column.tsx | 72 +++ .../spreadsheet-view/assignee-column/index.ts | 2 + .../spreadsheet-assignee-column.tsx | 62 +++ .../created-on-column/created-on-column.tsx | 34 ++ .../created-on-column/index.ts | 2 + .../spreadsheet-created-on-column.tsx | 62 +++ .../due-date-column/due-date-column.tsx | 38 ++ .../spreadsheet-view/due-date-column/index.ts | 2 + .../spreadsheet-due-date-column.tsx | 62 +++ .../estimate-column/estimate-column.tsx | 38 ++ .../spreadsheet-view/estimate-column/index.ts | 2 + .../spreadsheet-estimate-column.tsx | 62 +++ .../core/views/spreadsheet-view/index.ts | 14 +- .../spreadsheet-view/issue-column/index.ts | 2 + .../issue-column/issue-column.tsx | 179 ++++++ .../spreadsheet-issue-column.tsx} | 22 +- .../spreadsheet-view/label-column/index.ts | 2 + .../label-column/label-column.tsx | 47 ++ .../label-column/spreadsheet-label-column.tsx | 62 +++ .../spreadsheet-view/priority-column/index.ts | 2 + .../priority-column/priority-column.tsx | 64 +++ .../spreadsheet-priority-column.tsx | 62 +++ .../views/spreadsheet-view/single-issue.tsx | 522 ------------------ .../spreadsheet-view/spreadsheet-view.tsx | 315 +++++++---- .../start-date-column/index.ts | 2 + .../spreadsheet-start-date-column.tsx | 62 +++ .../start-date-column/start-date-column.tsx | 38 ++ .../spreadsheet-view/state-column/index.ts | 2 + .../state-column/spreadsheet-state-column.tsx | 62 +++ .../state-column/state-column.tsx | 87 +++ .../updated-on-column/index.ts | 2 + .../spreadsheet-updated-on-column.tsx | 62 +++ .../updated-on-column/updated-on-column.tsx | 34 ++ web/constants/spreadsheet.ts | 86 --- 34 files changed, 1445 insertions(+), 723 deletions(-) create mode 100644 web/components/core/views/spreadsheet-view/assignee-column/assignee-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/assignee-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/assignee-column/spreadsheet-assignee-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/created-on-column/created-on-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/created-on-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/created-on-column/spreadsheet-created-on-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/due-date-column/due-date-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/due-date-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/due-date-column/spreadsheet-due-date-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/estimate-column/estimate-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/estimate-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/estimate-column/spreadsheet-estimate-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/issue-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/issue-column/issue-column.tsx rename web/components/core/views/spreadsheet-view/{spreadsheet-issues.tsx => issue-column/spreadsheet-issue-column.tsx} (80%) create mode 100644 web/components/core/views/spreadsheet-view/label-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/label-column/label-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/label-column/spreadsheet-label-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/priority-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/priority-column/priority-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/priority-column/spreadsheet-priority-column.tsx delete mode 100644 web/components/core/views/spreadsheet-view/single-issue.tsx create mode 100644 web/components/core/views/spreadsheet-view/start-date-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/start-date-column/spreadsheet-start-date-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/start-date-column/start-date-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/state-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/state-column/spreadsheet-state-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/state-column/state-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/updated-on-column/index.ts create mode 100644 web/components/core/views/spreadsheet-view/updated-on-column/spreadsheet-updated-on-column.tsx create mode 100644 web/components/core/views/spreadsheet-view/updated-on-column/updated-on-column.tsx delete mode 100644 web/constants/spreadsheet.ts diff --git a/web/components/core/views/spreadsheet-view/assignee-column/assignee-column.tsx b/web/components/core/views/spreadsheet-view/assignee-column/assignee-column.tsx new file mode 100644 index 000000000..2f1231924 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/assignee-column/assignee-column.tsx @@ -0,0 +1,72 @@ +import React from "react"; + +import { useRouter } from "next/router"; + +// components +import { MembersSelect } from "components/project"; +// services +import trackEventServices from "services/track-event.service"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const AssigneeColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => { + const router = useRouter(); + + const { workspaceSlug } = router.query; + + const handleAssigneeChange = (data: any) => { + const newData = issue.assignees ?? []; + + if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); + else newData.push(data); + + partialUpdateIssue({ assignees_list: data }, issue); + + trackEventServices.trackIssuePartialPropertyUpdateEvent( + { + workspaceSlug, + workspaceId: issue.workspace, + projectId: issue.project_detail.id, + projectIdentifier: issue.project_detail.identifier, + projectName: issue.project_detail.name, + issueId: issue.id, + }, + "ISSUE_PROPERTY_UPDATE_ASSIGNEE", + user + ); + }; + + return ( +
+ + {properties.assignee && ( + + )} + +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/assignee-column/index.ts b/web/components/core/views/spreadsheet-view/assignee-column/index.ts new file mode 100644 index 000000000..8750550be --- /dev/null +++ b/web/components/core/views/spreadsheet-view/assignee-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-assignee-column"; +export * from "./assignee-column"; diff --git a/web/components/core/views/spreadsheet-view/assignee-column/spreadsheet-assignee-column.tsx b/web/components/core/views/spreadsheet-view/assignee-column/spreadsheet-assignee-column.tsx new file mode 100644 index 000000000..a864126c6 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/assignee-column/spreadsheet-assignee-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { AssigneeColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetAssigneeColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/created-on-column/created-on-column.tsx b/web/components/core/views/spreadsheet-view/created-on-column/created-on-column.tsx new file mode 100644 index 000000000..15c3f7810 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/created-on-column/created-on-column.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; +// helper +import { renderLongDetailDateFormat } from "helpers/date-time.helper"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const CreatedOnColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => ( +
+ + {properties.created_on && ( +
+ {renderLongDetailDateFormat(issue.created_at)} +
+ )} +
+
+); diff --git a/web/components/core/views/spreadsheet-view/created-on-column/index.ts b/web/components/core/views/spreadsheet-view/created-on-column/index.ts new file mode 100644 index 000000000..28781aa17 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/created-on-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-created-on-column"; +export * from "./created-on-column"; diff --git a/web/components/core/views/spreadsheet-view/created-on-column/spreadsheet-created-on-column.tsx b/web/components/core/views/spreadsheet-view/created-on-column/spreadsheet-created-on-column.tsx new file mode 100644 index 000000000..3ce3f2dbe --- /dev/null +++ b/web/components/core/views/spreadsheet-view/created-on-column/spreadsheet-created-on-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { CreatedOnColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetCreatedOnColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/due-date-column/due-date-column.tsx b/web/components/core/views/spreadsheet-view/due-date-column/due-date-column.tsx new file mode 100644 index 000000000..7e258f2f6 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/due-date-column/due-date-column.tsx @@ -0,0 +1,38 @@ +import React from "react"; + +// components +import { ViewDueDateSelect } from "components/issues"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const DueDateColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => ( +
+ + {properties.due_date && ( + + )} + +
+); diff --git a/web/components/core/views/spreadsheet-view/due-date-column/index.ts b/web/components/core/views/spreadsheet-view/due-date-column/index.ts new file mode 100644 index 000000000..64b454877 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/due-date-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-due-date-column"; +export * from "./due-date-column"; diff --git a/web/components/core/views/spreadsheet-view/due-date-column/spreadsheet-due-date-column.tsx b/web/components/core/views/spreadsheet-view/due-date-column/spreadsheet-due-date-column.tsx new file mode 100644 index 000000000..1cd2eac26 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/due-date-column/spreadsheet-due-date-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { DueDateColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetDueDateColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/estimate-column/estimate-column.tsx b/web/components/core/views/spreadsheet-view/estimate-column/estimate-column.tsx new file mode 100644 index 000000000..f763e7322 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/estimate-column/estimate-column.tsx @@ -0,0 +1,38 @@ +import React from "react"; + +// components +import { ViewEstimateSelect } from "components/issues"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const EstimateColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => ( +
+ + {properties.estimate && ( + + )} + +
+); diff --git a/web/components/core/views/spreadsheet-view/estimate-column/index.ts b/web/components/core/views/spreadsheet-view/estimate-column/index.ts new file mode 100644 index 000000000..31f07e6a7 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/estimate-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-estimate-column"; +export * from "./estimate-column"; diff --git a/web/components/core/views/spreadsheet-view/estimate-column/spreadsheet-estimate-column.tsx b/web/components/core/views/spreadsheet-view/estimate-column/spreadsheet-estimate-column.tsx new file mode 100644 index 000000000..a1cc74ad0 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/estimate-column/spreadsheet-estimate-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { EstimateColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetEstimateColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/index.ts b/web/components/core/views/spreadsheet-view/index.ts index 7729d5e93..e72819dad 100644 --- a/web/components/core/views/spreadsheet-view/index.ts +++ b/web/components/core/views/spreadsheet-view/index.ts @@ -1,4 +1,14 @@ +export * from "./assignee-column"; +export * from "./created-on-column"; +export * from "./due-date-column"; +export * from "./estimate-column"; +export * from "./issue-column"; +export * from "./label-column"; +export * from "./priority-column"; +export * from "./start-date-column"; +export * from "./state-column"; +export * from "./updated-on-column"; export * from "./spreadsheet-view"; -export * from "./single-issue"; +export * from "./issue-column/issue-column"; export * from "./spreadsheet-columns"; -export * from "./spreadsheet-issues"; +export * from "./issue-column/spreadsheet-issue-column"; diff --git a/web/components/core/views/spreadsheet-view/issue-column/index.ts b/web/components/core/views/spreadsheet-view/issue-column/index.ts new file mode 100644 index 000000000..b8d09d1df --- /dev/null +++ b/web/components/core/views/spreadsheet-view/issue-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-issue-column"; +export * from "./issue-column"; diff --git a/web/components/core/views/spreadsheet-view/issue-column/issue-column.tsx b/web/components/core/views/spreadsheet-view/issue-column/issue-column.tsx new file mode 100644 index 000000000..c1df89c92 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/issue-column/issue-column.tsx @@ -0,0 +1,179 @@ +import React, { useState } from "react"; + +import { useRouter } from "next/router"; + +// components +import { Popover2 } from "@blueprintjs/popover2"; +// icons +import { Icon } from "components/ui"; +import { + EllipsisHorizontalIcon, + LinkIcon, + PencilIcon, + TrashIcon, +} from "@heroicons/react/24/outline"; +// hooks +import useToast from "hooks/use-toast"; +// types +import { IIssue, Properties, UserAuth } from "types"; +// helper +import { copyTextToClipboard } from "helpers/string.helper"; + +type Props = { + issue: IIssue; + projectId: string; + expanded: boolean; + handleToggleExpand: (issueId: string) => void; + properties: Properties; + handleEditIssue: (issue: IIssue) => void; + handleDeleteIssue: (issue: IIssue) => void; + setCurrentProjectId: React.Dispatch>; + disableUserActions: boolean; + userAuth: UserAuth; + nestingLevel: number; +}; + +export const IssueColumn: React.FC = ({ + issue, + projectId, + expanded, + handleToggleExpand, + properties, + handleEditIssue, + handleDeleteIssue, + setCurrentProjectId, + disableUserActions, + userAuth, + nestingLevel, +}) => { + const [isOpen, setIsOpen] = useState(false); + + const router = useRouter(); + + const { workspaceSlug } = router.query; + + const { setToastAlert } = useToast(); + + const openPeekOverview = () => { + const { query } = router; + setCurrentProjectId(issue.project_detail.id); + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: issue.id }, + }); + }; + + 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 paddingLeft = `${nestingLevel * 54}px`; + + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; + + return ( +
+
+
+ {properties.key && ( + + {issue.project_detail?.identifier}-{issue.sequence_id} + + )} + {!isNotAllowed && !disableUserActions && ( +
+ setIsOpen(nextOpenState)} + content={ +
+ + + + + +
+ } + placement="bottom-start" + > + +
+
+ )} +
+ + {issue.sub_issues_count > 0 && ( +
+ +
+ )} +
+ + + +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/spreadsheet-issues.tsx b/web/components/core/views/spreadsheet-view/issue-column/spreadsheet-issue-column.tsx similarity index 80% rename from web/components/core/views/spreadsheet-view/spreadsheet-issues.tsx rename to web/components/core/views/spreadsheet-view/issue-column/spreadsheet-issue-column.tsx index 3cefd60e1..966852a5b 100644 --- a/web/components/core/views/spreadsheet-view/spreadsheet-issues.tsx +++ b/web/components/core/views/spreadsheet-view/issue-column/spreadsheet-issue-column.tsx @@ -1,40 +1,34 @@ import React from "react"; // components -import { SingleSpreadsheetIssue } from "components/core"; +import { IssueColumn } from "components/core"; // hooks import useSubIssue from "hooks/use-sub-issue"; // types -import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types"; +import { IIssue, Properties, UserAuth } from "types"; type Props = { issue: IIssue; projectId: string; - index: number; expandedIssues: string[]; setExpandedIssues: React.Dispatch>; properties: Properties; handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; setCurrentProjectId: React.Dispatch>; - gridTemplateColumns: string; disableUserActions: boolean; - user: ICurrentUserResponse | undefined; userAuth: UserAuth; nestingLevel?: number; }; -export const SpreadsheetIssues: React.FC = ({ - index, +export const SpreadsheetIssuesColumn: React.FC = ({ issue, projectId, expandedIssues, setExpandedIssues, - gridTemplateColumns, properties, handleIssueAction, setCurrentProjectId, disableUserActions, - user, userAuth, nestingLevel = 0, }) => { @@ -57,19 +51,16 @@ export const SpreadsheetIssues: React.FC = ({ return (
- handleIssueAction(issue, "edit")} handleDeleteIssue={() => handleIssueAction(issue, "delete")} setCurrentProjectId={setCurrentProjectId} disableUserActions={disableUserActions} - user={user} userAuth={userAuth} nestingLevel={nestingLevel} /> @@ -79,19 +70,16 @@ export const SpreadsheetIssues: React.FC = ({ subIssues && subIssues.length > 0 && subIssues.map((subIssue: IIssue) => ( - diff --git a/web/components/core/views/spreadsheet-view/label-column/index.ts b/web/components/core/views/spreadsheet-view/label-column/index.ts new file mode 100644 index 000000000..a1b69c1a9 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/label-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-label-column"; +export * from "./label-column"; diff --git a/web/components/core/views/spreadsheet-view/label-column/label-column.tsx b/web/components/core/views/spreadsheet-view/label-column/label-column.tsx new file mode 100644 index 000000000..cad1e7666 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/label-column/label-column.tsx @@ -0,0 +1,47 @@ +import React from "react"; + +// components +import { LabelSelect } from "components/project"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const LabelColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => { + const handleLabelChange = (data: any) => { + partialUpdateIssue({ labels_list: data }, issue); + }; + + return ( +
+ + {properties.labels && ( + + )} + +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/label-column/spreadsheet-label-column.tsx b/web/components/core/views/spreadsheet-view/label-column/spreadsheet-label-column.tsx new file mode 100644 index 000000000..5ab77e909 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/label-column/spreadsheet-label-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { LabelColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetLabelColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/priority-column/index.ts b/web/components/core/views/spreadsheet-view/priority-column/index.ts new file mode 100644 index 000000000..fc542331e --- /dev/null +++ b/web/components/core/views/spreadsheet-view/priority-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-priority-column"; +export * from "./priority-column"; diff --git a/web/components/core/views/spreadsheet-view/priority-column/priority-column.tsx b/web/components/core/views/spreadsheet-view/priority-column/priority-column.tsx new file mode 100644 index 000000000..feb8acdf5 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/priority-column/priority-column.tsx @@ -0,0 +1,64 @@ +import React from "react"; + +import { useRouter } from "next/router"; + +// components +import { PrioritySelect } from "components/project"; +// services +import trackEventServices from "services/track-event.service"; +// types +import { ICurrentUserResponse, IIssue, Properties, TIssuePriorities } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const PriorityColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => { + const router = useRouter(); + + const { workspaceSlug } = router.query; + + const handlePriorityChange = (data: TIssuePriorities) => { + partialUpdateIssue({ priority: data }, issue); + trackEventServices.trackIssuePartialPropertyUpdateEvent( + { + workspaceSlug, + workspaceId: issue.workspace, + projectId: issue.project_detail.id, + projectIdentifier: issue.project_detail.identifier, + projectName: issue.project_detail.name, + issueId: issue.id, + }, + "ISSUE_PROPERTY_UPDATE_PRIORITY", + user + ); + }; + + return ( +
+ + {properties.priority && ( + + )} + +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/priority-column/spreadsheet-priority-column.tsx b/web/components/core/views/spreadsheet-view/priority-column/spreadsheet-priority-column.tsx new file mode 100644 index 000000000..f0b84fb59 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/priority-column/spreadsheet-priority-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { PriorityColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetPriorityColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/single-issue.tsx b/web/components/core/views/spreadsheet-view/single-issue.tsx deleted file mode 100644 index 8bae0d40d..000000000 --- a/web/components/core/views/spreadsheet-view/single-issue.tsx +++ /dev/null @@ -1,522 +0,0 @@ -import React, { useCallback, useState } from "react"; - -import { useRouter } from "next/router"; - -import { mutate } from "swr"; - -// components -import { ViewDueDateSelect, ViewEstimateSelect, ViewStartDateSelect } from "components/issues"; -import { LabelSelect, MembersSelect, PrioritySelect } from "components/project"; -import { StateSelect } from "components/states"; -import { Popover2 } from "@blueprintjs/popover2"; -// icons -import { Icon } from "components/ui"; -import { - EllipsisHorizontalIcon, - LinkIcon, - PencilIcon, - TrashIcon, -} from "@heroicons/react/24/outline"; -// hooks -import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; -import useToast from "hooks/use-toast"; -import useWorkspaceIssuesFilters from "hooks/use-worskpace-issue-filter"; -// services -import issuesService from "services/issues.service"; -import trackEventServices from "services/track-event.service"; -// constant -import { - CYCLE_DETAILS, - CYCLE_ISSUES_WITH_PARAMS, - MODULE_DETAILS, - MODULE_ISSUES_WITH_PARAMS, - PROJECT_ISSUES_LIST_WITH_PARAMS, - SUB_ISSUES, - VIEW_ISSUES, - WORKSPACE_VIEW_ISSUES, -} from "constants/fetch-keys"; -// types -import { - ICurrentUserResponse, - IIssue, - IState, - ISubIssueResponse, - Properties, - TIssuePriorities, - UserAuth, -} from "types"; -// helper -import { copyTextToClipboard } from "helpers/string.helper"; -import { renderLongDetailDateFormat } from "helpers/date-time.helper"; - -type Props = { - issue: IIssue; - projectId: string; - index: number; - expanded: boolean; - handleToggleExpand: (issueId: string) => void; - properties: Properties; - handleEditIssue: (issue: IIssue) => void; - handleDeleteIssue: (issue: IIssue) => void; - setCurrentProjectId: React.Dispatch>; - gridTemplateColumns: string; - disableUserActions: boolean; - user: ICurrentUserResponse | undefined; - userAuth: UserAuth; - nestingLevel: number; -}; - -export const SingleSpreadsheetIssue: React.FC = ({ - issue, - projectId, - index, - expanded, - handleToggleExpand, - properties, - handleEditIssue, - handleDeleteIssue, - setCurrentProjectId, - gridTemplateColumns, - disableUserActions, - user, - userAuth, - nestingLevel, -}) => { - const [isOpen, setIsOpen] = useState(false); - - const router = useRouter(); - - const { workspaceSlug, cycleId, moduleId, viewId, workspaceViewId } = router.query; - - const { params } = useSpreadsheetIssuesView(); - - const { setToastAlert } = useToast(); - - const workspaceIssuesPath = [ - { - params: { - sub_issue: false, - }, - path: "workspace-views/all-issues", - }, - { - params: { - assignees: user?.id ?? undefined, - sub_issue: false, - }, - path: "workspace-views/assigned", - }, - { - params: { - created_by: user?.id ?? undefined, - sub_issue: false, - }, - path: "workspace-views/created", - }, - { - params: { - subscriber: user?.id ?? undefined, - sub_issue: false, - }, - path: "workspace-views/subscribed", - }, - ]; - - const currentWorkspaceIssuePath = workspaceIssuesPath.find((path) => - router.pathname.includes(path.path) - ); - - const { params: workspaceViewParams } = useWorkspaceIssuesFilters( - workspaceSlug?.toString(), - workspaceViewId?.toString() - ); - - const partialUpdateIssue = useCallback( - (formData: Partial, issue: IIssue) => { - if (!workspaceSlug || !projectId) return; - - const fetchKey = cycleId - ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) - : moduleId - ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) - : viewId - ? VIEW_ISSUES(viewId.toString(), params) - : workspaceViewId - ? WORKSPACE_VIEW_ISSUES(workspaceSlug.toString(), workspaceViewParams) - : currentWorkspaceIssuePath - ? WORKSPACE_VIEW_ISSUES(workspaceSlug.toString(), currentWorkspaceIssuePath?.params) - : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params); - - if (issue.parent) - mutate( - SUB_ISSUES(issue.parent.toString()), - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - sub_issues: (prevData.sub_issues ?? []).map((i) => { - if (i.id === issue.id) { - return { - ...i, - ...formData, - }; - } - return i; - }), - }; - }, - false - ); - else - mutate( - fetchKey, - (prevData) => - (prevData ?? []).map((p) => { - if (p.id === issue.id) { - return { - ...p, - ...formData, - }; - } - return p; - }), - false - ); - - issuesService - .patchIssue(workspaceSlug as string, projectId, issue.id as string, formData, user) - .then(() => { - if (issue.parent) { - mutate(SUB_ISSUES(issue.parent as string)); - } else { - mutate(fetchKey); - - if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); - if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); - } - }) - .catch((error) => { - console.log(error); - }); - }, - [ - workspaceSlug, - projectId, - cycleId, - moduleId, - viewId, - workspaceViewId, - currentWorkspaceIssuePath, - workspaceViewParams, - params, - user, - ] - ); - - const openPeekOverview = () => { - const { query } = router; - setCurrentProjectId(issue.project_detail.id); - router.push({ - pathname: router.pathname, - query: { ...query, peekIssue: issue.id }, - }); - }; - - 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 handleStateChange = (data: string, states: IState[] | undefined) => { - const oldState = states?.find((s) => s.id === issue.state); - const newState = states?.find((s) => s.id === data); - - partialUpdateIssue( - { - state: data, - state_detail: newState, - }, - issue - ); - trackEventServices.trackIssuePartialPropertyUpdateEvent( - { - workspaceSlug, - workspaceId: issue.workspace, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - "ISSUE_PROPERTY_UPDATE_STATE", - user - ); - if (oldState?.group !== "completed" && newState?.group !== "completed") { - trackEventServices.trackIssueMarkedAsDoneEvent( - { - workspaceSlug: issue.workspace_detail.slug, - workspaceId: issue.workspace_detail.id, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - user - ); - } - }; - - const handlePriorityChange = (data: TIssuePriorities) => { - partialUpdateIssue({ priority: data }, issue); - trackEventServices.trackIssuePartialPropertyUpdateEvent( - { - workspaceSlug, - workspaceId: issue.workspace, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - "ISSUE_PROPERTY_UPDATE_PRIORITY", - user - ); - }; - - const handleAssigneeChange = (data: any) => { - const newData = issue.assignees ?? []; - - if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); - else newData.push(data); - - partialUpdateIssue({ assignees_list: data }, issue); - - trackEventServices.trackIssuePartialPropertyUpdateEvent( - { - workspaceSlug, - workspaceId: issue.workspace, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - "ISSUE_PROPERTY_UPDATE_ASSIGNEE", - user - ); - }; - - const handleLabelChange = (data: any) => { - partialUpdateIssue({ labels_list: data }, issue); - }; - - const paddingLeft = `${nestingLevel * 68}px`; - - const tooltipPosition = index === 0 ? "bottom" : "top"; - - const isNotAllowed = userAuth.isGuest || userAuth.isViewer; - - return ( - <> -
-
-
-
- {properties.key && ( - - {issue.project_detail?.identifier}-{issue.sequence_id} - - )} - {!isNotAllowed && !disableUserActions && ( -
- setIsOpen(nextOpenState)} - content={ -
- - - - - -
- } - placement="bottom-start" - > - -
-
- )} -
- - {issue.sub_issues_count > 0 && ( -
- -
- )} -
- - -
- {properties.state && ( -
- -
- )} - {properties.priority && ( -
- -
- )} - {properties.assignee && ( -
- -
- )} - {properties.labels && ( -
- -
- )} - - {properties.start_date && ( -
- -
- )} - - {properties.due_date && ( -
- -
- )} - {properties.estimate && ( -
- -
- )} - {properties.created_on && ( -
- {renderLongDetailDateFormat(issue.created_at)} -
- )} - {properties.updated_on && ( -
- {renderLongDetailDateFormat(issue.updated_at)} -
- )} -
- - ); -}; diff --git a/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx b/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx index 4cd42cec6..797aa7785 100644 --- a/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx +++ b/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx @@ -1,22 +1,43 @@ -import React, { useState } from "react"; +import React, { useCallback, useState } from "react"; // next import { useRouter } from "next/router"; -import { KeyedMutator } from "swr"; +import { KeyedMutator, mutate } from "swr"; // components -import { SpreadsheetColumns, SpreadsheetIssues } from "components/core"; -import { CustomMenu, Spinner } from "components/ui"; +import { + SpreadsheetAssigneeColumn, + SpreadsheetCreatedOnColumn, + SpreadsheetDueDateColumn, + SpreadsheetEstimateColumn, + SpreadsheetIssuesColumn, + SpreadsheetLabelColumn, + SpreadsheetPriorityColumn, + SpreadsheetStartDateColumn, + SpreadsheetStateColumn, + SpreadsheetUpdatedOnColumn, +} from "components/core"; +import { Spinner } from "components/ui"; import { IssuePeekOverview } from "components/issues"; // hooks import useIssuesProperties from "hooks/use-issue-properties"; // types -import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types"; -// constants -import { SPREADSHEET_COLUMN } from "constants/spreadsheet"; +import { ICurrentUserResponse, IIssue, ISubIssueResponse, UserAuth } from "types"; +import useWorkspaceIssuesFilters from "hooks/use-worskpace-issue-filter"; +import { + CYCLE_DETAILS, + CYCLE_ISSUES_WITH_PARAMS, + MODULE_DETAILS, + MODULE_ISSUES_WITH_PARAMS, + PROJECT_ISSUES_LIST_WITH_PARAMS, + SUB_ISSUES, + VIEW_ISSUES, + WORKSPACE_VIEW_ISSUES, +} from "constants/fetch-keys"; +import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; +import projectIssuesServices from "services/issues.service"; // icon -import { PlusIcon } from "@heroicons/react/24/outline"; type Props = { spreadsheetIssues: IIssue[]; @@ -46,27 +67,162 @@ export const SpreadsheetView: React.FC = ({ const [currentProjectId, setCurrentProjectId] = useState(null); const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId } = router.query; - - const type = cycleId ? "cycle" : moduleId ? "module" : "issue"; + const { workspaceSlug, projectId, cycleId, moduleId, viewId, workspaceViewId } = router.query; const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); - const columnData = SPREADSHEET_COLUMN.map((column) => ({ - ...column, - isActive: properties - ? column.propertyName === "labels" - ? properties[column.propertyName as keyof Properties] - : column.propertyName === "title" - ? true - : properties[column.propertyName as keyof Properties] - : false, - })); + const workspaceIssuesPath = [ + { + params: { + sub_issue: false, + }, + path: "workspace-views/all-issues", + }, + { + params: { + assignees: user?.id ?? undefined, + sub_issue: false, + }, + path: "workspace-views/assigned", + }, + { + params: { + created_by: user?.id ?? undefined, + sub_issue: false, + }, + path: "workspace-views/created", + }, + { + params: { + subscriber: user?.id ?? undefined, + sub_issue: false, + }, + path: "workspace-views/subscribed", + }, + ]; - const gridTemplateColumns = columnData - .filter((column) => column.isActive) - .map((column) => column.colSize) - .join(" "); + const currentWorkspaceIssuePath = workspaceIssuesPath.find((path) => + router.pathname.includes(path.path) + ); + + const { params: workspaceViewParams } = useWorkspaceIssuesFilters( + workspaceSlug?.toString(), + workspaceViewId?.toString() + ); + + const { params } = useSpreadsheetIssuesView(); + + const partialUpdateIssue = useCallback( + (formData: Partial, issue: IIssue) => { + if (!workspaceSlug || !issue) return; + + const fetchKey = cycleId + ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) + : moduleId + ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) + : viewId + ? VIEW_ISSUES(viewId.toString(), params) + : workspaceViewId + ? WORKSPACE_VIEW_ISSUES(workspaceSlug.toString(), workspaceViewParams) + : currentWorkspaceIssuePath + ? WORKSPACE_VIEW_ISSUES(workspaceSlug.toString(), currentWorkspaceIssuePath?.params) + : PROJECT_ISSUES_LIST_WITH_PARAMS(issue.project_detail.id, params); + + if (issue.parent) + mutate( + SUB_ISSUES(issue.parent.toString()), + (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + sub_issues: (prevData.sub_issues ?? []).map((i) => { + if (i.id === issue.id) { + return { + ...i, + ...formData, + }; + } + return i; + }), + }; + }, + false + ); + else + mutate( + fetchKey, + (prevData) => + (prevData ?? []).map((p) => { + if (p.id === issue.id) { + return { + ...p, + ...formData, + }; + } + return p; + }), + false + ); + + projectIssuesServices + .patchIssue( + workspaceSlug as string, + issue.project_detail.id, + issue.id as string, + formData, + user + ) + .then(() => { + if (issue.parent) { + mutate(SUB_ISSUES(issue.parent as string)); + } else { + mutate(fetchKey); + + if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); + if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); + } + }) + .catch((error) => { + console.log(error); + }); + }, + [ + workspaceSlug, + cycleId, + moduleId, + viewId, + workspaceViewId, + currentWorkspaceIssuePath, + workspaceViewParams, + params, + user, + ] + ); + + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; + + const renderColumn = (header: string, Component: React.ComponentType) => ( +
+
+ {header} +
+
+ {spreadsheetIssues.map((issue: IIssue, index) => ( + + ))} +
+
+ ); return ( <> @@ -76,79 +232,46 @@ export const SpreadsheetView: React.FC = ({ workspaceSlug={workspaceSlug?.toString() ?? ""} readOnly={disableUserActions} /> -
-
- -
+
{spreadsheetIssues ? ( -
- {spreadsheetIssues.map((issue: IIssue, index) => ( - - ))} -
- {type === "issue" ? ( - - ) : ( - !disableUserActions && ( - - - Add Issue - - } - position="left" - optionsClassName="left-5 !w-36" - noBorder - > - { - const e = new KeyboardEvent("keydown", { key: "c" }); - document.dispatchEvent(e); - }} - > - Create new - - {openIssuesListModal && ( - - Add an existing issue - - )} - - ) - )} + <> +
+
+
+ + ID + + + Issue + +
+ + {spreadsheetIssues.map((issue: IIssue, index) => ( + + ))} +
-
+ {renderColumn("State", SpreadsheetStateColumn)} + {renderColumn("Priority", SpreadsheetPriorityColumn)} + {renderColumn("Assignees", SpreadsheetAssigneeColumn)} + {renderColumn("Label", SpreadsheetLabelColumn)} + {renderColumn("Start Date", SpreadsheetStartDateColumn)} + {renderColumn("Due Date", SpreadsheetDueDateColumn)} + {renderColumn("Estimate", SpreadsheetEstimateColumn)} + {renderColumn("Created On", SpreadsheetCreatedOnColumn)} + {renderColumn("Updated On", SpreadsheetUpdatedOnColumn)} + ) : (
diff --git a/web/components/core/views/spreadsheet-view/start-date-column/index.ts b/web/components/core/views/spreadsheet-view/start-date-column/index.ts new file mode 100644 index 000000000..94f229498 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/start-date-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-start-date-column"; +export * from "./start-date-column"; diff --git a/web/components/core/views/spreadsheet-view/start-date-column/spreadsheet-start-date-column.tsx b/web/components/core/views/spreadsheet-view/start-date-column/spreadsheet-start-date-column.tsx new file mode 100644 index 000000000..064506ca2 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/start-date-column/spreadsheet-start-date-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { StartDateColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetStartDateColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/start-date-column/start-date-column.tsx b/web/components/core/views/spreadsheet-view/start-date-column/start-date-column.tsx new file mode 100644 index 000000000..3b4b9a0f7 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/start-date-column/start-date-column.tsx @@ -0,0 +1,38 @@ +import React from "react"; + +// components +import { ViewStartDateSelect } from "components/issues"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const StartDateColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => ( +
+ + {properties.due_date && ( + + )} + +
+); diff --git a/web/components/core/views/spreadsheet-view/state-column/index.ts b/web/components/core/views/spreadsheet-view/state-column/index.ts new file mode 100644 index 000000000..f3cbef871 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/state-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-state-column"; +export * from "./state-column"; diff --git a/web/components/core/views/spreadsheet-view/state-column/spreadsheet-state-column.tsx b/web/components/core/views/spreadsheet-view/state-column/spreadsheet-state-column.tsx new file mode 100644 index 000000000..606f3e28a --- /dev/null +++ b/web/components/core/views/spreadsheet-view/state-column/spreadsheet-state-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { StateColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetStateColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/state-column/state-column.tsx b/web/components/core/views/spreadsheet-view/state-column/state-column.tsx new file mode 100644 index 000000000..6b3d3c696 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/state-column/state-column.tsx @@ -0,0 +1,87 @@ +import React from "react"; + +import { useRouter } from "next/router"; + +// components +import { StateSelect } from "components/states"; +// services +import trackEventServices from "services/track-event.service"; +// types +import { ICurrentUserResponse, IIssue, IState, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const StateColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => { + const router = useRouter(); + + const { workspaceSlug } = router.query; + + const handleStateChange = (data: string, states: IState[] | undefined) => { + const oldState = states?.find((s) => s.id === issue.state); + const newState = states?.find((s) => s.id === data); + + partialUpdateIssue( + { + state: data, + state_detail: newState, + }, + issue + ); + trackEventServices.trackIssuePartialPropertyUpdateEvent( + { + workspaceSlug, + workspaceId: issue.workspace, + projectId: issue.project_detail.id, + projectIdentifier: issue.project_detail.identifier, + projectName: issue.project_detail.name, + issueId: issue.id, + }, + "ISSUE_PROPERTY_UPDATE_STATE", + user + ); + if (oldState?.group !== "completed" && newState?.group !== "completed") { + trackEventServices.trackIssueMarkedAsDoneEvent( + { + workspaceSlug: issue.workspace_detail.slug, + workspaceId: issue.workspace_detail.id, + projectId: issue.project_detail.id, + projectIdentifier: issue.project_detail.identifier, + projectName: issue.project_detail.name, + issueId: issue.id, + }, + user + ); + } + }; + + return ( +
+ + {properties.state && ( + + )} + +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/updated-on-column/index.ts b/web/components/core/views/spreadsheet-view/updated-on-column/index.ts new file mode 100644 index 000000000..af1337a7f --- /dev/null +++ b/web/components/core/views/spreadsheet-view/updated-on-column/index.ts @@ -0,0 +1,2 @@ +export * from "./spreadsheet-updated-on-column"; +export * from "./updated-on-column"; diff --git a/web/components/core/views/spreadsheet-view/updated-on-column/spreadsheet-updated-on-column.tsx b/web/components/core/views/spreadsheet-view/updated-on-column/spreadsheet-updated-on-column.tsx new file mode 100644 index 000000000..bb29e460d --- /dev/null +++ b/web/components/core/views/spreadsheet-view/updated-on-column/spreadsheet-updated-on-column.tsx @@ -0,0 +1,62 @@ +import React from "react"; + +// components +import { UpdatedOnColumn } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + expandedIssues: string[]; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const SpreadsheetUpdatedOnColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + expandedIssues, + properties, + user, + isNotAllowed, +}) => { + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + + return ( +
+ + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue) => ( + + ))} +
+ ); +}; diff --git a/web/components/core/views/spreadsheet-view/updated-on-column/updated-on-column.tsx b/web/components/core/views/spreadsheet-view/updated-on-column/updated-on-column.tsx new file mode 100644 index 000000000..b63519095 --- /dev/null +++ b/web/components/core/views/spreadsheet-view/updated-on-column/updated-on-column.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +// types +import { ICurrentUserResponse, IIssue, Properties } from "types"; +// helper +import { renderLongDetailDateFormat } from "helpers/date-time.helper"; + +type Props = { + issue: IIssue; + projectId: string; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + properties: Properties; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const UpdatedOnColumn: React.FC = ({ + issue, + projectId, + partialUpdateIssue, + properties, + user, + isNotAllowed, +}) => ( +
+ + {properties.updated_on && ( +
+ {renderLongDetailDateFormat(issue.updated_at)} +
+ )} +
+
+); diff --git a/web/constants/spreadsheet.ts b/web/constants/spreadsheet.ts deleted file mode 100644 index df6cd5aef..000000000 --- a/web/constants/spreadsheet.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - CalendarDaysIcon, - PlayIcon, - Squares2X2Icon, - TagIcon, - UserGroupIcon, -} from "@heroicons/react/24/outline"; - -export const SPREADSHEET_COLUMN = [ - { - propertyName: "title", - colName: "Title", - colSize: "440px", - }, - { - propertyName: "state", - colName: "State", - colSize: "128px", - icon: Squares2X2Icon, - ascendingOrder: "state__name", - descendingOrder: "-state__name", - }, - { - propertyName: "priority", - colName: "Priority", - colSize: "128px", - ascendingOrder: "priority", - descendingOrder: "-priority", - }, - { - propertyName: "assignee", - colName: "Assignees", - colSize: "128px", - icon: UserGroupIcon, - ascendingOrder: "assignees__id", - descendingOrder: "-assignees__id", - }, - { - propertyName: "labels", - colName: "Labels", - colSize: "128px", - icon: TagIcon, - ascendingOrder: "labels__name", - descendingOrder: "-labels__name", - }, - { - propertyName: "start_date", - colName: "Start Date", - colSize: "128px", - icon: CalendarDaysIcon, - ascendingOrder: "-start_date", - descendingOrder: "start_date", - }, - { - propertyName: "due_date", - colName: "Due Date", - colSize: "128px", - icon: CalendarDaysIcon, - ascendingOrder: "-target_date", - descendingOrder: "target_date", - }, - { - propertyName: "estimate", - colName: "Estimate", - colSize: "128px", - icon: PlayIcon, - ascendingOrder: "estimate_point", - descendingOrder: "-estimate_point", - }, - { - propertyName: "created_on", - colName: "Created On", - colSize: "144px", - icon: CalendarDaysIcon, - ascendingOrder: "-created_at", - descendingOrder: "created_at", - }, - { - propertyName: "updated_on", - colName: "Updated On", - colSize: "144px", - icon: CalendarDaysIcon, - ascendingOrder: "-updated_at", - descendingOrder: "updated_at", - }, -];