From 36c2a3a2af832ca6dc3dff20852be5cacafc4ce2 Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Thu, 16 Oct 2025 15:26:53 +0400 Subject: [PATCH] feat: funnel dnd using pragmatic, not finished groups --- package.json | 7 + .../shared/DealCard/DealCard.module.css | 1 + .../shared/DealContainer/DealContainer.tsx | 25 - .../shared/DealGroupCard/DealGroupCard.tsx | 89 ++ .../components/FulfillmentGroupInfo.tsx | 51 ++ .../deals/components/shared/Funnel/Funnel.tsx | 117 ++- .../components/shared/PageBody/PageBody.tsx | 8 +- .../StatusColumnHeader/StatusColumnHeader.tsx | 13 +- src/app/deals/contexts/DealsContext.tsx | 9 + src/app/deals/hooks/useDealsAndGroups.ts | 43 + src/app/deals/hooks/useDealsAndStatusesDnd.ts | 291 ------- src/app/deals/hooks/useGetNewRank.ts | 104 --- .../DndFunnel/DndFunnel.module.css | 15 + .../dnd-pragmatic/DndFunnel/DndFunnel.tsx | 39 + .../DndFunnel/components/CardWrapper.tsx | 52 ++ .../DndFunnel/components/DndBoard.tsx | 42 + .../DndFunnel/components/DndCard.tsx | 146 ++++ .../DndFunnel/components/DndColumn.tsx | 306 +++++++ .../DndFunnel/components/DndGroup.tsx | 119 +++ .../components/SafariDndColumnPreview.tsx | 36 + .../DndFunnel/contexts/DndBoardContext.tsx | 206 +++++ .../DndFunnel/hooks/useFunnelActions.ts | 93 ++ .../DndFunnel/hooks/useGetNewRankForFunnel.ts | 118 +++ .../DndFunnel/hooks/useResolveDrop.ts | 191 +++++ .../dnd-pragmatic/DndFunnel/types/Base.ts | 12 + .../DndFunnel/types/DndStates.ts | 19 + .../DndFunnel/types/FunnelDndProps.ts | 23 + .../DndFunnel/utils/registry.tsx | 52 ++ .../DragHandle.tsx | 0 src/components/dnd/DragHandle/index.ts | 3 + src/components/dnd/FunnelDnd/FunnelDnd.tsx | 141 +--- .../SortableCombinableItem.tsx | 77 ++ .../dnd/SortableCombinableItem/index.ts | 3 + .../dnd/SortableItem/SortableItem.tsx | 13 +- src/hooks/cruds/useDealGroupCrud.tsx | 61 ++ src/lib/client/@tanstack/react-query.gen.ts | 169 ++++ src/lib/client/sdk.gen.ts | 128 +++ src/lib/client/types.gen.ts | 219 +++++ src/lib/client/zod.gen.ts | 120 +++ src/types/GroupWithDealsSchema.ts | 7 + yarn.lock | 798 +++++++++++++++++- 41 files changed, 3337 insertions(+), 629 deletions(-) delete mode 100644 src/app/deals/components/shared/DealContainer/DealContainer.tsx create mode 100644 src/app/deals/components/shared/DealGroupCard/DealGroupCard.tsx create mode 100644 src/app/deals/components/shared/DealGroupCard/components/FulfillmentGroupInfo.tsx create mode 100644 src/app/deals/hooks/useDealsAndGroups.ts delete mode 100644 src/app/deals/hooks/useDealsAndStatusesDnd.ts delete mode 100644 src/app/deals/hooks/useGetNewRank.ts create mode 100644 src/components/dnd-pragmatic/DndFunnel/DndFunnel.module.css create mode 100644 src/components/dnd-pragmatic/DndFunnel/DndFunnel.tsx create mode 100644 src/components/dnd-pragmatic/DndFunnel/components/CardWrapper.tsx create mode 100644 src/components/dnd-pragmatic/DndFunnel/components/DndBoard.tsx create mode 100644 src/components/dnd-pragmatic/DndFunnel/components/DndCard.tsx create mode 100644 src/components/dnd-pragmatic/DndFunnel/components/DndColumn.tsx create mode 100644 src/components/dnd-pragmatic/DndFunnel/components/DndGroup.tsx create mode 100644 src/components/dnd-pragmatic/DndFunnel/components/SafariDndColumnPreview.tsx create mode 100644 src/components/dnd-pragmatic/DndFunnel/contexts/DndBoardContext.tsx create mode 100644 src/components/dnd-pragmatic/DndFunnel/hooks/useFunnelActions.ts create mode 100644 src/components/dnd-pragmatic/DndFunnel/hooks/useGetNewRankForFunnel.ts create mode 100644 src/components/dnd-pragmatic/DndFunnel/hooks/useResolveDrop.ts create mode 100644 src/components/dnd-pragmatic/DndFunnel/types/Base.ts create mode 100644 src/components/dnd-pragmatic/DndFunnel/types/DndStates.ts create mode 100644 src/components/dnd-pragmatic/DndFunnel/types/FunnelDndProps.ts create mode 100644 src/components/dnd-pragmatic/DndFunnel/utils/registry.tsx rename src/components/dnd/{SortableItem => DragHandle}/DragHandle.tsx (100%) create mode 100644 src/components/dnd/DragHandle/index.ts create mode 100644 src/components/dnd/SortableCombinableItem/SortableCombinableItem.tsx create mode 100644 src/components/dnd/SortableCombinableItem/index.ts create mode 100644 src/hooks/cruds/useDealGroupCrud.tsx create mode 100644 src/types/GroupWithDealsSchema.ts diff --git a/package.json b/package.json index 0ccee7e..6592bec 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,12 @@ "generate-modules": "sudo npx tsc ./src/modules/modulesFileGen/modulesFileGen.ts && mv -f ./src/modules/modulesFileGen/modulesFileGen.js ./src/modules/modulesFileGen/modulesFileGen.cjs && sudo node ./src/modules/modulesFileGen/modulesFileGen.cjs" }, "dependencies": { + "@atlaskit/avatar": "^25.4.2", + "@atlaskit/pragmatic-drag-and-drop": "^1.7.7", + "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.2", + "@atlaskit/pragmatic-drag-and-drop-flourish": "^2.0.7", + "@atlaskit/pragmatic-drag-and-drop-live-region": "^1.3.1", + "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.7", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", @@ -27,6 +33,7 @@ "@tabler/icons-react": "^3.34.0", "@tailwindcss/postcss": "^4.1.11", "@tanstack/react-query": "^5.83.0", + "@types/react-dom": "19.1.2", "axios": "1.12.0", "classnames": "^2.5.1", "clsx": "^2.1.1", diff --git a/src/app/deals/components/shared/DealCard/DealCard.module.css b/src/app/deals/components/shared/DealCard/DealCard.module.css index fef50ef..4b58d29 100644 --- a/src/app/deals/components/shared/DealCard/DealCard.module.css +++ b/src/app/deals/components/shared/DealCard/DealCard.module.css @@ -1,5 +1,6 @@ .container { + flex: 1; padding: 0; @mixin light { background-color: var(--color-light-white-blue); diff --git a/src/app/deals/components/shared/DealContainer/DealContainer.tsx b/src/app/deals/components/shared/DealContainer/DealContainer.tsx deleted file mode 100644 index 2df3ac7..0000000 --- a/src/app/deals/components/shared/DealContainer/DealContainer.tsx +++ /dev/null @@ -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 = ({ deal }) => { - const dealBody = useMemo(() => , [deal]); - - return ( - - dealBody} - /> - - ); -}; - -export default DealContainer; diff --git a/src/app/deals/components/shared/DealGroupCard/DealGroupCard.tsx b/src/app/deals/components/shared/DealGroupCard/DealGroupCard.tsx new file mode 100644 index 0000000..0a969ca --- /dev/null +++ b/src/app/deals/components/shared/DealGroupCard/DealGroupCard.tsx @@ -0,0 +1,89 @@ +import { FC, useEffect, useState } from "react"; +import { IconGripHorizontal } from "@tabler/icons-react"; +import { Flex, rem, TextInput, useMantineColorScheme } from "@mantine/core"; +import { useDebouncedValue } from "@mantine/hooks"; +import DealCard from "@/app/deals/components/shared/DealCard/DealCard"; +import FulfillmentGroupInfo from "@/app/deals/components/shared/DealGroupCard/components/FulfillmentGroupInfo"; +import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; +import { notifications } from "@/lib/notifications"; +import { ModuleNames } from "@/modules/modules"; +import GroupWithDealsSchema from "@/types/GroupWithDealsSchema"; + +type Props = { + group: GroupWithDealsSchema; +}; + +const DealGroupCard: FC = ({ group }) => { + const theme = useMantineColorScheme(); + const [name, setName] = useState(group.name ?? ""); + const [debouncedName] = useDebouncedValue(name, 200); + const { modulesSet } = useProjectsContext(); + const isServicesAndProductsIncluded = modulesSet.has( + ModuleNames.FULFILLMENT_BASE + ); + + const updateName = () => { + if (debouncedName === group.name) return; + CardGroupService.updateCardGroup({ + requestBody: { + data: { + ...group, + name: debouncedName, + }, + }, + }).then(response => { + if (response.ok) return; + setName(group.name || ""); + notifications.guess(response.ok, { message: response.message }); + }); + }; + + useEffect(() => { + updateName(); + }, [debouncedName]); + + return ( + + + setName(event.currentTarget.value)} + variant={"unstyled"} + /> + + + + {group.deals?.map(deal => ( + + ))} + + {isServicesAndProductsIncluded && ( + + )} + + ); +}; + +export default DealGroupCard; diff --git a/src/app/deals/components/shared/DealGroupCard/components/FulfillmentGroupInfo.tsx b/src/app/deals/components/shared/DealGroupCard/components/FulfillmentGroupInfo.tsx new file mode 100644 index 0000000..cf43130 --- /dev/null +++ b/src/app/deals/components/shared/DealGroupCard/components/FulfillmentGroupInfo.tsx @@ -0,0 +1,51 @@ +import { Flex, Text, useMantineColorScheme } from "@mantine/core"; +import { FC, useMemo } from "react"; +import { DealGroupSchema } from "@/lib/client"; + +type Props = { + group: DealGroupSchema; +} + +const FulfillmentGroupInfo: FC = ({ group }) => { + const theme = useMantineColorScheme(); + + const totalPrice = useMemo( + () => + group.deals?.reduce((acc, deal) => acc + (deal.totalPrice ?? 0), 0), + [group.deals] + ); + const totalProducts = useMemo( + () => + group.deals?.reduce( + (acc, deal) => acc + (deal.productsQuantity ?? 0), + 0 + ), + [group.deals] + ); + + return ( + + + Сумма: {totalPrice?.toLocaleString("ru-RU")} руб. + + + Всего товаров: {totalProducts?.toLocaleString("ru-RU")}{" "} + шт. + + + ) +} + +export default FulfillmentGroupInfo; diff --git a/src/app/deals/components/shared/Funnel/Funnel.tsx b/src/app/deals/components/shared/Funnel/Funnel.tsx index e98a919..197d7c5 100644 --- a/src/app/deals/components/shared/Funnel/Funnel.tsx +++ b/src/app/deals/components/shared/Funnel/Funnel.tsx @@ -1,90 +1,67 @@ "use client"; -import React, { FC, ReactNode } from "react"; +import React, { FC } from "react"; +import { Box } from "@mantine/core"; import DealCard from "@/app/deals/components/shared/DealCard/DealCard"; -import DealContainer from "@/app/deals/components/shared/DealContainer/DealContainer"; 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"; import { useDealsContext } from "@/app/deals/contexts/DealsContext"; -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 { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; +import DndFunnel from "@/components/dnd-pragmatic/DndFunnel/DndFunnel"; import { sortByLexorank } from "@/utils/lexorank/sort"; const Funnel: FC = () => { - const { selectedBoard } = useBoardsContext(); - const { deals } = useDealsContext(); - const isMobile = useIsMobile(); + const { statuses, setStatuses, statusesCrud } = useStatusesContext(); + const { dealsWithoutGroup, groupsWithDeals, deals, setDeals, dealsCrud } = + useDealsContext(); - const { - sortedStatuses, - handleDragStart, - handleDragOver, - handleDragEnd, - activeStatus, - activeDeal, - swiperRef, - } = useDealsAndStatusesDnd(); + const updateStatus = (statusId: number, lexorank: string) => { + setStatuses( + statuses.map(status => + status.id === statusId ? { ...status, lexorank } : status + ) + ); + + statusesCrud.onUpdate(statusId, { lexorank }); + }; + + const updateDeal = (dealId: number, lexorank: string, statusId: number) => { + const status = statuses.find(s => s.id === statusId); + if (!status) return; + setDeals( + deals.map(deal => + deal.id === dealId ? { ...deal, lexorank, status } : deal + ) + ); + dealsCrud.onUpdate(dealId, { lexorank, statusId }); + }; return ( - `${status.id}-status`} - getItemsByContainer={(status: StatusSchema, items: DealSchema[]) => - sortByLexorank( - items.filter(deal => deal.status.id === status.id) - ) + + sortByLexorank([ + ...dealsWithoutGroup.filter(d => d.status.id === statusId), + ...groupsWithDeals.filter( + g => + g.items.length > 0 && + g.items[0].status.id === statusId + ), + ]) } - renderContainer={( - status: StatusSchema, - funnelColumnComponent: ReactNode, - renderDraggable, - index - ) => ( - - {funnelColumnComponent} - + renderColumnHeader={status => ( + )} - renderContainerHeader={status => ( - - )} - renderItem={(deal: DealSchema) => ( - ( + )} - activeContainer={activeStatus} - activeItem={activeDeal} - renderItemOverlay={(deal: DealSchema) => } - renderContainerOverlay={(status: StatusSchema, children) => ( - ( - - )}> - {children} - - )} - disabledColumns={isMobile} - isCreatingContainerEnabled={!!selectedBoard} + renderGroup={group => {group.name}} /> ); }; diff --git a/src/app/deals/components/shared/PageBody/PageBody.tsx b/src/app/deals/components/shared/PageBody/PageBody.tsx index fa12af1..107960d 100644 --- a/src/app/deals/components/shared/PageBody/PageBody.tsx +++ b/src/app/deals/components/shared/PageBody/PageBody.tsx @@ -1,6 +1,6 @@ "use client"; -import { Box } from "@mantine/core"; +import { Flex } from "@mantine/core"; import TopToolPanel from "@/app/deals/components/desktop/TopToolPanel/TopToolPanel"; import { BoardView, @@ -46,7 +46,11 @@ const PageBody = () => { - {getViewContent()} + + {getViewContent()} + ); diff --git a/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx b/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx index 85123b9..9cce818 100644 --- a/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx +++ b/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx @@ -8,10 +8,9 @@ import { StatusSchema } from "@/lib/client"; type Props = { status: StatusSchema; - isDragging: boolean; }; -const StatusColumnHeader: FC = ({ status, isDragging }) => { +const StatusColumnHeader: FC = ({ status }) => { const { statusesCrud, refetchStatuses } = useStatusesContext(); const { selectedBoard } = useBoardsContext(); @@ -28,6 +27,7 @@ const StatusColumnHeader: FC = ({ status, isDragging }) => { p={"sm"} wrap={"nowrap"} mb={"xs"} + w={"100%"} style={{ borderBottom: `solid ${status.color} 3px`, }}> @@ -42,14 +42,7 @@ const StatusColumnHeader: FC = ({ status, isDragging }) => { }} getChildren={startEditing => ( <> - - {status.name} - + {status.name} void; + dealsWithoutGroup: DealSchema[]; + groupsWithDeals: GroupWithDealsSchema[]; refetchDeals: () => void; dealsCrud: DealsCrud; paginationInfo?: PaginationInfoSchema; @@ -48,8 +52,13 @@ const useDealsContextState = ({ statuses, }); + const { dealsWithoutGroup, groupsWithDeals } = + useDealsAndGroups(dealsListObjects); + return { ...dealsListObjects, + dealsWithoutGroup, + groupsWithDeals, dealsCrud, }; }; diff --git a/src/app/deals/hooks/useDealsAndGroups.ts b/src/app/deals/hooks/useDealsAndGroups.ts new file mode 100644 index 0000000..8dbc338 --- /dev/null +++ b/src/app/deals/hooks/useDealsAndGroups.ts @@ -0,0 +1,43 @@ +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(); + + 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: [] }); + } + } + + return sortByLexorank(groupsWithDealMap.values().toArray()); + }, [deals]); + + console.log(groupsWithDeals); + + return { + dealsWithoutGroup, + groupsWithDeals, + }; +}; + +export default useDealsAndGroups; diff --git a/src/app/deals/hooks/useDealsAndStatusesDnd.ts b/src/app/deals/hooks/useDealsAndStatusesDnd.ts deleted file mode 100644 index 332986b..0000000 --- a/src/app/deals/hooks/useDealsAndStatusesDnd.ts +++ /dev/null @@ -1,291 +0,0 @@ -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/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; -}; - -const useDealsAndStatusesDnd = (): ReturnType => { - const swiperRef = useRef(null); - const [activeDeal, setActiveDeal] = useState(null); - const [activeStatus, setActiveStatus] = useState(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.status.id); - }; - - const getStatusById = (statusId: number) => { - return statuses.find(status => status.id === 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 { overStatus, newLexorank } = getDropTarget( - over.id, - activeDealId, - activeStatusId - ); - if (!overStatus) return; - - debouncedSetDeals( - deals.map(deal => - deal.id === activeDealId - ? { - ...deal, - status: overStatus, - 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.status.id; - } - - 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 - ): { overStatus?: StatusSchema; newLexorank?: string } => { - if (typeof overId === "string") { - return { - overStatus: getStatusById(getStatusId(overId)), - newLexorank: undefined, - }; - } - - const overDealId = Number(overId); - const overStatus = getStatusByDealId(overDealId); - - if (!overStatus || (!isOnDragEnd && activeDealId === overDealId)) { - return { overStatus: undefined, newLexorank: undefined }; - } - - const statusDeals = sortByLexorank( - deals.filter(deal => deal.status.id === overStatus.id) - ); - const overDealIndex = statusDeals.findIndex( - deal => deal.id === overDealId - ); - - if (activeStatusId === overStatus.id) { - const newLexorank = getNewRankForSameStatus( - statusDeals, - overDealIndex, - activeDealId - ); - return { overStatus, newLexorank }; - } - - const newLexorank = getNewRankForAnotherStatus( - statusDeals, - overDealIndex - ); - 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)) { - 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.status.id === over.id); - if (!deal) return; - overStatusId = deal.status.id; - } - - 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 { overStatus, newLexorank } = getDropTarget( - over.id, - activeDealId, - activeStatusId, - true - ); - if (!overStatus) return; - - onDealDragEnd(activeDealId, overStatus.id, 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; diff --git a/src/app/deals/hooks/useGetNewRank.ts b/src/app/deals/hooks/useGetNewRank.ts deleted file mode 100644 index ff39414..0000000 --- a/src/app/deals/hooks/useGetNewRank.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { LexoRank } from "lexorank"; -import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; -import { DealSchema } from "@/lib/client"; -import { sortByLexorank } from "@/utils/lexorank/sort"; -import { getNewLexorank } from "@/utils/lexorank/generation"; - -type NewRankGetters = { - getNewRankForSameStatus: ( - statusDeals: DealSchema[], - overDealIndex: number, - activeDealId: number - ) => string; - getNewRankForAnotherStatus: ( - statusDeals: DealSchema[], - overDealIndex: number - ) => string; - getNewStatusRank: ( - activeStatusId: number, - overStatusId: number - ) => string | null; -}; - -const useGetNewRank = (): NewRankGetters => { - const { statuses } = useStatusesContext(); - - const getNewRankForSameStatus = ( - statusDeals: DealSchema[], - overDealIndex: number, - activeDealId: number - ): string => { - 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(statusDeals[leftIndex].lexorank) - : null; - const rightLexorank = - rightIndex < statusDeals.length - ? LexoRank.parse(statusDeals[rightIndex].lexorank) - : null; - - return getNewLexorank(leftLexorank, rightLexorank).toString(); - }; - - const getNewRankForAnotherStatus = ( - statusDeals: DealSchema[], - overDealIndex: number - ): string => { - const leftLexorank = - overDealIndex > 0 - ? LexoRank.parse(statusDeals[overDealIndex - 1].lexorank) - : null; - const rightLexorank = LexoRank.parse( - statusDeals[overDealIndex].lexorank - ); - - return getNewLexorank(leftLexorank, rightLexorank).toString(); - }; - - const getNewStatusRank = ( - activeStatusId: number, - overStatusId: number - ): string | null => { - const sortedStatusList = sortByLexorank(statuses); - const overIndex = sortedStatusList.findIndex( - s => s.id === overStatusId - ); - const activeIndex = sortedStatusList.findIndex( - s => s.id === activeStatusId - ); - - if (overIndex === -1 || activeIndex === -1) return null; - - const [leftIndex, rightIndex] = - overIndex < activeIndex - ? [overIndex - 1, overIndex] - : [overIndex, overIndex + 1]; - - const leftLexorank = - leftIndex >= 0 - ? LexoRank.parse(statuses[leftIndex].lexorank) - : null; - const rightLexorank = - rightIndex < statuses.length - ? LexoRank.parse(statuses[rightIndex].lexorank) - : null; - - return getNewLexorank(leftLexorank, rightLexorank).toString(); - }; - - return { - getNewRankForSameStatus, - getNewRankForAnotherStatus, - getNewStatusRank, - }; -}; - -export default useGetNewRank; diff --git a/src/components/dnd-pragmatic/DndFunnel/DndFunnel.module.css b/src/components/dnd-pragmatic/DndFunnel/DndFunnel.module.css new file mode 100644 index 0000000..86f8ba0 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/DndFunnel.module.css @@ -0,0 +1,15 @@ +.visible-column { + border-radius: var(--mantine-spacing-lg); + gap: 0; + + @media (max-width: 48em) { + max-height: 100%; + } + + @mixin light { + background-color: var(--color-light-aqua); + } + @mixin dark { + background-color: var(--mantine-color-dark-6); + } +} diff --git a/src/components/dnd-pragmatic/DndFunnel/DndFunnel.tsx b/src/components/dnd-pragmatic/DndFunnel/DndFunnel.tsx new file mode 100644 index 0000000..6ecf421 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/DndFunnel.tsx @@ -0,0 +1,39 @@ +"use client"; + +import React from "react"; +import DndBoard from "@/components/dnd-pragmatic/DndFunnel/components/DndBoard"; +import { DndColumn } from "@/components/dnd-pragmatic/DndFunnel/components/DndColumn"; +import { DndFunnelContextProvider } from "@/components/dnd-pragmatic/DndFunnel/contexts/DndBoardContext"; +import { + BaseColumnType, + BaseGroupType, + BaseItemType, +} from "@/components/dnd-pragmatic/DndFunnel/types/Base"; +import FunnelDndProps from "@/components/dnd-pragmatic/DndFunnel/types/FunnelDndProps"; + +const DndFunnel = < + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +>( + props: FunnelDndProps +) => { + return ( + + + {props.columns.map(column => ( + + ))} + + + ); +}; + +export default DndFunnel; diff --git a/src/components/dnd-pragmatic/DndFunnel/components/CardWrapper.tsx b/src/components/dnd-pragmatic/DndFunnel/components/CardWrapper.tsx new file mode 100644 index 0000000..e931ae9 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/components/CardWrapper.tsx @@ -0,0 +1,52 @@ +import React, { ForwardedRef, forwardRef, ReactNode, type Ref } from "react"; +import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import { DropIndicator } from "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box"; +import { Flex, xcss } from "@atlaskit/primitives"; +import { BaseItemType } from "@/components/dnd-pragmatic/DndFunnel/types/Base"; +import { CardState } from "@/components/dnd-pragmatic/DndFunnel/types/DndStates"; + +const baseStyles = xcss({ + width: "100%", + position: "relative", +}); + +const stateStyles: { + [Key in CardState["type"]]: ReturnType | undefined; +} = { + idle: xcss({ + cursor: "grab", + }), + dragging: xcss({ + opacity: 0.4, + }), + // no shadow for preview - the platform will add it's own drop shadow + preview: undefined, +}; + +type CardPrimitiveProps = { + closestEdge: Edge | null; + item: ItemType; + renderItem: (item: any) => ReactNode; + state: CardState; + actionMenuTriggerRef?: Ref; +}; + +const CardWrapper = forwardRef( + ( + { closestEdge, item, renderItem, state }: CardPrimitiveProps, + ref: ForwardedRef + ) => { + return ( + + {renderItem(item)} + {closestEdge && } + + ); + } +); + +export default CardWrapper; diff --git a/src/components/dnd-pragmatic/DndFunnel/components/DndBoard.tsx b/src/components/dnd-pragmatic/DndFunnel/components/DndBoard.tsx new file mode 100644 index 0000000..7c51198 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/components/DndBoard.tsx @@ -0,0 +1,42 @@ +import React, { forwardRef, memo, useEffect, type ReactNode } from "react"; +import { autoScrollWindowForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; +import classNames from "classnames"; +import { Box } from "@mantine/core"; +import { useDndFunnelContext } from "../contexts/DndBoardContext"; + +type Props = { + children: ReactNode; +}; + +const boardStyles = classNames({ + display: "flex", + justifyContent: "center", + gap: "space.200", + flexDirection: "row", + flex: 1, + border: "1px white solid", +}); + +const DndBoard = memo( + forwardRef(({ children }: Props, ref) => { + const { instanceId } = useDndFunnelContext(); + + useEffect(() => { + return autoScrollWindowForElements({ + canScroll: ({ source }) => + source.data.instanceId === instanceId, + }); + }, [instanceId]); + + return ( + + {children} + + ); + }) +); + +export default DndBoard; diff --git a/src/components/dnd-pragmatic/DndFunnel/components/DndCard.tsx b/src/components/dnd-pragmatic/DndFunnel/components/DndCard.tsx new file mode 100644 index 0000000..542dc0f --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/components/DndCard.tsx @@ -0,0 +1,146 @@ +import React, { memo, ReactNode, useEffect, useRef, useState } from "react"; +import { + attachClosestEdge, + extractClosestEdge, + type Edge, +} from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; +import { + draggable, + dropTargetForElements, +} from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { preserveOffsetOnSource } from "@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source"; +import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; +import { dropTargetForExternal } from "@atlaskit/pragmatic-drag-and-drop/external/adapter"; +import { Box } from "@atlaskit/primitives"; +import ReactDOM from "react-dom"; +import invariant from "tiny-invariant"; +import CardWrapper from "@/components/dnd-pragmatic/DndFunnel/components/CardWrapper"; +import { useDndFunnelContext } from "@/components/dnd-pragmatic/DndFunnel/contexts/DndBoardContext"; +import { BaseItemType } from "@/components/dnd-pragmatic/DndFunnel/types/Base"; +import { CardState } from "../types/DndStates"; + +const idleState: CardState = { type: "idle" }; +const draggingState: CardState = { type: "dragging" }; + +type DndCardProps = { + item: ItemType; + renderItem: (item: any) => ReactNode; +}; + +export const DndCard = memo( + ({ + item, + renderItem, + }: DndCardProps) => { + const ref = useRef(null); + const [closestEdge, setClosestEdge] = useState(null); + const [state, setState] = useState(idleState); + + const { instanceId } = useDndFunnelContext(); + + useEffect(() => { + const element = ref.current; + invariant(element); + + return combine( + draggable({ + element, + getInitialData: () => ({ + type: "card", + itemId: item.id, + instanceId, + }), + onGenerateDragPreview: ({ + location, + source, + nativeSetDragImage, + }) => { + const rect = source.element.getBoundingClientRect(); + + setCustomNativeDragPreview({ + nativeSetDragImage, + getOffset: preserveOffsetOnSource({ + element, + input: location.current.input, + }), + render({ container }) { + setState({ type: "preview", container, rect }); + return () => setState(draggingState); + }, + }); + }, + + onDragStart: () => setState(draggingState), + onDrop: () => setState(idleState), + }), + dropTargetForExternal({ + element, + }), + dropTargetForElements({ + element, + canDrop: ({ source }) => { + return ( + source.data.instanceId === instanceId && + source.data.type === "card" + ); + }, + getIsSticky: () => true, + getData: ({ input, element }) => { + const data = { type: "card", itemId: item.id }; + + return attachClosestEdge(data, { + input, + element, + allowedEdges: ["top", "bottom"], + }); + }, + onDragEnter: args => { + if (args.source.data.itemId !== item.id) { + setClosestEdge(extractClosestEdge(args.self.data)); + } + }, + onDrag: args => { + if (args.source.data.itemId !== item.id) { + setClosestEdge(extractClosestEdge(args.self.data)); + } + }, + onDragLeave: () => { + setClosestEdge(null); + }, + onDrop: () => { + setClosestEdge(null); + }, + }) + ); + }, [instanceId, item.id]); + + return ( + <> + + {state.type === "preview" && + ReactDOM.createPortal( + + + , + state.container + )} + + ); + } +); diff --git a/src/components/dnd-pragmatic/DndFunnel/components/DndColumn.tsx b/src/components/dnd-pragmatic/DndFunnel/components/DndColumn.tsx new file mode 100644 index 0000000..a771e3f --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/components/DndColumn.tsx @@ -0,0 +1,306 @@ +import React, { memo, ReactNode, useEffect, useRef, useState } from "react"; +import { easeInOut } from "@atlaskit/motion/curves"; +import { durations } from "@atlaskit/motion/durations"; +import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; +import { + attachClosestEdge, + extractClosestEdge, + type Edge, +} from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import { DropIndicator } from "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box"; +import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; +import { + draggable, + dropTargetForElements, +} from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { centerUnderPointer } from "@atlaskit/pragmatic-drag-and-drop/element/center-under-pointer"; +import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; +import { Box, Flex, Inline, Stack, xcss } from "@atlaskit/primitives"; +import { createPortal } from "react-dom"; +import invariant from "tiny-invariant"; +import { Stack as MantineStack, ScrollArea } from "@mantine/core"; +import DndGroup from "@/components/dnd-pragmatic/DndFunnel/components/DndGroup"; +import SafariDndColumnPreview from "@/components/dnd-pragmatic/DndFunnel/components/SafariDndColumnPreview"; +import { useDndFunnelContext } from "@/components/dnd-pragmatic/DndFunnel/contexts/DndBoardContext"; +import { + BaseColumnType, + BaseGroupType, + BaseItemType, +} from "@/components/dnd-pragmatic/DndFunnel/types/Base"; +import { ColumnState } from "@/components/dnd-pragmatic/DndFunnel/types/DndStates"; +import { DndCard } from "./DndCard"; +import styles from "../DndFunnel.module.css"; + +const columnStyles = xcss({ + width: "250px", + borderRadius: "radius.xlarge", + transition: `background ${durations.medium}ms ${easeInOut}`, + position: "relative", + // Replace height: "100%" with these: + alignSelf: "stretch", // fill parent's height + minHeight: "0", // allow it to shrink inside parent flex + border: "1px solid red", +}); + +const stackStyles = xcss({ + minHeight: "0", + border: "1px solid red", + flexGrow: 1, +}); + +const scrollContainerStyles = xcss({ + flex: 1, + overflowY: "auto", +}); + +const cardListStyles = xcss({ + boxSizing: "border-box", + minHeight: "100%", + padding: "space.100", + gap: "space.100", +}); + +const columnHeaderStyles = xcss({ + paddingInlineStart: "space.200", + paddingInlineEnd: "space.200", + paddingBlockStart: "space.100", + color: "color.text.subtlest", + userSelect: "none", +}); + +// preventing re-renders with stable state objects +const idle: ColumnState = { type: "idle" }; + +const stateStyles: { + [key in ColumnState["type"]]: ReturnType | undefined; +} = { + "idle": xcss({ + cursor: "grab", + }), + "is-column-over": undefined, + "generate-column-preview": xcss({ + isolation: "isolate", + }), + "generate-safari-column-preview": undefined, +}; + +const isDraggingStyles = xcss({ + opacity: 0.4, +}); + +type Props< + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +> = { + column: ColumnType; + columnItemsAndGroups: (ItemType | GroupType)[]; + renderColumnHeader: (column: any) => ReactNode; + renderItem: (item: any) => ReactNode; + renderGroup: (group: any) => ReactNode; +}; + +export const DndColumn = memo( + < + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, + >({ + column, + columnItemsAndGroups, + renderColumnHeader, + renderItem, + renderGroup, + }: Props) => { + const columnId = column.id; + const columnRef = useRef(null); + const columnInnerRef = useRef(null); + const headerRef = useRef(null); + const scrollableRef = useRef(null); + const [state, setState] = useState(idle); + const [isDragging, setIsDragging] = useState(false); + + const { instanceId } = useDndFunnelContext(); + + useEffect(() => { + invariant(columnRef.current); + invariant(columnInnerRef.current); + invariant(headerRef.current); + invariant(scrollableRef.current); + + return combine( + draggable({ + element: columnRef.current, + dragHandle: headerRef.current, + getInitialData: () => ({ + columnId, + type: "column", + instanceId, + }), + onGenerateDragPreview: ({ nativeSetDragImage }) => { + const isSafari: boolean = + navigator.userAgent.includes("AppleWebKit") && + !navigator.userAgent.includes("Chrome"); + + if (!isSafari) { + setState({ type: "generate-column-preview" }); + return; + } + setCustomNativeDragPreview({ + getOffset: centerUnderPointer, + render: ({ container }) => { + setState({ + type: "generate-safari-column-preview", + container, + }); + return () => setState(idle); + }, + nativeSetDragImage, + }); + }, + onDragStart: () => { + setIsDragging(true); + }, + onDrop() { + setState(idle); + setIsDragging(false); + }, + }), + dropTargetForElements({ + element: columnInnerRef.current, + getData: () => ({ columnId }), + canDrop: ({ source }) => { + return ( + source.data.instanceId === instanceId && + source.data.type === "card" + ); + }, + getIsSticky: () => true, + onDragLeave: () => setState(idle), + onDrop: () => setState(idle), + }), + dropTargetForElements({ + element: columnRef.current, + canDrop: ({ source }) => { + return ( + source.data.instanceId === instanceId && + source.data.type === "column" + ); + }, + getIsSticky: () => true, + getData: ({ input, element }) => { + const data = { + columnId, + }; + return attachClosestEdge(data, { + input, + element, + allowedEdges: ["left", "right"], + }); + }, + onDragEnter: args => { + setState({ + type: "is-column-over", + closestEdge: extractClosestEdge(args.self.data), + }); + }, + onDrag: args => { + // skip react re-render if edge is not changing + setState(current => { + const closestEdge: Edge | null = extractClosestEdge( + args.self.data + ); + if ( + current.type === "is-column-over" && + current.closestEdge === closestEdge + ) { + return current; + } + return { + type: "is-column-over", + closestEdge, + }; + }); + }, + onDragLeave: () => setState(idle), + onDrop: () => setState(idle), + }), + autoScrollForElements({ + element: scrollableRef.current, + canScroll: ({ source }) => + source.data.instanceId === instanceId && + source.data.type === "card", + }) + ); + }, [columnId, instanceId]); + + const renderItemsAndGroups = () => + columnItemsAndGroups.map(groupOrItem => + "items" in groupOrItem ? ( + + ) : ( + + ) + ); + + return ( + <> + + + + + + + {renderColumnHeader(column)} + + + + {renderItemsAndGroups()} + + + + + + + {state.type === "is-column-over" && state.closestEdge && ( + + )} + + {state.type === "generate-safari-column-preview" + ? createPortal( + , + state.container + ) + : null} + + ); + } +); diff --git a/src/components/dnd-pragmatic/DndFunnel/components/DndGroup.tsx b/src/components/dnd-pragmatic/DndFunnel/components/DndGroup.tsx new file mode 100644 index 0000000..7603e59 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/components/DndGroup.tsx @@ -0,0 +1,119 @@ +import React, { memo, ReactNode, useEffect, useRef, useState } from "react"; +import FocusRing from "@atlaskit/focus-ring"; +import { attachClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import { DropIndicator } from "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box"; +import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; +import { + draggable, + dropTargetForElements, +} from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { centerUnderPointer } from "@atlaskit/pragmatic-drag-and-drop/element/center-under-pointer"; +import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; +import invariant from "tiny-invariant"; +import { useDndFunnelContext } from "@/components/dnd-pragmatic/DndFunnel/contexts/DndBoardContext"; +import { + BaseGroupType, + BaseItemType, +} from "@/components/dnd-pragmatic/DndFunnel/types/Base"; +import { GroupState } from "@/components/dnd-pragmatic/DndFunnel/types/DndStates"; + +const idleState: GroupState = { type: "idle" }; +const draggingState: GroupState = { type: "dragging" }; +const isCardOver: GroupState = { type: "is-card-over" }; +const isGroupOver: GroupState = { type: "is-group-over" }; + +type Props< + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +> = { + group: GroupType; + renderGroup: (group: any) => ReactNode; +}; + +const DndGroup = memo( + >({ + group, + renderGroup, + }: Props) => { + const ref = useRef(null); + const { instanceId } = useDndFunnelContext(); + + const [state, setState] = useState(idleState); + + useEffect(() => { + const element = ref.current; + invariant(element); + + return combine( + draggable({ + element, + getInitialData: () => ({ + groupId: group.id, + type: "group", + instanceId, + }), + onGenerateDragPreview: ({ source, nativeSetDragImage }) => { + const rect = source.element.getBoundingClientRect(); + + setCustomNativeDragPreview({ + nativeSetDragImage, + getOffset: centerUnderPointer, + render({ container }) { + setState({ type: "preview", container, rect }); + return () => setState(draggingState); + }, + }); + }, + onDragStart: () => setState(draggingState), + onDrop: () => setState(idleState), + }), + dropTargetForElements({ + element, + getData: ({ input, element }) => { + const data = { + groupId, + }; + return attachClosestEdge(data, { + input, + element, + allowedEdges: ["top", "bottom"], + }); + }, + canDrop: ({ source }) => + source.data.instanceId === instanceId && + source.data.type === "group" && + source.data.id !== group.id, + onDragEnter: ({ element }) => + setState( + element.type === "card" ? isCardOver : isGroupOver + ), + onDragLeave: () => setState(idleState), + onDragStart: () => setState(isCardOver), + }) + ); + }, [group]); + + return ( +
+ +
+ {renderGroup(group)} + {(state.type === "is-card-over" || + state.type === "is-group-over") && + state.closestEdge && ( + + )} +
+
+
+ ); + } +); + +export default DndGroup; diff --git a/src/components/dnd-pragmatic/DndFunnel/components/SafariDndColumnPreview.tsx b/src/components/dnd-pragmatic/DndFunnel/components/SafariDndColumnPreview.tsx new file mode 100644 index 0000000..b1cc530 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/components/SafariDndColumnPreview.tsx @@ -0,0 +1,36 @@ +import React, { ReactNode } from "react"; +import { Box, xcss } from "@atlaskit/primitives"; +import { BaseColumnType } from "@/components/dnd-pragmatic/DndFunnel/types/Base"; + +const safariPreviewStyles = xcss({ + width: "250px", + backgroundColor: "elevation.surface.sunken", + borderRadius: "radius.small", + padding: "space.200", +}); + +const columnHeaderStyles = xcss({ + paddingInlineStart: "space.200", + paddingInlineEnd: "space.200", + paddingBlockStart: "space.100", + color: "color.text.subtlest", + userSelect: "none", +}); + +type Props = { + column: ColumnType; + renderColumnHeader: (column: ColumnType) => ReactNode; +}; + +const SafariDndColumnPreview = ({ + column, + renderColumnHeader, +}: Props) => { + return ( + + {renderColumnHeader(column)} + + ); +}; + +export default SafariDndColumnPreview; diff --git a/src/components/dnd-pragmatic/DndFunnel/contexts/DndBoardContext.tsx b/src/components/dnd-pragmatic/DndFunnel/contexts/DndBoardContext.tsx new file mode 100644 index 0000000..ac99f84 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/contexts/DndBoardContext.tsx @@ -0,0 +1,206 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import * as liveRegion from "@atlaskit/pragmatic-drag-and-drop-live-region"; +import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; +import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { SwiperRef } from "swiper/swiper-react"; +import useResolveDrop from "@/components/dnd-pragmatic/DndFunnel/hooks/useResolveDrop"; +import { + BaseColumnType, + BaseGroupType, + BaseItemType, +} from "@/components/dnd-pragmatic/DndFunnel/types/Base"; +import FunnelDndProps from "@/components/dnd-pragmatic/DndFunnel/types/FunnelDndProps"; +import makeContext from "@/lib/contextFactory/contextFactory"; +import { sortByLexorank } from "@/utils/lexorank/sort"; + +export type DndFunnelContextState< + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +> = { + instanceId: symbol; + mixedItemsAndGroups: Array; +}; + +const useFunnelContextState = < + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +>({ + columns, + updateColumn, + items, + updateItem, + getColumnItemsGroups, + groups, +}: FunnelDndProps): DndFunnelContextState< + ItemType, + GroupType +> => { + const swiperRef = useRef(null); + const sortedColumns = useMemo(() => sortByLexorank(columns), [columns]); + + // const getColumnByItemId = (itemId: number) => { + // const item = items.find(item => item.id === itemId); + // if (!item) return; + // return columnes.find(column => column.id === item.column.id); + // }; + // + // const swipeSliderDuringDrag = () => { + // const activeColumn = getColumnByItemId(activeId); + // const swiperActiveColumn = + // columnes[swiperRef.current?.swiper.activeIndex ?? 0]; + // if (swiperActiveColumn.id !== activeColumn?.id) return; + // + // const activeColumnLexorank = activeColumn?.lexorank; + // let overColumnLexorank: string | undefined; + // + // if (typeof over.id === "string" && isColumnId(over.id)) { + // const overColumnId = getColumnId(over.id); + // overColumnLexorank = columnes.find( + // s => s.id === overColumnId + // )?.lexorank; + // } else { + // overColumnLexorank = getColumnByItemId(Number(over.id))?.lexorank; + // } + // + // if ( + // !activeColumnLexorank || + // !overColumnLexorank || + // !swiperRef.current?.swiper + // ) + // return; + // + // const activeIndex = sortedColumns.findIndex( + // s => s.lexorank === activeColumnLexorank + // ); + // const overIndex = sortedColumns.findIndex( + // s => s.lexorank === overColumnLexorank + // ); + // + // if (activeIndex > overIndex) { + // swiperRef.current.swiper.slidePrev(); + // return; + // } + // if (activeIndex < overIndex) { + // swiperRef.current.swiper.slideNext(); + // } + // }; + + // const lastOperation = useRef(null); + // + // useEffect(() => { + // if (lastOperation.current === null) return; + // const { outcome, trigger } = lastOperation.current; + // + // if (outcome.type === "column-reorder") { + // const { startIndex, finishIndex } = outcome; + // + // const sourceColumn = sortedColumns[finishIndex]; + // + // const entry = registry.getColumn(sourceColumn.id); + // triggerPostMoveFlash(entry.element); + // + // console.log( + // `You've moved ${sourceColumn.name} from position ${ + // startIndex + 1 + // } to position ${finishIndex + 1} of ${sortedColumns.length}.` + // ); + // + // return; + // } + // + // if (outcome.type === "card-reorder") { + // const { columnId, startIndex, finishIndex } = outcome; + // + // const column = sortedColumns.find(s => s.id === columnId); + // if (!column) return; + // const columnItems = items.filter(d => d.column.id === columnId); + // const item = columnItems[finishIndex]; + // + // const entry = registry.getCard(item.id); + // triggerPostMoveFlash(entry.element); + // + // if (trigger !== "keyboard") return; + // + // console.log( + // `You've moved ${item.name} from position ${ + // startIndex + 1 + // } to position ${finishIndex + 1} of ${columnItems.length} in the ${column.name} column.` + // ); + // + // return; + // } + // + // if (outcome.type === "card-move") { + // const { + // finishColumnId, + // itemIndexInStartColumn, + // itemIndexInFinishColumn, + // } = outcome; + // + // const destColumn = sortedColumns.find( + // s => s.id === finishColumnId + // ); + // if (!destColumn) return; + // const columnItems = items.filter( + // d => d.column.id === destColumn.id + // ); + // + // const item = columnItems[itemIndexInFinishColumn]; + // + // const finishPosition = + // typeof itemIndexInFinishColumn === "number" + // ? itemIndexInFinishColumn + 1 + // : columnItems.length; + // + // const entry = registry.getCard(item.id); + // triggerPostMoveFlash(entry.element); + // + // if (trigger !== "keyboard") return; + // + // console.log( + // `You've moved ${item.name} from position ${ + // itemIndexInStartColumn + 1 + // } to position ${finishPosition} in the ${destColumn.name} column.` + // ); + // } + // }, [lastOperation, registry]); + + useEffect(() => { + return liveRegion.cleanup(); + }, []); + + const [instanceId] = useState(() => Symbol("instance-id")); + + const { onDrop } = useResolveDrop({ + sortedColumns, + updateColumn, + updateItem, + getColumnItemsGroups, + }); + + const mixedItemsAndGroups: Array = sortByLexorank([ + ...items, + ...groups, + ]); + + useEffect(() => { + return combine( + monitorForElements({ + canMonitor: ({ source }) => + source.data.instanceId === instanceId, + onDrop, + }) + ); + }, [items, sortedColumns, instanceId]); + + return { + instanceId, + mixedItemsAndGroups, + }; +}; + +export const [DndFunnelContextProvider, useDndFunnelContext] = makeContext< + DndFunnelContextState, + FunnelDndProps +>(useFunnelContextState, "DndFunnel"); diff --git a/src/components/dnd-pragmatic/DndFunnel/hooks/useFunnelActions.ts b/src/components/dnd-pragmatic/DndFunnel/hooks/useFunnelActions.ts new file mode 100644 index 0000000..e88653d --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/hooks/useFunnelActions.ts @@ -0,0 +1,93 @@ +import useGetNewRankForFunnel from "@/components/dnd-pragmatic/DndFunnel/hooks/useGetNewRankForFunnel"; +import { + BaseColumnType, + BaseGroupType, + BaseItemType, +} from "@/components/dnd-pragmatic/DndFunnel/types/Base"; + +type Props< + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +> = { + sortedColumns: ColumnType[]; + updateColumn: (id: number, lexorank: string) => void; + updateItem: (id: number, lexorank: string, columnId: number) => void; + getColumnItemsGroups: (columnId: number) => (ItemType | GroupType)[]; +}; + +const useFunnelActions = < + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +>({ + sortedColumns, + updateColumn, + updateItem, + getColumnItemsGroups, +}: Props) => { + const { + getNewRankForSameColumn, + getNewRankForAnotherColumn, + getNewColumnRank, + } = useGetNewRankForFunnel(); + + const reorderItem = ( + columnId: number, + startIndex: number, + finishIndex: number, + columnItems: ItemType[] + ) => { + const startItemId = columnItems[startIndex].id; + + const newLexorank = getNewRankForSameColumn( + columnItems, + finishIndex, + startItemId + ); + + updateItem(startItemId, newLexorank, columnId); + }; + + const moveItem = ( + startColumnId: number, + itemIndexInStartColumn: number, + finishColumnId: number, + finishItemIndex?: number + ) => { + const startColumnItems = getColumnItemsGroups(startColumnId); + const startItemId = startColumnItems[itemIndexInStartColumn].id; + + const finishColumnItems = getColumnItemsGroups(finishColumnId); + + const newLexorank = getNewRankForAnotherColumn( + finishColumnItems, + finishItemIndex + ); + + updateItem(startItemId, newLexorank, finishColumnId); + }; + + const reorderColumn = (startIndex: number, finishIndex: number) => { + const startColumnId = sortedColumns[startIndex].id; + const finishColumnId = sortedColumns[finishIndex].id; + + if (startColumnId === finishColumnId) return; + const newRank = getNewColumnRank( + sortedColumns, + startColumnId, + finishColumnId + ); + if (!newRank) return; + + updateColumn(startColumnId, newRank); + }; + + return { + moveItem, + reorderColumn, + reorderItem, + }; +}; + +export default useFunnelActions; diff --git a/src/components/dnd-pragmatic/DndFunnel/hooks/useGetNewRankForFunnel.ts b/src/components/dnd-pragmatic/DndFunnel/hooks/useGetNewRankForFunnel.ts new file mode 100644 index 0000000..e6f6c95 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/hooks/useGetNewRankForFunnel.ts @@ -0,0 +1,118 @@ +import { LexoRank } from "lexorank"; +import { BaseColumnType, BaseItemType } from "@/components/dnd-pragmatic/types/base"; +import { getNewLexorank } from "@/utils/lexorank/generation"; +import { sortByLexorank } from "@/utils/lexorank/sort"; +import { BaseGroupType } from "@/components/dnd-pragmatic/DndFunnel/types/Base"; + +interface NewRankGetters< + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +> { + getNewRankForSameColumn: ( + columnItems: ItemType[], + overItemIndex: number, + activeItemId: number + ) => string; + getNewRankForAnotherColumn: ( + columnItems: ItemType[], + overItemIndex?: number + ) => string; + getNewColumnRank: ( + columns: ColumnType[], + activeColumnId: number, + overColumnId: number + ) => string | null; +} + +const useGetNewRankForFunnel = < + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +>(): NewRankGetters => { + const getNewRankForSameColumn = ( + columnItems: ItemType[], + overItemIndex: number, + activeItemId: number + ): string => { + const activeItemIndex = columnItems.findIndex( + item => item.id === activeItemId + ); + const [leftIndex, rightIndex] = + overItemIndex < activeItemIndex + ? [overItemIndex - 1, overItemIndex] + : [overItemIndex, overItemIndex + 1]; + + const leftLexorank = + leftIndex >= 0 + ? LexoRank.parse(columnItems[leftIndex].lexorank) + : null; + const rightLexorank = + rightIndex < columnItems.length + ? LexoRank.parse(columnItems[rightIndex].lexorank) + : null; + + return getNewLexorank(leftLexorank, rightLexorank).toString(); + }; + + const getNewRankForAnotherColumn = ( + columnItems: ItemType[], + overItemIndex?: number + ): string => { + if (columnItems.length === 0) return LexoRank.middle().toString(); + + if (!overItemIndex || overItemIndex >= columnItems.length) { + return getNewLexorank( + LexoRank.parse(columnItems[columnItems.length - 1].lexorank) + ).toString(); + } + + const leftLexorank = + overItemIndex > 0 + ? LexoRank.parse(columnItems[overItemIndex - 1].lexorank) + : null; + const rightLexorank = LexoRank.parse( + columnItems[overItemIndex].lexorank + ); + + return getNewLexorank(leftLexorank, rightLexorank).toString(); + }; + + const getNewColumnRank = ( + columns: ColumnType[], + activeColumnId: number, + overColumnId: number + ): string | null => { + const sortedColumnsList = sortByLexorank(columns); + const overIndex = sortedColumnsList.findIndex( + s => s.id === overColumnId + ); + const activeIndex = sortedColumnsList.findIndex( + s => s.id === activeColumnId + ); + + if (overIndex === -1 || activeIndex === -1) return null; + + const [leftIndex, rightIndex] = + overIndex < activeIndex + ? [overIndex - 1, overIndex] + : [overIndex, overIndex + 1]; + + const leftLexorank = + leftIndex >= 0 ? LexoRank.parse(columns[leftIndex].lexorank) : null; + const rightLexorank = + rightIndex < columns.length + ? LexoRank.parse(columns[rightIndex].lexorank) + : null; + + return getNewLexorank(leftLexorank, rightLexorank).toString(); + }; + + return { + getNewRankForSameColumn, + getNewRankForAnotherColumn, + getNewColumnRank, + }; +}; + +export default useGetNewRankForFunnel; diff --git a/src/components/dnd-pragmatic/DndFunnel/hooks/useResolveDrop.ts b/src/components/dnd-pragmatic/DndFunnel/hooks/useResolveDrop.ts new file mode 100644 index 0000000..aea6da5 --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/hooks/useResolveDrop.ts @@ -0,0 +1,191 @@ +import { extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/types"; +import { getReorderDestinationIndex } from "@atlaskit/pragmatic-drag-and-drop-hitbox/util/get-reorder-destination-index"; +import { + BaseEventPayload, + ElementDragType, +} from "@atlaskit/pragmatic-drag-and-drop/types"; +import useFunnelActions from "@/components/dnd-pragmatic/DndFunnel/hooks/useFunnelActions"; +import { + BaseColumnType, + BaseGroupType, + BaseItemType, +} from "@/components/dnd-pragmatic/DndFunnel/types/Base"; + +type Props< + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +> = { + sortedColumns: ColumnType[]; + getColumnItemsGroups: (columnId: number) => (ItemType | GroupType)[]; + updateColumn: (id: number, lexorank: string) => void; + updateItem: (id: number, lexorank: string, columnId: number) => void; +}; + +const useResolveDrop = < + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +>({ + sortedColumns, + getColumnItemsGroups, + ...props +}: Props) => { + const { moveItem, reorderColumn, reorderItem } = useFunnelActions< + ColumnType, + ItemType, + GroupType + >({ + sortedColumns, + getColumnItemsGroups, + ...props, + }); + + const onItemDrop = ({ + location, + source, + }: BaseEventPayload) => { + const itemId: number = source.data.itemId as number; + const [, startColumnRecord] = location.initial.dropTargets; + const sourceColumnId: number = startColumnRecord.data + .columnId as number; + const sourceColumn = sortedColumns.find(s => s.id === sourceColumnId); + if (!sourceColumn) return; + const sourceColumnItems = getColumnItemsGroups(sourceColumnId); + const startItemIndex = sourceColumnItems.findIndex( + d => d.id === itemId + ); + if (startItemIndex === -1) return; + + if (location.current.dropTargets.length === 1) { + const [destinationColumnRecord] = location.current.dropTargets; + const destinationId: number = destinationColumnRecord.data + .columnId as number; + const destinationColumn = sortedColumns.find( + s => s.id === destinationId + ); + if (!destinationColumn) return; + + // reordering in same column + if (sourceColumn === destinationColumn) { + const destinationIndex = getReorderDestinationIndex({ + startIndex: startItemIndex, + indexOfTarget: sourceColumnItems.length - 1, + closestEdgeOfTarget: null, + axis: "vertical", + }); + reorderItem( + sourceColumn.id, + startItemIndex, + destinationIndex, + sourceColumnItems + ); + return; + } + + // moving to a new column + moveItem(sourceColumn.id, startItemIndex, destinationId); + return; + } + + // dropping in a column (relative to a card) + if (location.current.dropTargets.length === 2) { + const [destinationCardRecord, destinationColumnRecord] = + location.current.dropTargets; + const destinationColumnId: number = destinationColumnRecord.data + .columnId as number; + const destinationColumn = sortedColumns.find( + s => s.id === destinationColumnId + ); + if (!destinationColumn) return; + const destColumnItems = getColumnItemsGroups(destinationColumnId); + + const indexOfTarget = destColumnItems.findIndex( + item => + item.id === destinationCardRecord.data.itemId && + !("items" in item) + ); + const closestEdgeOfTarget: Edge | null = extractClosestEdge( + destinationCardRecord.data + ); + + // case 1: ordering in the same column + if (sourceColumn === destinationColumn) { + const destinationIndex = getReorderDestinationIndex({ + startIndex: startItemIndex, + indexOfTarget, + closestEdgeOfTarget, + axis: "vertical", + }); + reorderItem( + sourceColumn.id, + startItemIndex, + destinationIndex, + destColumnItems + ); + return; + } + + // case 2: moving into a new column relative to a card + const destinationIndex = + closestEdgeOfTarget === "bottom" + ? indexOfTarget + 1 + : indexOfTarget; + + moveItem( + sourceColumn.id, + startItemIndex, + destinationColumn.id, + destinationIndex + ); + } + }; + + const onColumnDrop = ({ + location, + source, + }: BaseEventPayload) => { + const startIndex: number = sortedColumns.findIndex( + column => column.id === source.data.columnId + ); + + const target = location.current.dropTargets[0]; + const indexOfTarget: number = sortedColumns.findIndex( + column => column.id === target.data.columnId + ); + const closestEdgeOfTarget: Edge | null = extractClosestEdge( + target.data + ); + + const finishIndex = getReorderDestinationIndex({ + startIndex, + indexOfTarget, + closestEdgeOfTarget, + axis: "horizontal", + }); + + reorderColumn(startIndex, finishIndex); + }; + + const onDrop = ({ + location, + source, + }: BaseEventPayload) => { + if (!location.current.dropTargets.length) return; + + if (source.data.type === "column") { + onColumnDrop({ location, source }); + return; + } + if (source.data.type === "card") { + onItemDrop({ location, source }); + } + }; + + return { + onDrop, + }; +}; + +export default useResolveDrop; diff --git a/src/components/dnd-pragmatic/DndFunnel/types/Base.ts b/src/components/dnd-pragmatic/DndFunnel/types/Base.ts new file mode 100644 index 0000000..16a815e --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/types/Base.ts @@ -0,0 +1,12 @@ +type BaseFunnelType = { + id: number; + lexorank: string; +}; + +export type BaseColumnType = BaseFunnelType; + +export type BaseItemType = BaseFunnelType; + +export type BaseGroupType = BaseFunnelType & { + items: ItemType[]; +}; diff --git a/src/components/dnd-pragmatic/DndFunnel/types/DndStates.ts b/src/components/dnd-pragmatic/DndFunnel/types/DndStates.ts new file mode 100644 index 0000000..129b6ff --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/types/DndStates.ts @@ -0,0 +1,19 @@ +import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; + +export type CardState = + | { type: "idle" } + | { type: "preview"; container: HTMLElement; rect: DOMRect } + | { type: "dragging" }; + +export type GroupState = + | { type: "idle" } + | { type: "is-card-over"; closestEdge: Edge | null } + | { type: "is-group-over"; closestEdge: Edge | null } + | { type: "preview"; container: HTMLElement; rect: DOMRect } + | { type: "dragging" }; + +export type ColumnState = + | { type: "idle" } + | { type: "is-column-over"; closestEdge: Edge | null } + | { type: "generate-safari-column-preview"; container: HTMLElement } + | { type: "generate-column-preview" }; diff --git a/src/components/dnd-pragmatic/DndFunnel/types/FunnelDndProps.ts b/src/components/dnd-pragmatic/DndFunnel/types/FunnelDndProps.ts new file mode 100644 index 0000000..506b51e --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/types/FunnelDndProps.ts @@ -0,0 +1,23 @@ +import { ReactNode } from "react"; +import { + BaseColumnType, BaseGroupType, + BaseItemType, +} from "@/components/dnd-pragmatic/DndFunnel/types/Base"; + +type FunnelDndProps< + ColumnType extends BaseColumnType, + ItemType extends BaseItemType, + GroupType extends BaseGroupType, +> = { + columns: ColumnType[]; + updateColumn: (id: number, lexorank: string) => void; + items: ItemType[]; + updateItem: (id: number, lexorank: string, columnId: number) => void; + getColumnItemsGroups: (columnId: number) => (ItemType | GroupType)[]; + renderColumnHeader: (column: ColumnType) => ReactNode; + renderItem: (item: ItemType) => ReactNode; + groups: GroupType[]; + renderGroup: (group: GroupType) => ReactNode; +}; + +export default FunnelDndProps; diff --git a/src/components/dnd-pragmatic/DndFunnel/utils/registry.tsx b/src/components/dnd-pragmatic/DndFunnel/utils/registry.tsx new file mode 100644 index 0000000..f53174f --- /dev/null +++ b/src/components/dnd-pragmatic/DndFunnel/utils/registry.tsx @@ -0,0 +1,52 @@ +import type { CleanupFn } from "@atlaskit/pragmatic-drag-and-drop/types"; +import invariant from "tiny-invariant"; + +export type Entry = { + element: HTMLElement; +}; + +export type RegisterCardProps = { + cardId: number; + entry: Entry; +}; + +export type RegisterColumnProps = { + columnId: number; + entry: Entry; +}; + +// TODO delete + +export function createRegistry() { + const cards = new Map(); + const columns = new Map(); + + function registerCard({ cardId, entry }: RegisterCardProps): CleanupFn { + cards.set(cardId, entry); + return () => cards.delete(cardId); + } + + function registerColumn({ + columnId, + entry, + }: RegisterColumnProps): CleanupFn { + columns.set(columnId, entry); + return function cleanup() { + cards.delete(columnId); + }; + } + + function getCard(cardId: number): Entry { + const entry = cards.get(cardId); + invariant(entry); + return entry; + } + + function getColumn(columnId: number): Entry { + const entry = columns.get(columnId); + invariant(entry); + return entry; + } + + return { registerCard, registerColumn, getCard, getColumn }; +} diff --git a/src/components/dnd/SortableItem/DragHandle.tsx b/src/components/dnd/DragHandle/DragHandle.tsx similarity index 100% rename from src/components/dnd/SortableItem/DragHandle.tsx rename to src/components/dnd/DragHandle/DragHandle.tsx diff --git a/src/components/dnd/DragHandle/index.ts b/src/components/dnd/DragHandle/index.ts new file mode 100644 index 0000000..8204b06 --- /dev/null +++ b/src/components/dnd/DragHandle/index.ts @@ -0,0 +1,3 @@ +import DragHandle from './DragHandle'; + +export default DragHandle; diff --git a/src/components/dnd/FunnelDnd/FunnelDnd.tsx b/src/components/dnd/FunnelDnd/FunnelDnd.tsx index 6359f36..14f43ca 100644 --- a/src/components/dnd/FunnelDnd/FunnelDnd.tsx +++ b/src/components/dnd/FunnelDnd/FunnelDnd.tsx @@ -1,27 +1,16 @@ "use client"; -import React, { ReactNode, RefObject, useMemo } from "react"; -import { - DndContext, - DragEndEvent, - DragOverEvent, - DragStartEvent, -} from "@dnd-kit/core"; -import { - horizontalListSortingStrategy, - SortableContext, -} from "@dnd-kit/sortable"; +import React, { ReactNode, RefObject } from "react"; import { FreeMode, Pagination, Scrollbar } from "swiper/modules"; import { Swiper, SwiperRef, SwiperSlide } from "swiper/react"; import { Box } from "@mantine/core"; import CreateStatusButton from "@/app/deals/components/shared/CreateStatusButton/CreateStatusButton"; -import useDndSensors from "@/app/deals/hooks/useSensors"; import FunnelColumn from "@/components/dnd/FunnelDnd/FunnelColumn"; -import FunnelOverlay from "@/components/dnd/FunnelDnd/FunnelOverlay"; import { BaseDraggable } from "@/components/dnd/types/types"; import useIsMobile from "@/hooks/utils/useIsMobile"; import SortableItem from "../SortableItem"; import classes from "./FunnelDnd.module.css"; +import { DragEndEvent, DragOverEvent, DragStartEvent } from "@dnd-kit/core"; type Props = { containers: TContainer[]; @@ -73,9 +62,7 @@ const FunnelDnd = < isCreatingContainerEnabled = true, disabledColumns = false, }: Props) => { - const sensors = useDndSensors(); const isMobile = useIsMobile(); - const frequency = useMemo(() => (isMobile ? 1 : undefined), [isMobile]); const renderContainers = () => containers.map((container, index) => { @@ -107,92 +94,52 @@ const FunnelDnd = < ); }); - const renderBody = () => { - if (isMobile) { - return ( - - { - swiper.allowTouchMove = !activeItem; - }} - onTouchMove={swiper => { - swiper.allowTouchMove = !activeItem; - }} - className={classes["swiper-container"]} - spaceBetween={15} - style={{ paddingInline: "10vw" }} - modules={[Pagination]} - freeMode={{ enabled: false }} - pagination={{ enabled: true, clickable: true }}> - {renderContainers()} - {isCreatingContainerEnabled && ( - - - - )} - - - ); - } + if (isMobile) { return ( - - {renderContainers()} - {isCreatingContainerEnabled && ( - - - - )} - + + { + swiper.allowTouchMove = !activeItem; + }} + onTouchMove={swiper => { + swiper.allowTouchMove = !activeItem; + }} + className={classes["swiper-container"]} + spaceBetween={15} + style={{ paddingInline: "10vw" }} + modules={[Pagination]} + freeMode={{ enabled: false }} + pagination={{ enabled: true, clickable: true }}> + {renderContainers()} + {isCreatingContainerEnabled && ( + + + + )} + + ); - }; + } return ( - - - {renderBody()} - { - const containerItems = getItemsByContainer( - container, - items - ); - const containerId = getContainerId(container); - return renderContainerOverlay( - container, - - ); - }} - renderItem={renderItemOverlay} - /> - - + + {renderContainers()} + {isCreatingContainerEnabled && ( + + + + )} + ); }; diff --git a/src/components/dnd/SortableCombinableItem/SortableCombinableItem.tsx b/src/components/dnd/SortableCombinableItem/SortableCombinableItem.tsx new file mode 100644 index 0000000..3036c0c --- /dev/null +++ b/src/components/dnd/SortableCombinableItem/SortableCombinableItem.tsx @@ -0,0 +1,77 @@ +import React, { CSSProperties, ReactNode, useCallback, useMemo } from "react"; +import { useDndContext } from "@dnd-kit/core"; +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import DragHandle from "@/components/dnd/DragHandle"; +// import collisionsShouldBeCombined from "@/components/dnd/utils/collisionsShouldBeCombined"; + +type Props = { + id: number | string; + renderItem: (renderDraggable?: () => ReactNode) => ReactNode; + renderDraggable?: () => ReactNode; // if not passed - the whole item renders as draggable + disabled?: boolean; + dragHandleStyle?: CSSProperties; +}; + +const SortableCombinableItem = ({ + renderItem, + dragHandleStyle, + renderDraggable, + id, + disabled = false, +}: Props) => { + const { isDragging, setNodeRef, transform, transition, over, active } = + useSortable({ + id, + animateLayoutChanges: () => false, + }); + + const { collisions } = useDndContext(); + + // const isCurrentCombining = useMemo( + // () => collisionsShouldBeCombined({ collisions }) && over?.id === id, + // [over, collisions] + // ); + + const getBorder = useCallback(() => { + // if (isCurrentCombining) return "1px solid red"; + return ""; + }, [active, over, id]); + + const style = { + // transform: collisionsShouldBeCombined({ collisions }) + // ? "none" + // : CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.4 : 1, + border: getBorder(), + }; + + const renderDragHandle = () => ( + + {renderDraggable && renderDraggable()} + + ); + + return ( +
+ {renderDraggable ? ( + renderItem(renderDragHandle) + ) : ( + + {renderItem()} + + )} +
+ ); +}; + +export default SortableCombinableItem; diff --git a/src/components/dnd/SortableCombinableItem/index.ts b/src/components/dnd/SortableCombinableItem/index.ts new file mode 100644 index 0000000..629fe16 --- /dev/null +++ b/src/components/dnd/SortableCombinableItem/index.ts @@ -0,0 +1,3 @@ +import SortableCombinableItem from "./SortableCombinableItem"; + +export default SortableCombinableItem; diff --git a/src/components/dnd/SortableItem/SortableItem.tsx b/src/components/dnd/SortableItem/SortableItem.tsx index 0968976..a9ba2c2 100644 --- a/src/components/dnd/SortableItem/SortableItem.tsx +++ b/src/components/dnd/SortableItem/SortableItem.tsx @@ -1,7 +1,6 @@ import React, { CSSProperties, ReactNode } from "react"; import { useSortable } from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; -import DragHandle from "./DragHandle"; +import DragHandle from "@/components/dnd/DragHandle"; type Props = { id: number | string; @@ -23,12 +22,6 @@ const SortableItem = ({ animateLayoutChanges: () => false, }); - const style: CSSProperties = { - opacity: isDragging ? 0.4 : undefined, - transform: CSS.Translate.toString(transform), - transition, - }; - const renderDragHandle = () => ( +
{renderDraggable ? ( renderItem(renderDragHandle) ) : ( diff --git a/src/hooks/cruds/useDealGroupCrud.tsx b/src/hooks/cruds/useDealGroupCrud.tsx new file mode 100644 index 0000000..37bfc7f --- /dev/null +++ b/src/hooks/cruds/useDealGroupCrud.tsx @@ -0,0 +1,61 @@ +import { useMutation } from "@tanstack/react-query"; +import { UpdateDealGroupSchema } from "@/lib/client"; +import { + addDealMutation, + createDealGroupMutation, + removeDealMutation, + updateDealGroupMutation, +} from "@/lib/client/@tanstack/react-query.gen"; + +const useDealGroupCrud = () => { + const updateMutation = useMutation(updateDealGroupMutation()); + + const onUpdate = (entity: UpdateDealGroupSchema) => { + updateMutation.mutate({ + body: { + entity, + }, + }); + }; + + const createMutation = useMutation(createDealGroupMutation()); + + const onCreate = (draggingDealId: number, hoveredDealId: number) => { + createMutation.mutate({ + body: { + draggingDealId, + hoveredDealId, + }, + }); + }; + + const addDealToGroupMutation = useMutation(addDealMutation()); + + const onAddDeal = (dealId: number, groupId: number) => { + addDealToGroupMutation.mutate({ + body: { + dealId, + groupId, + }, + }); + }; + + const removeDealFromGroupMutation = useMutation(removeDealMutation()); + + const onRemoveDeal = (dealId: number) => { + removeDealFromGroupMutation.mutate({ + body: { + dealId, + }, + }); + }; + + return { + onUpdate, + onCreate, + onAddDeal, + onRemoveDeal, + }; +}; + +export default useDealGroupCrud; diff --git a/src/lib/client/@tanstack/react-query.gen.ts b/src/lib/client/@tanstack/react-query.gen.ts index 807996b..4d2939f 100644 --- a/src/lib/client/@tanstack/react-query.gen.ts +++ b/src/lib/client/@tanstack/react-query.gen.ts @@ -9,12 +9,14 @@ import { import type { AxiosError } from "axios"; import { client as _heyApiClient } from "../client.gen"; import { + addDeal, addKitToDeal, addKitToDealProduct, createBarcodeTemplate, createBoard, createClient, createDeal, + createDealGroup, createDealProduct, createDealProductService, createDealService, @@ -59,10 +61,12 @@ import { getServicesKits, getStatuses, getStatusHistory, + removeDeal, updateBarcodeTemplate, updateBoard, updateClient, updateDeal, + updateDealGroup, updateDealProduct, updateDealProductService, updateDealService, @@ -76,6 +80,9 @@ import { type Options, } from "../sdk.gen"; import type { + AddDealData, + AddDealError, + AddDealResponse, AddKitToDealData, AddKitToDealError, AddKitToDealProductData, @@ -93,6 +100,9 @@ import type { CreateClientResponse2, CreateDealData, CreateDealError, + CreateDealGroupData, + CreateDealGroupError, + CreateDealGroupResponse2, CreateDealProductData, CreateDealProductError, CreateDealProductResponse2, @@ -194,6 +204,9 @@ import type { GetServicesKitsData, GetStatusesData, GetStatusHistoryData, + RemoveDealData, + RemoveDealError, + RemoveDealResponse, UpdateBarcodeTemplateData, UpdateBarcodeTemplateError, UpdateBarcodeTemplateResponse2, @@ -205,6 +218,9 @@ import type { UpdateClientResponse2, UpdateDealData, UpdateDealError, + UpdateDealGroupData, + UpdateDealGroupError, + UpdateDealGroupResponse2, UpdateDealProductData, UpdateDealProductError, UpdateDealProductResponse2, @@ -605,6 +621,159 @@ export const updateDealMutation = ( return mutationOptions; }; +/** + * Update Group + */ +export const updateDealGroupMutation = ( + options?: Partial> +): UseMutationOptions< + UpdateDealGroupResponse2, + AxiosError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateDealGroupResponse2, + AxiosError, + Options + > = { + mutationFn: async localOptions => { + const { data } = await updateDealGroup({ + ...options, + ...localOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const createDealGroupQueryKey = ( + options: Options +) => createQueryKey("createDealGroup", options); + +/** + * Create Group + */ +export const createDealGroupOptions = ( + options: Options +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await createDealGroup({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: createDealGroupQueryKey(options), + }); +}; + +/** + * Create Group + */ +export const createDealGroupMutation = ( + options?: Partial> +): UseMutationOptions< + CreateDealGroupResponse2, + AxiosError, + Options +> => { + const mutationOptions: UseMutationOptions< + CreateDealGroupResponse2, + AxiosError, + Options + > = { + mutationFn: async localOptions => { + const { data } = await createDealGroup({ + ...options, + ...localOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +/** + * Remove Deal + */ +export const removeDealMutation = ( + options?: Partial> +): UseMutationOptions< + RemoveDealResponse, + AxiosError, + Options +> => { + const mutationOptions: UseMutationOptions< + RemoveDealResponse, + AxiosError, + Options + > = { + mutationFn: async localOptions => { + const { data } = await removeDeal({ + ...options, + ...localOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +export const addDealQueryKey = (options: Options) => + createQueryKey("addDeal", options); + +/** + * Add Deal + */ +export const addDealOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await addDeal({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: addDealQueryKey(options), + }); +}; + +/** + * Add Deal + */ +export const addDealMutation = ( + options?: Partial> +): UseMutationOptions< + AddDealResponse, + AxiosError, + Options +> => { + const mutationOptions: UseMutationOptions< + AddDealResponse, + AxiosError, + Options + > = { + mutationFn: async localOptions => { + const { data } = await addDeal({ + ...options, + ...localOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + export const getBuiltInModulesQueryKey = ( options?: Options ) => createQueryKey("getBuiltInModules", options); diff --git a/src/lib/client/sdk.gen.ts b/src/lib/client/sdk.gen.ts index cb11ef2..6812f71 100644 --- a/src/lib/client/sdk.gen.ts +++ b/src/lib/client/sdk.gen.ts @@ -3,6 +3,9 @@ import type { Client, Options as ClientOptions, TDataShape } from "./client"; import { client as _heyApiClient } from "./client.gen"; import type { + AddDealData, + AddDealErrors, + AddDealResponses, AddKitToDealData, AddKitToDealErrors, AddKitToDealProductData, @@ -20,6 +23,9 @@ import type { CreateClientResponses, CreateDealData, CreateDealErrors, + CreateDealGroupData, + CreateDealGroupErrors, + CreateDealGroupResponses, CreateDealProductData, CreateDealProductErrors, CreateDealProductResponses, @@ -144,6 +150,9 @@ import type { GetStatusHistoryData, GetStatusHistoryErrors, GetStatusHistoryResponses, + RemoveDealData, + RemoveDealErrors, + RemoveDealResponses, UpdateBarcodeTemplateData, UpdateBarcodeTemplateErrors, UpdateBarcodeTemplateResponses, @@ -155,6 +164,9 @@ import type { UpdateClientResponses, UpdateDealData, UpdateDealErrors, + UpdateDealGroupData, + UpdateDealGroupErrors, + UpdateDealGroupResponses, UpdateDealProductData, UpdateDealProductErrors, UpdateDealProductResponses, @@ -188,6 +200,8 @@ import type { UpdateStatusResponses, } from "./types.gen"; import { + zAddDealData, + zAddDealResponse, zAddKitToDealData, zAddKitToDealProductData, zAddKitToDealProductResponse, @@ -199,6 +213,8 @@ import { zCreateClientData, zCreateClientResponse2, zCreateDealData, + zCreateDealGroupData, + zCreateDealGroupResponse2, zCreateDealProductData, zCreateDealProductResponse2, zCreateDealProductServiceData, @@ -288,6 +304,8 @@ import { zGetStatusesResponse2, zGetStatusHistoryData, zGetStatusHistoryResponse2, + zRemoveDealData, + zRemoveDealResponse, zUpdateBarcodeTemplateData, zUpdateBarcodeTemplateResponse2, zUpdateBoardData, @@ -295,6 +313,8 @@ import { zUpdateClientData, zUpdateClientResponse2, zUpdateDealData, + zUpdateDealGroupData, + zUpdateDealGroupResponse2, zUpdateDealProductData, zUpdateDealProductResponse2, zUpdateDealProductServiceData, @@ -535,6 +555,114 @@ export const updateDeal = ( }); }; +/** + * Update Group + */ +export const updateDealGroup = ( + options: Options +) => { + return (options.client ?? _heyApiClient).patch< + UpdateDealGroupResponses, + UpdateDealGroupErrors, + ThrowOnError + >({ + requestValidator: async data => { + return await zUpdateDealGroupData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zUpdateDealGroupResponse2.parseAsync(data); + }, + url: "/deal-group/", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); +}; + +/** + * Create Group + */ +export const createDealGroup = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + CreateDealGroupResponses, + CreateDealGroupErrors, + ThrowOnError + >({ + requestValidator: async data => { + return await zCreateDealGroupData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zCreateDealGroupResponse2.parseAsync(data); + }, + url: "/deal-group/", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); +}; + +/** + * Remove Deal + */ +export const removeDeal = ( + options: Options +) => { + return (options.client ?? _heyApiClient).delete< + RemoveDealResponses, + RemoveDealErrors, + ThrowOnError + >({ + requestValidator: async data => { + return await zRemoveDealData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zRemoveDealResponse.parseAsync(data); + }, + url: "/deal-group/deal", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); +}; + +/** + * Add Deal + */ +export const addDeal = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + AddDealResponses, + AddDealErrors, + ThrowOnError + >({ + requestValidator: async data => { + return await zAddDealData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zAddDealResponse.parseAsync(data); + }, + url: "/deal-group/deal", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); +}; + /** * Get Built In Modules */ diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts index 7ed56e0..7300f9a 100644 --- a/src/lib/client/types.gen.ts +++ b/src/lib/client/types.gen.ts @@ -1,5 +1,29 @@ // This file is auto-generated by @hey-api/openapi-ts +/** + * AddDealToGroupRequest + */ +export type AddDealToGroupRequest = { + /** + * Dealid + */ + dealId: number; + /** + * Groupid + */ + groupId: number; +}; + +/** + * AddDealToGroupResponse + */ +export type AddDealToGroupResponse = { + /** + * Message + */ + message: string; +}; + /** * BarcodeTemplateAttributeSchema */ @@ -347,6 +371,27 @@ export type CreateClientSchema = { details: ClientDetailsSchema; }; +/** + * CreateDealGroupRequest + */ +export type CreateDealGroupRequest = { + /** + * Draggingdealid + */ + draggingDealId: number; + /** + * Hovereddealid + */ + hoveredDealId: number; +}; + +/** + * CreateDealGroupResponse + */ +export type CreateDealGroupResponse = { + entity: DealGroupSchema; +}; + /** * CreateDealProductRequest */ @@ -832,6 +877,24 @@ export type DealAddKitResponse = { message: string; }; +/** + * DealGroupSchema + */ +export type DealGroupSchema = { + /** + * Id + */ + id: number; + /** + * Name + */ + name?: string | null; + /** + * Lexorank + */ + lexorank: string; +}; + /** * DealProductAddKitRequest */ @@ -887,6 +950,26 @@ export type DealProductSchema = { productServices: Array; }; +/** + * DealRemoveFromGroupRequest + */ +export type DealRemoveFromGroupRequest = { + /** + * Dealid + */ + dealId: number; +}; + +/** + * DealRemoveFromGroupResponse + */ +export type DealRemoveFromGroupResponse = { + /** + * Message + */ + message: string; +}; + /** * DealSchema */ @@ -909,6 +992,7 @@ export type DealSchema = { * Createdat */ createdAt: string; + group: DealGroupSchema | null; /** * Productsquantity */ @@ -1753,6 +1837,37 @@ export type UpdateClientSchema = { details?: ClientDetailsSchema | null; }; +/** + * UpdateDealGroupRequest + */ +export type UpdateDealGroupRequest = { + entity: UpdateDealGroupSchema; +}; + +/** + * UpdateDealGroupResponse + */ +export type UpdateDealGroupResponse = { + /** + * Message + */ + message: string; +}; + +/** + * UpdateDealGroupSchema + */ +export type UpdateDealGroupSchema = { + /** + * Name + */ + name?: string | null; + /** + * Lexorank + */ + lexorank?: string | null; +}; + /** * UpdateDealProductRequest */ @@ -2466,6 +2581,110 @@ export type UpdateDealResponses = { export type UpdateDealResponse2 = UpdateDealResponses[keyof UpdateDealResponses]; +export type UpdateDealGroupData = { + body: UpdateDealGroupRequest; + path?: never; + query?: never; + url: "/deal-group/"; +}; + +export type UpdateDealGroupErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UpdateDealGroupError = + UpdateDealGroupErrors[keyof UpdateDealGroupErrors]; + +export type UpdateDealGroupResponses = { + /** + * Successful Response + */ + 200: UpdateDealGroupResponse; +}; + +export type UpdateDealGroupResponse2 = + UpdateDealGroupResponses[keyof UpdateDealGroupResponses]; + +export type CreateDealGroupData = { + body: CreateDealGroupRequest; + path?: never; + query?: never; + url: "/deal-group/"; +}; + +export type CreateDealGroupErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type CreateDealGroupError = + CreateDealGroupErrors[keyof CreateDealGroupErrors]; + +export type CreateDealGroupResponses = { + /** + * Successful Response + */ + 200: CreateDealGroupResponse; +}; + +export type CreateDealGroupResponse2 = + CreateDealGroupResponses[keyof CreateDealGroupResponses]; + +export type RemoveDealData = { + body: DealRemoveFromGroupRequest; + path?: never; + query?: never; + url: "/deal-group/deal"; +}; + +export type RemoveDealErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type RemoveDealError = RemoveDealErrors[keyof RemoveDealErrors]; + +export type RemoveDealResponses = { + /** + * Successful Response + */ + 200: DealRemoveFromGroupResponse; +}; + +export type RemoveDealResponse = RemoveDealResponses[keyof RemoveDealResponses]; + +export type AddDealData = { + body: AddDealToGroupRequest; + path?: never; + query?: never; + url: "/deal-group/deal"; +}; + +export type AddDealErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type AddDealError = AddDealErrors[keyof AddDealErrors]; + +export type AddDealResponses = { + /** + * Successful Response + */ + 200: AddDealToGroupResponse; +}; + +export type AddDealResponse = AddDealResponses[keyof AddDealResponses]; + export type GetBuiltInModulesData = { body?: never; path?: never; diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts index 008eecd..84891f4 100644 --- a/src/lib/client/zod.gen.ts +++ b/src/lib/client/zod.gen.ts @@ -2,6 +2,21 @@ import { z } from "zod"; +/** + * AddDealToGroupRequest + */ +export const zAddDealToGroupRequest = z.object({ + dealId: z.int(), + groupId: z.int(), +}); + +/** + * AddDealToGroupResponse + */ +export const zAddDealToGroupResponse = z.object({ + message: z.string(), +}); + /** * BarcodeTemplateAttributeSchema */ @@ -193,6 +208,30 @@ export const zCreateClientResponse = z.object({ message: z.string(), }); +/** + * CreateDealGroupRequest + */ +export const zCreateDealGroupRequest = z.object({ + draggingDealId: z.int(), + hoveredDealId: z.int(), +}); + +/** + * DealGroupSchema + */ +export const zDealGroupSchema = z.object({ + id: z.int(), + name: z.optional(z.union([z.string(), z.null()])), + lexorank: z.string(), +}); + +/** + * CreateDealGroupResponse + */ +export const zCreateDealGroupResponse = z.object({ + entity: zDealGroupSchema, +}); + /** * CreateDealProductSchema */ @@ -336,6 +375,7 @@ export const zDealSchema = z.object({ createdAt: z.iso.datetime({ offset: true, }), + group: z.union([zDealGroupSchema, z.null()]), productsQuantity: z.optional(z.int()).default(0), totalPrice: z.optional(z.number()).default(0), client: z.optional(z.union([zClientSchema, z.null()])), @@ -654,6 +694,20 @@ export const zDealProductAddKitResponse = z.object({ message: z.string(), }); +/** + * DealRemoveFromGroupRequest + */ +export const zDealRemoveFromGroupRequest = z.object({ + dealId: z.int(), +}); + +/** + * DealRemoveFromGroupResponse + */ +export const zDealRemoveFromGroupResponse = z.object({ + message: z.string(), +}); + /** * DeleteBarcodeTemplateResponse */ @@ -1034,6 +1088,28 @@ export const zUpdateClientResponse = z.object({ message: z.string(), }); +/** + * UpdateDealGroupSchema + */ +export const zUpdateDealGroupSchema = z.object({ + name: z.optional(z.union([z.string(), z.null()])), + lexorank: z.optional(z.union([z.string(), z.null()])), +}); + +/** + * UpdateDealGroupRequest + */ +export const zUpdateDealGroupRequest = z.object({ + entity: zUpdateDealGroupSchema, +}); + +/** + * UpdateDealGroupResponse + */ +export const zUpdateDealGroupResponse = z.object({ + message: z.string(), +}); + /** * UpdateDealProductSchema */ @@ -1412,6 +1488,50 @@ export const zUpdateDealData = z.object({ */ export const zUpdateDealResponse2 = zUpdateDealResponse; +export const zUpdateDealGroupData = z.object({ + body: zUpdateDealGroupRequest, + path: z.optional(z.never()), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zUpdateDealGroupResponse2 = zUpdateDealGroupResponse; + +export const zCreateDealGroupData = z.object({ + body: zCreateDealGroupRequest, + path: z.optional(z.never()), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zCreateDealGroupResponse2 = zCreateDealGroupResponse; + +export const zRemoveDealData = z.object({ + body: zDealRemoveFromGroupRequest, + path: z.optional(z.never()), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zRemoveDealResponse = zDealRemoveFromGroupResponse; + +export const zAddDealData = z.object({ + body: zAddDealToGroupRequest, + path: z.optional(z.never()), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zAddDealResponse = zAddDealToGroupResponse; + export const zGetBuiltInModulesData = z.object({ body: z.optional(z.never()), path: z.optional(z.never()), diff --git a/src/types/GroupWithDealsSchema.ts b/src/types/GroupWithDealsSchema.ts new file mode 100644 index 0000000..baa2900 --- /dev/null +++ b/src/types/GroupWithDealsSchema.ts @@ -0,0 +1,7 @@ +import { DealGroupSchema, DealSchema } from "@/lib/client"; + +type GroupWithDealsSchema = DealGroupSchema & { + items: DealSchema[]; +}; + +export default GroupWithDealsSchema; diff --git a/yarn.lock b/yarn.lock index a2ed141..126fb28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,6 +42,355 @@ __metadata: languageName: node linkType: hard +"@atlaskit/analytics-next-stable-react-context@npm:1.0.1": + version: 1.0.1 + resolution: "@atlaskit/analytics-next-stable-react-context@npm:1.0.1" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ^16.8.0 + checksum: 10c0/b32e35c2606cf21848f182144f01571de9cc87da5a8fce9f3c2003fa74462d9b7d0dde6bd7dd0dceecb484574cb97d6103343324e9746a2e16d01ba004773ffb + languageName: node + linkType: hard + +"@atlaskit/analytics-next@npm:^11.1.0": + version: 11.1.3 + resolution: "@atlaskit/analytics-next@npm:11.1.3" + dependencies: + "@atlaskit/analytics-next-stable-react-context": "npm:1.0.1" + "@atlaskit/platform-feature-flags": "npm:^1.1.0" + "@babel/runtime": "npm:^7.0.0" + prop-types: "npm:^15.5.10" + use-memo-one: "npm:^1.1.1" + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + checksum: 10c0/695bd42e3dbadea5d1d6e504db2b860781a4dbf70540538fe4f189607a1a8f2ab3deab3e15d9259184ca6e1fd74f820c976137ac9ddc8906f798e86184107e4d + languageName: node + linkType: hard + +"@atlaskit/app-provider@npm:^3.2.0": + version: 3.2.3 + resolution: "@atlaskit/app-provider@npm:3.2.3" + dependencies: + "@atlaskit/css": "npm:^0.15.0" + "@atlaskit/platform-feature-flags": "npm:^1.1.0" + "@atlaskit/tokens": "npm:^6.5.0" + "@babel/runtime": "npm:^7.0.0" + bind-event-listener: "npm:^3.0.0" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/1fcef02760c239827afccf30ae1c7b5dfa7a6c0039725e7bb603a59780e5dcdf55fdd4c778b280f5cd07bac6e62abe26ce3bb220f60a6ef92e495cb2fa2579d5 + languageName: node + linkType: hard + +"@atlaskit/atlassian-context@npm:^0.6.0": + version: 0.6.0 + resolution: "@atlaskit/atlassian-context@npm:0.6.0" + dependencies: + "@babel/runtime": "npm:^7.0.0" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/8eb5912db5ea6d9025f55f684e597220070f923c9359370a7553362b167cb86739559ed93b68dd11f740c4c46fc746f619aa341d4dc0b82c60c32fe835411489 + languageName: node + linkType: hard + +"@atlaskit/avatar@npm:^25.4.2": + version: 25.4.2 + resolution: "@atlaskit/avatar@npm:25.4.2" + dependencies: + "@atlaskit/analytics-next": "npm:^11.1.0" + "@atlaskit/css": "npm:^0.15.0" + "@atlaskit/ds-lib": "npm:^5.1.0" + "@atlaskit/focus-ring": "npm:^3.0.0" + "@atlaskit/icon": "npm:^28.5.0" + "@atlaskit/platform-feature-flags": "npm:^1.1.0" + "@atlaskit/primitives": "npm:^15.0.0" + "@atlaskit/theme": "npm:^21.0.0" + "@atlaskit/tokens": "npm:^6.5.0" + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/054eed78320e90a3cace45a069f2567142c7f6178a40c23561e21be00b507df957fe858ade36b7e012b45c987c48a4fa94f5a317e571f7a2b0ae998fc5f54771 + languageName: node + linkType: hard + +"@atlaskit/codemod-utils@npm:^4.2.0": + version: 4.2.5 + resolution: "@atlaskit/codemod-utils@npm:4.2.5" + dependencies: + "@babel/runtime": "npm:^7.0.0" + checksum: 10c0/7f1a2ed337f5ef423411cfb937feb8941d696bb0e6aa71b2dddf66cda33e53530884b81eac641a9ed1a8cd5821974868ce6e278e2e00110a4c759625af70a22f + languageName: node + linkType: hard + +"@atlaskit/css@npm:^0.15.0": + version: 0.15.0 + resolution: "@atlaskit/css@npm:0.15.0" + dependencies: + "@atlaskit/tokens": "npm:^6.5.0" + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/11760f2d8d3c01ef1f42bccf43e7ec9df8ed813f4fa14a6343e0e311d273662cbe2e1c1370efedce496051eb88a2f606141dfbda65455e2ada86fc1acf713025 + languageName: node + linkType: hard + +"@atlaskit/ds-lib@npm:^5.0.0, @atlaskit/ds-lib@npm:^5.1.0": + version: 5.1.1 + resolution: "@atlaskit/ds-lib@npm:5.1.1" + dependencies: + "@atlaskit/platform-feature-flags": "npm:^1.1.0" + "@babel/runtime": "npm:^7.0.0" + bind-event-listener: "npm:^3.0.0" + react-uid: "npm:^2.2.0" + tiny-invariant: "npm:^1.2.0" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/09cb5f5b963a58fbd61ec99d10bf68accb1c41cf3a738f2dd0a629135330341e4d491ee64af41168fc648e71de8482f77c0d17af728c72eedee068a17e2b2398 + languageName: node + linkType: hard + +"@atlaskit/feature-gate-js-client@npm:^5.5.0": + version: 5.5.7 + resolution: "@atlaskit/feature-gate-js-client@npm:5.5.7" + dependencies: + "@atlaskit/atlassian-context": "npm:^0.6.0" + "@babel/runtime": "npm:^7.0.0" + "@statsig/client-core": "npm:^3.21.1" + "@statsig/js-client": "npm:^3.21.1" + eventemitter3: "npm:^4.0.0" + checksum: 10c0/4b702bf7d5b2d81b383558a5f7b9f15b33639a721451b66121ccbdb19f31c02142d6a40f9654b8e548c7ab6adf70d20d2b292722b982deccff718c0c2f725e8a + languageName: node + linkType: hard + +"@atlaskit/focus-ring@npm:^3.0.0": + version: 3.0.5 + resolution: "@atlaskit/focus-ring@npm:3.0.5" + dependencies: + "@atlaskit/tokens": "npm:^6.3.0" + "@babel/runtime": "npm:^7.0.0" + "@emotion/react": "npm:^11.7.1" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/6e2829651eeefff608e737e0ac3b14db4e155ac5658c0dcd44af0f41ea039caf27e103cc9bf7018d28cd100ce5818e6641442e2030afe5bb19a3b65343d2c59d + languageName: node + linkType: hard + +"@atlaskit/icon@npm:^28.5.0": + version: 28.5.1 + resolution: "@atlaskit/icon@npm:28.5.1" + dependencies: + "@atlaskit/platform-feature-flags": "npm:^1.1.0" + "@atlaskit/tile": "npm:^0.2.0" + "@atlaskit/tokens": "npm:^6.4.0" + "@babel/register": "npm:^7.25.9" + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/1e1ef84921b766997f3b1cb6f41722abdb42eaf8cb4450e0f2a612c3563dcf7ddea121136d61e1d3dd9fc628abe0120602df16b7cc9d3462642fd17e8532ff7a + languageName: node + linkType: hard + +"@atlaskit/interaction-context@npm:^3.1.0": + version: 3.1.0 + resolution: "@atlaskit/interaction-context@npm:3.1.0" + dependencies: + "@babel/runtime": "npm:^7.0.0" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/3c63423fbaedbd6c9323cd85202e7dd9dece292fda14e34beba19ce48086df63f827100e7066506ef1daacaca3ddf97fddc342d2839eb8ad634bd3e1a47cf668 + languageName: node + linkType: hard + +"@atlaskit/motion@npm:^5.3.0": + version: 5.3.8 + resolution: "@atlaskit/motion@npm:5.3.8" + dependencies: + "@atlaskit/css": "npm:^0.15.0" + "@atlaskit/ds-lib": "npm:^5.1.0" + "@atlaskit/platform-feature-flags": "npm:^1.1.0" + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + bind-event-listener: "npm:^3.0.0" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/bfa5d0e0d0759d6b3f75c4e38407916e6ec6f81b04ba5c9150497ca34acca5b0a433a148e479e61987885bd466883f2bbfcda6ad071c27b11c1442b97c1df635 + languageName: node + linkType: hard + +"@atlaskit/platform-feature-flags@npm:^1.1.0": + version: 1.1.2 + resolution: "@atlaskit/platform-feature-flags@npm:1.1.2" + dependencies: + "@atlaskit/feature-gate-js-client": "npm:^5.5.0" + "@babel/runtime": "npm:^7.0.0" + checksum: 10c0/3e727fe8fc414e28b9ff3d409d142e6ffcc1487eb0bbcc07ca0a8553ceea4959f5e0fed40d6607c5d60a1b0e25b37f558a6d7fa669d1e474d847398ac32d3c87 + languageName: node + linkType: hard + +"@atlaskit/pragmatic-drag-and-drop-auto-scroll@npm:^2.1.2": + version: 2.1.2 + resolution: "@atlaskit/pragmatic-drag-and-drop-auto-scroll@npm:2.1.2" + dependencies: + "@atlaskit/pragmatic-drag-and-drop": "npm:^1.7.0" + "@babel/runtime": "npm:^7.0.0" + checksum: 10c0/d391b7d33fa326ee724c8e1b93f0140b220c2c4f35c3df6037e951c4491918805bb8fb44ecb3b107a73758ea2d60e26acba56911eb6c8d7f4cc3f0579d19609e + languageName: node + linkType: hard + +"@atlaskit/pragmatic-drag-and-drop-flourish@npm:^2.0.7": + version: 2.0.7 + resolution: "@atlaskit/pragmatic-drag-and-drop-flourish@npm:2.0.7" + dependencies: + "@atlaskit/motion": "npm:^5.3.0" + "@atlaskit/tokens": "npm:^6.3.0" + "@babel/runtime": "npm:^7.0.0" + checksum: 10c0/c02b9b31940e11dcef9755ca1334ef6b2b4d7359646cfb259f8245bb74e7676627ccbe1c13b0beccde634855d746b8ccbef5ef4bb08a6872a42d48447969d85d + languageName: node + linkType: hard + +"@atlaskit/pragmatic-drag-and-drop-hitbox@npm:^1.1.0": + version: 1.1.0 + resolution: "@atlaskit/pragmatic-drag-and-drop-hitbox@npm:1.1.0" + dependencies: + "@atlaskit/pragmatic-drag-and-drop": "npm:^1.6.0" + "@babel/runtime": "npm:^7.0.0" + checksum: 10c0/65155bc980696abe9600bcf25d7dd9ea520e37f2bb68627a8dc7a3eb1c4f30588c95accbb00dd4720b86c0b15e51cd53d7633de427569a7a67e1cf614e425eee + languageName: node + linkType: hard + +"@atlaskit/pragmatic-drag-and-drop-live-region@npm:^1.3.1": + version: 1.3.1 + resolution: "@atlaskit/pragmatic-drag-and-drop-live-region@npm:1.3.1" + dependencies: + "@babel/runtime": "npm:^7.0.0" + checksum: 10c0/91d762ba1412037ba94a491696c8f12dd782b9412a382c161b5c97ef3be36be50149cd188dabdc900f1f3d624e9d0de7092f715fa8ea3a5d0e7fc938310e23d8 + languageName: node + linkType: hard + +"@atlaskit/pragmatic-drag-and-drop-react-drop-indicator@npm:^3.2.7": + version: 3.2.7 + resolution: "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator@npm:3.2.7" + dependencies: + "@atlaskit/pragmatic-drag-and-drop": "npm:^1.7.0" + "@atlaskit/pragmatic-drag-and-drop-hitbox": "npm:^1.1.0" + "@atlaskit/tokens": "npm:^6.3.0" + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + peerDependencies: + react: ^18.2.0 || ^19.0.0 + checksum: 10c0/a188b64776f967a057f39cedcfa5db47d86783ff1fa37e2d52abdd0245e0dab1e42cc53b650ab2ff1584a422219159c56e83a75557a265bf823deb439cc4415e + languageName: node + linkType: hard + +"@atlaskit/pragmatic-drag-and-drop@npm:^1.6.0, @atlaskit/pragmatic-drag-and-drop@npm:^1.7.0, @atlaskit/pragmatic-drag-and-drop@npm:^1.7.7": + version: 1.7.7 + resolution: "@atlaskit/pragmatic-drag-and-drop@npm:1.7.7" + dependencies: + "@babel/runtime": "npm:^7.0.0" + bind-event-listener: "npm:^3.0.0" + raf-schd: "npm:^4.0.3" + checksum: 10c0/cf10ddc3decaa00623ffd06179b27302385ba6bbf047a93009dea1c61569019dd19936efd7270411feccb955e74c706a367829e72b04ee634589b9a9b1435930 + languageName: node + linkType: hard + +"@atlaskit/primitives@npm:^15.0.0": + version: 15.0.0 + resolution: "@atlaskit/primitives@npm:15.0.0" + dependencies: + "@atlaskit/analytics-next": "npm:^11.1.0" + "@atlaskit/app-provider": "npm:^3.2.0" + "@atlaskit/css": "npm:^0.15.0" + "@atlaskit/ds-lib": "npm:^5.1.0" + "@atlaskit/interaction-context": "npm:^3.1.0" + "@atlaskit/platform-feature-flags": "npm:^1.1.0" + "@atlaskit/tokens": "npm:^6.5.0" + "@atlaskit/visually-hidden": "npm:^3.0.0" + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + "@emotion/react": "npm:^11.7.1" + "@emotion/serialize": "npm:^1.1.0" + bind-event-listener: "npm:^3.0.0" + tiny-invariant: "npm:^1.2.0" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/73bfa31c86b5a0c9bbeaf70c22bca713d8d451285fa5ea97b2a40844fc795081a38d3b229d12a2cf24bbe556dee8d8082c79cb0c5da32ce5b74d728b2e8e73eb + languageName: node + linkType: hard + +"@atlaskit/skeleton@npm:^2.1.0": + version: 2.1.2 + resolution: "@atlaskit/skeleton@npm:2.1.2" + dependencies: + "@atlaskit/tokens": "npm:^6.3.0" + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/73cc7f44d7f105fad24342b362cc7ae8905df67e11a34ab96430fa6c1263bc9f026e989df4cbdfb016bbe2ed5bd6126bdce9ec64d325ebadfd6dbc443db4b904 + languageName: node + linkType: hard + +"@atlaskit/theme@npm:^21.0.0": + version: 21.0.0 + resolution: "@atlaskit/theme@npm:21.0.0" + dependencies: + "@atlaskit/codemod-utils": "npm:^4.2.0" + "@atlaskit/ds-lib": "npm:^5.0.0" + "@atlaskit/tokens": "npm:^6.3.0" + "@babel/runtime": "npm:^7.0.0" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/659916e5e7fd762d8158ee96616d0cfce2f90fe6f199af34d95b744f57e4c1d865a674b10639e9f28627a6d158e9fe77ad868710cab780e6da928d0f917088bb + languageName: node + linkType: hard + +"@atlaskit/tile@npm:^0.2.0": + version: 0.2.1 + resolution: "@atlaskit/tile@npm:0.2.1" + dependencies: + "@atlaskit/skeleton": "npm:^2.1.0" + "@atlaskit/tokens": "npm:^6.5.0" + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/f283717f9262e0c0f589cf71527586fa1e70e47d0a8f90f5e96826c6d3c3736b8018fc6bfdd4b2b45e834018b8f417f340d54e5382b276bf485fcea326cbd63b + languageName: node + linkType: hard + +"@atlaskit/tokens@npm:^6.3.0, @atlaskit/tokens@npm:^6.4.0, @atlaskit/tokens@npm:^6.5.0": + version: 6.5.0 + resolution: "@atlaskit/tokens@npm:6.5.0" + dependencies: + "@atlaskit/ds-lib": "npm:^5.1.0" + "@atlaskit/platform-feature-flags": "npm:^1.1.0" + "@babel/runtime": "npm:^7.0.0" + "@babel/traverse": "npm:^7.23.2" + "@babel/types": "npm:^7.20.0" + bind-event-listener: "npm:^3.0.0" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/2113bcff43625556a276cd51a4418f6678c1941d3ea986692e1718478f503ee5b7049883dd45e9f6360f1b71a099a49f57f99256a48d90ad303cd5a4df065498 + languageName: node + linkType: hard + +"@atlaskit/visually-hidden@npm:^3.0.0": + version: 3.0.3 + resolution: "@atlaskit/visually-hidden@npm:3.0.3" + dependencies: + "@babel/runtime": "npm:^7.0.0" + "@compiled/react": "npm:^0.18.3" + peerDependencies: + react: ^18.2.0 + checksum: 10c0/6cddb8ff7c07c227d50c698110b57342e9c66677888ceca5f262557b164c06f93108f1b76d11536d0d895101b2644897793a7376cd51d8704492815cfb376a85 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1": version: 7.27.1 resolution: "@babel/code-frame@npm:7.27.1" @@ -96,6 +445,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/generator@npm:7.28.3" + dependencies: + "@babel/parser": "npm:^7.28.3" + "@babel/types": "npm:^7.28.2" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.3": version: 7.27.3 resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" @@ -180,7 +542,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.27.1": +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-module-imports@npm:7.27.1" dependencies: @@ -308,6 +670,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/parser@npm:7.28.4" + dependencies: + "@babel/types": "npm:^7.28.4" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/58b239a5b1477ac7ed7e29d86d675cc81075ca055424eba6485872626db2dc556ce63c45043e5a679cd925e999471dba8a3ed4864e7ab1dbf64306ab72c52707 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.27.1" @@ -1402,6 +1775,21 @@ __metadata: languageName: node linkType: hard +"@babel/register@npm:^7.25.9": + version: 7.28.3 + resolution: "@babel/register@npm:7.28.3" + dependencies: + clone-deep: "npm:^4.0.1" + find-cache-dir: "npm:^2.0.0" + make-dir: "npm:^2.1.0" + pirates: "npm:^4.0.6" + source-map-support: "npm:^0.5.16" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ff31870a24e862fca36d5c481eda40be610af215a922560834333a78000b0e159a209dae606d4d93d7456d35ea8caeaaea674cdeaa0c0362e7e30d7f095d2436 + languageName: node + linkType: hard + "@babel/runtime-corejs3@npm:^7.24.4": version: 7.28.2 resolution: "@babel/runtime-corejs3@npm:7.28.2" @@ -1411,6 +1799,13 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.26.7": + version: 7.28.4 + resolution: "@babel/runtime@npm:7.28.4" + checksum: 10c0/792ce7af9750fb9b93879cc9d1db175701c4689da890e6ced242ea0207c9da411ccf16dc04e689cc01158b28d7898c40d75598f4559109f761c12ce01e959bf7 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.28.2 resolution: "@babel/runtime@npm:7.28.2" @@ -1418,13 +1813,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.26.7": - version: 7.28.4 - resolution: "@babel/runtime@npm:7.28.4" - checksum: 10c0/792ce7af9750fb9b93879cc9d1db175701c4689da890e6ced242ea0207c9da411ccf16dc04e689cc01158b28d7898c40d75598f4559109f761c12ce01e959bf7 - languageName: node - linkType: hard - "@babel/template@npm:^7.27.1, @babel/template@npm:^7.27.2": version: 7.27.2 resolution: "@babel/template@npm:7.27.2" @@ -1451,6 +1839,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.23.2": + version: 7.28.4 + resolution: "@babel/traverse@npm:7.28.4" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.3" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.4" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.4" + debug: "npm:^4.3.1" + checksum: 10c0/ee678fdd49c9f54a32e07e8455242390d43ce44887cea6567b233fe13907b89240c377e7633478a32c6cf1be0e17c2f7f3b0c59f0666e39c5074cc47b968489c + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.0, @babel/types@npm:^7.28.2, @babel/types@npm:^7.4.4": version: 7.28.2 resolution: "@babel/types@npm:7.28.2" @@ -1461,6 +1864,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.20.0, @babel/types@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/types@npm:7.28.4" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/ac6f909d6191319e08c80efbfac7bd9a25f80cc83b43cd6d82e7233f7a6b9d6e7b90236f3af7400a3f83b576895bcab9188a22b584eb0f224e80e6d4e95f4517 + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -1468,6 +1881,17 @@ __metadata: languageName: node linkType: hard +"@compiled/react@npm:^0.18.3": + version: 0.18.6 + resolution: "@compiled/react@npm:0.18.6" + dependencies: + csstype: "npm:^3.1.3" + peerDependencies: + react: ">= 16.12.0" + checksum: 10c0/78e3893fb13f26ea8262149fc5a011d967560b755babf98f92fe59784df39a39761daee07a0523e0f76356a18be1a0975e43471871469f5390ca80a17b35e9bc + languageName: node + linkType: hard + "@csstools/color-helpers@npm:^5.0.2": version: 5.0.2 resolution: "@csstools/color-helpers@npm:5.0.2" @@ -1637,6 +2061,123 @@ __metadata: languageName: node linkType: hard +"@emotion/babel-plugin@npm:^11.13.5": + version: 11.13.5 + resolution: "@emotion/babel-plugin@npm:11.13.5" + dependencies: + "@babel/helper-module-imports": "npm:^7.16.7" + "@babel/runtime": "npm:^7.18.3" + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/serialize": "npm:^1.3.3" + babel-plugin-macros: "npm:^3.1.0" + convert-source-map: "npm:^1.5.0" + escape-string-regexp: "npm:^4.0.0" + find-root: "npm:^1.1.0" + source-map: "npm:^0.5.7" + stylis: "npm:4.2.0" + checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb + languageName: node + linkType: hard + +"@emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + "@emotion/sheet": "npm:^1.4.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + stylis: "npm:4.2.0" + checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724 + languageName: node + linkType: hard + +"@emotion/hash@npm:^0.9.2": + version: 0.9.2 + resolution: "@emotion/hash@npm:0.9.2" + checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 + languageName: node + linkType: hard + +"@emotion/react@npm:^11.7.1": + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + hoist-non-react-statics: "npm:^3.3.1" + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031 + languageName: node + linkType: hard + +"@emotion/serialize@npm:^1.1.0, @emotion/serialize@npm:^1.3.3": + version: 1.3.3 + resolution: "@emotion/serialize@npm:1.3.3" + dependencies: + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/unitless": "npm:^0.10.0" + "@emotion/utils": "npm:^1.4.2" + csstype: "npm:^3.0.2" + checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4 + languageName: node + linkType: hard + +"@emotion/sheet@npm:^1.4.0": + version: 1.4.0 + resolution: "@emotion/sheet@npm:1.4.0" + checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7 + languageName: node + linkType: hard + +"@emotion/unitless@npm:^0.10.0": + version: 0.10.0 + resolution: "@emotion/unitless@npm:0.10.0" + checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 + languageName: node + linkType: hard + +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08 + languageName: node + linkType: hard + +"@emotion/utils@npm:^1.4.2": + version: 1.4.2 + resolution: "@emotion/utils@npm:1.4.2" + checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a + languageName: node + linkType: hard + +"@emotion/weak-memoize@npm:^0.4.0": + version: 0.4.0 + resolution: "@emotion/weak-memoize@npm:0.4.0" + checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/aix-ppc64@npm:0.25.8" @@ -3242,6 +3783,22 @@ __metadata: languageName: node linkType: hard +"@statsig/client-core@npm:3.26.0, @statsig/client-core@npm:^3.21.1": + version: 3.26.0 + resolution: "@statsig/client-core@npm:3.26.0" + checksum: 10c0/98e73de609e5a68b5b3cd3c5869292a72b123d8324e84747696d37d1587b33fb78609a6900b13e692c60b19f8d365eb4f13b3cce1f64faeab5aedd29561873ef + languageName: node + linkType: hard + +"@statsig/js-client@npm:^3.21.1": + version: 3.26.0 + resolution: "@statsig/js-client@npm:3.26.0" + dependencies: + "@statsig/client-core": "npm:3.26.0" + checksum: 10c0/f67571c59061b9367bb30cf3b6c14263d62a4bb330d0a1b39b491904c6a2c0cb2234d386524d86b66b1ea05728f4fbc77cc59164b8171a67e35fa34cd676d2d8 + languageName: node + linkType: hard + "@storybook/builder-webpack5@npm:8.6.14": version: 8.6.14 resolution: "@storybook/builder-webpack5@npm:8.6.14" @@ -4061,6 +4618,15 @@ __metadata: languageName: node linkType: hard +"@types/react-dom@npm:19.1.2": + version: 19.1.2 + resolution: "@types/react-dom@npm:19.1.2" + peerDependencies: + "@types/react": ^19.0.0 + checksum: 10c0/100c341cacba9ec8ae1d47ee051072a3450e9573bf8eeb7262490e341cb246ea0f95a07a1f2077e61cf92648f812a0324c602fcd811bd87b7ce41db2811510cd + languageName: node + linkType: hard + "@types/react-redux@npm:^7.1.34": version: 7.1.34 resolution: "@types/react-redux@npm:7.1.34" @@ -5212,6 +5778,17 @@ __metadata: languageName: node linkType: hard +"babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + cosmiconfig: "npm:^7.0.0" + resolve: "npm:^1.19.0" + checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c + languageName: node + linkType: hard + "babel-plugin-polyfill-corejs2@npm:^0.4.14": version: 0.4.14 resolution: "babel-plugin-polyfill-corejs2@npm:0.4.14" @@ -5329,6 +5906,13 @@ __metadata: languageName: node linkType: hard +"bind-event-listener@npm:^3.0.0": + version: 3.0.0 + resolution: "bind-event-listener@npm:3.0.0" + checksum: 10c0/08eadf1c7d3a58633f25c2bbd8dc066f77ef4e5df1049e81ff2f43a40c00f6581aba37387caa4878782b1f1f7c337b827757f52b637052a465ad74a7e1db8def + languageName: node + linkType: hard + "bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9": version: 4.12.2 resolution: "bn.js@npm:4.12.2" @@ -5855,6 +6439,17 @@ __metadata: languageName: node linkType: hard +"clone-deep@npm:^4.0.1": + version: 4.0.1 + resolution: "clone-deep@npm:4.0.1" + dependencies: + is-plain-object: "npm:^2.0.4" + kind-of: "npm:^6.0.2" + shallow-clone: "npm:^3.0.0" + checksum: 10c0/637753615aa24adf0f2d505947a1bb75e63964309034a1cf56ba4b1f30af155201edd38d26ffe26911adaae267a3c138b344a4947d39f5fc1b6d6108125aa758 + languageName: node + linkType: hard + "clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" @@ -6021,7 +6616,7 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:^1.7.0": +"convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.7.0": version: 1.9.0 resolution: "convert-source-map@npm:1.9.0" checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b @@ -6058,7 +6653,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^7.0.1": +"cosmiconfig@npm:^7.0.0, cosmiconfig@npm:^7.0.1": version: 7.1.0 resolution: "cosmiconfig@npm:7.1.0" dependencies: @@ -6141,6 +6736,12 @@ __metadata: version: 0.0.0-use.local resolution: "crm-frontend@workspace:." dependencies: + "@atlaskit/avatar": "npm:^25.4.2" + "@atlaskit/pragmatic-drag-and-drop": "npm:^1.7.7" + "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "npm:^2.1.2" + "@atlaskit/pragmatic-drag-and-drop-flourish": "npm:^2.0.7" + "@atlaskit/pragmatic-drag-and-drop-live-region": "npm:^1.3.1" + "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "npm:^3.2.7" "@babel/core": "npm:^7.27.4" "@dnd-kit/core": "npm:^6.3.1" "@dnd-kit/modifiers": "npm:^9.0.0" @@ -6174,6 +6775,7 @@ __metadata: "@types/lodash": "npm:^4.17.20" "@types/node": "npm:^22.13.11" "@types/react": "npm:19.1.8" + "@types/react-dom": "npm:19.1.2" "@types/react-redux": "npm:^7.1.34" "@types/react-slick": "npm:^0" "@types/redux-persist": "npm:^4.3.1" @@ -6351,7 +6953,7 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.2": +"csstype@npm:^3.0.2, csstype@npm:^3.1.3": version: 3.1.3 resolution: "csstype@npm:3.1.3" checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 @@ -7432,6 +8034,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^4.0.0": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b + languageName: node + linkType: hard + "events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" @@ -7645,6 +8254,17 @@ __metadata: languageName: node linkType: hard +"find-cache-dir@npm:^2.0.0": + version: 2.1.0 + resolution: "find-cache-dir@npm:2.1.0" + dependencies: + commondir: "npm:^1.0.1" + make-dir: "npm:^2.0.0" + pkg-dir: "npm:^3.0.0" + checksum: 10c0/556117fd0af14eb88fb69250f4bba9e905e7c355c6136dff0e161b9cbd1f5285f761b778565a278da73a130f42eccc723d7ad4c002ae547ed1d698d39779dabb + languageName: node + linkType: hard + "find-cache-dir@npm:^3.3.1": version: 3.3.2 resolution: "find-cache-dir@npm:3.3.2" @@ -7666,6 +8286,22 @@ __metadata: languageName: node linkType: hard +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa + languageName: node + linkType: hard + +"find-up@npm:^3.0.0": + version: 3.0.0 + resolution: "find-up@npm:3.0.0" + dependencies: + locate-path: "npm:^3.0.0" + checksum: 10c0/2c2e7d0a26db858e2f624f39038c74739e38306dee42b45f404f770db357947be9d0d587f1cac72d20c114deb38aa57316e879eb0a78b17b46da7dab0a3bd6e3 + languageName: node + linkType: hard + "find-up@npm:^4.0.0, find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" @@ -8288,7 +8924,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.0": +"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -8815,6 +9451,15 @@ __metadata: languageName: node linkType: hard +"is-plain-object@npm:^2.0.4": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 + languageName: node + linkType: hard + "is-plain-object@npm:^5.0.0": version: 5.0.0 resolution: "is-plain-object@npm:5.0.0" @@ -8966,6 +9611,13 @@ __metadata: languageName: node linkType: hard +"isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" @@ -10005,6 +10657,16 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-path@npm:3.0.0" + dependencies: + p-locate: "npm:^3.0.0" + path-exists: "npm:^3.0.0" + checksum: 10c0/3db394b7829a7fe2f4fbdd25d3c4689b85f003c318c5da4052c7e56eed697da8f1bce5294f685c69ff76e32cba7a33629d94396976f6d05fb7f4c755c5e2ae8b + languageName: node + linkType: hard + "locate-path@npm:^5.0.0": version: 5.0.0 resolution: "locate-path@npm:5.0.0" @@ -10128,6 +10790,16 @@ __metadata: languageName: node linkType: hard +"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0": + version: 2.1.0 + resolution: "make-dir@npm:2.1.0" + dependencies: + pify: "npm:^4.0.1" + semver: "npm:^5.6.0" + checksum: 10c0/ada869944d866229819735bee5548944caef560d7a8536ecbc6536edca28c72add47cc4f6fc39c54fb25d06b58da1f8994cf7d9df7dadea047064749efc085d8 + languageName: node + linkType: hard + "make-dir@npm:^3.0.2": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -10966,7 +11638,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^2.2.0": +"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" dependencies: @@ -10993,6 +11665,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^3.0.0": + version: 3.0.0 + resolution: "p-locate@npm:3.0.0" + dependencies: + p-limit: "npm:^2.0.0" + checksum: 10c0/7b7f06f718f19e989ce6280ed4396fb3c34dabdee0df948376483032f9d5ec22fdf7077ec942143a75827bb85b11da72016497fc10dac1106c837ed593969ee8 + languageName: node + linkType: hard + "p-locate@npm:^4.1.0": version: 4.1.0 resolution: "p-locate@npm:4.1.0" @@ -11119,6 +11800,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^3.0.0": + version: 3.0.0 + resolution: "path-exists@npm:3.0.0" + checksum: 10c0/17d6a5664bc0a11d48e2b2127d28a0e58822c6740bde30403f08013da599182289c56518bec89407e3f31d3c2b6b296a4220bc3f867f0911fee6952208b04167 + languageName: node + linkType: hard + "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -11241,13 +11929,29 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.7": +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + +"pirates@npm:^4.0.6, pirates@npm:^4.0.7": version: 4.0.7 resolution: "pirates@npm:4.0.7" checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a languageName: node linkType: hard +"pkg-dir@npm:^3.0.0": + version: 3.0.0 + resolution: "pkg-dir@npm:3.0.0" + dependencies: + find-up: "npm:^3.0.0" + checksum: 10c0/902a3d0c1f8ac43b1795fa1ba6ffeb37dfd53c91469e969790f6ed5e29ff2bdc50b63ba6115dc056d2efb4a040aa2446d512b3804bdafdf302f734fb3ec21847 + languageName: node + linkType: hard + "pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" @@ -11585,7 +12289,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.10, prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -11912,6 +12616,21 @@ __metadata: languageName: node linkType: hard +"react-uid@npm:^2.2.0": + version: 2.4.0 + resolution: "react-uid@npm:2.4.0" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/5e01e8d3a4144d160af26f7ea8300887ed8379ee14b1fad7979ea755d5bfa4badfdab531cb7d4495488e94083f829bd5b28a8f6661522b11729fceb42dcf73ea + languageName: node + linkType: hard + "react@npm:19.1.0": version: 19.1.0 resolution: "react@npm:19.1.0" @@ -12193,7 +12912,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.8": +"resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.8": version: 1.22.10 resolution: "resolve@npm:1.22.10" dependencies: @@ -12219,7 +12938,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": +"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": version: 1.22.10 resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: @@ -12442,6 +13161,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^5.6.0": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25 + languageName: node + linkType: hard + "semver@npm:^6.0.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -12517,6 +13245,15 @@ __metadata: languageName: node linkType: hard +"shallow-clone@npm:^3.0.0": + version: 3.0.1 + resolution: "shallow-clone@npm:3.0.1" + dependencies: + kind-of: "npm:^6.0.2" + checksum: 10c0/7bab09613a1b9f480c85a9823aebec533015579fa055ba6634aa56ba1f984380670eaf33b8217502931872aa1401c9fcadaa15f9f604d631536df475b05bcf1e + languageName: node + linkType: hard + "sharp@npm:^0.33.3": version: 0.33.5 resolution: "sharp@npm:0.33.5" @@ -12825,7 +13562,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:~0.5.20": +"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -12842,6 +13579,13 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 + languageName: node + linkType: hard + "source-map@npm:^0.7.3": version: 0.7.6 resolution: "source-map@npm:0.7.6" @@ -13308,6 +14052,13 @@ __metadata: languageName: node linkType: hard +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 + languageName: node + linkType: hard + "sugarss@npm:^4.0.1": version: 4.0.1 resolution: "sugarss@npm:4.0.1" @@ -13500,7 +14251,7 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.0.6, tiny-invariant@npm:^1.3.3": +"tiny-invariant@npm:^1.0.6, tiny-invariant@npm:^1.2.0, tiny-invariant@npm:^1.3.3": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" checksum: 10c0/65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a @@ -14087,6 +14838,15 @@ __metadata: languageName: node linkType: hard +"use-memo-one@npm:^1.1.1": + version: 1.1.3 + resolution: "use-memo-one@npm:1.1.3" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10c0/3d596e65a6b47b2f1818061599738e00daad1f9a9bb4e5ce1f014b20a35b297e50fe4bf1d8c1699ab43ea97f01f84649a736c15ceff96de83bfa696925f6cc6b + languageName: node + linkType: hard + "use-sidecar@npm:^1.1.3": version: 1.1.3 resolution: "use-sidecar@npm:1.1.3"