From df97b35a99dc3fa3819f755ecf1c00202057ff7f Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:19:19 +0530 Subject: [PATCH] chore: Refactor Spreadsheet view for better code maintainability and performance (#3322) * refcator spreadsheet to use table and roow based approach rather than column based * update spreadsheet and optimized layout * fix issues in spread sheet * close quick action menu on click --------- Co-authored-by: Rahul R --- packages/ui/package.json | 1 + packages/ui/src/dropdowns/custom-menu.tsx | 60 +++-- packages/ui/src/dropdowns/helper.tsx | 2 + .../issue-layouts/list/list-view-types.d.ts | 1 + .../quick-action-dropdowns/all-issue.tsx | 18 +- .../quick-action-dropdowns/archived-issue.tsx | 14 +- .../quick-action-dropdowns/cycle-issue.tsx | 20 +- .../quick-action-dropdowns/module-issue.tsx | 18 +- .../quick-action-dropdowns/project-issue.tsx | 18 +- .../roots/all-issue-layout-root.tsx | 6 +- .../spreadsheet/base-spreadsheet-root.tsx | 40 +-- .../spreadsheet/columns/assignee-column.tsx | 62 ++--- .../spreadsheet/columns/attachment-column.tsx | 39 +-- .../spreadsheet/columns/columns-list.tsx | 176 ------------- .../spreadsheet/columns/created-on-column.tsx | 37 +-- .../spreadsheet/columns/due-date-column.tsx | 57 ++--- .../spreadsheet/columns/estimate-column.tsx | 61 ++--- .../spreadsheet/columns/header-column.tsx | 123 +++++++++ .../spreadsheet/columns/index.ts | 4 +- .../spreadsheet/columns/issue/index.ts | 2 - .../columns/issue/issue-column.tsx | 114 --------- .../issue/spreadsheet-issue-column.tsx | 81 ------ .../spreadsheet/columns/label-column.tsx | 77 ++---- .../spreadsheet/columns/link-column.tsx | 39 +-- .../spreadsheet/columns/priority-column.tsx | 55 ++--- .../spreadsheet/columns/start-date-column.tsx | 60 ++--- .../spreadsheet/columns/state-column.tsx | 63 ++--- .../spreadsheet/columns/sub-issue-column.tsx | 37 +-- .../spreadsheet/columns/updated-on-column.tsx | 42 +--- .../issues/issue-layouts/spreadsheet/index.ts | 1 - .../issue-layouts/spreadsheet/issue-row.tsx | 186 ++++++++++++++ .../spreadsheet/spreadsheet-column.tsx | 233 ------------------ .../spreadsheet/spreadsheet-header.tsx | 59 +++++ .../spreadsheet/spreadsheet-view.tsx | 173 +++++-------- web/constants/spreadsheet.ts | 44 +++- 35 files changed, 749 insertions(+), 1274 deletions(-) delete mode 100644 web/components/issues/issue-layouts/spreadsheet/columns/columns-list.tsx create mode 100644 web/components/issues/issue-layouts/spreadsheet/columns/header-column.tsx delete mode 100644 web/components/issues/issue-layouts/spreadsheet/columns/issue/index.ts delete mode 100644 web/components/issues/issue-layouts/spreadsheet/columns/issue/issue-column.tsx delete mode 100644 web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx create mode 100644 web/components/issues/issue-layouts/spreadsheet/issue-row.tsx delete mode 100644 web/components/issues/issue-layouts/spreadsheet/spreadsheet-column.tsx create mode 100644 web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx diff --git a/packages/ui/package.json b/packages/ui/package.json index b643d47d4..def464623 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -36,6 +36,7 @@ "@headlessui/react": "^1.7.17", "@popperjs/core": "^2.11.8", "react-color": "^2.19.3", + "react-dom": "^18.2.0", "react-popper": "^2.3.0" } } diff --git a/packages/ui/src/dropdowns/custom-menu.tsx b/packages/ui/src/dropdowns/custom-menu.tsx index d6b0281ce..094a8092f 100644 --- a/packages/ui/src/dropdowns/custom-menu.tsx +++ b/packages/ui/src/dropdowns/custom-menu.tsx @@ -1,5 +1,5 @@ import * as React from "react"; - +import ReactDOM from "react-dom"; // react-poppper import { usePopper } from "react-popper"; // hooks @@ -29,8 +29,10 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => { optionsClassName = "", verticalEllipsis = false, width = "auto", + portalElement, menuButtonOnClick, tabIndex, + closeOnSelect, } = props; const [referenceElement, setReferenceElement] = React.useState(null); @@ -51,6 +53,39 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => { const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen); useOutsideClickDetector(dropdownRef, closeDropdown); + let menuItems = ( + { + if (closeOnSelect) closeDropdown(); + }} + static + > +
+ {children} +
+
+ ); + + if (portalElement) { + menuItems = ReactDOM.createPortal(menuItems, portalElement); + } + return ( { )} )} - {isOpen && ( - -
- {children} -
-
- )} + {isOpen && menuItems} )}
diff --git a/packages/ui/src/dropdowns/helper.tsx b/packages/ui/src/dropdowns/helper.tsx index 618d5b6bd..9c0ae0566 100644 --- a/packages/ui/src/dropdowns/helper.tsx +++ b/packages/ui/src/dropdowns/helper.tsx @@ -24,6 +24,8 @@ export interface ICustomMenuDropdownProps extends IDropdownProps { noBorder?: boolean; verticalEllipsis?: boolean; menuButtonOnClick?: (...args: any) => void; + closeOnSelect?: boolean; + portalElement?: Element | null; } export interface ICustomSelectProps extends IDropdownProps { diff --git a/web/components/issues/issue-layouts/list/list-view-types.d.ts b/web/components/issues/issue-layouts/list/list-view-types.d.ts index 674ae92d1..9e3bb8701 100644 --- a/web/components/issues/issue-layouts/list/list-view-types.d.ts +++ b/web/components/issues/issue-layouts/list/list-view-types.d.ts @@ -4,4 +4,5 @@ export interface IQuickActionProps { handleUpdate?: (data: TIssue) => Promise; handleRemoveFromView?: () => Promise; customActionButton?: React.ReactElement; + portalElement?: HTMLDivElement | null; } diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx index efd9490d7..d8448cc0e 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx @@ -13,7 +13,7 @@ import { TIssue } from "@plane/types"; import { IQuickActionProps } from "../list/list-view-types"; export const AllIssueQuickActions: React.FC = (props) => { - const { issue, handleDelete, handleUpdate, customActionButton } = props; + const { issue, handleDelete, handleUpdate, customActionButton, portalElement } = props; // states const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false); const [issueToEdit, setIssueToEdit] = useState(undefined); @@ -59,11 +59,15 @@ export const AllIssueQuickActions: React.FC = (props) => { if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); }} /> - + { - e.preventDefault(); - e.stopPropagation(); handleCopyIssueLink(); }} > @@ -74,8 +78,6 @@ export const AllIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); setIssueToEdit(issue); setCreateUpdateIssueModal(true); }} @@ -87,8 +89,6 @@ export const AllIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); setCreateUpdateIssueModal(true); }} > @@ -99,8 +99,6 @@ export const AllIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); setDeleteIssueModal(true); }} > diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx index 8d6735277..100ae99db 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx @@ -12,7 +12,7 @@ import { copyUrlToClipboard } from "helpers/string.helper"; import { IQuickActionProps } from "../list/list-view-types"; export const ArchivedIssueQuickActions: React.FC = (props) => { - const { issue, handleDelete, customActionButton } = props; + const { issue, handleDelete, customActionButton, portalElement } = props; const router = useRouter(); const { workspaceSlug } = router.query; @@ -40,11 +40,15 @@ export const ArchivedIssueQuickActions: React.FC = (props) => handleClose={() => setDeleteIssueModal(false)} onSubmit={handleDelete} /> - + { - e.preventDefault(); - e.stopPropagation(); handleCopyIssueLink(); }} > @@ -55,8 +59,6 @@ export const ArchivedIssueQuickActions: React.FC = (props) => { - e.preventDefault(); - e.stopPropagation(); setDeleteIssueModal(true); }} > diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx index 6d7e08152..7d708145b 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx @@ -13,7 +13,7 @@ import { TIssue } from "@plane/types"; import { IQuickActionProps } from "../list/list-view-types"; export const CycleIssueQuickActions: React.FC = (props) => { - const { issue, handleDelete, handleUpdate, handleRemoveFromView, customActionButton } = props; + const { issue, handleDelete, handleUpdate, handleRemoveFromView, customActionButton, portalElement } = props; // states const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false); const [issueToEdit, setIssueToEdit] = useState(undefined); @@ -59,11 +59,15 @@ export const CycleIssueQuickActions: React.FC = (props) => { if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); }} /> - + { - e.preventDefault(); - e.stopPropagation(); handleCopyIssueLink(); }} > @@ -74,8 +78,6 @@ export const CycleIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); setIssueToEdit({ ...issue, cycle: cycleId?.toString() ?? null, @@ -90,8 +92,6 @@ export const CycleIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); handleRemoveFromView && handleRemoveFromView(); }} > @@ -102,8 +102,6 @@ export const CycleIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); setCreateUpdateIssueModal(true); }} > @@ -114,8 +112,6 @@ export const CycleIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); setDeleteIssueModal(true); }} > diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx index 27d16c781..ac12c0d9b 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx @@ -13,7 +13,7 @@ import { TIssue } from "@plane/types"; import { IQuickActionProps } from "../list/list-view-types"; export const ModuleIssueQuickActions: React.FC = (props) => { - const { issue, handleDelete, handleUpdate, handleRemoveFromView, customActionButton } = props; + const { issue, handleDelete, handleUpdate, handleRemoveFromView, customActionButton, portalElement } = props; // states const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false); const [issueToEdit, setIssueToEdit] = useState(undefined); @@ -59,11 +59,15 @@ export const ModuleIssueQuickActions: React.FC = (props) => { if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); }} /> - + { - e.preventDefault(); - e.stopPropagation(); handleCopyIssueLink(); }} > @@ -74,8 +78,6 @@ export const ModuleIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); setIssueToEdit({ ...issue, module: moduleId?.toString() ?? null }); setCreateUpdateIssueModal(true); }} @@ -87,8 +89,6 @@ export const ModuleIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); handleRemoveFromView && handleRemoveFromView(); }} > @@ -99,8 +99,6 @@ export const ModuleIssueQuickActions: React.FC = (props) => { { - e.preventDefault(); - e.stopPropagation(); setCreateUpdateIssueModal(true); }} > diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx index 083a22e35..c0fa556b3 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx @@ -16,7 +16,7 @@ import { IQuickActionProps } from "../list/list-view-types"; import { EUserProjectRoles } from "constants/project"; export const ProjectIssueQuickActions: React.FC = (props) => { - const { issue, handleDelete, handleUpdate, customActionButton } = props; + const { issue, handleDelete, handleUpdate, customActionButton, portalElement } = props; // router const router = useRouter(); const { workspaceSlug } = router.query; @@ -68,11 +68,15 @@ export const ProjectIssueQuickActions: React.FC = (props) => if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data }); }} /> - + { - e.preventDefault(); - e.stopPropagation(); handleCopyIssueLink(); }} > @@ -85,8 +89,6 @@ export const ProjectIssueQuickActions: React.FC = (props) => <> { - e.preventDefault(); - e.stopPropagation(); setIssueToEdit(issue); setCreateUpdateIssueModal(true); }} @@ -98,8 +100,6 @@ export const ProjectIssueQuickActions: React.FC = (props) => { - e.preventDefault(); - e.stopPropagation(); setCreateUpdateIssueModal(true); }} > @@ -110,8 +110,6 @@ export const ProjectIssueQuickActions: React.FC = (props) => { - e.preventDefault(); - e.stopPropagation(); setDeleteIssueModal(true); }} > diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx index 0b4e334c7..cf585a6fc 100644 --- a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; // hooks -import { useGlobalView, useIssues, useLabel, useUser } from "hooks/store"; +import { useGlobalView, useIssues, useUser } from "hooks/store"; // components import { GlobalViewsAppliedFiltersRoot } from "components/issues"; import { SpreadsheetView } from "components/issues/issue-layouts"; @@ -37,9 +37,6 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { membership: { currentWorkspaceAllProjectsRole }, } = useUser(); const { fetchAllGlobalViews } = useGlobalView(); - const { - workspace: { workspaceLabels }, - } = useLabel(); // derived values const currentIssueView = type ?? globalViewId; @@ -134,7 +131,6 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)} /> )} - labels={workspaceLabels || undefined} handleIssues={handleIssues} canEditProperties={canEditProperties} viewId={currentIssueView} diff --git a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx index 543e33aad..31c27e729 100644 --- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx @@ -2,7 +2,7 @@ import { FC, useCallback } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // hooks -import { useIssues, useLabel, useProjectState, useUser } from "hooks/store"; +import { useIssues, useUser } from "hooks/store"; // views import { SpreadsheetView } from "./spreadsheet-view"; // types @@ -40,10 +40,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { const { membership: { currentProjectRole }, } = useUser(); - const { - project: { projectLabels }, - } = useLabel(); - const { projectStates } = useProjectState(); // derived values const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {}; // user role validation @@ -86,27 +82,31 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { [issueFiltersStore, projectId, workspaceSlug, viewId] ); + const renderQuickActions = useCallback( + (issue: TIssue, customActionButton?: React.ReactElement, portalElement?: HTMLDivElement | null) => ( + handleIssues(issue, EIssueActions.DELETE)} + handleUpdate={ + issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined + } + handleRemoveFromView={ + issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined + } + portalElement={portalElement} + /> + ), + [handleIssues] + ); + return ( ( - handleIssues(issue, EIssueActions.DELETE)} - handleUpdate={ - issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined - } - handleRemoveFromView={ - issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined - } - /> - )} - labels={projectLabels ?? []} - states={projectStates} + quickActions={renderQuickActions} handleIssues={handleIssues} canEditProperties={canEditProperties} quickAddCallback={issueStore.quickAddIssue} diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx index 89d8367f3..2656143ac 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx @@ -1,56 +1,34 @@ import React from "react"; -// hooks -import { useIssueDetail } from "hooks/store"; +import { observer } from "mobx-react-lite"; // components import { ProjectMemberDropdown } from "components/dropdowns"; // types import { TIssue } from "@plane/types"; type Props = { - issueId: string; + issue: TIssue; onChange: (issue: TIssue, data: Partial) => void; - expandedIssues: string[]; disabled: boolean; }; -export const SpreadsheetAssigneeColumn: React.FC = ({ issueId, onChange, expandedIssues, disabled }) => { - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); +export const SpreadsheetAssigneeColumn: React.FC = observer((props: Props) => { + const { issue, onChange, disabled } = props; return ( - <> - {issueDetail && ( -
- onChange(issueDetail, { assignee_ids: data })} - projectId={issueDetail?.project_id} - disabled={disabled} - multiple - placeholder="Assignees" - buttonVariant={issueDetail.assignee_ids?.length > 0 ? "transparent-without-text" : "transparent-with-text"} - buttonClassName="text-left" - buttonContainerClassName="w-full" - /> -
- )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId) => ( - - ))} - +
+ onChange(issue, { assignee_ids: data })} + projectId={issue?.project_id} + disabled={disabled} + multiple + placeholder="Assignees" + buttonVariant={ + issue?.assignee_ids && issue.assignee_ids.length > 0 ? "transparent-without-text" : "transparent-with-text" + } + buttonClassName="text-left" + buttonContainerClassName="w-full" + /> +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/attachment-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/attachment-column.tsx index 4b4bdbb53..c17a433b8 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/attachment-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/attachment-column.tsx @@ -1,39 +1,18 @@ import React from "react"; -// hooks +import { observer } from "mobx-react-lite"; // types -import { useIssueDetail } from "hooks/store"; +import { TIssue } from "@plane/types"; type Props = { - issueId: string; - expandedIssues: string[]; + issue: TIssue; }; -export const SpreadsheetAttachmentColumn: React.FC = (props) => { - const { issueId, expandedIssues } = props; - - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); - - // const { subIssues, isLoading } = useSubIssue(issue.project_id, issue.id, isExpanded); +export const SpreadsheetAttachmentColumn: React.FC = observer((props) => { + const { issue } = props; return ( - <> -
- {issueDetail?.attachment_count} {issueDetail?.attachment_count === 1 ? "attachment" : "attachments"} -
- - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( -
- -
- ))} - +
+ {issue?.attachment_count} {issue?.attachment_count === 1 ? "attachment" : "attachments"} +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/columns-list.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/columns-list.tsx deleted file mode 100644 index e7f046796..000000000 --- a/web/components/issues/issue-layouts/spreadsheet/columns/columns-list.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { observer } from "mobx-react-lite"; -// hooks -import { useProject } from "hooks/store"; -// components -import { SpreadsheetColumn } from "components/issues"; -// types -import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabel, IState } from "@plane/types"; - -type Props = { - displayFilters: IIssueDisplayFilterOptions; - displayProperties: IIssueDisplayProperties; - canEditProperties: (projectId: string | undefined) => boolean; - expandedIssues: string[]; - handleDisplayFilterUpdate: (data: Partial) => void; - handleUpdateIssue: (issue: TIssue, data: Partial) => void; - issues: TIssue[] | undefined; - labels?: IIssueLabel[] | undefined; - states?: IState[] | undefined; -}; - -export const SpreadsheetColumnsList: React.FC = observer((props) => { - const { - canEditProperties, - displayFilters, - displayProperties, - expandedIssues, - handleDisplayFilterUpdate, - handleUpdateIssue, - issues, - labels, - states, - } = props; - // store hooks - const { currentProjectDetails } = useProject(); - - const isEstimateEnabled: boolean = currentProjectDetails?.estimate !== null; - - return ( - <> - {displayProperties.state && ( - - )} - {displayProperties.priority && ( - - )} - {displayProperties.assignee && ( - - )} - {displayProperties.labels && ( - - )}{" "} - {displayProperties.start_date && ( - - )} - {displayProperties.due_date && ( - - )} - {displayProperties.estimate && isEstimateEnabled && ( - - )} - {displayProperties.created_on && ( - - )} - {displayProperties.updated_on && ( - - )} - {displayProperties.link && ( - - )} - {displayProperties.attachment_count && ( - - )} - {displayProperties.sub_issue_count && ( - - )} - - ); -}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/created-on-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/created-on-column.tsx index 176b8ea14..8d373efb4 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/created-on-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/created-on-column.tsx @@ -1,38 +1,19 @@ import React from "react"; -// hooks -import { useIssueDetail } from "hooks/store"; +import { observer } from "mobx-react-lite"; // helpers import { renderFormattedDate } from "helpers/date-time.helper"; // types +import { TIssue } from "@plane/types"; type Props = { - issueId: string; - expandedIssues: string[]; + issue: TIssue; }; -export const SpreadsheetCreatedOnColumn: React.FC = ({ issueId, expandedIssues }) => { - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); - +export const SpreadsheetCreatedOnColumn: React.FC = observer((props: Props) => { + const { issue } = props; return ( - <> - {issueDetail && ( -
- {renderFormattedDate(issueDetail.created_at)} -
- )} - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( -
- -
- ))} - +
+ {renderFormattedDate(issue.created_at)} +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx index 32c871b90..dbc27a3db 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx @@ -1,6 +1,5 @@ import React from "react"; -// hooks -import { useIssueDetail } from "hooks/store"; +import { observer } from "mobx-react-lite"; // components import { DateDropdown } from "components/dropdowns"; // helpers @@ -9,49 +8,25 @@ import { renderFormattedPayloadDate } from "helpers/date-time.helper"; import { TIssue } from "@plane/types"; type Props = { - issueId: string; + issue: TIssue; onChange: (issue: TIssue, data: Partial) => void; - expandedIssues: string[]; disabled: boolean; }; -export const SpreadsheetDueDateColumn: React.FC = ({ issueId, onChange, expandedIssues, disabled }) => { - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading, mutateSubIssues } = useSubIssue(issue.project_id, issue.id, isExpanded); - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); +export const SpreadsheetDueDateColumn: React.FC = observer((props: Props) => { + const { issue, onChange, disabled } = props; return ( - <> - {issueDetail && ( -
- onChange(issueDetail, { target_date: data ? renderFormattedPayloadDate(data) : null })} - disabled={disabled} - placeholder="Due date" - buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" - buttonContainerClassName="w-full" - /> -
- )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId) => ( - - ))} - +
+ onChange(issue, { target_date: data ? renderFormattedPayloadDate(data) : null })} + disabled={disabled} + placeholder="Due date" + buttonVariant="transparent-with-text" + buttonClassName="rounded-none text-left" + buttonContainerClassName="w-full" + /> +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/estimate-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/estimate-column.tsx index 041da65c6..50878ccce 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/estimate-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/estimate-column.tsx @@ -1,56 +1,29 @@ -// hooks -import { useIssueDetail } from "hooks/store"; // components import { EstimateDropdown } from "components/dropdowns"; +import { observer } from "mobx-react-lite"; // types import { TIssue } from "@plane/types"; type Props = { - issueId: string; - onChange: (issue: TIssue, formData: Partial) => void; - expandedIssues: string[]; + issue: TIssue; + onChange: (issue: TIssue, data: Partial) => void; disabled: boolean; }; -export const SpreadsheetEstimateColumn: React.FC = (props) => { - const { issueId, onChange, expandedIssues, disabled } = props; - - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading, mutateSubIssues } = useSubIssue(issue.project_id, issue.id, isExpanded); - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); +export const SpreadsheetEstimateColumn: React.FC = observer((props: Props) => { + const { issue, onChange, disabled } = props; return ( - <> - {issueDetail && ( -
- onChange(issueDetail, { estimate_point: data })} - projectId={issueDetail.project_id} - disabled={disabled} - buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" - buttonContainerClassName="w-full" - /> -
- )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId) => ( - - ))} - +
+ onChange(issue, { estimate_point: data })} + projectId={issue.project_id} + disabled={disabled} + buttonVariant="transparent-with-text" + buttonClassName="rounded-none text-left" + buttonContainerClassName="w-full" + /> +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/header-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/header-column.tsx new file mode 100644 index 000000000..040000218 --- /dev/null +++ b/web/components/issues/issue-layouts/spreadsheet/columns/header-column.tsx @@ -0,0 +1,123 @@ +//ui +import { CustomMenu } from "@plane/ui"; +import { + ArrowDownWideNarrow, + ArrowUpNarrowWide, + CheckIcon, + ChevronDownIcon, + Eraser, + ListFilter, + MoveRight, +} from "lucide-react"; +//hooks +import useLocalStorage from "hooks/use-local-storage"; +//types +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssueOrderByOptions } from "@plane/types"; +//constants +import { SPREADSHEET_PROPERTY_DETAILS } from "constants/spreadsheet"; + +interface Props { + property: keyof IIssueDisplayProperties; + displayFilters: IIssueDisplayFilterOptions; + handleDisplayFilterUpdate: (data: Partial) => void; +} + +export const SpreadsheetHeaderColumn = (props: Props) => { + const { displayFilters, handleDisplayFilterUpdate, property } = props; + + const { storedValue: selectedMenuItem, setValue: setSelectedMenuItem } = useLocalStorage( + "spreadsheetViewSorting", + "" + ); + const { storedValue: activeSortingProperty, setValue: setActiveSortingProperty } = useLocalStorage( + "spreadsheetViewActiveSortingProperty", + "" + ); + const propertyDetails = SPREADSHEET_PROPERTY_DETAILS[property]; + + const handleOrderBy = (order: TIssueOrderByOptions, itemKey: string) => { + handleDisplayFilterUpdate({ order_by: order }); + + setSelectedMenuItem(`${order}_${itemKey}`); + setActiveSortingProperty(order === "-created_at" ? "" : itemKey); + }; + + return ( + +
+ {} + {propertyDetails.title} +
+
+ {activeSortingProperty === property && ( +
+ +
+ )} +
+ + } + width="xl" + placement="bottom-end" + > + handleOrderBy(propertyDetails.ascendingOrderKey, property)}> +
+
+ + {propertyDetails.ascendingOrderTitle} + + {propertyDetails.descendingOrderTitle} +
+ + {selectedMenuItem === `${propertyDetails.ascendingOrderKey}_${property}` && } +
+
+ handleOrderBy(propertyDetails.descendingOrderKey, property)}> +
+
+ + {propertyDetails.descendingOrderTitle} + + {propertyDetails.ascendingOrderTitle} +
+ + {selectedMenuItem === `${propertyDetails.descendingOrderKey}_${property}` && ( + + )} +
+
+ {selectedMenuItem && + selectedMenuItem !== "" && + displayFilters?.order_by !== "-created_at" && + selectedMenuItem.includes(property) && ( + handleOrderBy("-created_at", property)} + > +
+ + Clear sorting +
+
+ )} +
+ ); +}; diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/index.ts b/web/components/issues/issue-layouts/spreadsheet/columns/index.ts index a6c4979b3..acfd02fc5 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/index.ts +++ b/web/components/issues/issue-layouts/spreadsheet/columns/index.ts @@ -1,7 +1,5 @@ -export * from "./issue"; export * from "./assignee-column"; export * from "./attachment-column"; -export * from "./columns-list"; export * from "./created-on-column"; export * from "./due-date-column"; export * from "./estimate-column"; @@ -11,4 +9,4 @@ export * from "./priority-column"; export * from "./start-date-column"; export * from "./state-column"; export * from "./sub-issue-column"; -export * from "./updated-on-column"; +export * from "./updated-on-column"; \ No newline at end of file diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/issue/index.ts b/web/components/issues/issue-layouts/spreadsheet/columns/issue/index.ts deleted file mode 100644 index b8d09d1df..000000000 --- a/web/components/issues/issue-layouts/spreadsheet/columns/issue/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./spreadsheet-issue-column"; -export * from "./issue-column"; diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/issue/issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/issue/issue-column.tsx deleted file mode 100644 index 612bba9df..000000000 --- a/web/components/issues/issue-layouts/spreadsheet/columns/issue/issue-column.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useRef, useState } from "react"; -import { useRouter } from "next/router"; -import { ChevronRight, MoreHorizontal } from "lucide-react"; -// components -import { Tooltip } from "@plane/ui"; -// hooks -import useOutsideClickDetector from "hooks/use-outside-click-detector"; -// types -import { TIssue, IIssueDisplayProperties } from "@plane/types"; -import { useProject } from "hooks/store"; - -type Props = { - issue: TIssue; - expanded: boolean; - handleToggleExpand: (issueId: string) => void; - properties: IIssueDisplayProperties; - quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; - canEditProperties: (projectId: string | undefined) => boolean; - nestingLevel: number; -}; - -export const IssueColumn: React.FC = ({ - issue, - expanded, - handleToggleExpand, - properties, - quickActions, - canEditProperties, - nestingLevel, -}) => { - // router - const router = useRouter(); - // hooks - const { getProjectById } = useProject(); - // states - const [isMenuActive, setIsMenuActive] = useState(false); - - const menuActionRef = useRef(null); - - const handleIssuePeekOverview = (issue: TIssue) => { - const { query } = router; - - router.push({ - pathname: router.pathname, - query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project_id }, - }); - }; - - const paddingLeft = `${nestingLevel * 54}px`; - - useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false)); - - const customActionButton = ( -
setIsMenuActive(!isMenuActive)} - > - -
- ); - - return ( - <> -
- {properties.key && ( -
-
- - {getProjectById(issue.project_id)?.identifier}-{issue.sequence_id} - - - {canEditProperties(issue.project_id) && ( - - )} -
- - {issue.sub_issues_count > 0 && ( -
- -
- )} -
- )} -
- -
handleIssuePeekOverview(issue)} - > - {issue.name} -
-
-
-
- - ); -}; diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx deleted file mode 100644 index d906e522a..000000000 --- a/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from "react"; - -// components -import { IssueColumn } from "components/issues"; -// hooks -import { useIssueDetail } from "hooks/store"; -// types -import { TIssue, IIssueDisplayProperties } from "@plane/types"; - -type Props = { - issueId: string; - expandedIssues: string[]; - setExpandedIssues: React.Dispatch>; - properties: IIssueDisplayProperties; - quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode; - canEditProperties: (projectId: string | undefined) => boolean; - nestingLevel?: number; -}; - -export const SpreadsheetIssuesColumn: React.FC = ({ - issueId, - expandedIssues, - setExpandedIssues, - properties, - quickActions, - canEditProperties, - nestingLevel = 0, -}) => { - const handleToggleExpand = (issueId: string) => { - setExpandedIssues((prevState) => { - const newArray = [...prevState]; - const index = newArray.indexOf(issueId); - - if (index > -1) newArray.splice(index, 1); - else newArray.push(issueId); - - return newArray; - }); - }; - - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading } = useSubIssue(issue.project_id, issue.id, isExpanded); - - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); - - return ( - <> - {issueDetail && ( - - )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( - - ))} - - ); -}; diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx index a2fef5a5e..82015056e 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx @@ -1,70 +1,39 @@ import React from "react"; - +import { observer } from "mobx-react-lite"; // components import { IssuePropertyLabels } from "../../properties"; // hooks -import { useIssueDetail, useLabel } from "hooks/store"; +import { useLabel } from "hooks/store"; // types -import { TIssue, IIssueLabel } from "@plane/types"; +import { TIssue } from "@plane/types"; type Props = { - issueId: string; - onChange: (issue: TIssue, formData: Partial) => void; - labels: IIssueLabel[] | undefined; - expandedIssues: string[]; + issue: TIssue; + onChange: (issue: TIssue, data: Partial) => void; disabled: boolean; }; -export const SpreadsheetLabelColumn: React.FC = (props) => { - const { issueId, onChange, labels, expandedIssues, disabled } = props; +export const SpreadsheetLabelColumn: React.FC = observer((props: Props) => { + const { issue, onChange, disabled } = props; // hooks const { labelMap } = useLabel(); - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading, mutateSubIssues } = useSubIssue(issue.project_id, issue.id, isExpanded); - - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); - - const defaultLabelOptions = issueDetail?.label_ids?.map((id) => labelMap[id]) || []; + const defaultLabelOptions = issue?.label_ids?.map((id) => labelMap[id]) || []; return ( - <> - {issueDetail && ( - { - onChange(issueDetail, { label_ids: data }); - }} - className="h-11 w-full border-b-[0.5px] border-custom-border-200 hover:bg-custom-background-80" - buttonClassName="px-2.5 h-full" - hideDropdownArrow - maxRender={1} - disabled={disabled} - placeholderText="Select labels" - /> - )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( -
- -
- ))} - + { + onChange(issue, { label_ids: data }); + }} + className="h-11 w-full border-b-[0.5px] border-custom-border-200 hover:bg-custom-background-80" + buttonClassName="px-2.5 h-full" + hideDropdownArrow + maxRender={1} + disabled={disabled} + placeholderText="Select labels" + /> ); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx index a86dcedd7..2d3e7b670 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx @@ -1,39 +1,18 @@ import React from "react"; -// hooks -import { useIssueDetail } from "hooks/store"; +import { observer } from "mobx-react-lite"; // types +import { TIssue } from "@plane/types"; type Props = { - issueId: string; - expandedIssues: string[]; + issue: TIssue; }; -export const SpreadsheetLinkColumn: React.FC = (props) => { - const { issueId, expandedIssues } = props; - - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading } = useSubIssue(issue.project_id, issue.id, isExpanded); - - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); +export const SpreadsheetLinkColumn: React.FC = observer((props: Props) => { + const { issue } = props; return ( - <> -
- {issueDetail?.link_count} {issueDetail?.link_count === 1 ? "link" : "links"} -
- - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( -
- -
- ))} - +
+ {issue?.link_count} {issue?.link_count === 1 ? "link" : "links"} +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx index 5462a9e13..0a8321740 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx @@ -1,54 +1,29 @@ import React from "react"; -// hooks -import { useIssueDetail } from "hooks/store"; +import { observer } from "mobx-react-lite"; // components import { PriorityDropdown } from "components/dropdowns"; // types import { TIssue } from "@plane/types"; type Props = { - issueId: string; + issue: TIssue; onChange: (issue: TIssue, data: Partial) => void; - expandedIssues: string[]; disabled: boolean; }; -export const SpreadsheetPriorityColumn: React.FC = (props) => { - const { issueId, onChange, expandedIssues, disabled } = props; - // store hooks - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - // derived values - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); - const isExpanded = expandedIssues.indexOf(issueId) > -1; +export const SpreadsheetPriorityColumn: React.FC = observer((props: Props) => { + const { issue, onChange, disabled } = props; return ( - <> - {issueDetail && ( -
- onChange(issueDetail, { priority: data })} - disabled={disabled} - buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" - buttonContainerClassName="w-full" - /> -
- )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( - - ))} - +
+ onChange(issue, { priority: data })} + disabled={disabled} + buttonVariant="transparent-with-text" + buttonClassName="rounded-none text-left" + buttonContainerClassName="w-full" + /> +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx index 09248e320..778f9cdac 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx @@ -1,6 +1,5 @@ import React from "react"; -// hooks -import { useIssueDetail } from "hooks/store"; +import { observer } from "mobx-react-lite"; // components import { DateDropdown } from "components/dropdowns"; // helpers @@ -9,50 +8,25 @@ import { renderFormattedPayloadDate } from "helpers/date-time.helper"; import { TIssue } from "@plane/types"; type Props = { - issueId: string; - onChange: (issue: TIssue, formData: Partial) => void; - expandedIssues: string[]; + issue: TIssue; + onChange: (issue: TIssue, data: Partial) => void; disabled: boolean; }; -export const SpreadsheetStartDateColumn: React.FC = ({ issueId, onChange, expandedIssues, disabled }) => { - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading, mutateSubIssues } = useSubIssue(issue.project_id, issue.id, isExpanded); - - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); +export const SpreadsheetStartDateColumn: React.FC = observer((props: Props) => { + const { issue, onChange, disabled } = props; return ( - <> - {issueDetail && ( -
- onChange(issueDetail, { start_date: data ? renderFormattedPayloadDate(data) : null })} - disabled={disabled} - placeholder="Start date" - buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" - buttonContainerClassName="w-full" - /> -
- )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId) => ( - - ))} - +
+ onChange(issue, { start_date: data ? renderFormattedPayloadDate(data) : null })} + disabled={disabled} + placeholder="Start date" + buttonVariant="transparent-with-text" + buttonClassName="rounded-none text-left" + buttonContainerClassName="w-full" + /> +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx index 39508ca37..0050c8acf 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx @@ -1,59 +1,30 @@ import React from "react"; -// hooks -import { useIssueDetail } from "hooks/store"; +import { observer } from "mobx-react-lite"; // components import { StateDropdown } from "components/dropdowns"; // types -import { TIssue, IState } from "@plane/types"; +import { TIssue } from "@plane/types"; type Props = { - issueId: string; + issue: TIssue; onChange: (issue: TIssue, data: Partial) => void; - states: IState[] | undefined; - expandedIssues: string[]; disabled: boolean; }; -export const SpreadsheetStateColumn: React.FC = (props) => { - const { issueId, onChange, states, expandedIssues, disabled } = props; - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); - - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading, mutateSubIssues } = useSubIssue(issue.project_id, issue.id, isExpanded); +export const SpreadsheetStateColumn: React.FC = observer((props) => { + const { issue, onChange, disabled } = props; return ( - <> - {issueDetail && ( -
- onChange(issueDetail, { state_id: data })} - disabled={disabled} - buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" - buttonContainerClassName="w-full" - /> -
- )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId) => ( - - ))} - +
+ onChange(issue, { state_id: data })} + disabled={disabled} + buttonVariant="transparent-with-text" + buttonClassName="rounded-none text-left" + buttonContainerClassName="w-full" + /> +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx index e641c1e01..c0e41d2c0 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx @@ -1,37 +1,18 @@ import React from "react"; +import { observer } from "mobx-react-lite"; // hooks -import { useIssueDetail } from "hooks/store"; +import { TIssue } from "@plane/types"; type Props = { - issueId: string; - expandedIssues: string[]; + issue: TIssue; }; -export const SpreadsheetSubIssueColumn: React.FC = (props) => { - const { issueId, expandedIssues } = props; - - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading } = useSubIssue(issue.project_id, issue.id, isExpanded); - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); +export const SpreadsheetSubIssueColumn: React.FC = observer((props: Props) => { + const { issue } = props; return ( - <> -
- {issueDetail?.sub_issues_count} {issueDetail?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} -
- - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( -
- -
- ))} - +
+ {issue?.sub_issues_count} {issue?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx index 3ce036d69..f84989192 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx @@ -1,43 +1,19 @@ import React from "react"; -// hooks -// import useSubIssue from "hooks/use-sub-issue"; +import { observer } from "mobx-react-lite"; // helpers import { renderFormattedDate } from "helpers/date-time.helper"; // types -import { useIssueDetail } from "hooks/store"; +import { TIssue } from "@plane/types"; type Props = { - issueId: string; - expandedIssues: string[]; + issue: TIssue; }; -export const SpreadsheetUpdatedOnColumn: React.FC = (props) => { - const { issueId, expandedIssues } = props; - - const isExpanded = expandedIssues.indexOf(issueId) > -1; - - // const { subIssues, isLoading } = useSubIssue(issue.project_id, issue.id, isExpanded); - const { subIssues: subIssuesStore, issue } = useIssueDetail(); - - const issueDetail = issue.getIssueById(issueId); - const subIssues = subIssuesStore.subIssuesByIssueId(issueId); - +export const SpreadsheetUpdatedOnColumn: React.FC = observer((props: Props) => { + const { issue } = props; return ( - <> - {issueDetail && ( -
- {renderFormattedDate(issueDetail.updated_at)} -
- )} - - {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( -
- -
- ))} - +
+ {renderFormattedDate(issue.updated_at)} +
); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/index.ts b/web/components/issues/issue-layouts/spreadsheet/index.ts index 10fc26219..8f7c4a7fd 100644 --- a/web/components/issues/issue-layouts/spreadsheet/index.ts +++ b/web/components/issues/issue-layouts/spreadsheet/index.ts @@ -1,5 +1,4 @@ export * from "./columns"; export * from "./roots"; -export * from "./spreadsheet-column"; export * from "./spreadsheet-view"; export * from "./quick-add-issue-form"; diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx new file mode 100644 index 000000000..0091f000c --- /dev/null +++ b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx @@ -0,0 +1,186 @@ +import { IIssueDisplayProperties, TIssue, TIssueMap } from "@plane/types"; +import { SPREADSHEET_PROPERTY_DETAILS, SPREADSHEET_PROPERTY_LIST } from "constants/spreadsheet"; +import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; +import { Tooltip } from "@plane/ui"; +import useOutsideClickDetector from "hooks/use-outside-click-detector"; +import { useIssueDetail, useProject } from "hooks/store"; +import { useRef, useState } from "react"; +import { useRouter } from "next/router"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; +import { EIssueActions } from "../types"; +import { observer } from "mobx-react-lite"; + +interface Props { + displayProperties: IIssueDisplayProperties; + isEstimateEnabled: boolean; + quickActions: ( + issue: TIssue, + customActionButton?: React.ReactElement, + portalElement?: HTMLDivElement | null + ) => React.ReactNode; + canEditProperties: (projectId: string | undefined) => boolean; + handleIssues: (issue: TIssue, action: EIssueActions) => Promise; + portalElement: React.MutableRefObject; + nestingLevel: number; + issueId: string; +} + +export const SpreadsheetIssueRow = observer((props: Props) => { + const { + displayProperties, + issueId, + isEstimateEnabled, + nestingLevel, + portalElement, + handleIssues, + quickActions, + canEditProperties, + } = props; + // router + const router = useRouter(); + + const { workspaceSlug } = router.query; + + const { getProjectById } = useProject(); + // states + const [isMenuActive, setIsMenuActive] = useState(false); + const [isExpanded, setExpanded] = useState(false); + + const menuActionRef = useRef(null); + + const handleIssuePeekOverview = (issue: TIssue) => { + const { query } = router; + + router.push({ + pathname: router.pathname, + query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project_id }, + }); + }; + + const { subIssues: subIssuesStore, issue } = useIssueDetail(); + + const issueDetail = issue.getIssueById(issueId); + const subIssues = subIssuesStore.subIssuesByIssueId(issueId); + + const paddingLeft = `${nestingLevel * 54}px`; + + useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false)); + + const handleToggleExpand = () => { + setExpanded((prevState) => { + if (!prevState && workspaceSlug && issueDetail) + subIssuesStore.fetchSubIssues(workspaceSlug.toString(), issueDetail.project_id, issueDetail.id); + return !prevState; + }); + }; + + const customActionButton = ( +
setIsMenuActive(!isMenuActive)} + > + +
+ ); + + if (!issueDetail) return null; + + const disableUserActions = !canEditProperties(issueDetail.project_id); + + return ( + <> + + {/* first column/ issue name and key column */} + + +
+
+ + {getProjectById(issueDetail.project_id)?.identifier}-{issueDetail.sequence_id} + + + {canEditProperties(issueDetail.project_id) && ( + + )} +
+ + {issueDetail.sub_issues_count > 0 && ( +
+ +
+ )} +
+
+
+ +
handleIssuePeekOverview(issueDetail)} + > + {issueDetail.name} +
+
+
+ + {/* Rest of the columns */} + {SPREADSHEET_PROPERTY_LIST.map((property) => { + const { Column } = SPREADSHEET_PROPERTY_DETAILS[property]; + + const shouldRenderProperty = property === "estimate" ? isEstimateEnabled : true; + + return ( + + + ) => + handleIssues({ ...issue, ...data }, EIssueActions.UPDATE) + } + disabled={disableUserActions} + /> + + + ); + })} + + + {isExpanded && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssueId: string) => ( + + ))} + + ); +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-column.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-column.tsx deleted file mode 100644 index 0a0fbe9c0..000000000 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-column.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { - ArrowDownWideNarrow, - ArrowUpNarrowWide, - CheckIcon, - ChevronDownIcon, - Eraser, - ListFilter, - MoveRight, -} from "lucide-react"; -// hooks -import useLocalStorage from "hooks/use-local-storage"; -// components -import { - SpreadsheetAssigneeColumn, - SpreadsheetAttachmentColumn, - SpreadsheetCreatedOnColumn, - SpreadsheetDueDateColumn, - SpreadsheetEstimateColumn, - SpreadsheetLabelColumn, - SpreadsheetLinkColumn, - SpreadsheetPriorityColumn, - SpreadsheetStartDateColumn, - SpreadsheetStateColumn, - SpreadsheetSubIssueColumn, - SpreadsheetUpdatedOnColumn, -} from "components/issues"; -// ui -import { CustomMenu } from "@plane/ui"; -// types -import { TIssue, IIssueDisplayFilterOptions, IIssueLabel, IState, TIssueOrderByOptions } from "@plane/types"; -// constants -import { SPREADSHEET_PROPERTY_DETAILS } from "constants/spreadsheet"; - -type Props = { - canEditProperties: (projectId: string | undefined) => boolean; - displayFilters: IIssueDisplayFilterOptions; - expandedIssues: string[]; - handleDisplayFilterUpdate: (data: Partial) => void; - handleUpdateIssue: (issue: TIssue, data: Partial) => void; - issues: TIssue[] | undefined; - property: string; - labels?: IIssueLabel[] | undefined; - states?: IState[] | undefined; -}; - -export const SpreadsheetColumn: React.FC = (props) => { - const { - canEditProperties, - displayFilters, - expandedIssues, - handleDisplayFilterUpdate, - handleUpdateIssue, - issues, - property, - labels, - states, - } = props; - - const { storedValue: selectedMenuItem, setValue: setSelectedMenuItem } = useLocalStorage( - "spreadsheetViewSorting", - "" - ); - const { storedValue: activeSortingProperty, setValue: setActiveSortingProperty } = useLocalStorage( - "spreadsheetViewActiveSortingProperty", - "" - ); - - const handleOrderBy = (order: TIssueOrderByOptions, itemKey: string) => { - handleDisplayFilterUpdate({ order_by: order }); - - setSelectedMenuItem(`${order}_${itemKey}`); - setActiveSortingProperty(order === "-created_at" ? "" : itemKey); - }; - - const propertyDetails = SPREADSHEET_PROPERTY_DETAILS[property]; - - return ( -
-
- -
- {} - {propertyDetails.title} -
-
- {activeSortingProperty === property && ( -
- -
- )} -
-
- } - width="xl" - placement="bottom-end" - > - handleOrderBy(propertyDetails.ascendingOrderKey, property)}> -
-
- - {propertyDetails.ascendingOrderTitle} - - {propertyDetails.descendingOrderTitle} -
- - {selectedMenuItem === `${propertyDetails.ascendingOrderKey}_${property}` && ( - - )} -
-
- handleOrderBy(propertyDetails.descendingOrderKey, property)}> -
-
- - {propertyDetails.descendingOrderTitle} - - {propertyDetails.ascendingOrderTitle} -
- - {selectedMenuItem === `${propertyDetails.descendingOrderKey}_${property}` && ( - - )} -
-
- {selectedMenuItem && - selectedMenuItem !== "" && - displayFilters?.order_by !== "-created_at" && - selectedMenuItem.includes(property) && ( - handleOrderBy("-created_at", property)} - > -
- - Clear sorting -
-
- )} - -
- -
- {issues?.map((issue) => { - const disableUserActions = !canEditProperties(issue.project_id); - return ( -
- {property === "state" ? ( - ) => handleUpdateIssue(issue, data)} - states={states} - /> - ) : property === "priority" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "estimate" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "assignee" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "labels" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "start_date" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "due_date" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "created_on" ? ( - - ) : property === "updated_on" ? ( - - ) : property === "link" ? ( - - ) : property === "attachment_count" ? ( - - ) : property === "sub_issue_count" ? ( - - ) : null} -
- ); - })} -
- - ); -}; diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx new file mode 100644 index 000000000..704c9f904 --- /dev/null +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx @@ -0,0 +1,59 @@ +// ui +import { LayersIcon } from "@plane/ui"; +// types +import { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types"; +// constants +import { SPREADSHEET_PROPERTY_LIST } from "constants/spreadsheet"; +// components +import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; +import { SpreadsheetHeaderColumn } from "./columns/header-column"; + + +interface Props { + displayProperties: IIssueDisplayProperties; + displayFilters: IIssueDisplayFilterOptions; + handleDisplayFilterUpdate: (data: Partial) => void; + isEstimateEnabled: boolean; +} + +export const SpreadsheetHeader = (props: Props) => { + const { displayProperties, displayFilters, handleDisplayFilterUpdate, isEstimateEnabled } = props; + + return ( + + + + + + #ID + + + + + Issue + + + + {SPREADSHEET_PROPERTY_LIST.map((property) => { + const shouldRenderProperty = property === "estimate" ? isEstimateEnabled : true; + + return ( + + + + + + ); + })} + + + ); +}; diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 0e5d2ba94..adf1e53f4 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -1,20 +1,26 @@ import React, { useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; // components -import { SpreadsheetColumnsList, SpreadsheetIssuesColumn, SpreadsheetQuickAddIssueForm } from "components/issues"; -import { Spinner, LayersIcon } from "@plane/ui"; +import { Spinner } from "@plane/ui"; +import { SpreadsheetQuickAddIssueForm } from "components/issues"; // types -import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabel, IState } from "@plane/types"; +import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types"; import { EIssueActions } from "../types"; +import { useProject } from "hooks/store"; +import { SpreadsheetHeader } from "./spreadsheet-header"; +import { SpreadsheetIssueRow } from "./issue-row"; +import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; type Props = { displayProperties: IIssueDisplayProperties; displayFilters: IIssueDisplayFilterOptions; handleDisplayFilterUpdate: (data: Partial) => void; issues: TIssue[] | undefined; - labels?: IIssueLabel[] | undefined; - states?: IState[] | undefined; - quickActions: (issue: TIssue, customActionButton: any) => React.ReactNode; + quickActions: ( + issue: TIssue, + customActionButton?: React.ReactElement, + portalElement?: HTMLDivElement | null + ) => React.ReactNode; handleIssues: (issue: TIssue, action: EIssueActions) => Promise; openIssuesListModal?: (() => void) | null; quickAddCallback?: ( @@ -35,8 +41,6 @@ export const SpreadsheetView: React.FC = observer((props) => { displayFilters, handleDisplayFilterUpdate, issues, - labels, - states, quickActions, handleIssues, quickAddCallback, @@ -46,16 +50,36 @@ export const SpreadsheetView: React.FC = observer((props) => { disableIssueCreation, } = props; // states - const [expandedIssues, setExpandedIssues] = useState([]); - const [isScrolled, setIsScrolled] = useState(false); + const isScrolled = useRef(false); // refs - const containerRef = useRef(null); + const containerRef = useRef(null); + const portalRef = useRef(null); + + const { currentProjectDetails } = useProject(); + + const isEstimateEnabled: boolean = currentProjectDetails?.estimate !== null; const handleScroll = () => { if (!containerRef.current) return; - const scrollLeft = containerRef.current.scrollLeft; - setIsScrolled(scrollLeft > 0); + + const columnShadow = "8px 22px 22px 10px rgba(0, 0, 0, 0.05)"; // shadow for regular columns + const headerShadow = "8px -22px 22px 10px rgba(0, 0, 0, 0.05)"; // shadow for headers + + //The shadow styles are added this way to avoid re-render of all the rows of table, which could be costly + if (scrollLeft > 0 !== isScrolled.current) { + const firtColumns = containerRef.current.querySelectorAll("table tr td:first-child, th:first-child"); + + for (let i = 0; i < firtColumns.length; i++) { + const shadow = i === 0 ? headerShadow : columnShadow; + if (scrollLeft > 0) { + (firtColumns[i] as HTMLElement).style.boxShadow = shadow; + } else { + (firtColumns[i] as HTMLElement).style.boxShadow = "none"; + } + } + isScrolled.current = scrollLeft > 0; + } }; useEffect(() => { @@ -76,105 +100,38 @@ export const SpreadsheetView: React.FC = observer((props) => { ); return ( -
-
-
- {issues && issues.length > 0 && ( - <> -
-
-
- {displayProperties.key && ( - - #ID - - )} - - - Issue - -
- - {issues.map((issue, index) => - issue ? ( - - ) : null - )} -
-
- - +
+
+ + + + {issues.map(({ id }) => ( + handleIssues({ ...issue, ...data }, EIssueActions.UPDATE)} - issues={issues} - labels={labels} - states={states} + nestingLevel={0} + isEstimateEnabled={isEstimateEnabled} + handleIssues={handleIssues} + portalElement={portalRef} /> - + ))} + +
+
+
+
+ {enableQuickCreateIssue && !disableIssueCreation && ( + )} -
{/* empty div to show right most border */} -
- -
-
- {enableQuickCreateIssue && !disableIssueCreation && ( - - )} -
- - {/* {!disableUserActions && - !isInlineCreateIssueFormOpen && - (type === "issue" ? ( - - ) : ( - - - New Issue - - } - optionsClassName="left-5 !w-36" - noBorder - > - setIsInlineCreateIssueFormOpen(true)}> - Create new - - {openIssuesListModal && ( - Add an existing issue - )} - - ))} */}
diff --git a/web/constants/spreadsheet.ts b/web/constants/spreadsheet.ts index 1f759b43c..6a5e55a62 100644 --- a/web/constants/spreadsheet.ts +++ b/web/constants/spreadsheet.ts @@ -1,8 +1,22 @@ -import { TIssueOrderByOptions } from "@plane/types"; +import { IIssueDisplayProperties, TIssue, TIssueOrderByOptions } from "@plane/types"; import { LayersIcon, DoubleCircleIcon, UserGroupIcon } from "@plane/ui"; import { CalendarDays, Link2, Signal, Tag, Triangle, Paperclip, CalendarClock, CalendarCheck } from "lucide-react"; import { FC } from "react"; import { ISvgIcons } from "@plane/ui/src/icons/type"; +import { + SpreadsheetAssigneeColumn, + SpreadsheetAttachmentColumn, + SpreadsheetCreatedOnColumn, + SpreadsheetDueDateColumn, + SpreadsheetEstimateColumn, + SpreadsheetLabelColumn, + SpreadsheetLinkColumn, + SpreadsheetPriorityColumn, + SpreadsheetStartDateColumn, + SpreadsheetStateColumn, + SpreadsheetSubIssueColumn, + SpreadsheetUpdatedOnColumn, +} from "components/issues/issue-layouts/spreadsheet"; export const SPREADSHEET_PROPERTY_DETAILS: { [key: string]: { @@ -12,6 +26,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: TIssueOrderByOptions; descendingOrderTitle: string; icon: FC; + Column: React.FC<{ issue: TIssue; onChange: (issue: TIssue, data: Partial) => void; disabled: boolean }>; }; } = { assignee: { @@ -21,6 +36,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "-assignees__first_name", descendingOrderTitle: "Z", icon: UserGroupIcon, + Column: SpreadsheetAssigneeColumn, }, created_on: { title: "Created on", @@ -29,6 +45,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "created_at", descendingOrderTitle: "Old", icon: CalendarDays, + Column: SpreadsheetCreatedOnColumn, }, due_date: { title: "Due date", @@ -37,6 +54,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "target_date", descendingOrderTitle: "Old", icon: CalendarCheck, + Column: SpreadsheetDueDateColumn, }, estimate: { title: "Estimate", @@ -45,6 +63,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "-estimate_point", descendingOrderTitle: "High", icon: Triangle, + Column: SpreadsheetEstimateColumn, }, labels: { title: "Labels", @@ -53,6 +72,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "-labels__name", descendingOrderTitle: "Z", icon: Tag, + Column: SpreadsheetLabelColumn, }, priority: { title: "Priority", @@ -61,6 +81,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "-priority", descendingOrderTitle: "Urgent", icon: Signal, + Column: SpreadsheetPriorityColumn, }, start_date: { title: "Start date", @@ -69,6 +90,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "start_date", descendingOrderTitle: "Old", icon: CalendarClock, + Column: SpreadsheetStartDateColumn, }, state: { title: "State", @@ -77,6 +99,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "-state__name", descendingOrderTitle: "Z", icon: DoubleCircleIcon, + Column: SpreadsheetStateColumn, }, updated_on: { title: "Updated on", @@ -85,6 +108,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "updated_at", descendingOrderTitle: "Old", icon: CalendarDays, + Column: SpreadsheetUpdatedOnColumn, }, link: { title: "Link", @@ -93,6 +117,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "link_count", descendingOrderTitle: "Least", icon: Link2, + Column: SpreadsheetLinkColumn, }, attachment_count: { title: "Attachment", @@ -101,6 +126,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "attachment_count", descendingOrderTitle: "Least", icon: Paperclip, + Column: SpreadsheetAttachmentColumn, }, sub_issue_count: { title: "Sub-issue", @@ -109,5 +135,21 @@ export const SPREADSHEET_PROPERTY_DETAILS: { descendingOrderKey: "sub_issues_count", descendingOrderTitle: "Least", icon: LayersIcon, + Column: SpreadsheetSubIssueColumn, }, }; + +export const SPREADSHEET_PROPERTY_LIST: (keyof IIssueDisplayProperties)[] = [ + "state", + "priority", + "assignee", + "labels", + "start_date", + "due_date", + "estimate", + "created_on", + "updated_on", + "link", + "attachment_count", + "sub_issue_count", +];