2024-03-06 13:09:14 +00:00
|
|
|
import { MutableRefObject, useCallback, useEffect, useRef } from "react";
|
2024-01-12 08:22:04 +00:00
|
|
|
import { observer } from "mobx-react-lite";
|
|
|
|
//types
|
2024-03-06 13:09:14 +00:00
|
|
|
import { useTableKeyboardNavigation } from "hooks/use-table-keyboard-navigation";
|
2024-01-12 08:22:04 +00:00
|
|
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssue } from "@plane/types";
|
|
|
|
//components
|
|
|
|
import { SpreadsheetIssueRow } from "./issue-row";
|
|
|
|
import { SpreadsheetHeader } from "./spreadsheet-header";
|
2024-03-13 06:56:10 +00:00
|
|
|
import { useIntersectionObserver } from "hooks/use-intersection-observer";
|
2024-01-12 08:22:04 +00:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
displayProperties: IIssueDisplayProperties;
|
|
|
|
displayFilters: IIssueDisplayFilterOptions;
|
|
|
|
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
2024-01-18 10:21:17 +00:00
|
|
|
issueIds: string[];
|
2024-01-12 08:22:04 +00:00
|
|
|
isEstimateEnabled: boolean;
|
|
|
|
quickActions: (
|
|
|
|
issue: TIssue,
|
|
|
|
customActionButton?: React.ReactElement,
|
|
|
|
portalElement?: HTMLDivElement | null
|
|
|
|
) => React.ReactNode;
|
2024-03-13 06:56:10 +00:00
|
|
|
updateIssue:
|
|
|
|
| ((projectId: string | undefined | null, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
|
|
|
| undefined;
|
2024-01-12 08:22:04 +00:00
|
|
|
canEditProperties: (projectId: string | undefined) => boolean;
|
|
|
|
portalElement: React.MutableRefObject<HTMLDivElement | null>;
|
2024-02-09 10:23:15 +00:00
|
|
|
containerRef: MutableRefObject<HTMLTableElement | null>;
|
2024-03-13 06:56:10 +00:00
|
|
|
onEndOfListTrigger: () => void;
|
2024-01-12 08:22:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const SpreadsheetTable = observer((props: Props) => {
|
|
|
|
const {
|
|
|
|
displayProperties,
|
|
|
|
displayFilters,
|
|
|
|
handleDisplayFilterUpdate,
|
2024-01-18 10:21:17 +00:00
|
|
|
issueIds,
|
2024-01-12 08:22:04 +00:00
|
|
|
isEstimateEnabled,
|
|
|
|
portalElement,
|
|
|
|
quickActions,
|
2024-03-06 15:17:38 +00:00
|
|
|
updateIssue,
|
2024-01-12 08:22:04 +00:00
|
|
|
canEditProperties,
|
2024-02-09 10:23:15 +00:00
|
|
|
containerRef,
|
2024-03-13 06:56:10 +00:00
|
|
|
onEndOfListTrigger,
|
2024-01-12 08:22:04 +00:00
|
|
|
} = props;
|
|
|
|
|
2024-02-09 10:23:15 +00:00
|
|
|
// states
|
|
|
|
const isScrolled = useRef(false);
|
2024-03-13 06:56:10 +00:00
|
|
|
const intersectionRef = useRef<HTMLTableSectionElement | null>(null);
|
2024-02-09 10:23:15 +00:00
|
|
|
|
2024-03-06 13:09:14 +00:00
|
|
|
const handleScroll = useCallback(() => {
|
2024-02-09 10:23:15 +00:00
|
|
|
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) {
|
2024-03-06 13:09:14 +00:00
|
|
|
const firstColumns = containerRef.current.querySelectorAll("table tr td:first-child, th:first-child");
|
2024-02-09 10:23:15 +00:00
|
|
|
|
2024-03-06 13:09:14 +00:00
|
|
|
for (let i = 0; i < firstColumns.length; i++) {
|
2024-02-09 10:23:15 +00:00
|
|
|
const shadow = i === 0 ? headerShadow : columnShadow;
|
|
|
|
if (scrollLeft > 0) {
|
2024-03-06 13:09:14 +00:00
|
|
|
(firstColumns[i] as HTMLElement).style.boxShadow = shadow;
|
2024-02-09 10:23:15 +00:00
|
|
|
} else {
|
2024-03-06 13:09:14 +00:00
|
|
|
(firstColumns[i] as HTMLElement).style.boxShadow = "none";
|
2024-02-09 10:23:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
isScrolled.current = scrollLeft > 0;
|
|
|
|
}
|
2024-03-06 13:09:14 +00:00
|
|
|
}, [containerRef]);
|
2024-02-09 10:23:15 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const currentContainerRef = containerRef.current;
|
|
|
|
|
|
|
|
if (currentContainerRef) currentContainerRef.addEventListener("scroll", handleScroll);
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
if (currentContainerRef) currentContainerRef.removeEventListener("scroll", handleScroll);
|
|
|
|
};
|
2024-03-06 13:09:14 +00:00
|
|
|
}, [handleScroll, containerRef]);
|
2024-02-09 10:23:15 +00:00
|
|
|
|
2024-03-13 06:56:10 +00:00
|
|
|
// useEffect(() => {
|
|
|
|
// if (intersectionRef.current) {
|
|
|
|
// const observer = new IntersectionObserver(
|
|
|
|
// (entries) => {
|
|
|
|
// if (entries[0].isIntersecting) onEndOfListTrigger();
|
|
|
|
// },
|
|
|
|
// {
|
|
|
|
// root: containerRef?.current,
|
|
|
|
// rootMargin: `50% 0% 50% 0%`,
|
|
|
|
// }
|
|
|
|
// );
|
|
|
|
// observer.observe(intersectionRef.current);
|
|
|
|
// return () => {
|
|
|
|
// if (intersectionRef.current) {
|
|
|
|
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
// observer.unobserve(intersectionRef.current);
|
|
|
|
// }
|
|
|
|
// };
|
|
|
|
// }
|
|
|
|
// }, [intersectionRef, containerRef]);
|
|
|
|
useIntersectionObserver(containerRef, intersectionRef, onEndOfListTrigger, `50% 0% 50% 0%`);
|
|
|
|
|
2024-02-08 06:19:00 +00:00
|
|
|
const handleKeyBoardNavigation = useTableKeyboardNavigation();
|
|
|
|
|
2024-01-12 08:22:04 +00:00
|
|
|
return (
|
2024-02-08 06:19:00 +00:00
|
|
|
<table className="overflow-y-auto" onKeyDown={handleKeyBoardNavigation}>
|
2024-01-12 08:22:04 +00:00
|
|
|
<SpreadsheetHeader
|
|
|
|
displayProperties={displayProperties}
|
|
|
|
displayFilters={displayFilters}
|
|
|
|
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
|
|
|
isEstimateEnabled={isEstimateEnabled}
|
|
|
|
/>
|
|
|
|
<tbody>
|
2024-01-18 10:21:17 +00:00
|
|
|
{issueIds.map((id) => (
|
2024-01-12 08:22:04 +00:00
|
|
|
<SpreadsheetIssueRow
|
|
|
|
key={id}
|
|
|
|
issueId={id}
|
|
|
|
displayProperties={displayProperties}
|
|
|
|
quickActions={quickActions}
|
|
|
|
canEditProperties={canEditProperties}
|
|
|
|
nestingLevel={0}
|
|
|
|
isEstimateEnabled={isEstimateEnabled}
|
2024-03-06 15:17:38 +00:00
|
|
|
updateIssue={updateIssue}
|
2024-01-12 08:22:04 +00:00
|
|
|
portalElement={portalElement}
|
2024-02-09 10:23:15 +00:00
|
|
|
containerRef={containerRef}
|
|
|
|
isScrolled={isScrolled}
|
|
|
|
issueIds={issueIds}
|
2024-01-12 08:22:04 +00:00
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</tbody>
|
2024-03-13 06:56:10 +00:00
|
|
|
<tfoot ref={intersectionRef}>Loading...</tfoot>
|
2024-01-12 08:22:04 +00:00
|
|
|
</table>
|
|
|
|
);
|
|
|
|
});
|