mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
df97b35a99
* 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>
140 lines
4.9 KiB
TypeScript
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>
|
|
);
|
|
});
|