bb-plane-fork/apps/space/components/issues/issue-layouts/kanban/kanban-group.tsx
sriram veeraghanta 7fb6696c67
chore: space folders (#8707)
* chore: change the space folders structure

* fix: format
2026-03-05 14:03:54 +05:30

137 lines
4.3 KiB
TypeScript

/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { MutableRefObject } from "react";
import { forwardRef, useCallback, useRef, useState } from "react";
import { observer } from "mobx-react";
//types
import type {
TGroupedIssues,
IIssueDisplayProperties,
TSubGroupedIssues,
TIssueGroupByOptions,
TPaginationData,
TLoader,
} from "@plane/types";
import { cn } from "@plane/utils";
// hooks
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
// local imports
import { KanbanIssueBlocksList } from "./blocks-list";
interface IKanbanGroup {
groupId: string;
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
displayProperties: IIssueDisplayProperties | undefined;
subGroupBy: TIssueGroupByOptions | undefined;
subGroupId: string;
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
getGroupIssueCount: (
groupId: string | undefined,
subGroupId: string | undefined,
isSubGroupCumulative: boolean
) => number | undefined;
getPaginationData: (groupId: string | undefined, subGroupId: string | undefined) => TPaginationData | undefined;
getIssueLoader: (groupId?: string, subGroupId?: string) => TLoader;
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
}
// Loader components
const KanbanIssueBlockLoader = forwardRef(function KanbanIssueBlockLoader(
props: Record<string, unknown>,
ref: React.ForwardedRef<HTMLSpanElement>
) {
return (
<span ref={ref} className="m-1.5 block h-28 animate-pulse rounded-sm bg-[var(--illustration-fill-quaternary)]" />
);
});
KanbanIssueBlockLoader.displayName = "KanbanIssueBlockLoader";
export const KanbanGroup = observer(function KanbanGroup(props: IKanbanGroup) {
const {
groupId,
subGroupId,
subGroupBy,
displayProperties,
groupedIssueIds,
loadMoreIssues,
getGroupIssueCount,
getPaginationData,
getIssueLoader,
scrollableContainerRef,
} = props;
// hooks
const [intersectionElement, setIntersectionElement] = useState<HTMLSpanElement | null>(null);
const columnRef = useRef<HTMLDivElement | null>(null);
const containerRef = subGroupBy && scrollableContainerRef ? scrollableContainerRef : columnRef;
const loadMoreIssuesInThisGroup = useCallback(() => {
loadMoreIssues(groupId, subGroupId === "null" ? undefined : subGroupId);
}, [loadMoreIssues, groupId, subGroupId]);
const isPaginating = !!getIssueLoader(groupId, subGroupId);
useIntersectionObserver(
containerRef,
isPaginating ? null : intersectionElement,
loadMoreIssuesInThisGroup,
`0% 100% 100% 100%`
);
const isSubGroup = !!subGroupId && subGroupId !== "null";
const issueIds = isSubGroup
? ((groupedIssueIds as TSubGroupedIssues)?.[groupId]?.[subGroupId] ?? [])
: ((groupedIssueIds as TGroupedIssues)?.[groupId] ?? []);
const groupIssueCount = getGroupIssueCount(groupId, subGroupId, false) ?? 0;
const nextPageResults = getPaginationData(groupId, subGroupId)?.nextPageResults;
const loadMore = isPaginating ? (
<KanbanIssueBlockLoader />
) : (
<div
className="w-full cursor-pointer p-3 text-13 font-medium text-accent-primary hover:text-accent-secondary hover:underline"
onClick={loadMoreIssuesInThisGroup}
role="button"
>
{" "}
Load More &darr;
</div>
);
const shouldLoadMore = nextPageResults === undefined ? issueIds?.length < groupIssueCount : !!nextPageResults;
return (
<div
id={`${groupId}__${subGroupId}`}
className={cn("relative h-full min-h-[120px] transition-all", { "vertical-scrollbar scrollbar-md": !subGroupBy })}
ref={columnRef}
>
<KanbanIssueBlocksList
subGroupId={subGroupId}
groupId={groupId}
issueIds={issueIds || []}
displayProperties={displayProperties}
scrollableContainerRef={scrollableContainerRef}
/>
{shouldLoadMore &&
(isSubGroup ? (
<>{loadMore}</>
) : (
<div className="flex flex-col gap-2">
{Array.from({ length: 2 }).map((_, index) => (
<KanbanIssueBlockLoader key={index} />
))}
<KanbanIssueBlockLoader ref={setIntersectionElement} />
</div>
))}
</div>
);
});