Files
Crm-Frontend/src/app/deals/components/StatusColumnsDnd/StatusColumnsDnd.tsx

209 lines
6.2 KiB
TypeScript

"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 { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
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 } = useStatusesContext();
const [activeDeal, setActiveDeal] = useState<DealSchema | null>(null);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const handleDragStart = ({ active }: DragStartEvent) => {
setActiveDeal(
deals.find(deal => deal.id === (active.id 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?.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) => {
setActiveDeal(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);
};
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}>
{activeDeal ? <DealCard deal={activeDeal} /> : null}
</DragOverlay>
</Group>
</DndContext>
);
};
export default StatusColumnsDnd;