refactor: refactoring of deals and statuses dnd
This commit is contained in:
@ -1,35 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC, useMemo, useState } from "react";
|
||||
import {
|
||||
closestCorners,
|
||||
defaultDropAnimation,
|
||||
DndContext,
|
||||
DragOverEvent,
|
||||
DragOverlay,
|
||||
DragStartEvent,
|
||||
KeyboardSensor,
|
||||
Over,
|
||||
PointerSensor,
|
||||
TouchSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import React, { FC } from "react";
|
||||
import { closestCorners, DndContext } from "@dnd-kit/core";
|
||||
import {
|
||||
horizontalListSortingStrategy,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { LexoRank } from "lexorank";
|
||||
import { throttle } from "lodash";
|
||||
import { Group, ScrollArea } from "@mantine/core";
|
||||
import DealCard from "@/app/deals/components/DealCard/DealCard";
|
||||
import DndOverlay from "@/app/deals/components/DndOverlay/DndOverlay";
|
||||
import StatusColumn from "@/app/deals/components/StatusColumn/StatusColumn";
|
||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||
import useDnd from "@/app/deals/hooks/useDnd";
|
||||
import { SortableItem } from "@/components/SortableDnd/SortableItem";
|
||||
import { DealSchema } from "@/types/DealSchema";
|
||||
import { StatusSchema } from "@/types/StatusSchema";
|
||||
import { getNewLexorank, sortByLexorank } from "@/utils/lexorank";
|
||||
import useDndSensors from "../../hooks/useSensors";
|
||||
|
||||
type Props = {
|
||||
onDealDragEnd: (
|
||||
@ -41,270 +24,18 @@ type Props = {
|
||||
};
|
||||
|
||||
const StatusColumnsDnd: FC<Props> = props => {
|
||||
const { statuses, deals, setDeals, setStatuses } = useStatusesContext();
|
||||
const [activeDeal, setActiveDeal] = useState<DealSchema | null>(null);
|
||||
const [activeStatus, setActiveStatus] = useState<StatusSchema | null>(null);
|
||||
const sortedStatuses = useMemo(() => sortByLexorank(statuses), [statuses]);
|
||||
const { deals } = useStatusesContext();
|
||||
|
||||
const throttledSetStatuses = useMemo(
|
||||
() => throttle(setStatuses, 200),
|
||||
[setStatuses]
|
||||
);
|
||||
const throttledSetDeals = useMemo(
|
||||
() => throttle(setDeals, 200),
|
||||
[setDeals]
|
||||
);
|
||||
const {
|
||||
sortedStatuses,
|
||||
handleDragStart,
|
||||
handleDragOver,
|
||||
handleDragEnd,
|
||||
activeStatus,
|
||||
activeDeal,
|
||||
} = useDnd(props);
|
||||
|
||||
const sensorOptions = {
|
||||
activationConstraint: {
|
||||
distance: 5,
|
||||
},
|
||||
};
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, sensorOptions),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
}),
|
||||
useSensor(TouchSensor, sensorOptions)
|
||||
);
|
||||
|
||||
const handleDragStart = ({ active }: DragStartEvent) => {
|
||||
const activeId = active.id as string | number;
|
||||
|
||||
if (typeof activeId === "string" && activeId.endsWith("-status")) {
|
||||
const statusId = Number(activeId.replace("-status", ""));
|
||||
setActiveStatus(
|
||||
statuses.find(status => status.id === statusId) ?? null
|
||||
);
|
||||
} else {
|
||||
setActiveDeal(
|
||||
deals.find(deal => deal.id === (activeId as number)) ?? null
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusByDealId = (dealId: number) => {
|
||||
const deal = deals.find(deal => deal.id === dealId);
|
||||
if (!deal) return;
|
||||
return statuses.find(status => status.id === deal.statusId);
|
||||
};
|
||||
|
||||
const handleDragOver = ({ active, over }: DragOverEvent) => {
|
||||
if (!over) return;
|
||||
const activeId = active.id as string | number;
|
||||
|
||||
if (typeof activeId === "string" && activeId.endsWith("-status")) {
|
||||
handleColumnDragOver(activeId, over);
|
||||
} else {
|
||||
handleDealDragOver(activeId, over);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDealDragOver = (activeId: string | number, over: Over) => {
|
||||
const activeDealId = Number(activeId);
|
||||
const activeStatusId = getStatusByDealId(activeDealId)?.id;
|
||||
if (!activeStatusId) return;
|
||||
|
||||
const { overStatusId, newLexorank } = getDropTarget(
|
||||
over.id,
|
||||
activeDealId,
|
||||
activeStatusId
|
||||
);
|
||||
if (!overStatusId) return;
|
||||
|
||||
throttledSetDeals(deals =>
|
||||
deals.map(deal =>
|
||||
deal.id === activeDealId
|
||||
? {
|
||||
...deal,
|
||||
statusId: overStatusId,
|
||||
rank: newLexorank || deal.rank,
|
||||
}
|
||||
: deal
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleColumnDragOver = (activeId: string, over: Over) => {
|
||||
const activeStatusId = Number(activeId.replace("-status", ""));
|
||||
let overStatusId: number;
|
||||
|
||||
if (typeof over.id === "string" && over.id.endsWith("-status")) {
|
||||
overStatusId = Number(over.id.replace("-status", ""));
|
||||
} else {
|
||||
const deal = deals.find(deal => deal.id === over.id);
|
||||
if (!deal) return;
|
||||
overStatusId = deal.statusId;
|
||||
}
|
||||
|
||||
if (!overStatusId || activeStatusId === overStatusId) return;
|
||||
|
||||
const newRank = getNewStatusRank(activeStatusId, overStatusId);
|
||||
if (!newRank) return;
|
||||
|
||||
throttledSetStatuses(statuses =>
|
||||
statuses.map(status =>
|
||||
status.id === activeStatusId
|
||||
? { ...status, rank: newRank }
|
||||
: status
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const getDropTarget = (
|
||||
overId: string | number,
|
||||
activeDealId: number,
|
||||
activeStatusId: number
|
||||
) => {
|
||||
if (typeof overId === "string") {
|
||||
return {
|
||||
overStatusId: Number(overId.replace(/-status$/, "")),
|
||||
newLexorank: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const overDealId = Number(overId);
|
||||
const overStatusId = getStatusByDealId(overDealId)?.id;
|
||||
|
||||
if (!overStatusId || activeDealId === overDealId) {
|
||||
return { overStatusId: undefined, newLexorank: undefined };
|
||||
}
|
||||
|
||||
const statusDeals = sortByLexorank(
|
||||
deals.filter(deal => deal.statusId === overStatusId)
|
||||
);
|
||||
const overDealIndex = statusDeals.findIndex(
|
||||
deal => deal.id === overDealId
|
||||
);
|
||||
|
||||
let newLexorank;
|
||||
if (activeStatusId === overStatusId) {
|
||||
newLexorank = getNewRankForSameStatus(
|
||||
statusDeals,
|
||||
overDealIndex,
|
||||
activeDealId
|
||||
);
|
||||
} else {
|
||||
newLexorank = getNewRankForAnotherStatus(
|
||||
statusDeals,
|
||||
overDealIndex
|
||||
);
|
||||
}
|
||||
|
||||
return { overStatusId, newLexorank };
|
||||
};
|
||||
|
||||
const getNewRankForSameStatus = (
|
||||
statusDeals: DealSchema[],
|
||||
overDealIndex: number,
|
||||
activeDealId: number
|
||||
) => {
|
||||
const activeDealIndex = statusDeals.findIndex(
|
||||
deal => deal.id === activeDealId
|
||||
);
|
||||
const [leftIndex, rightIndex] =
|
||||
overDealIndex < activeDealIndex
|
||||
? [overDealIndex - 1, overDealIndex]
|
||||
: [overDealIndex, overDealIndex + 1];
|
||||
|
||||
const leftLexorank =
|
||||
leftIndex >= 0 ? LexoRank.parse(deals[leftIndex].rank) : null;
|
||||
const rightLexorank =
|
||||
rightIndex < deals.length
|
||||
? LexoRank.parse(deals[rightIndex].rank)
|
||||
: null;
|
||||
|
||||
return getNewLexorank(leftLexorank, rightLexorank).toString();
|
||||
};
|
||||
|
||||
const getNewRankForAnotherStatus = (
|
||||
statusDeals: DealSchema[],
|
||||
overDealIndex: number
|
||||
) => {
|
||||
const leftLexorank =
|
||||
overDealIndex > 0
|
||||
? LexoRank.parse(statusDeals[overDealIndex - 1].rank)
|
||||
: null;
|
||||
const rightLexorank = LexoRank.parse(statusDeals[overDealIndex].rank);
|
||||
|
||||
return getNewLexorank(leftLexorank, rightLexorank).toString();
|
||||
};
|
||||
|
||||
const getNewStatusRank = (activeStatusId: number, overStatusId: number) => {
|
||||
const sortedStatusList = sortByLexorank(statuses);
|
||||
const overIndex = sortedStatusList.findIndex(
|
||||
s => s.id === overStatusId
|
||||
);
|
||||
const activeIndex = sortedStatusList.findIndex(
|
||||
s => s.id === activeStatusId
|
||||
);
|
||||
|
||||
if (overIndex === -1 || activeIndex === -1) return null;
|
||||
|
||||
const [leftIndex, rightIndex] =
|
||||
overIndex < activeIndex
|
||||
? [overIndex - 1, overIndex]
|
||||
: [overIndex, overIndex + 1];
|
||||
|
||||
const leftLexorank =
|
||||
leftIndex >= 0 ? LexoRank.parse(statuses[leftIndex].rank) : null;
|
||||
const rightLexorank =
|
||||
rightIndex < statuses.length
|
||||
? LexoRank.parse(statuses[rightIndex].rank)
|
||||
: null;
|
||||
|
||||
return getNewLexorank(leftLexorank, rightLexorank).toString();
|
||||
};
|
||||
|
||||
const handleDragEnd = ({ active, over }: DragOverEvent) => {
|
||||
setActiveDeal(null);
|
||||
setActiveStatus(null);
|
||||
if (!over) return;
|
||||
|
||||
const activeId: string | number = active.id;
|
||||
|
||||
if (typeof activeId === "string" && activeId.endsWith("-status")) {
|
||||
handleStatusColumnDragEnd(activeId, over);
|
||||
} else {
|
||||
handleDealDragEnd(activeId, over);
|
||||
}
|
||||
};
|
||||
|
||||
const handleStatusColumnDragEnd = (activeId: string, over: Over) => {
|
||||
const activeStatusId = Number(activeId.replace("-status", ""));
|
||||
let overStatusId: number;
|
||||
|
||||
if (typeof over.id === "string" && over.id.endsWith("-status")) {
|
||||
overStatusId = Number(over.id.replace("-status", ""));
|
||||
} else {
|
||||
const deal = deals.find(deal => deal.statusId === over.id);
|
||||
if (!deal) return;
|
||||
overStatusId = deal.statusId;
|
||||
}
|
||||
|
||||
if (!overStatusId || activeStatusId === overStatusId) return;
|
||||
|
||||
const newRank = getNewStatusRank(activeStatusId, overStatusId);
|
||||
if (!newRank) return;
|
||||
|
||||
props.onStatusDragEnd?.(activeStatusId, newRank);
|
||||
};
|
||||
|
||||
const handleDealDragEnd = (activeId: number | string, over: Over) => {
|
||||
const activeDealId = Number(activeId);
|
||||
const activeStatusId = getStatusByDealId(activeDealId)?.id;
|
||||
if (!activeStatusId) return;
|
||||
|
||||
const { overStatusId, newLexorank } = getDropTarget(
|
||||
over.id,
|
||||
activeDealId,
|
||||
activeStatusId
|
||||
);
|
||||
if (!overStatusId) return;
|
||||
|
||||
props.onDealDragEnd(activeDealId, overStatusId, newLexorank);
|
||||
};
|
||||
const sensors = useDndSensors();
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
@ -335,24 +66,10 @@ const StatusColumnsDnd: FC<Props> = props => {
|
||||
/>
|
||||
</SortableItem>
|
||||
))}
|
||||
<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>
|
||||
<DndOverlay
|
||||
activeStatus={activeStatus}
|
||||
activeDeal={activeDeal}
|
||||
/>
|
||||
</Group>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
Reference in New Issue
Block a user