refactor: moved dnd part from Funnel into FunnelDnd
This commit is contained in:
@ -6,6 +6,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const DealCard = ({ deal }: Props) => {
|
||||
console.log("deal");
|
||||
return <Card>{deal.name}</Card>;
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React, { FC, useMemo } from "react";
|
||||
import { Box } from "@mantine/core";
|
||||
import DealCard from "@/app/deals/components/DealCard/DealCard";
|
||||
import SortableItem from "@/components/dnd/SortableItem";
|
||||
import { DealSchema } from "@/lib/client";
|
||||
import { SortableItem } from "@/components/dnd/SortableDnd/SortableItem";
|
||||
|
||||
type Props = {
|
||||
deal: DealSchema;
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import React from "react";
|
||||
import { defaultDropAnimation, DragOverlay } from "@dnd-kit/core";
|
||||
import DealCard from "@/app/deals/components/DealCard/DealCard";
|
||||
import StatusColumn from "@/app/deals/components/StatusColumn/StatusColumn";
|
||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
activeDeal: DealSchema | null;
|
||||
activeStatus: StatusSchema | null;
|
||||
};
|
||||
|
||||
const DndOverlay = ({ activeStatus, activeDeal }: Props) => {
|
||||
const { deals } = useStatusesContext();
|
||||
|
||||
return (
|
||||
<DragOverlay dropAnimation={defaultDropAnimation}>
|
||||
<div style={{ cursor: "grabbing" }}>
|
||||
{activeDeal ? (
|
||||
<DealCard deal={activeDeal} />
|
||||
) : activeStatus ? (
|
||||
<StatusColumn
|
||||
id={`${activeStatus.id}-status`}
|
||||
status={activeStatus}
|
||||
deals={deals.filter(
|
||||
deal => deal.statusId === activeStatus.id
|
||||
)}
|
||||
isDragging
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</DragOverlay>
|
||||
);
|
||||
};
|
||||
|
||||
export default DndOverlay;
|
||||
68
src/app/deals/components/Funnel/Funnel.tsx
Normal file
68
src/app/deals/components/Funnel/Funnel.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import DealCard from "@/app/deals/components/DealCard/DealCard";
|
||||
import DealContainer from "@/app/deals/components/DealContainer/DealContainer";
|
||||
import StatusColumnWrapper from "@/app/deals/components/StatusColumnWrapper/StatusColumnWrapper";
|
||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||
import useDealsAndStatusesDnd from "@/app/deals/hooks/useDealsAndStatusesDnd";
|
||||
import FunnelDnd from "@/components/dnd/FunnelDnd/FunnelDnd";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
import { sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
const Funnel: FC = () => {
|
||||
const { deals } = useStatusesContext();
|
||||
|
||||
const {
|
||||
sortedStatuses,
|
||||
handleDragStart,
|
||||
handleDragOver,
|
||||
handleDragEnd,
|
||||
activeStatus,
|
||||
activeDeal,
|
||||
} = useDealsAndStatusesDnd();
|
||||
|
||||
return (
|
||||
<FunnelDnd
|
||||
containers={sortedStatuses}
|
||||
items={deals}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}
|
||||
getContainerId={(status: StatusSchema) => `${status.id}-status`}
|
||||
getItemsByContainer={(status: StatusSchema, items: DealSchema[]) =>
|
||||
sortByLexorank(
|
||||
items.filter(deal => deal.statusId === status.id)
|
||||
)
|
||||
}
|
||||
renderContainer={(
|
||||
status: StatusSchema,
|
||||
funnelColumnComponent: ReactNode
|
||||
) => (
|
||||
<StatusColumnWrapper
|
||||
status={status}
|
||||
isDragging={activeStatus?.id === status.id}>
|
||||
{funnelColumnComponent}
|
||||
</StatusColumnWrapper>
|
||||
)}
|
||||
renderItem={(deal: DealSchema) => (
|
||||
<DealContainer
|
||||
key={deal.id}
|
||||
deal={deal}
|
||||
/>
|
||||
)}
|
||||
activeContainer={activeStatus}
|
||||
activeItem={activeDeal}
|
||||
renderItemOverlay={(deal: DealSchema) => <DealCard deal={deal} />}
|
||||
renderContainerOverlay={(status: StatusSchema, children) => (
|
||||
<StatusColumnWrapper
|
||||
status={status}
|
||||
isDragging>
|
||||
{children}
|
||||
</StatusColumnWrapper>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Funnel;
|
||||
@ -1,67 +0,0 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import {
|
||||
SortableContext,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { Box, Stack, Text } from "@mantine/core";
|
||||
import DealContainer from "@/app/deals/components/DealContainer/DealContainer";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
import { sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
status: StatusSchema;
|
||||
deals: DealSchema[];
|
||||
isDragging?: boolean;
|
||||
};
|
||||
|
||||
const StatusColumn = ({ id, status, deals, isDragging }: Props) => {
|
||||
const { setNodeRef } = useDroppable({ id });
|
||||
const sortedDeals = useMemo(
|
||||
() => sortByLexorank(deals.filter(deal => deal.statusId === status.id)),
|
||||
[deals]
|
||||
);
|
||||
|
||||
const columnBody = useMemo(() => {
|
||||
return (
|
||||
<SortableContext
|
||||
id={id}
|
||||
items={sortedDeals}
|
||||
strategy={verticalListSortingStrategy}>
|
||||
<Stack
|
||||
gap={"xs"}
|
||||
ref={setNodeRef}>
|
||||
{sortedDeals.map(deal => (
|
||||
<DealContainer
|
||||
key={deal.id}
|
||||
deal={deal}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</SortableContext>
|
||||
);
|
||||
}, [sortedDeals]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#eee",
|
||||
padding: 2,
|
||||
width: "15vw",
|
||||
minWidth: 150,
|
||||
}}>
|
||||
<Text
|
||||
style={{
|
||||
cursor: "grab",
|
||||
userSelect: "none",
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
}}>
|
||||
{status.name}
|
||||
</Text>
|
||||
{columnBody}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumn;
|
||||
@ -0,0 +1,33 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { Box, Text } from "@mantine/core";
|
||||
import { StatusSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
status: StatusSchema;
|
||||
isDragging?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const StatusColumnWrapper = ({ status, children, isDragging = false }: Props) => {
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#eee",
|
||||
padding: 2,
|
||||
width: "15vw",
|
||||
minWidth: 150,
|
||||
}}>
|
||||
<Text
|
||||
style={{
|
||||
cursor: "grab",
|
||||
userSelect: "none",
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
}}>
|
||||
{status.name}
|
||||
</Text>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumnWrapper;
|
||||
@ -1,76 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import StatusColumnsDnd from "@/app/deals/components/StatusColumnsDnd/StatusColumnsDnd";
|
||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||
import {
|
||||
updateDealMutation,
|
||||
updateStatusMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
const StatusColumns = () => {
|
||||
const { refetchStatuses, refetchDeals } = useStatusesContext();
|
||||
|
||||
const updateStatus = useMutation({
|
||||
...updateStatusMutation(),
|
||||
onError: error => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchStatuses();
|
||||
},
|
||||
});
|
||||
|
||||
const updateDeals = useMutation({
|
||||
...updateDealMutation(),
|
||||
onError: error => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchDeals();
|
||||
},
|
||||
});
|
||||
|
||||
const onDealDragEnd = (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => {
|
||||
updateDeals.mutate({
|
||||
path: {
|
||||
dealId,
|
||||
},
|
||||
body: {
|
||||
deal: {
|
||||
statusId,
|
||||
lexorank,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onStatusDragEnd = (statusId: number, lexorank: string) => {
|
||||
updateStatus.mutate({
|
||||
path: {
|
||||
statusId,
|
||||
},
|
||||
body: {
|
||||
status: {
|
||||
lexorank,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StatusColumnsDnd
|
||||
onDealDragEnd={onDealDragEnd}
|
||||
onStatusDragEnd={onStatusDragEnd}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumns;
|
||||
@ -1,80 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { closestCorners, DndContext } from "@dnd-kit/core";
|
||||
import {
|
||||
horizontalListSortingStrategy,
|
||||
SortableContext,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { Group, ScrollArea } from "@mantine/core";
|
||||
import DndOverlay from "@/app/deals/components/DndOverlay/DndOverlay";
|
||||
import StatusColumn from "@/app/deals/components/StatusColumn/StatusColumn";
|
||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||
import useDealsAndStatusesDnd from "@/app/deals/hooks/useDealsAndStatusesDnd";
|
||||
import { SortableItem } from "@/components/dnd/SortableDnd/SortableItem";
|
||||
import useDndSensors from "../../hooks/useSensors";
|
||||
|
||||
type Props = {
|
||||
onDealDragEnd: (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => void;
|
||||
onStatusDragEnd: (statusId: number, lexorank: string) => void;
|
||||
};
|
||||
|
||||
const StatusColumnsDnd: FC<Props> = props => {
|
||||
const { deals } = useStatusesContext();
|
||||
|
||||
const {
|
||||
sortedStatuses,
|
||||
handleDragStart,
|
||||
handleDragOver,
|
||||
handleDragEnd,
|
||||
activeStatus,
|
||||
activeDeal,
|
||||
} = useDealsAndStatusesDnd(props);
|
||||
|
||||
const sensors = useDndSensors();
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
offsetScrollbars={"x"}
|
||||
scrollbarSize={"0.5rem"}>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCorners}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}>
|
||||
<SortableContext
|
||||
items={sortedStatuses.map(status => `${status.id}-status`)}
|
||||
strategy={horizontalListSortingStrategy}>
|
||||
<Group
|
||||
gap={"xs"}
|
||||
wrap={"nowrap"}
|
||||
align={"start"}>
|
||||
{sortedStatuses.map(status => (
|
||||
<SortableItem
|
||||
key={status.id}
|
||||
id={`${status.id}-status`}>
|
||||
<StatusColumn
|
||||
id={`${status.id}-status`}
|
||||
status={status}
|
||||
deals={deals}
|
||||
isDragging={activeStatus?.id === status.id}
|
||||
/>
|
||||
</SortableItem>
|
||||
))}
|
||||
<DndOverlay
|
||||
activeStatus={activeStatus}
|
||||
activeDeal={activeDeal}
|
||||
/>
|
||||
</Group>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumnsDnd;
|
||||
@ -1,17 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import React, { createContext, FC, useContext, useEffect } from "react";
|
||||
import { useMutation, UseMutationResult } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
import useDealsList from "@/hooks/useDealsList";
|
||||
import useStatusesList from "@/hooks/useStatusesList";
|
||||
import {
|
||||
DealSchema,
|
||||
HttpValidationError,
|
||||
Options,
|
||||
StatusSchema,
|
||||
UpdateDealData,
|
||||
UpdateDealResponse,
|
||||
UpdateStatusData,
|
||||
UpdateStatusResponse,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
updateDealMutation,
|
||||
updateStatusMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
type StatusesContextState = {
|
||||
statuses: StatusSchema[];
|
||||
setStatuses: React.Dispatch<React.SetStateAction<StatusSchema[]>>;
|
||||
updateStatus: UseMutationResult<
|
||||
UpdateStatusResponse,
|
||||
AxiosError<HttpValidationError>,
|
||||
Options<UpdateStatusData>
|
||||
>;
|
||||
refetchStatuses: () => void;
|
||||
deals: DealSchema[];
|
||||
setDeals: React.Dispatch<React.SetStateAction<DealSchema[]>>;
|
||||
refetchStatuses: () => void;
|
||||
updateDeal: UseMutationResult<
|
||||
UpdateDealResponse,
|
||||
AxiosError<HttpValidationError>,
|
||||
Options<UpdateDealData>
|
||||
>;
|
||||
refetchDeals: () => void;
|
||||
};
|
||||
|
||||
@ -39,12 +65,36 @@ const useStatusesContextState = () => {
|
||||
refetchStatuses();
|
||||
}, [selectedBoard]);
|
||||
|
||||
const updateStatus = useMutation({
|
||||
...updateStatusMutation(),
|
||||
onError: error => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchStatuses();
|
||||
},
|
||||
});
|
||||
|
||||
const updateDeal = useMutation({
|
||||
...updateDealMutation(),
|
||||
onError: error => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchDeals();
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
statuses,
|
||||
setStatuses,
|
||||
updateStatus,
|
||||
refetchStatuses,
|
||||
deals,
|
||||
setDeals,
|
||||
refetchStatuses,
|
||||
updateDeal,
|
||||
refetchDeals,
|
||||
};
|
||||
};
|
||||
|
||||
@ -7,19 +7,11 @@ import { getStatusId, isStatusId } from "@/app/deals/utils/statusId";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
import { sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
type Props = {
|
||||
onDealDragEnd: (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => void;
|
||||
onStatusDragEnd: (statusId: number, lexorank: string) => void;
|
||||
};
|
||||
|
||||
const useDealsAndStatusesDnd = (props: Props) => {
|
||||
const useDealsAndStatusesDnd = () => {
|
||||
const [activeDeal, setActiveDeal] = useState<DealSchema | null>(null);
|
||||
const [activeStatus, setActiveStatus] = useState<StatusSchema | null>(null);
|
||||
const { statuses, deals, setDeals, setStatuses } = useStatusesContext();
|
||||
const { statuses, deals, setDeals, setStatuses, updateDeal, updateStatus } =
|
||||
useStatusesContext();
|
||||
const sortedStatuses = useMemo(() => sortByLexorank(statuses), [statuses]);
|
||||
|
||||
const {
|
||||
@ -173,7 +165,20 @@ const useDealsAndStatusesDnd = (props: Props) => {
|
||||
const newRank = getNewStatusRank(activeStatusId, overStatusId);
|
||||
if (!newRank) return;
|
||||
|
||||
props.onStatusDragEnd?.(activeStatusId, newRank);
|
||||
onStatusDragEnd?.(activeStatusId, newRank);
|
||||
};
|
||||
|
||||
const onStatusDragEnd = (statusId: number, lexorank: string) => {
|
||||
updateStatus.mutate({
|
||||
path: {
|
||||
statusId,
|
||||
},
|
||||
body: {
|
||||
status: {
|
||||
lexorank,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDealDragEnd = (activeId: number | string, over: Over) => {
|
||||
@ -189,7 +194,25 @@ const useDealsAndStatusesDnd = (props: Props) => {
|
||||
);
|
||||
if (!overStatusId) return;
|
||||
|
||||
props.onDealDragEnd(activeDealId, overStatusId, newLexorank);
|
||||
onDealDragEnd(activeDealId, overStatusId, newLexorank);
|
||||
};
|
||||
|
||||
const onDealDragEnd = (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => {
|
||||
updateDeal.mutate({
|
||||
path: {
|
||||
dealId,
|
||||
},
|
||||
body: {
|
||||
deal: {
|
||||
statusId,
|
||||
lexorank,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDragStart = ({ active }: DragStartEvent) => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Divider } from "@mantine/core";
|
||||
import Boards from "@/app/deals/components/Boards/Boards";
|
||||
import Funnel from "@/app/deals/components/Funnel/Funnel";
|
||||
import Header from "@/app/deals/components/Header/Header";
|
||||
import StatusColumns from "@/app/deals/components/StatusColumns/StatusColumns";
|
||||
import { BoardsContextProvider } from "@/app/deals/contexts/BoardsContext";
|
||||
import { ProjectsContextProvider } from "@/app/deals/contexts/ProjectsContext";
|
||||
import { StatusesContextProvider } from "@/app/deals/contexts/StatusesContext";
|
||||
@ -18,7 +18,7 @@ export default function DealsPage() {
|
||||
<Boards />
|
||||
<Divider my={"xl"} />
|
||||
<StatusesContextProvider>
|
||||
<StatusColumns />
|
||||
<Funnel />
|
||||
</StatusesContextProvider>
|
||||
</BoardsContextProvider>
|
||||
</ProjectsContextProvider>
|
||||
|
||||
42
src/components/dnd/FunnelDnd/FunnelColumn.tsx
Normal file
42
src/components/dnd/FunnelDnd/FunnelColumn.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import {
|
||||
SortableContext,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { Stack } from "@mantine/core";
|
||||
import { BaseDraggable } from "@/components/dnd/types/types";
|
||||
|
||||
type Props<TItem> = {
|
||||
id: string;
|
||||
items: TItem[];
|
||||
renderItem: (item: TItem) => ReactNode;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
const FunnelColumn = <TItem extends BaseDraggable>({
|
||||
id,
|
||||
items,
|
||||
renderItem,
|
||||
children,
|
||||
}: Props<TItem>) => {
|
||||
const { setNodeRef } = useDroppable({ id });
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<SortableContext
|
||||
id={id}
|
||||
items={items}
|
||||
strategy={verticalListSortingStrategy}>
|
||||
<Stack
|
||||
gap="xs"
|
||||
ref={setNodeRef}>
|
||||
{items.map(renderItem)}
|
||||
</Stack>
|
||||
</SortableContext>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FunnelColumn;
|
||||
126
src/components/dnd/FunnelDnd/FunnelDnd.tsx
Normal file
126
src/components/dnd/FunnelDnd/FunnelDnd.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
"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 { Group, ScrollArea } from "@mantine/core";
|
||||
import useDndSensors from "@/app/deals/hooks/useSensors";
|
||||
import SortableItem from "@/components/dnd/SortableItem";
|
||||
import { BaseDraggable } from "@/components/dnd/types/types";
|
||||
import FunnelColumn from "./FunnelColumn";
|
||||
import FunnelOverlay from "./FunnelOverlay";
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
const FunnelDnd = <
|
||||
TContainer extends BaseDraggable,
|
||||
TItem extends BaseDraggable,
|
||||
>({
|
||||
containers,
|
||||
items,
|
||||
onDragStart,
|
||||
onDragOver,
|
||||
onDragEnd,
|
||||
renderContainer,
|
||||
renderContainerOverlay,
|
||||
renderItem,
|
||||
renderItemOverlay,
|
||||
getContainerId,
|
||||
getItemsByContainer,
|
||||
activeContainer,
|
||||
activeItem,
|
||||
}: Props<TContainer, TItem>) => {
|
||||
const sensors = useDndSensors();
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
offsetScrollbars="x"
|
||||
scrollbarSize="0.5rem">
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCorners}
|
||||
onDragStart={onDragStart}
|
||||
onDragOver={onDragOver}
|
||||
onDragEnd={onDragEnd}>
|
||||
<SortableContext
|
||||
items={containers.map(getContainerId)}
|
||||
strategy={horizontalListSortingStrategy}>
|
||||
<Group
|
||||
gap="xs"
|
||||
wrap="nowrap"
|
||||
align="start">
|
||||
{containers.map(container => {
|
||||
const containerItems = getItemsByContainer(
|
||||
container,
|
||||
items
|
||||
);
|
||||
const containerId = getContainerId(container);
|
||||
return (
|
||||
<SortableItem
|
||||
key={containerId}
|
||||
id={containerId}>
|
||||
{renderContainer(
|
||||
container,
|
||||
<FunnelColumn
|
||||
id={containerId}
|
||||
items={containerItems}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
)}
|
||||
</SortableItem>
|
||||
);
|
||||
})}
|
||||
<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}
|
||||
/>
|
||||
</Group>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
|
||||
export default FunnelDnd;
|
||||
30
src/components/dnd/FunnelDnd/FunnelOverlay.tsx
Normal file
30
src/components/dnd/FunnelDnd/FunnelOverlay.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { defaultDropAnimation, DragOverlay } from "@dnd-kit/core";
|
||||
|
||||
type Props<TContainer, TItem> = {
|
||||
activeContainer: TContainer | null;
|
||||
activeItem: TItem | null;
|
||||
renderContainer: (container: TContainer) => ReactNode;
|
||||
renderItem: (item: TItem) => ReactNode;
|
||||
};
|
||||
|
||||
const FunnelOverlay = <TContainer, TItem>({
|
||||
activeContainer,
|
||||
activeItem,
|
||||
renderContainer,
|
||||
renderItem,
|
||||
}: Props<TContainer, TItem>) => {
|
||||
return (
|
||||
<DragOverlay dropAnimation={defaultDropAnimation}>
|
||||
<div style={{ cursor: "grabbing" }}>
|
||||
{activeItem
|
||||
? renderItem(activeItem)
|
||||
: activeContainer
|
||||
? renderContainer(activeContainer)
|
||||
: null}
|
||||
</div>
|
||||
</DragOverlay>
|
||||
);
|
||||
};
|
||||
|
||||
export default FunnelOverlay;
|
||||
3
src/components/dnd/FunnelDnd/index.ts
Normal file
3
src/components/dnd/FunnelDnd/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import FunnelDnd from "./FunnelDnd";
|
||||
|
||||
export default FunnelDnd;
|
||||
@ -12,8 +12,8 @@ import { SortableContext } from "@dnd-kit/sortable";
|
||||
import { LexoRank } from "lexorank";
|
||||
import { Box, Group } from "@mantine/core";
|
||||
import useDndSensors from "@/app/deals/hooks/useSensors";
|
||||
import { SortableItem } from "@/components/dnd/SortableDnd/SortableItem";
|
||||
import { SortableOverlay } from "@/components/dnd/SortableDnd/SortableOverlay";
|
||||
import SortableItem from "@/components/dnd/SortableItem";
|
||||
import { getNewLexorank, sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
type BaseItem = {
|
||||
@ -119,9 +119,7 @@ const SortableDnd = <T extends BaseItem>({
|
||||
</Group>
|
||||
</SortableContext>
|
||||
<SortableOverlay>
|
||||
<div style={{ cursor: "grabbing" }}>
|
||||
{activeItem ? renderItem(activeItem) : null}
|
||||
</div>
|
||||
</SortableOverlay>
|
||||
</DndContext>
|
||||
);
|
||||
|
||||
@ -18,7 +18,7 @@ const dropAnimationConfig: DropAnimation = {
|
||||
export function SortableOverlay({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<DragOverlay dropAnimation={dropAnimationConfig}>
|
||||
{children}
|
||||
<div style={{ cursor: "grabbing" }}>{children}</div>
|
||||
</DragOverlay>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { CSSProperties, ReactNode, useContext } from "react";
|
||||
import SortableItemContext from "@/components/dnd/SortableDnd/SortableItemContext";
|
||||
import SortableItemContext from "@/components/dnd/SortableItem/SortableItemContext";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { CSSProperties, PropsWithChildren, useMemo } from "react";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import DragHandle from "@/components/dnd/SortableDnd/DragHandle";
|
||||
import DragHandle from "./DragHandle";
|
||||
import SortableItemContext from "./SortableItemContext";
|
||||
|
||||
type Props = {
|
||||
@ -10,7 +10,7 @@ type Props = {
|
||||
dragHandleStyle?: CSSProperties;
|
||||
};
|
||||
|
||||
export const SortableItem = ({
|
||||
const SortableItem = ({
|
||||
children,
|
||||
itemStyle,
|
||||
id,
|
||||
@ -52,3 +52,5 @@ export const SortableItem = ({
|
||||
</SortableItemContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default SortableItem;
|
||||
3
src/components/dnd/SortableItem/index.ts
Normal file
3
src/components/dnd/SortableItem/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import SortableItem from "./SortableItem";
|
||||
|
||||
export default SortableItem;
|
||||
3
src/components/dnd/types/types.ts
Normal file
3
src/components/dnd/types/types.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type BaseDraggable = {
|
||||
id: number;
|
||||
};
|
||||
@ -2,5 +2,5 @@ import type { CreateClientConfig } from "@/lib/client/client.gen";
|
||||
|
||||
export const createClientConfig: CreateClientConfig = config => ({
|
||||
...config,
|
||||
baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user