feat: displaying and sorting groups of deals

This commit is contained in:
2025-10-17 11:52:19 +04:00
parent fc176ec9e4
commit 0fe41656f8
26 changed files with 1310 additions and 224 deletions

View File

@ -1,11 +1,13 @@
.create-button {
cursor: pointer;
min-height: max-content;
border: 1px dashed;
@mixin light {
background-color: var(--color-light-white-blue);
border-color: lightblue;
}
@mixin dark {
background-color: var(--mantine-color-dark-7);
border-color: var(--mantine-color-dark-5);
}
}

View File

@ -1,11 +1,27 @@
.container {
padding: 0;
border: 1px dashed;
@mixin light {
background-color: var(--color-light-white-blue);
border-color: lightblue;
}
@mixin dark {
background-color: var(--mantine-color-dark-7);
border-color: var(--mantine-color-dark-5);
}
}
.container-in-group {
padding: 0;
border: 1px dashed;
@mixin light {
background-color: var(--color-light-aqua);
border-color: lightblue;
}
@mixin dark {
background-color: var(--mantine-color-dark-6);
border-color: var(--mantine-color-dark-5);
}
}

View File

@ -8,9 +8,10 @@ import styles from "./DealCard.module.css";
type Props = {
deal: DealSchema;
isInGroup?: boolean;
};
const DealCard = ({ deal }: Props) => {
const DealCard = ({ deal, isInGroup = false }: Props) => {
const { selectedProject, modulesSet } = useProjectsContext();
const { dealsCrud, refetchDeals } = useDealsContext();
const { openDrawer } = useDrawersContext();
@ -31,7 +32,9 @@ const DealCard = ({ deal }: Props) => {
return (
<Card
onClick={onClick}
className={styles.container}>
className={
isInGroup ? styles["container-in-group"] : styles.container
}>
<Group
justify={"space-between"}
wrap={"nowrap"}

View File

@ -1,25 +0,0 @@
import React, { FC, useMemo } from "react";
import { Box } from "@mantine/core";
import DealCard from "@/app/deals/components/shared/DealCard/DealCard";
import SortableItem from "@/components/dnd/SortableItem";
import { DealSchema } from "@/lib/client";
type Props = {
deal: DealSchema;
};
const DealContainer: FC<Props> = ({ deal }) => {
const dealBody = useMemo(() => <DealCard deal={deal} />, [deal]);
return (
<Box>
<SortableItem
dragHandleStyle={{ cursor: "pointer" }}
id={deal.id}
renderItem={() => dealBody}
/>
</Box>
);
};
export default DealContainer;

View File

@ -0,0 +1,12 @@
.group-container {
border: 1px dashed;
@mixin light {
background-color: var(--color-light-white-blue);
border-color: lightblue;
}
@mixin dark {
background-color: var(--mantine-color-dark-7);
border-color: var(--mantine-color-dark-5);
}
}

View File

@ -0,0 +1,30 @@
import { FC } from "react";
import { Stack, Text } from "@mantine/core";
import DealCard from "@/app/deals/components/shared/DealCard/DealCard";
import GroupWithDealsSchema from "@/types/GroupWithDealsSchema";
import styles from "./DealsGroup.module.css";
type Props = {
group: GroupWithDealsSchema;
};
const DealsGroup: FC<Props> = ({ group }) => {
return (
<Stack
className={styles["group-container"]}
gap={"xs"}
bdrs={"lg"}
p={"xs"}>
<Text mx={"xs"}>{group.name}</Text>
{group.items.map(deal => (
<DealCard
deal={deal}
isInGroup
key={deal.id}
/>
))}
</Stack>
);
};
export default DealsGroup;

View File

@ -2,7 +2,7 @@
import React, { FC, ReactNode } from "react";
import DealCard from "@/app/deals/components/shared/DealCard/DealCard";
import DealContainer from "@/app/deals/components/shared/DealContainer/DealContainer";
import DealsGroup from "@/app/deals/components/shared/DealsGroup/DealsGroup";
import StatusColumnHeader from "@/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader";
import StatusColumnWrapper from "@/app/deals/components/shared/StatusColumnWrapper/StatusColumnWrapper";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
@ -11,37 +11,36 @@ import useDealsAndStatusesDnd from "@/app/deals/hooks/useDealsAndStatusesDnd";
import FunnelDnd from "@/components/dnd/FunnelDnd/FunnelDnd";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema, StatusSchema } from "@/lib/client";
import GroupWithDealsSchema from "@/types/GroupWithDealsSchema";
import { sortByLexorank } from "@/utils/lexorank/sort";
const Funnel: FC = () => {
const { selectedBoard } = useBoardsContext();
const { deals } = useDealsContext();
const { dealsWithoutGroup, groupsWithDeals } = useDealsContext();
const isMobile = useIsMobile();
const {
sortedStatuses,
handleDragStart,
handleDragOver,
handleDragEnd,
activeStatus,
activeDeal,
swiperRef,
} = useDealsAndStatusesDnd();
const { sortedStatuses, handleDragOver, handleDragEnd, swiperRef } =
useDealsAndStatusesDnd();
return (
<FunnelDnd
<FunnelDnd<StatusSchema, DealSchema, GroupWithDealsSchema>
containers={sortedStatuses}
items={deals}
onDragStart={handleDragStart}
itemsAndGroups={sortByLexorank([
...dealsWithoutGroup,
...groupsWithDeals,
])}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
swiperRef={swiperRef}
getContainerId={(status: StatusSchema) => `${status.id}-status`}
getItemsByContainer={(status: StatusSchema, items: DealSchema[]) =>
sortByLexorank(
items.filter(deal => deal.status.id === status.id)
)
getItemsByContainer={(status: StatusSchema) =>
sortByLexorank([
...dealsWithoutGroup.filter(
deal => deal.status.id === status.id
),
...groupsWithDeals.filter(
group => group.items[0].status.id === status.id
),
])
}
renderContainer={(
status: StatusSchema,
@ -59,25 +58,28 @@ const Funnel: FC = () => {
renderContainerHeader={status => (
<StatusColumnHeader
status={status}
isDragging={activeStatus?.id === status.id}
isDragging={false}
/>
)}
renderItem={(deal: DealSchema) => (
<DealContainer
<DealCard
key={deal.id}
deal={deal}
/>
)}
activeContainer={activeStatus}
activeItem={activeDeal}
renderItemOverlay={(deal: DealSchema) => <DealCard deal={deal} />}
renderGroup={(group: GroupWithDealsSchema) => (
<DealsGroup
key={`${group.id}group`}
group={group}
/>
)}
renderContainerOverlay={(status: StatusSchema, children) => (
<StatusColumnWrapper
status={status}
renderHeader={() => (
<StatusColumnHeader
status={status}
isDragging={activeStatus?.id === status.id}
isDragging
/>
)}>
{children}

View File

@ -3,18 +3,24 @@
import React from "react";
import { UseFormReturnType } from "@mantine/form";
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
import useDealsAndGroups from "@/app/deals/hooks/useDealsAndGroups";
import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters";
import useDealGroupCrud, { GroupsCrud } from "@/hooks/cruds/useDealGroupCrud";
import { DealsCrud, useDealsCrud } from "@/hooks/cruds/useDealsCrud";
import useDealsList from "@/hooks/lists/useDealsList";
import { SortingForm } from "@/hooks/utils/useSorting";
import { DealSchema, PaginationInfoSchema } from "@/lib/client";
import makeContext from "@/lib/contextFactory/contextFactory";
import GroupWithDealsSchema from "@/types/GroupWithDealsSchema";
type DealsContextState = {
deals: DealSchema[];
setDeals: (deals: DealSchema[]) => void;
dealsWithoutGroup: DealSchema[];
groupsWithDeals: GroupWithDealsSchema[];
refetchDeals: () => void;
dealsCrud: DealsCrud;
groupsCrud: GroupsCrud;
paginationInfo?: PaginationInfoSchema;
page: number;
setPage: React.Dispatch<React.SetStateAction<number>>;
@ -48,9 +54,17 @@ const useDealsContextState = ({
statuses,
});
const groupsCrud = useDealGroupCrud();
const { dealsWithoutGroup, groupsWithDeals } =
useDealsAndGroups(dealsListObjects);
return {
...dealsListObjects,
dealsCrud,
groupsCrud,
dealsWithoutGroup,
groupsWithDeals,
};
};

View File

@ -0,0 +1,44 @@
import { useMemo } from "react";
import { isNull } from "lodash";
import { DealSchema } from "@/lib/client";
import GroupWithDealsSchema from "@/types/GroupWithDealsSchema";
import { sortByLexorank } from "@/utils/lexorank/sort";
type Props = {
deals: DealSchema[];
};
const useDealsAndGroups = ({ deals }: Props) => {
const dealsWithoutGroup: DealSchema[] = useMemo(
() => deals.filter(d => isNull(d.group)),
[deals]
);
const groupsWithDeals: GroupWithDealsSchema[] = useMemo(() => {
const groupsWithDealMap = new Map<number, GroupWithDealsSchema>();
for (const deal of deals) {
if (isNull(deal.group)) continue;
const groupData = groupsWithDealMap.get(deal.group.id);
if (groupData) {
groupData.items.push(deal);
groupsWithDealMap.set(deal.group.id, groupData);
} else {
groupsWithDealMap.set(deal.group.id, {
...deal.group,
items: [deal],
});
}
}
return sortByLexorank(groupsWithDealMap.values().toArray());
}, [deals]);
return {
dealsWithoutGroup,
groupsWithDeals,
};
};
export default useDealsAndGroups;

View File

@ -1,32 +1,41 @@
import { RefObject, useMemo, useRef, useState } from "react";
import { DragOverEvent, DragStartEvent, Over } from "@dnd-kit/core";
import { RefObject, useMemo, useRef } from "react";
import { DragOverEvent, 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 isItemGroup from "@/app/deals/utils/isItemGroup";
import {
getContainerId,
isContainerId,
} from "@/components/dnd/FunnelDnd/utils/columnId";
import {
getGroupId,
isGroupId,
} from "@/components/dnd/FunnelDnd/utils/groupId";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema, StatusSchema } from "@/lib/client";
import { StatusSchema } from "@/lib/client";
import { sortByLexorank } from "@/utils/lexorank/sort";
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 {
deals,
dealsWithoutGroup,
groupsWithDeals,
setDeals,
dealsCrud,
groupsCrud,
} = useDealsContext();
const sortedStatuses = useMemo(() => sortByLexorank(statuses), [statuses]);
const isMobile = useIsMobile();
@ -40,15 +49,29 @@ const useDealsAndStatusesDnd = (): ReturnType => {
const debouncedSetDeals = useDebouncedCallback(setDeals, 200);
const getStatusByDealId = (dealId: number) => {
const deal = deals.find(deal => deal.id === dealId);
const deal = dealsWithoutGroup.find(deal => deal.id === dealId);
if (!deal) return;
return statuses.find(status => status.id === deal.status.id);
};
const getStatusByGroupId = (groupId: number) => {
const group = groupsWithDeals.find(group => group.id === groupId);
if (!group || group.items.length === 0) return;
return statuses.find(status => status.id === group.items[0].status.id);
};
const getStatusById = (statusId: number) => {
return statuses.find(status => status.id === statusId);
};
const getStatusDealsAndGroups = (statusId: number) =>
sortByLexorank([
...dealsWithoutGroup.filter(d => d.status.id === statusId),
...groupsWithDeals.filter(
g => g.items.length > 0 && g.items[0].status.id === statusId
),
]);
const swipeSliderDuringDrag = (activeId: number, over: Over) => {
const activeStatus = getStatusByDealId(activeId);
const swiperActiveStatus =
@ -58,8 +81,8 @@ const useDealsAndStatusesDnd = (): ReturnType => {
const activeStatusLexorank = activeStatus?.lexorank;
let overStatusLexorank: string | undefined;
if (typeof over.id === "string" && isStatusId(over.id)) {
const overStatusId = getStatusId(over.id);
if (typeof over.id === "string" && isContainerId(over.id)) {
const overStatusId = getContainerId(over.id);
overStatusLexorank = statuses.find(
s => s.id === overStatusId
)?.lexorank;
@ -98,11 +121,16 @@ const useDealsAndStatusesDnd = (): ReturnType => {
swipeSliderDuringDrag(activeId, over);
}
if (typeof activeId === "string" && isStatusId(activeId)) {
if (typeof activeId !== "string") {
handleDealDragOver(activeId, over);
return;
}
if (isContainerId(activeId)) {
handleColumnDragOver(activeId, over);
return;
}
handleDealDragOver(activeId, over);
handleGroupDragOver(activeId, over);
};
const handleDealDragOver = (activeId: string | number, over: Over) => {
@ -113,6 +141,7 @@ const useDealsAndStatusesDnd = (): ReturnType => {
const { overStatus, newLexorank } = getDropTarget(
over.id,
activeDealId,
undefined,
activeStatusId
);
if (!overStatus) return;
@ -130,14 +159,49 @@ const useDealsAndStatusesDnd = (): ReturnType => {
);
};
const handleGroupDragOver = (activeId: string, over: Over) => {
const activeGroupId = getGroupId(activeId);
const activeStatusId = getStatusByGroupId(activeGroupId)?.id;
if (!activeStatusId) return;
const { overStatus, newLexorank } = getDropTarget(
over.id,
undefined,
activeGroupId,
activeStatusId
);
if (!overStatus) return;
debouncedSetDeals(
deals.map(deal =>
deal.group && deal.group.id === activeGroupId
? {
...deal,
status: overStatus,
group: {
...deal.group,
lexorank: newLexorank || deal.group.lexorank,
},
}
: deal
)
);
};
const handleColumnDragOver = (activeId: string, over: Over) => {
const activeStatusId = getStatusId(activeId);
const activeStatusId = getContainerId(activeId);
let overStatusId: number;
if (typeof over.id === "string" && isStatusId(over.id)) {
overStatusId = getStatusId(over.id);
if (typeof over.id === "string") {
if (isContainerId(over.id)) {
overStatusId = getContainerId(over.id);
} else {
const status = getStatusByGroupId(getGroupId(over.id));
if (!status) return;
overStatusId = status.id;
}
} else {
const deal = deals.find(deal => deal.id === over.id);
const deal = dealsWithoutGroup.find(deal => deal.id === over.id);
if (!deal) return;
overStatusId = deal.status.id;
}
@ -158,17 +222,44 @@ const useDealsAndStatusesDnd = (): ReturnType => {
const getDropTarget = (
overId: string | number,
activeDealId: number,
activeDealId: number | undefined,
activeGroupId: number | undefined,
activeStatusId: number,
isOnDragEnd: boolean = false
): { overStatus?: StatusSchema; newLexorank?: string } => {
if (typeof overId === "string") {
return {
overStatus: getStatusById(getStatusId(overId)),
newLexorank: undefined,
};
if (isContainerId(overId)) {
return getStatusDropTarget(overId);
}
if (isGroupId(overId)) {
return getGroupDropTarget(
overId,
activeGroupId,
activeStatusId,
isOnDragEnd
);
}
}
return getDealDropTarget(
Number(overId),
activeDealId,
activeStatusId,
isOnDragEnd
);
};
const getStatusDropTarget = (overId: string) => ({
overStatus: getStatusById(getContainerId(overId)),
newLexorank: undefined,
});
const getDealDropTarget = (
overId: number,
activeDealId: number | undefined,
activeStatusId: number,
isOnDragEnd: boolean = false
) => {
const overDealId = Number(overId);
const overStatus = getStatusByDealId(overDealId);
@ -176,51 +267,93 @@ const useDealsAndStatusesDnd = (): ReturnType => {
return { overStatus: undefined, newLexorank: undefined };
}
const statusDeals = sortByLexorank(
deals.filter(deal => deal.status.id === overStatus.id)
const statusItems = getStatusDealsAndGroups(overStatus.id);
const overDealIndex = statusItems.findIndex(
deal => !isItemGroup(deal) && deal.id === overDealId
);
const overDealIndex = statusDeals.findIndex(
deal => deal.id === overDealId
const activeDealIndex = statusItems.findIndex(
deal => !isItemGroup(deal) && deal.id === activeDealId
);
if (activeStatusId === overStatus.id) {
const newLexorank = getNewRankForSameStatus(
statusDeals,
statusItems,
overDealIndex,
activeDealId
activeDealIndex
);
return { overStatus, newLexorank };
}
const newLexorank = getNewRankForAnotherStatus(
statusDeals,
statusItems,
overDealIndex
);
return { overStatus, newLexorank };
};
const getGroupDropTarget = (
overId: string,
activeGroupId: number | undefined,
activeStatusId: number,
isOnDragEnd: boolean = false
) => {
const overGroupId = getGroupId(overId);
const overStatus = getStatusByGroupId(overGroupId);
if (!overStatus || (!isOnDragEnd && activeGroupId === overGroupId)) {
return { overStatus: undefined, newLexorank: undefined };
}
const statusItems = getStatusDealsAndGroups(overStatus.id);
const overGroupIndex = statusItems.findIndex(
group => isItemGroup(group) && group.id === overGroupId
);
const activeGroupIndex = statusItems.findIndex(
group => isItemGroup(group) && group.id === activeGroupId
);
if (activeStatusId === overStatus.id) {
const newLexorank = getNewRankForSameStatus(
statusItems,
overGroupIndex,
activeGroupIndex
);
return { overStatus, newLexorank };
}
const newLexorank = getNewRankForAnotherStatus(
statusItems,
overGroupIndex
);
return { overStatus, 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)) {
if (typeof activeId !== "string") {
handleDealDragEnd(activeId, over);
return;
}
if (isContainerId(activeId)) {
handleStatusColumnDragEnd(activeId, over);
return;
}
handleDealDragEnd(activeId, over);
handleGroupDragEnd(activeId, over);
};
const handleStatusColumnDragEnd = (activeId: string, over: Over) => {
const activeStatusId = getStatusId(activeId);
const activeStatusId = getContainerId(activeId);
let overStatusId: number;
if (typeof over.id === "string" && isStatusId(over.id)) {
overStatusId = getStatusId(over.id);
if (typeof over.id === "string" && isContainerId(over.id)) {
overStatusId = getContainerId(over.id);
} else {
const deal = deals.find(deal => deal.status.id === over.id);
const deal = dealsWithoutGroup.find(
deal => deal.status.id === over.id
);
if (!deal) return;
overStatusId = deal.status.id;
}
@ -245,6 +378,7 @@ const useDealsAndStatusesDnd = (): ReturnType => {
const { overStatus, newLexorank } = getDropTarget(
over.id,
activeDealId,
undefined,
activeStatusId,
true
);
@ -261,30 +395,36 @@ const useDealsAndStatusesDnd = (): ReturnType => {
dealsCrud.onUpdate(dealId, { statusId, lexorank, name: null });
};
const handleDragStart = ({ active }: DragStartEvent) => {
const activeId = active.id as string | number;
const handleGroupDragEnd = (activeId: string, over: Over) => {
const activeGroupId = getGroupId(activeId);
const activeStatusId = getStatusByGroupId(activeGroupId)?.id;
if (!activeStatusId) return;
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
const { overStatus, newLexorank } = getDropTarget(
over.id,
undefined,
activeGroupId,
activeStatusId,
true
);
if (!overStatus) return;
onGroupDragEnd(activeGroupId, overStatus.id, newLexorank);
};
const onGroupDragEnd = (
groupId: number,
statusId: number,
lexorank?: string
) => {
groupsCrud.onUpdate(groupId, { statusId, lexorank, name: null });
};
return {
swiperRef,
sortedStatuses,
handleDragStart,
handleDragOver,
handleDragEnd,
activeStatus,
activeDeal,
};
};

View File

@ -1,18 +1,24 @@
import { LexoRank } from "lexorank";
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
import { DealSchema } from "@/lib/client";
import { sortByLexorank } from "@/utils/lexorank/sort";
import {
BaseDraggable,
BaseGroupDraggable,
} from "@/components/dnd/types/types";
import { getNewLexorank } from "@/utils/lexorank/generation";
import { sortByLexorank } from "@/utils/lexorank/sort";
type NewRankGetters = {
type NewRankGetters<
TItem extends BaseDraggable,
TGroup extends BaseGroupDraggable<TItem>,
> = {
getNewRankForSameStatus: (
statusDeals: DealSchema[],
overDealIndex: number,
activeDealId: number
statusItemsAndGroups: (TItem | TGroup)[],
overItemOrGroupIndex: number,
activeItemOrGroupIndex: number
) => string;
getNewRankForAnotherStatus: (
statusDeals: DealSchema[],
overDealIndex: number
statusItemsAndGroups: (TItem | TGroup)[],
overItemOrGroupIndex: number
) => string;
getNewStatusRank: (
activeStatusId: number,
@ -20,44 +26,46 @@ type NewRankGetters = {
) => string | null;
};
const useGetNewRank = (): NewRankGetters => {
const useGetNewRank = <
TItem extends BaseDraggable,
TGroup extends BaseGroupDraggable<TItem>,
>(): NewRankGetters<TItem, TGroup> => {
const { statuses } = useStatusesContext();
const getNewRankForSameStatus = (
statusDeals: DealSchema[],
overDealIndex: number,
activeDealId: number
statusItemsAndGroups: (TItem | TGroup)[],
overItemOrGroupIndex: number,
activeItemOrGroupIndex: number
): string => {
const activeDealIndex = statusDeals.findIndex(
deal => deal.id === activeDealId
);
const [leftIndex, rightIndex] =
overDealIndex < activeDealIndex
? [overDealIndex - 1, overDealIndex]
: [overDealIndex, overDealIndex + 1];
overItemOrGroupIndex < activeItemOrGroupIndex
? [overItemOrGroupIndex - 1, overItemOrGroupIndex]
: [overItemOrGroupIndex, overItemOrGroupIndex + 1];
const leftLexorank =
leftIndex >= 0
? LexoRank.parse(statusDeals[leftIndex].lexorank)
? LexoRank.parse(statusItemsAndGroups[leftIndex].lexorank)
: null;
const rightLexorank =
rightIndex < statusDeals.length
? LexoRank.parse(statusDeals[rightIndex].lexorank)
rightIndex < statusItemsAndGroups.length
? LexoRank.parse(statusItemsAndGroups[rightIndex].lexorank)
: null;
return getNewLexorank(leftLexorank, rightLexorank).toString();
};
const getNewRankForAnotherStatus = (
statusDeals: DealSchema[],
overDealIndex: number
statusItemsAndGroups: (TItem | TGroup)[],
overItemOrGroupIndex: number
): string => {
const leftLexorank =
overDealIndex > 0
? LexoRank.parse(statusDeals[overDealIndex - 1].lexorank)
overItemOrGroupIndex > 0
? LexoRank.parse(
statusItemsAndGroups[overItemOrGroupIndex - 1].lexorank
)
: null;
const rightLexorank = LexoRank.parse(
statusDeals[overDealIndex].lexorank
statusItemsAndGroups[overItemOrGroupIndex].lexorank
);
return getNewLexorank(leftLexorank, rightLexorank).toString();

View File

@ -0,0 +1,15 @@
import {
BaseDraggable,
BaseGroupDraggable,
} from "@/components/dnd/types/types";
const isItemGroup = <
TItem extends BaseDraggable,
TGroup extends BaseGroupDraggable<TItem>,
>(
item: TItem | TGroup
): boolean => {
return "items" in item;
};
export default isItemGroup;

View File

@ -1,6 +0,0 @@
const STATUS_POSTFIX = "-status";
export const isStatusId = (rawId: string) => rawId.endsWith(STATUS_POSTFIX);
export const getStatusId = (rawId: string) =>
Number(rawId.replace(STATUS_POSTFIX, ""));