287 lines
9.2 KiB
TypeScript
287 lines
9.2 KiB
TypeScript
import { RefObject, useMemo, useRef, useState } from "react";
|
|
import { DragOverEvent, DragStartEvent, Over } from "@dnd-kit/core";
|
|
import { SwiperRef } from "swiper/swiper-react";
|
|
import { useDebouncedCallback } from "@mantine/hooks";
|
|
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
|
|
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
|
import useGetNewRank from "@/app/deals/hooks/useGetNewRank";
|
|
import { getStatusId, isStatusId } from "@/app/deals/utils/statusId";
|
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
|
import { DealSchema, StatusSchema } from "@/lib/client";
|
|
import { sortByLexorank } from "@/utils/lexorank";
|
|
|
|
type ReturnType = {
|
|
sortedStatuses: StatusSchema[];
|
|
handleDragStart: ({ active }: DragStartEvent) => void;
|
|
handleDragOver: ({ active, over }: DragOverEvent) => void;
|
|
handleDragEnd: ({ active, over }: DragOverEvent) => void;
|
|
activeStatus: StatusSchema | null;
|
|
activeDeal: DealSchema | null;
|
|
swiperRef: RefObject<SwiperRef | null>;
|
|
};
|
|
|
|
const useDealsAndStatusesDnd = (): ReturnType => {
|
|
const swiperRef = useRef<SwiperRef>(null);
|
|
const [activeDeal, setActiveDeal] = useState<DealSchema | null>(null);
|
|
const [activeStatus, setActiveStatus] = useState<StatusSchema | null>(null);
|
|
const { statuses, setStatuses, statusesCrud } = useStatusesContext();
|
|
const { deals, setDeals, dealsCrud } = useDealsContext();
|
|
const sortedStatuses = useMemo(() => sortByLexorank(statuses), [statuses]);
|
|
const isMobile = useIsMobile();
|
|
|
|
const {
|
|
getNewRankForSameStatus,
|
|
getNewRankForAnotherStatus,
|
|
getNewStatusRank,
|
|
} = useGetNewRank();
|
|
|
|
const debouncedSetStatuses = useDebouncedCallback(setStatuses, 200);
|
|
const debouncedSetDeals = useDebouncedCallback(setDeals, 200);
|
|
|
|
const getStatusByDealId = (dealId: number) => {
|
|
const deal = deals.find(deal => deal.id === dealId);
|
|
if (!deal) return;
|
|
return statuses.find(status => status.id === deal.statusId);
|
|
};
|
|
|
|
const swipeSliderDuringDrag = (activeId: number, over: Over) => {
|
|
const activeStatus = getStatusByDealId(activeId);
|
|
const swiperActiveStatus =
|
|
statuses[swiperRef.current?.swiper.activeIndex ?? 0];
|
|
if (swiperActiveStatus.id !== activeStatus?.id) return;
|
|
|
|
const activeStatusLexorank = activeStatus?.lexorank;
|
|
let overStatusLexorank: string | undefined;
|
|
|
|
if (typeof over.id === "string" && isStatusId(over.id)) {
|
|
const overStatusId = getStatusId(over.id);
|
|
overStatusLexorank = statuses.find(
|
|
s => s.id === overStatusId
|
|
)?.lexorank;
|
|
} else {
|
|
overStatusLexorank = getStatusByDealId(Number(over.id))?.lexorank;
|
|
}
|
|
|
|
if (
|
|
!activeStatusLexorank ||
|
|
!overStatusLexorank ||
|
|
!swiperRef.current?.swiper
|
|
)
|
|
return;
|
|
|
|
const activeIndex = sortedStatuses.findIndex(
|
|
s => s.lexorank === activeStatusLexorank
|
|
);
|
|
const overIndex = sortedStatuses.findIndex(
|
|
s => s.lexorank === overStatusLexorank
|
|
);
|
|
|
|
if (activeIndex > overIndex) {
|
|
swiperRef.current.swiper.slidePrev();
|
|
return;
|
|
}
|
|
if (activeIndex < overIndex) {
|
|
swiperRef.current.swiper.slideNext();
|
|
}
|
|
};
|
|
|
|
const handleDragOver = ({ active, over }: DragOverEvent) => {
|
|
if (!over) return;
|
|
const activeId = active.id as string | number;
|
|
|
|
if (isMobile && typeof activeId !== "string") {
|
|
swipeSliderDuringDrag(activeId, over);
|
|
}
|
|
|
|
if (typeof activeId === "string" && isStatusId(activeId)) {
|
|
handleColumnDragOver(activeId, over);
|
|
return;
|
|
}
|
|
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;
|
|
|
|
debouncedSetDeals(
|
|
deals.map(deal =>
|
|
deal.id === activeDealId
|
|
? {
|
|
...deal,
|
|
statusId: overStatusId,
|
|
lexorank: newLexorank || deal.lexorank,
|
|
}
|
|
: deal
|
|
)
|
|
);
|
|
};
|
|
|
|
const handleColumnDragOver = (activeId: string, over: Over) => {
|
|
const activeStatusId = getStatusId(activeId);
|
|
let overStatusId: number;
|
|
|
|
if (typeof over.id === "string" && isStatusId(over.id)) {
|
|
overStatusId = getStatusId(over.id);
|
|
} 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;
|
|
|
|
debouncedSetStatuses(
|
|
statuses.map(status =>
|
|
status.id === activeStatusId
|
|
? { ...status, lexorank: newRank }
|
|
: status
|
|
)
|
|
);
|
|
};
|
|
|
|
const getDropTarget = (
|
|
overId: string | number,
|
|
activeDealId: number,
|
|
activeStatusId: number,
|
|
isOnDragEnd: boolean = false
|
|
) => {
|
|
if (typeof overId === "string") {
|
|
return {
|
|
overStatusId: getStatusId(overId),
|
|
newLexorank: undefined,
|
|
};
|
|
}
|
|
|
|
const overDealId = Number(overId);
|
|
const overStatusId = getStatusByDealId(overDealId)?.id;
|
|
|
|
if (!overStatusId || (!isOnDragEnd && activeDealId === overDealId)) {
|
|
return { overStatusId: undefined, newLexorank: undefined };
|
|
}
|
|
|
|
const statusDeals = sortByLexorank(
|
|
deals.filter(deal => deal.statusId === overStatusId)
|
|
);
|
|
const overDealIndex = statusDeals.findIndex(
|
|
deal => deal.id === overDealId
|
|
);
|
|
|
|
if (activeStatusId === overStatusId) {
|
|
const newLexorank = getNewRankForSameStatus(
|
|
statusDeals,
|
|
overDealIndex,
|
|
activeDealId
|
|
);
|
|
return { overStatusId, newLexorank };
|
|
}
|
|
|
|
const newLexorank = getNewRankForAnotherStatus(
|
|
statusDeals,
|
|
overDealIndex
|
|
);
|
|
return { overStatusId, newLexorank };
|
|
};
|
|
|
|
const handleDragEnd = ({ active, over }: DragOverEvent) => {
|
|
setActiveDeal(null);
|
|
setActiveStatus(null);
|
|
if (!over) return;
|
|
|
|
const activeId: string | number = active.id;
|
|
|
|
if (typeof activeId === "string" && isStatusId(activeId)) {
|
|
handleStatusColumnDragEnd(activeId, over);
|
|
return;
|
|
}
|
|
handleDealDragEnd(activeId, over);
|
|
};
|
|
|
|
const handleStatusColumnDragEnd = (activeId: string, over: Over) => {
|
|
const activeStatusId = getStatusId(activeId);
|
|
let overStatusId: number;
|
|
|
|
if (typeof over.id === "string" && isStatusId(over.id)) {
|
|
overStatusId = getStatusId(over.id);
|
|
} else {
|
|
const deal = deals.find(deal => deal.statusId === over.id);
|
|
if (!deal) return;
|
|
overStatusId = deal.statusId;
|
|
}
|
|
|
|
if (!overStatusId) return;
|
|
|
|
const newRank = getNewStatusRank(activeStatusId, overStatusId);
|
|
if (!newRank) return;
|
|
|
|
onStatusDragEnd?.(activeStatusId, newRank);
|
|
};
|
|
|
|
const onStatusDragEnd = (statusId: number, lexorank: string) => {
|
|
statusesCrud.onUpdate(statusId, { lexorank });
|
|
};
|
|
|
|
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,
|
|
true
|
|
);
|
|
if (!overStatusId) return;
|
|
|
|
onDealDragEnd(activeDealId, overStatusId, newLexorank);
|
|
};
|
|
|
|
const onDealDragEnd = (
|
|
dealId: number,
|
|
statusId: number,
|
|
lexorank?: string
|
|
) => {
|
|
dealsCrud.onUpdate(dealId, { statusId, lexorank, name: null });
|
|
};
|
|
|
|
const handleDragStart = ({ active }: DragStartEvent) => {
|
|
const activeId = active.id as string | number;
|
|
|
|
if (typeof activeId === "string" && isStatusId(activeId)) {
|
|
const statusId = getStatusId(activeId);
|
|
setActiveStatus(
|
|
statuses.find(status => status.id === statusId) ?? null
|
|
);
|
|
return;
|
|
}
|
|
|
|
setActiveDeal(
|
|
deals.find(deal => deal.id === (activeId as number)) ?? null
|
|
);
|
|
};
|
|
|
|
return {
|
|
swiperRef,
|
|
sortedStatuses,
|
|
handleDragStart,
|
|
handleDragOver,
|
|
handleDragEnd,
|
|
activeStatus,
|
|
activeDeal,
|
|
};
|
|
};
|
|
|
|
export default useDealsAndStatusesDnd;
|