plane/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx
rahulramesha df97b35a99 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 <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:19:43 +05:30

140 lines
4.9 KiB
TypeScript

import React, { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
// components
import { Spinner } from "@plane/ui";
import { SpreadsheetQuickAddIssueForm } from "components/issues";
// 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<IIssueDisplayFilterOptions>) => void;
issues: TIssue[] | undefined;
quickActions: (
issue: TIssue,
customActionButton?: React.ReactElement,
portalElement?: HTMLDivElement | null
) => React.ReactNode;
handleIssues: (issue: TIssue, action: EIssueActions) => Promise<void>;
openIssuesListModal?: (() => void) | null;
quickAddCallback?: (
workspaceSlug: string,
projectId: string,
data: TIssue,
viewId?: string
) => Promise<TIssue | undefined>;
viewId?: string;
canEditProperties: (projectId: string | undefined) => boolean;
enableQuickCreateIssue?: boolean;
disableIssueCreation?: boolean;
};
export const SpreadsheetView: React.FC<Props> = observer((props) => {
const {
displayProperties,
displayFilters,
handleDisplayFilterUpdate,
issues,
quickActions,
handleIssues,
quickAddCallback,
viewId,
canEditProperties,
enableQuickCreateIssue,
disableIssueCreation,
} = props;
// states
const isScrolled = useRef(false);
// refs
const containerRef = useRef<HTMLTableElement | null>(null);
const portalRef = useRef<HTMLDivElement | null>(null);
const { currentProjectDetails } = useProject();
const isEstimateEnabled: boolean = currentProjectDetails?.estimate !== null;
const handleScroll = () => {
if (!containerRef.current) return;
const scrollLeft = containerRef.current.scrollLeft;
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(() => {
const currentContainerRef = containerRef.current;
if (currentContainerRef) currentContainerRef.addEventListener("scroll", handleScroll);
return () => {
if (currentContainerRef) currentContainerRef.removeEventListener("scroll", handleScroll);
};
}, []);
if (!issues || issues.length === 0)
return (
<div className="grid h-full w-full place-items-center">
<Spinner />
</div>
);
return (
<div className="relative flex flex-col h-full w-full overflow-x-hidden whitespace-nowrap rounded-lg bg-custom-background-200 text-custom-text-200">
<div ref={portalRef} className="spreadsheet-menu-portal" />
<div ref={containerRef} className="horizontal-scroll-enable h-full w-full">
<table className="divide-x-[0.5px] divide-custom-border-200 overflow-y-auto">
<SpreadsheetHeader
displayProperties={displayProperties}
displayFilters={displayFilters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
isEstimateEnabled={isEstimateEnabled}
/>
<tbody>
{issues.map(({ id }) => (
<SpreadsheetIssueRow
key={id}
issueId={id}
displayProperties={displayProperties}
quickActions={quickActions}
canEditProperties={canEditProperties}
nestingLevel={0}
isEstimateEnabled={isEstimateEnabled}
handleIssues={handleIssues}
portalElement={portalRef}
/>
))}
</tbody>
</table>
</div>
<div className="border-t border-custom-border-100">
<div className="z-5 sticky bottom-0 left-0 mb-3">
{enableQuickCreateIssue && !disableIssueCreation && (
<SpreadsheetQuickAddIssueForm formKey="name" quickAddCallback={quickAddCallback} viewId={viewId} />
)}
</div>
</div>
</div>
);
});