155 lines
4.9 KiB
TypeScript
155 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { ReactNode } from "react";
|
|
import {
|
|
closestCorners,
|
|
DndContext,
|
|
DragEndEvent,
|
|
DragOverEvent,
|
|
DragStartEvent,
|
|
} from "@dnd-kit/core";
|
|
import {
|
|
horizontalListSortingStrategy,
|
|
SortableContext,
|
|
} from "@dnd-kit/sortable";
|
|
import { Carousel } from "@mantine/carousel";
|
|
import { Group } from "@mantine/core";
|
|
import CreateStatusButton from "@/app/deals/components/shared/CreateStatusButton/CreateStatusButton";
|
|
import useDndSensors from "@/app/deals/hooks/useSensors";
|
|
import FunnelColumn from "@/components/dnd/FunnelDnd/FunnelColumn";
|
|
import FunnelOverlay from "@/components/dnd/FunnelDnd/FunnelOverlay";
|
|
import { BaseDraggable } from "@/components/dnd/types/types";
|
|
import useIsMobile from "@/hooks/useIsMobile";
|
|
import SortableItem from "../SortableItem";
|
|
import styles from "./FunnelDnd.module.css";
|
|
|
|
type Props<TContainer, TItem> = {
|
|
containers: TContainer[];
|
|
items: TItem[];
|
|
onDragStart: (event: DragStartEvent) => void;
|
|
onDragOver: (event: DragOverEvent) => void;
|
|
onDragEnd: (event: DragEndEvent) => void;
|
|
renderContainer: (container: TContainer, children: ReactNode) => ReactNode;
|
|
renderContainerOverlay: (
|
|
container: TContainer,
|
|
children: ReactNode
|
|
) => ReactNode;
|
|
renderItem: (item: TItem) => ReactNode;
|
|
renderItemOverlay: (item: TItem) => ReactNode;
|
|
getContainerId: (container: TContainer) => string;
|
|
getItemsByContainer: (container: TContainer, items: TItem[]) => TItem[];
|
|
activeContainer: TContainer | null;
|
|
activeItem: TItem | null;
|
|
disabledColumns?: boolean;
|
|
};
|
|
|
|
const FunnelDnd = <
|
|
TContainer extends BaseDraggable,
|
|
TItem extends BaseDraggable,
|
|
>({
|
|
containers,
|
|
items,
|
|
onDragStart,
|
|
onDragOver,
|
|
onDragEnd,
|
|
renderContainer,
|
|
renderContainerOverlay,
|
|
renderItem,
|
|
renderItemOverlay,
|
|
getContainerId,
|
|
getItemsByContainer,
|
|
activeContainer,
|
|
activeItem,
|
|
disabledColumns = false,
|
|
}: Props<TContainer, TItem>) => {
|
|
const sensors = useDndSensors();
|
|
const isMobile = useIsMobile();
|
|
|
|
const renderContainers = () =>
|
|
containers.map(container => {
|
|
const containerItems = getItemsByContainer(container, items);
|
|
const containerId = getContainerId(container);
|
|
const item = (
|
|
<SortableItem
|
|
key={containerId}
|
|
id={containerId}
|
|
disabled={disabledColumns}
|
|
renderItem={() =>
|
|
renderContainer(
|
|
container,
|
|
<FunnelColumn
|
|
id={containerId}
|
|
items={containerItems}
|
|
renderItem={renderItem}
|
|
/>
|
|
)
|
|
}
|
|
/>
|
|
);
|
|
if (!isMobile) return item;
|
|
return <Carousel.Slide key={containerId}>{item}</Carousel.Slide>;
|
|
});
|
|
|
|
const renderBody = () => {
|
|
if (isMobile) {
|
|
return (
|
|
<Carousel
|
|
slideSize={"80%"}
|
|
slideGap={"md"}
|
|
pb={"xl"}
|
|
withControls={false}
|
|
withIndicators
|
|
classNames={styles}>
|
|
{renderContainers()}
|
|
<CreateStatusButton />
|
|
</Carousel>
|
|
);
|
|
}
|
|
return (
|
|
<Group
|
|
gap={"xs"}
|
|
wrap="nowrap"
|
|
align="start">
|
|
{renderContainers()}
|
|
</Group>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<DndContext
|
|
sensors={sensors}
|
|
collisionDetection={closestCorners}
|
|
onDragStart={onDragStart}
|
|
onDragOver={onDragOver}
|
|
onDragEnd={onDragEnd}>
|
|
<SortableContext
|
|
items={containers.map(getContainerId)}
|
|
strategy={horizontalListSortingStrategy}>
|
|
{renderBody()}
|
|
<FunnelOverlay
|
|
activeContainer={activeContainer}
|
|
activeItem={activeItem}
|
|
renderContainer={container => {
|
|
const containerItems = getItemsByContainer(
|
|
container,
|
|
items
|
|
);
|
|
const containerId = getContainerId(container);
|
|
return renderContainerOverlay(
|
|
container,
|
|
<FunnelColumn
|
|
id={containerId}
|
|
items={containerItems}
|
|
renderItem={renderItem}
|
|
/>
|
|
);
|
|
}}
|
|
renderItem={renderItemOverlay}
|
|
/>
|
|
</SortableContext>
|
|
</DndContext>
|
|
);
|
|
};
|
|
|
|
export default FunnelDnd;
|