feat: raw deals dnd between statuses
This commit is contained in:
210
src/app/deals/components/StatusColumnsDnd/StatusColumnsDnd.tsx
Normal file
210
src/app/deals/components/StatusColumnsDnd/StatusColumnsDnd.tsx
Normal file
@ -0,0 +1,210 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC, useState } from "react";
|
||||
import {
|
||||
closestCorners,
|
||||
defaultDropAnimation,
|
||||
DndContext,
|
||||
DragOverEvent,
|
||||
DragOverlay,
|
||||
DragStartEvent,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
|
||||
import { LexoRank } from "lexorank";
|
||||
import { Group } from "@mantine/core";
|
||||
import DealCard from "@/app/deals/components/DealCard/DealCard";
|
||||
import StatusColumn from "@/app/deals/components/StatusColumn/StatusColumn";
|
||||
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
|
||||
import { DealSchema } from "@/types/DealSchema";
|
||||
import { getNewLexorank, sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
type Props = {
|
||||
onDealDragEnd: (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => void;
|
||||
};
|
||||
|
||||
const StatusColumnsDnd: FC<Props> = props => {
|
||||
const { statuses, deals, setDeals } = useBoardsContext();
|
||||
const [activeDealId, setActiveDealId] = useState<null | number>(null);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
const handleDragStart = ({ active }: DragStartEvent) => {
|
||||
setActiveDealId(active.id as number);
|
||||
};
|
||||
|
||||
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?.id) return;
|
||||
|
||||
const activeDealId = Number(active.id);
|
||||
const activeStatusId = getStatusByDealId(activeDealId)?.id;
|
||||
if (!activeStatusId) return;
|
||||
|
||||
const { overStatusId, newLexorank } = getDropTarget(
|
||||
over.id,
|
||||
activeDealId,
|
||||
activeStatusId
|
||||
);
|
||||
if (!overStatusId) return;
|
||||
|
||||
setDeals(deals =>
|
||||
deals.map(deal =>
|
||||
deal.id === activeDealId
|
||||
? {
|
||||
...deal,
|
||||
statusId: overStatusId,
|
||||
rank: newLexorank || deal.rank,
|
||||
}
|
||||
: deal
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
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 handleDragEnd = ({ active, over }: DragOverEvent) => {
|
||||
setActiveDealId(null);
|
||||
if (!over?.id) return;
|
||||
|
||||
const activeDealId = Number(active.id);
|
||||
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 deal = activeDealId
|
||||
? deals.find(deal => deal.id === activeDealId)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCorners}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}>
|
||||
<Group
|
||||
gap={"xs"}
|
||||
justify={"start"}>
|
||||
{statuses.map(status => (
|
||||
<StatusColumn
|
||||
key={status.id}
|
||||
id={`${status.id}-status`}
|
||||
title={status.name}
|
||||
deals={deals.filter(
|
||||
deal => deal.statusId === status.id
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
<DragOverlay dropAnimation={defaultDropAnimation}>
|
||||
{deal ? <DealCard deal={deal} /> : null}
|
||||
</DragOverlay>
|
||||
</Group>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumnsDnd;
|
||||
Reference in New Issue
Block a user