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/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; }; const useDealsAndStatusesDnd = (): ReturnType => { const swiperRef = useRef(null); const [activeDeal, setActiveDeal] = useState(null); const [activeStatus, setActiveStatus] = useState(null); const { statuses, setStatuses, updateStatus } = useStatusesContext(); const { deals, setDeals, updateDeal } = 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 => 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 => 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) => { updateStatus.mutate({ path: { statusId, }, body: { status: { 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 ) => { updateDeal.mutate({ path: { dealId, }, body: { deal: { statusId, lexorank, }, }, }); }; 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;