diff --git a/src/app/deals/components/shared/Funnel/Funnel.tsx b/src/app/deals/components/shared/Funnel/Funnel.tsx index da1a5bc..e98a919 100644 --- a/src/app/deals/components/shared/Funnel/Funnel.tsx +++ b/src/app/deals/components/shared/Funnel/Funnel.tsx @@ -11,7 +11,8 @@ 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 { sortByLexorank } from "@/utils/lexorank"; + +import { sortByLexorank } from "@/utils/lexorank/sort"; const Funnel: FC = () => { const { selectedBoard } = useBoardsContext(); diff --git a/src/app/deals/hooks/useDealsAndStatusesDnd.ts b/src/app/deals/hooks/useDealsAndStatusesDnd.ts index 524a1f8..332986b 100644 --- a/src/app/deals/hooks/useDealsAndStatusesDnd.ts +++ b/src/app/deals/hooks/useDealsAndStatusesDnd.ts @@ -8,7 +8,8 @@ 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"; + +import { sortByLexorank } from "@/utils/lexorank/sort"; type ReturnType = { sortedStatuses: StatusSchema[]; diff --git a/src/app/deals/hooks/useGetNewRank.ts b/src/app/deals/hooks/useGetNewRank.ts index b5a7534..ff39414 100644 --- a/src/app/deals/hooks/useGetNewRank.ts +++ b/src/app/deals/hooks/useGetNewRank.ts @@ -1,7 +1,8 @@ import { LexoRank } from "lexorank"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import { DealSchema } from "@/lib/client"; -import { getNewLexorank, sortByLexorank } from "@/utils/lexorank"; +import { sortByLexorank } from "@/utils/lexorank/sort"; +import { getNewLexorank } from "@/utils/lexorank/generation"; type NewRankGetters = { getNewRankForSameStatus: ( diff --git a/src/app/services/components/shared/PageBody/PageBody.tsx b/src/app/services/components/shared/PageBody/PageBody.tsx index 5e407ac..54ff71f 100644 --- a/src/app/services/components/shared/PageBody/PageBody.tsx +++ b/src/app/services/components/shared/PageBody/PageBody.tsx @@ -1,31 +1,24 @@ "use client"; -import { useState } from "react"; import { Stack } from "@mantine/core"; import ServicesDesktopHeader from "@/app/services/components/desktop/ServicesDesktopHeader/ServicesDesktopHeader"; import ServicesMobileHeader from "@/app/services/components/mobile/ServicesMobileHeader/ServicesMobileHeader"; import ServicesKitsTable from "@/app/services/components/shared/ServicesKitTable/ServicesKitTable"; import ServicesTable from "@/app/services/components/shared/ServicesTable/ServicesTable"; import { ServicesTab } from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl"; +import { useServicesContext } from "@/app/services/contexts/ServicesContext"; import PageBlock from "@/components/layout/PageBlock/PageBlock"; import useIsMobile from "@/hooks/utils/useIsMobile"; -import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service"; const PageBody = () => { const isMobile = useIsMobile(); - - const [servicesTab, setServicesTab] = useState( - ServicesTab.PRODUCT_SERVICE - ); + const { servicesTab, setServicesTab } = useServicesContext(); const getPageBody = () => { switch (servicesTab) { case ServicesTab.PRODUCT_SERVICE: - return ( - - ); case ServicesTab.DEAL_SERVICE: - return ; + return ; case ServicesTab.SERVICES_KITS: return ; default: diff --git a/src/app/services/components/shared/ServicesTable/ServicesTable.tsx b/src/app/services/components/shared/ServicesTable/ServicesTable.tsx index 02c9f91..e9b91fa 100644 --- a/src/app/services/components/shared/ServicesTable/ServicesTable.tsx +++ b/src/app/services/components/shared/ServicesTable/ServicesTable.tsx @@ -1,28 +1,17 @@ "use client"; -import { FC, useMemo, useState } from "react"; -import { DragDropContext, Draggable } from "@hello-pangea/dnd"; -import { IconGripVertical } from "@tabler/icons-react"; -import clsx from "clsx"; -import { DataTableDraggableRow } from "mantine-datatable"; -import { Center, TableTd } from "@mantine/core"; +import { FC, useState } from "react"; import InnerServicesTableDndWrapper from "@/app/services/components/shared/ServicesTable/components/InnerServicesTableDndWrapper"; import useServicesOuterTableColumns from "@/app/services/components/shared/ServicesTable/hooks/servicesOuterTableColumns"; -import { GroupedServices } from "@/app/services/components/shared/ServicesTable/types/GroupedServices"; +import ServicesTableDndWrapper from "@/app/services/components/shared/ServicesTableDndWrapper/ServicesTableDndWrapper"; import { useServicesContext } from "@/app/services/contexts/ServicesContext"; -import { useServicesDndContext } from "@/app/services/contexts/ServicesDndContext"; -import Droppable from "@/components/dnd-pangea/Droppable/Droppable"; +import DraggableTableRow from "@/components/dnd-pangea/DraggableTableRow/DraggableTableRow"; import BaseTable from "@/components/ui/BaseTable/BaseTable"; -import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service"; -import classes from "./ServicesTable.module.css"; +import useIsMobile from "@/hooks/utils/useIsMobile"; -type Props = { - serviceType: ServiceType; -}; - -const ServicesTable: FC = ({ serviceType }) => { - const { servicesList } = useServicesContext(); - const { onDragEnd, onDragStart } = useServicesDndContext(); +const ServicesTable: FC = () => { + const isMobile = useIsMobile(); + const { groupedServices } = useServicesContext(); const [expandedCategoryIds, setExpandedCategoryIds] = useState( [] @@ -33,46 +22,14 @@ const ServicesTable: FC = ({ serviceType }) => { setExpandedCategoryIds, }); - const groupedServices: GroupedServices[] = useMemo(() => { - const grouped: GroupedServices[] = []; - servicesList.services.forEach(service => { - if (service.serviceType !== serviceType) return; - - const existingGroup = grouped.find( - group => group.category.id === service.category.id - ); - if (existingGroup) { - existingGroup.services.push(service); - } else { - grouped.push({ - category: service.category, - services: [service], - }); - } - }); - return grouped; - }, [servicesList.services, serviceType]); - return ( - + ( - - {children} - - )} rowExpansion={{ allowMultiple: true, expanded: { @@ -82,47 +39,21 @@ const ServicesTable: FC = ({ serviceType }) => { content: ({ record }) => ( ), }} styles={{ table: { tableLayout: "fixed" } }} - rowFactory={({ - record, - index, - rowProps, - children, - expandedElement, - }) => ( - <> - - {(provided, snapshot) => ( - - -
- -
-
- {children} -
- )} -
- {expandedElement} - + rowFactory={({ record, children, ...props }) => ( + )} + mx={isMobile ? "xs" : 0} /> -
+ ); }; diff --git a/src/app/services/components/shared/ServicesTable/components/InnerServicesTable.tsx b/src/app/services/components/shared/ServicesTable/components/InnerServicesTable.tsx index e83146a..10b3e68 100644 --- a/src/app/services/components/shared/ServicesTable/components/InnerServicesTable.tsx +++ b/src/app/services/components/shared/ServicesTable/components/InnerServicesTable.tsx @@ -1,54 +1,35 @@ +"use client"; + import { FC } from "react"; -import { Draggable } from "@hello-pangea/dnd"; -import { IconGripVertical } from "@tabler/icons-react"; -import clsx from "clsx"; -import { DataTableDraggableRow } from "mantine-datatable"; -import { Center, TableTd } from "@mantine/core"; import useServicesInnerTableColumns from "@/app/services/components/shared/ServicesTable/hooks/servicesInnerTableColumns"; -import classes from "@/app/services/components/shared/ServicesTable/ServicesTable.module.css"; +import DraggableTableRow from "@/components/dnd-pangea/DraggableTableRow/DraggableTableRow"; import BaseTable from "@/components/ui/BaseTable/BaseTable"; import { ServiceSchema } from "@/lib/client"; type Props = { services: ServiceSchema[]; + categoryId: number; }; -const InnerServicesTable: FC = ({ services }) => { +const InnerServicesTable: FC = ({ services, categoryId }) => { const innerColumns = useServicesInnerTableColumns(); return ( ( - ( + - {(provided, snapshot) => ( - - -
- -
-
- {children} -
- )} -
+ rowElement={children} + disableDndOnMobile + {...props} + /> )} /> ); diff --git a/src/app/services/components/shared/ServicesTable/components/InnerServicesTableDndWrapper.tsx b/src/app/services/components/shared/ServicesTable/components/InnerServicesTableDndWrapper.tsx index edba0c3..71c69ec 100644 --- a/src/app/services/components/shared/ServicesTable/components/InnerServicesTableDndWrapper.tsx +++ b/src/app/services/components/shared/ServicesTable/components/InnerServicesTableDndWrapper.tsx @@ -1,19 +1,26 @@ import { FC } from "react"; import InnerServicesTable from "@/app/services/components/shared/ServicesTable/components/InnerServicesTable"; +import { useServicesDndContext } from "@/app/services/contexts/ServicesDndContext"; +import ServiceDragState from "@/app/services/enums/DragState"; import Droppable from "@/components/dnd-pangea/Droppable/Droppable"; import { ServiceSchema } from "@/lib/client"; type Props = { services: ServiceSchema[]; + categoryId: number; }; -const InnerServicesTableDndWrapper: FC = ({ services }) => { +const InnerServicesTableDndWrapper: FC = ({ services, categoryId }) => { + const { dragState } = useServicesDndContext(); + return ( - + droppableId={`services-${categoryId}`} + isDropDisabled={dragState !== ServiceDragState.DRAG_SERVICE}> + ); }; diff --git a/src/app/services/components/shared/ServicesTable/hooks/servicesInnerTableColumns.tsx b/src/app/services/components/shared/ServicesTable/hooks/servicesInnerTableColumns.tsx index 2df0640..69c8b12 100644 --- a/src/app/services/components/shared/ServicesTable/hooks/servicesInnerTableColumns.tsx +++ b/src/app/services/components/shared/ServicesTable/hooks/servicesInnerTableColumns.tsx @@ -34,30 +34,30 @@ const useServicesInnerTableColumns = () => { return useMemo( () => [ - { accessor: "", hiddenContent: true, width: 0 }, + { accessor: "", hiddenContent: true, width: 2 }, { accessor: "name", title: "Название", - width: 350, + width: isMobile ? 70 : 350, }, { accessor: "price", title: "Цена", render: service => getPriceRow(service), - width: 200, + width: isMobile ? 40 : 200, }, { accessor: "cost", title: isMobile ? "Себестоим." : "Себестоимость", render: service => `${service.cost?.toLocaleString("ru")}₽`, - width: 200, + width: isMobile ? 40 : 200, }, { accessor: "actions", title: isMobile ? "" : "Действия", sortable: false, textAlign: "center", - width: 70, + width: "0%", render: service => (
[ - { accessor: "", hiddenContent: true, width: 3 }, + { accessor: "", hiddenContent: true, width: isMobile ? 1 : 3 }, { accessor: "category.name", title: ( @@ -58,9 +58,10 @@ const useServicesOuterTableColumns = ({ Категория ), - noWrap: true, render: ({ category: { id, name } }) => ( - + {expandedCategoryIds.includes(id) ? ( ) : ( @@ -69,13 +70,14 @@ const useServicesOuterTableColumns = ({ {name} ), - width: 450, + width: isMobile ? 100 : 450, }, { accessor: "actions", title: isMobile ? "" : "Действия", sortable: false, textAlign: "center", + width: isMobile ? 2 : 50, render: ({ category }) => (
), - width: 50, }, ] as DataTableColumn[], [expandedCategoryIds, categories] diff --git a/src/app/services/components/shared/ServicesTableDndWrapper/ServicesTableDndWrapper.tsx b/src/app/services/components/shared/ServicesTableDndWrapper/ServicesTableDndWrapper.tsx new file mode 100644 index 0000000..fa9c493 --- /dev/null +++ b/src/app/services/components/shared/ServicesTableDndWrapper/ServicesTableDndWrapper.tsx @@ -0,0 +1,27 @@ +import { FC, ReactNode } from "react"; +import { DragDropContext } from "@hello-pangea/dnd"; +import ServiceDragState from "@/app/services/enums/DragState"; +import Droppable from "@/components/dnd-pangea/Droppable/Droppable"; +import { useServicesDndContext } from "@/app/services/contexts/ServicesDndContext"; + +type Props = { + children: ReactNode; +}; + +const ServicesTableDndWrapper: FC = ({ children }) => { + const { onDragEnd, onDragStart, dragState } = useServicesDndContext(); + + return ( + + + {children} + + + ); +}; + +export default ServicesTableDndWrapper; diff --git a/src/app/services/contexts/ServicesContext.tsx b/src/app/services/contexts/ServicesContext.tsx index 43f9356..352ce0a 100644 --- a/src/app/services/contexts/ServicesContext.tsx +++ b/src/app/services/contexts/ServicesContext.tsx @@ -1,5 +1,9 @@ "use client"; +import { Dispatch, SetStateAction, useState } from "react"; +import { GroupedServices } from "@/app/services/components/shared/ServicesTable/types/GroupedServices"; +import { ServicesTab } from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl"; +import useGroupedServices from "@/app/services/hooks/useGroupedServices"; import { ServiceCategorySchema } from "@/lib/client"; import makeContext from "@/lib/contextFactory/contextFactory"; import { @@ -22,6 +26,9 @@ import useServicesList, { } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesList"; type ServicesContextState = { + servicesTab: ServicesTab; + setServicesTab: Dispatch>; + groupedServices: GroupedServices[]; servicesList: ServicesList; servicesCrud: ServicesCrud; servicesKitList: ServicesKitsList; @@ -31,6 +38,10 @@ type ServicesContextState = { }; const useServicesContextState = (): ServicesContextState => { + const [servicesTab, setServicesTab] = useState( + ServicesTab.PRODUCT_SERVICE + ); + const servicesList = useServicesList(); const servicesCrud = useServicesCrud(servicesList); @@ -43,7 +54,15 @@ const useServicesContextState = (): ServicesContextState => { categories, }); + const groupedServices = useGroupedServices({ + services: servicesList.services, + servicesTab, + }); + return { + servicesTab, + setServicesTab, + groupedServices, servicesList, servicesCrud, servicesKitList, diff --git a/src/app/services/contexts/ServicesDndContext.tsx b/src/app/services/contexts/ServicesDndContext.tsx index 9c4ccf1..11fcc27 100644 --- a/src/app/services/contexts/ServicesDndContext.tsx +++ b/src/app/services/contexts/ServicesDndContext.tsx @@ -2,8 +2,11 @@ import { Dispatch, SetStateAction, useState } from "react"; import type { DragStart, DropResult } from "@hello-pangea/dnd"; +import { ServicesTab } from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl"; +import { useServicesContext } from "@/app/services/contexts/ServicesContext"; import ServiceDragState from "@/app/services/enums/DragState"; import makeContext from "@/lib/contextFactory/contextFactory"; +import { getNewDndLexorank } from "@/utils/lexorank/generation"; type ServicesDndContextState = { onDragStart: (start: DragStart) => void; @@ -13,36 +16,107 @@ type ServicesDndContextState = { }; const useServiceDndContextState = (): ServicesDndContextState => { + const { + categories, + groupedServices, + servicesTab, + categoriesCrud, + servicesCrud, + servicesList, + } = useServicesContext(); + const [dragState, setDragState] = useState( ServiceDragState.DRAG_ENDED ); - const [value, setValue] = useState(false); - const onDragStart = (start: DragStart) => { - setValue(prev => !prev); + if (start.draggableId.includes("category")) { + setDragState(ServiceDragState.DRAG_CATEGORY); + return; + } + setDragState(ServiceDragState.DRAG_SERVICE); + }; + + const getCategoriesFromGroupedServices = () => [ + ...new Set(groupedServices.map(grouped => grouped.category)), + ]; + + const onCategoryDrag = (result: DropResult) => { + if ( + !result.draggableId.startsWith("category-") || + result.source.index === result.destination!.index + ) + return; + + const draggingCatId = Number( + result.draggableId.replace("category-", "") + ); + + const rankKey = + servicesTab === ServicesTab.DEAL_SERVICE + ? "dealServiceRank" + : "productServiceRank"; + + const categories = getCategoriesFromGroupedServices(); + + const newLexorank = getNewDndLexorank( + result.source.index, + result.destination!.index, + categories, + rankKey + ); + + categoriesCrud.onUpdate(draggingCatId, { + [rankKey]: newLexorank, + }); + }; + + const onServiceDrag = (result: DropResult) => { + if (!result.draggableId.startsWith("service-") || !result.destination) + return; + + const sourceCatId = Number( + result.source.droppableId.replace("services-", "") + ); + const targetCatId = Number( + result.destination!.droppableId.replace("services-", "") + ); + + const draggingServiceId = Number( + result.draggableId.replace("service-", "") + ); + + const services = servicesList.services.filter( + s => s.category.id === targetCatId && s.serviceType === servicesTab + ); + + const newLexorank = getNewDndLexorank( + result.source.index, + result.destination!.index, + services, + "lexorank" + ); + + const category = + sourceCatId !== targetCatId + ? categories.find(c => c.id === targetCatId) + : null; + + servicesCrud.onUpdate(draggingServiceId, { + lexorank: newLexorank, + category, + }); }; const onDragEnd = (result: DropResult) => { if (!result.destination) return; - const sourceId = result.source.droppableId; - const destinationId = result.destination.droppableId; - console.log(destinationId); - - // const items = Array.from(records); - // const sourceIndex = result.source.index; - // const destinationIndex = result.destination.index; - // const [reorderedItem] = items.splice(sourceIndex, 1); - // items.splice(destinationIndex, 0, reorderedItem); - // - // setRecords(items); - // notifications.show({ - // title: 'Table reordered', - // message: `The company named "${items[sourceIndex].name}" has been moved from position ${sourceIndex + 1} to ${destinationIndex + 1}.`, - // color: 'blue', - // }); + if (sourceId === "categories") { + onCategoryDrag(result); + return; + } + onServiceDrag(result); }; return { diff --git a/src/app/services/hooks/useGroupedServices.ts b/src/app/services/hooks/useGroupedServices.ts new file mode 100644 index 0000000..b18f99e --- /dev/null +++ b/src/app/services/hooks/useGroupedServices.ts @@ -0,0 +1,46 @@ +import { useMemo } from "react"; +import { GroupedServices } from "@/app/services/components/shared/ServicesTable/types/GroupedServices"; +import { ServicesTab } from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl"; +import { ServiceCategorySchema, ServiceSchema } from "@/lib/client"; + +type Props = { + services: ServiceSchema[]; + servicesTab: ServicesTab; +}; + +const useGroupedServices = ({ services, servicesTab }: Props) => { + const sortGroupedServices = (grouped: GroupedServices[]) => { + const key: keyof ServiceCategorySchema = + servicesTab === ServicesTab.DEAL_SERVICE + ? "dealServiceRank" + : "productServiceRank"; + + return grouped.sort((a, b) => { + const aRank = a.category[key]; + const bRank = b.category[key]; + return aRank < bRank ? -1 : aRank > bRank ? 1 : 0; + }); + }; + + return useMemo(() => { + const grouped: GroupedServices[] = []; + services.forEach(service => { + if (service.serviceType !== servicesTab) return; + + const existingGroup = grouped.find( + group => group.category.id === service.category.id + ); + if (existingGroup) { + existingGroup.services.push(service); + } else { + grouped.push({ + category: service.category, + services: [service], + }); + } + }); + + return sortGroupedServices(grouped); + }, [services, servicesTab]); +}; +export default useGroupedServices; diff --git a/src/components/dnd-pangea/DraggableTableRow/DraggableTableRow.tsx b/src/components/dnd-pangea/DraggableTableRow/DraggableTableRow.tsx new file mode 100644 index 0000000..bf38c24 --- /dev/null +++ b/src/components/dnd-pangea/DraggableTableRow/DraggableTableRow.tsx @@ -0,0 +1,58 @@ +import { FC, ReactNode } from "react"; +import { Draggable } from "@hello-pangea/dnd"; +import { IconGripVertical } from "@tabler/icons-react"; +import classNames from "classnames"; +import { DataTableDraggableRow } from "mantine-datatable"; +import { Center, TableTd, TableTrProps } from "@mantine/core"; +import classes from "@/app/services/components/shared/ServicesTable/ServicesTable.module.css"; +import useIsMobile from "@/hooks/utils/useIsMobile"; + +type Props = { + draggableId: string; + index: number; + rowElement: ReactNode; + expandedElement?: ReactNode; + rowProps: TableTrProps; + disableDndOnMobile?: boolean; +}; + +const DraggableTableRow: FC = ({ + draggableId, + index, + rowElement, + expandedElement, + rowProps, +}) => { + const isMobile = useIsMobile(); + + return ( + <> + + {(provided, snapshot) => ( + + +
+ +
+
+ {rowElement} +
+ )} +
+ {expandedElement} + + ); +}; +export default DraggableTableRow; diff --git a/src/components/dnd/SortableDnd/SortableDnd.tsx b/src/components/dnd/SortableDnd/SortableDnd.tsx index fb7ebf0..1e426a0 100644 --- a/src/components/dnd/SortableDnd/SortableDnd.tsx +++ b/src/components/dnd/SortableDnd/SortableDnd.tsx @@ -13,14 +13,14 @@ import { restrictToVerticalAxis, } from "@dnd-kit/modifiers"; import { SortableContext } from "@dnd-kit/sortable"; -import { LexoRank } from "lexorank"; import { FreeMode, Mousewheel, Scrollbar } from "swiper/modules"; import { Swiper, SwiperSlide } from "swiper/react"; import { Box, Flex } from "@mantine/core"; import useDndSensors from "@/app/deals/hooks/useSensors"; import { SortableOverlay } from "@/components/dnd/SortableDnd/SortableOverlay"; import SortableItem from "@/components/dnd/SortableItem"; -import { getNewLexorank, sortByLexorank } from "@/utils/lexorank"; +import { getNewDndLexorank } from "@/utils/lexorank/generation"; +import { sortByLexorank } from "@/utils/lexorank/sort"; import classes from "./SortableDnd.module.css"; type BaseItem = { @@ -80,24 +80,12 @@ const SortableDnd = ({ ({ id }) => id === activeItem.id ); - let leftIndex = overIndex; - let rightIndex = overIndex + 1; - if (overIndex < activeIndex) { - leftIndex = overIndex - 1; - rightIndex = overIndex; - } - - const leftLexorank: LexoRank | null = - leftIndex >= 0 ? LexoRank.parse(items[leftIndex].lexorank) : null; - const rightLexorank: LexoRank | null = - rightIndex < items.length - ? LexoRank.parse(items[rightIndex].lexorank) - : null; - - const newLexorank = getNewLexorank( - leftLexorank, - rightLexorank - ).toString(); + const newLexorank = getNewDndLexorank( + activeIndex, + overIndex, + items, + "lexorank" + ); items[activeIndex].lexorank = newLexorank; onDragEnd(items[activeIndex].id, newLexorank); diff --git a/src/hooks/cruds/baseCrud/useCrudOperations.tsx b/src/hooks/cruds/baseCrud/useCrudOperations.tsx index b27ca93..f6f058d 100644 --- a/src/hooks/cruds/baseCrud/useCrudOperations.tsx +++ b/src/hooks/cruds/baseCrud/useCrudOperations.tsx @@ -5,13 +5,13 @@ import { Text } from "@mantine/core"; import { modals } from "@mantine/modals"; import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient"; import { HttpValidationError } from "@/lib/client"; -import { sortByLexorank } from "@/utils/lexorank"; import { BaseEntity, CreateMutationOptions, DeleteMutationOptions, UpdateMutationOptions, } from "./types"; +import { sortByLexorank } from "@/utils/lexorank/sort"; type CrudOperations< TEntity, diff --git a/src/hooks/cruds/useBoardsCrud.tsx b/src/hooks/cruds/useBoardsCrud.tsx index 993df43..30411c7 100644 --- a/src/hooks/cruds/useBoardsCrud.tsx +++ b/src/hooks/cruds/useBoardsCrud.tsx @@ -10,7 +10,8 @@ import { deleteBoardMutation, updateBoardMutation, } from "@/lib/client/@tanstack/react-query.gen"; -import { getMaxByLexorank, getNewLexorank } from "@/utils/lexorank"; +import { getMaxByLexorank } from "@/utils/lexorank/max"; +import { getNewLexorank } from "@/utils/lexorank/generation"; type UseBoardsOperationsProps = { boards: BoardSchema[]; diff --git a/src/hooks/cruds/useDealsCrud.tsx b/src/hooks/cruds/useDealsCrud.tsx index 4c2632a..55e8d53 100644 --- a/src/hooks/cruds/useDealsCrud.tsx +++ b/src/hooks/cruds/useDealsCrud.tsx @@ -11,7 +11,8 @@ import { deleteDealMutation, updateDealMutation, } from "@/lib/client/@tanstack/react-query.gen"; -import { getNewLexorank } from "@/utils/lexorank"; + +import { getNewLexorank } from "@/utils/lexorank/generation"; type UseDealsOperationsProps = { deals: DealSchema[]; diff --git a/src/hooks/cruds/useStatusesCrud.tsx b/src/hooks/cruds/useStatusesCrud.tsx index 7778ace..5d7003b 100644 --- a/src/hooks/cruds/useStatusesCrud.tsx +++ b/src/hooks/cruds/useStatusesCrud.tsx @@ -10,7 +10,8 @@ import { deleteStatusMutation, updateStatusMutation, } from "@/lib/client/@tanstack/react-query.gen"; -import { getMaxByLexorank, getNewLexorank } from "@/utils/lexorank"; +import { getMaxByLexorank } from "@/utils/lexorank/max"; +import { getNewLexorank } from "@/utils/lexorank/generation"; type Props = { statuses: StatusSchema[]; diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts index 6b0f0b0..8ac58ef 100644 --- a/src/lib/client/types.gen.ts +++ b/src/lib/client/types.gen.ts @@ -451,6 +451,10 @@ export type CreateServiceSchema = { */ name: string; category: ServiceCategorySchema; + /** + * Categoryid + */ + categoryId?: number | null; /** * Price */ @@ -1116,6 +1120,10 @@ export type ServiceSchema = { */ name: string; category: ServiceCategorySchema; + /** + * Categoryid + */ + categoryId?: number | null; /** * Price */ @@ -1485,19 +1493,15 @@ export type UpdateServiceCategorySchema = { /** * Name */ - name: string; + name?: string | null; /** * Dealservicerank */ - dealServiceRank: string; + dealServiceRank?: string | null; /** * Productservicerank */ - productServiceRank: string; - /** - * Id - */ - id: number; + productServiceRank?: string | null; }; /** @@ -1524,32 +1528,32 @@ export type UpdateServiceSchema = { /** * Name */ - name: string; - category: ServiceCategorySchema; + name?: string | null; + category?: ServiceCategorySchema | null; + /** + * Categoryid + */ + categoryId?: number | null; /** * Price */ - price: number; + price?: number | null; /** * Servicetype */ - serviceType: number; + serviceType?: number | null; /** * Priceranges */ - priceRanges: Array; + priceRanges?: Array | null; /** * Cost */ - cost: number | null; + cost?: number | null; /** * Lexorank */ - lexorank: string; - /** - * Id - */ - id: number; + lexorank?: string | null; }; /** diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts index 974bc4e..86860ec 100644 --- a/src/lib/client/zod.gen.ts +++ b/src/lib/client/zod.gen.ts @@ -141,6 +141,7 @@ export const zServicePriceRangeSchema = z.object({ export const zServiceSchema = z.object({ name: z.string(), category: zServiceCategorySchema, + categoryId: z.optional(z.union([z.int(), z.null()])), price: z.number(), serviceType: z.int(), priceRanges: z.array(zServicePriceRangeSchema), @@ -381,6 +382,7 @@ export const zCreateServiceCategoryResponse = z.object({ export const zCreateServiceSchema = z.object({ name: z.string(), category: zServiceCategorySchema, + categoryId: z.optional(z.union([z.int(), z.null()])), price: z.number(), serviceType: z.int(), priceRanges: z.array(zServicePriceRangeSchema), @@ -886,10 +888,9 @@ export const zUpdateProjectResponse = z.object({ * UpdateServiceCategorySchema */ export const zUpdateServiceCategorySchema = z.object({ - name: z.string(), - dealServiceRank: z.string(), - productServiceRank: z.string(), - id: z.int(), + name: z.optional(z.union([z.string(), z.null()])), + dealServiceRank: z.optional(z.union([z.string(), z.null()])), + productServiceRank: z.optional(z.union([z.string(), z.null()])), }); /** @@ -910,14 +911,16 @@ export const zUpdateServiceCategoryResponse = z.object({ * UpdateServiceSchema */ export const zUpdateServiceSchema = z.object({ - name: z.string(), - category: zServiceCategorySchema, - price: z.number(), - serviceType: z.int(), - priceRanges: z.array(zServicePriceRangeSchema), - cost: z.union([z.number(), z.null()]), - lexorank: z.string(), - id: z.int(), + name: z.optional(z.union([z.string(), z.null()])), + category: z.optional(z.union([zServiceCategorySchema, z.null()])), + categoryId: z.optional(z.union([z.int(), z.null()])), + price: z.optional(z.union([z.number(), z.null()])), + serviceType: z.optional(z.union([z.int(), z.null()])), + priceRanges: z.optional( + z.union([z.array(zServicePriceRangeSchema), z.null()]) + ), + cost: z.optional(z.union([z.number(), z.null()])), + lexorank: z.optional(z.union([z.string(), z.null()])), }); /** diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServiceCategoriesCrud.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServiceCategoriesCrud.tsx index 0ec1513..9cbcc09 100644 --- a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServiceCategoriesCrud.tsx +++ b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServiceCategoriesCrud.tsx @@ -11,7 +11,8 @@ import { updateServiceCategoryMutation, } from "@/lib/client/@tanstack/react-query.gen"; import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service"; -import { getMaxLexorankByKey, getNewLexorank } from "@/utils/lexorank"; +import { getMaxLexorankByKey } from "@/utils/lexorank/max"; +import { getNewLexorank } from "@/utils/lexorank/generation"; type UseServiceCategoryProps = { queryKey: any[]; diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServicesCrud.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServicesCrud.tsx index 7f69a2f..c1446b9 100644 --- a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServicesCrud.tsx +++ b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServicesCrud.tsx @@ -10,7 +10,8 @@ import { deleteServiceMutation, updateServiceMutation, } from "@/lib/client/@tanstack/react-query.gen"; -import { getMaxByLexorank, getNewLexorank } from "@/utils/lexorank"; +import { getMaxByLexorank } from "@/utils/lexorank/max"; +import { getNewLexorank } from "@/utils/lexorank/generation"; type UseServicesProps = { queryKey: any[]; diff --git a/src/utils/lexorank.ts b/src/utils/lexorank.ts deleted file mode 100644 index 854679b..0000000 --- a/src/utils/lexorank.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { LexoRank } from "lexorank"; - -type LexorankSortable = { - lexorank: string; -}; - -export function compareByLexorank( - a: T, - b: T -): -1 | 1 | 0 { - if (a.lexorank < b.lexorank) { - return -1; - } - if (a.lexorank > b.lexorank) { - return 1; - } - return 0; -} - -export function sortByLexorank(items: T[]): T[] { - return items.sort(compareByLexorank); -} - -export function getMaxByLexorank( - items: T[] -): T | null { - return items.reduce( - (max, item) => (!max || item.lexorank > max.lexorank ? item : max), - null as T | null - ); -} - -export function getMaxLexorankByKey(items: T[], key: keyof T): T | null { - return items.reduce( - (max, item) => (!max || item[key] > max[key] ? item : max), - null as T | null - ); -} - -export function getNewLexorank( - left?: LexoRank | null, - right?: LexoRank | null -): LexoRank { - if (right) { - if (left) return left?.between(right); - return right.between(LexoRank.min()); - } - if (left) { - return left.between(LexoRank.max()); - } - return LexoRank.middle(); -} diff --git a/src/utils/lexorank/compare.ts b/src/utils/lexorank/compare.ts new file mode 100644 index 0000000..7c9d5d1 --- /dev/null +++ b/src/utils/lexorank/compare.ts @@ -0,0 +1,22 @@ +import { LexorankSortable } from "@/utils/lexorank/types"; + +export function compareByLexorank( + a: T, + b: T +): -1 | 1 | 0 { + return compareByLexorankWithKey(a, b, "lexorank"); +} + +export function compareByLexorankWithKey( + a: T, + b: T, + key: keyof T +): -1 | 1 | 0 { + if (a[key] < b[key]) { + return -1; + } + if (a[key] > b[key]) { + return 1; + } + return 0; +} diff --git a/src/utils/lexorank/generation.ts b/src/utils/lexorank/generation.ts new file mode 100644 index 0000000..92ce973 --- /dev/null +++ b/src/utils/lexorank/generation.ts @@ -0,0 +1,38 @@ +import { LexoRank } from "lexorank"; + +export function getNewLexorank( + left?: LexoRank | null, + right?: LexoRank | null +): LexoRank { + if (right) { + if (left) return left?.between(right); + return right.between(LexoRank.min()); + } + if (left) { + return left.between(LexoRank.max()); + } + return LexoRank.middle(); +} + +export const getNewDndLexorank = ( + sourceIdx: number, + targetIdx: number, + items: T[], + key: keyof T +): string => { + let leftIndex = targetIdx; + let rightIndex = targetIdx + 1; + if (targetIdx < sourceIdx) { + leftIndex = targetIdx - 1; + rightIndex = targetIdx; + } + + const leftLexorank: LexoRank | null = + leftIndex >= 0 ? LexoRank.parse(items[leftIndex][key] as string) : null; + const rightLexorank: LexoRank | null = + rightIndex < items.length + ? LexoRank.parse(items[rightIndex][key] as string) + : null; + + return getNewLexorank(leftLexorank, rightLexorank).toString(); +}; diff --git a/src/utils/lexorank/max.ts b/src/utils/lexorank/max.ts new file mode 100644 index 0000000..4973373 --- /dev/null +++ b/src/utils/lexorank/max.ts @@ -0,0 +1,17 @@ +import { LexorankSortable } from "@/utils/lexorank/types"; + +export function getMaxByLexorank( + items: T[] +): T | null { + return items.reduce( + (max, item) => (!max || item.lexorank > max.lexorank ? item : max), + null as T | null + ); +} + +export function getMaxLexorankByKey(items: T[], key: keyof T): T | null { + return items.reduce( + (max, item) => (!max || item[key] > max[key] ? item : max), + null as T | null + ); +} diff --git a/src/utils/lexorank/sort.ts b/src/utils/lexorank/sort.ts new file mode 100644 index 0000000..3c2b396 --- /dev/null +++ b/src/utils/lexorank/sort.ts @@ -0,0 +1,10 @@ +import { compareByLexorankWithKey } from "@/utils/lexorank/compare"; +import { LexorankSortable } from "@/utils/lexorank/types"; + +export function sortByLexorank(items: T[]): T[] { + return sortByLexorankWithKey(items, "lexorank"); +} + +export function sortByLexorankWithKey(items: T[], key: keyof T): T[] { + return items.sort((a, b) => compareByLexorankWithKey(a, b, key)); +} diff --git a/src/utils/lexorank/types.ts b/src/utils/lexorank/types.ts new file mode 100644 index 0000000..82c4924 --- /dev/null +++ b/src/utils/lexorank/types.ts @@ -0,0 +1,3 @@ +export type LexorankSortable = { + lexorank: string; +};