feat: services table with dnd
This commit is contained in:
@ -11,7 +11,8 @@ import useDealsAndStatusesDnd from "@/app/deals/hooks/useDealsAndStatusesDnd";
|
|||||||
import FunnelDnd from "@/components/dnd/FunnelDnd/FunnelDnd";
|
import FunnelDnd from "@/components/dnd/FunnelDnd/FunnelDnd";
|
||||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||||
import { sortByLexorank } from "@/utils/lexorank";
|
|
||||||
|
import { sortByLexorank } from "@/utils/lexorank/sort";
|
||||||
|
|
||||||
const Funnel: FC = () => {
|
const Funnel: FC = () => {
|
||||||
const { selectedBoard } = useBoardsContext();
|
const { selectedBoard } = useBoardsContext();
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import useGetNewRank from "@/app/deals/hooks/useGetNewRank";
|
|||||||
import { getStatusId, isStatusId } from "@/app/deals/utils/statusId";
|
import { getStatusId, isStatusId } from "@/app/deals/utils/statusId";
|
||||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||||
import { sortByLexorank } from "@/utils/lexorank";
|
|
||||||
|
import { sortByLexorank } from "@/utils/lexorank/sort";
|
||||||
|
|
||||||
type ReturnType = {
|
type ReturnType = {
|
||||||
sortedStatuses: StatusSchema[];
|
sortedStatuses: StatusSchema[];
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { LexoRank } from "lexorank";
|
import { LexoRank } from "lexorank";
|
||||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||||
import { DealSchema } from "@/lib/client";
|
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 = {
|
type NewRankGetters = {
|
||||||
getNewRankForSameStatus: (
|
getNewRankForSameStatus: (
|
||||||
|
|||||||
@ -1,31 +1,24 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Stack } from "@mantine/core";
|
import { Stack } from "@mantine/core";
|
||||||
import ServicesDesktopHeader from "@/app/services/components/desktop/ServicesDesktopHeader/ServicesDesktopHeader";
|
import ServicesDesktopHeader from "@/app/services/components/desktop/ServicesDesktopHeader/ServicesDesktopHeader";
|
||||||
import ServicesMobileHeader from "@/app/services/components/mobile/ServicesMobileHeader/ServicesMobileHeader";
|
import ServicesMobileHeader from "@/app/services/components/mobile/ServicesMobileHeader/ServicesMobileHeader";
|
||||||
import ServicesKitsTable from "@/app/services/components/shared/ServicesKitTable/ServicesKitTable";
|
import ServicesKitsTable from "@/app/services/components/shared/ServicesKitTable/ServicesKitTable";
|
||||||
import ServicesTable from "@/app/services/components/shared/ServicesTable/ServicesTable";
|
import ServicesTable from "@/app/services/components/shared/ServicesTable/ServicesTable";
|
||||||
import { ServicesTab } from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl";
|
import { ServicesTab } from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl";
|
||||||
|
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
|
||||||
import PageBlock from "@/components/layout/PageBlock/PageBlock";
|
import PageBlock from "@/components/layout/PageBlock/PageBlock";
|
||||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
|
||||||
|
|
||||||
const PageBody = () => {
|
const PageBody = () => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const { servicesTab, setServicesTab } = useServicesContext();
|
||||||
const [servicesTab, setServicesTab] = useState<ServicesTab>(
|
|
||||||
ServicesTab.PRODUCT_SERVICE
|
|
||||||
);
|
|
||||||
|
|
||||||
const getPageBody = () => {
|
const getPageBody = () => {
|
||||||
switch (servicesTab) {
|
switch (servicesTab) {
|
||||||
case ServicesTab.PRODUCT_SERVICE:
|
case ServicesTab.PRODUCT_SERVICE:
|
||||||
return (
|
|
||||||
<ServicesTable serviceType={ServiceType.PRODUCT_SERVICE} />
|
|
||||||
);
|
|
||||||
case ServicesTab.DEAL_SERVICE:
|
case ServicesTab.DEAL_SERVICE:
|
||||||
return <ServicesTable serviceType={ServiceType.DEAL_SERVICE} />;
|
return <ServicesTable />;
|
||||||
case ServicesTab.SERVICES_KITS:
|
case ServicesTab.SERVICES_KITS:
|
||||||
return <ServicesKitsTable />;
|
return <ServicesKitsTable />;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -1,28 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, useMemo, useState } from "react";
|
import { FC, 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 InnerServicesTableDndWrapper from "@/app/services/components/shared/ServicesTable/components/InnerServicesTableDndWrapper";
|
import InnerServicesTableDndWrapper from "@/app/services/components/shared/ServicesTable/components/InnerServicesTableDndWrapper";
|
||||||
import useServicesOuterTableColumns from "@/app/services/components/shared/ServicesTable/hooks/servicesOuterTableColumns";
|
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 { useServicesContext } from "@/app/services/contexts/ServicesContext";
|
||||||
import { useServicesDndContext } from "@/app/services/contexts/ServicesDndContext";
|
import DraggableTableRow from "@/components/dnd-pangea/DraggableTableRow/DraggableTableRow";
|
||||||
import Droppable from "@/components/dnd-pangea/Droppable/Droppable";
|
|
||||||
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
import classes from "./ServicesTable.module.css";
|
|
||||||
|
|
||||||
type Props = {
|
const ServicesTable: FC = () => {
|
||||||
serviceType: ServiceType;
|
const isMobile = useIsMobile();
|
||||||
};
|
const { groupedServices } = useServicesContext();
|
||||||
|
|
||||||
const ServicesTable: FC<Props> = ({ serviceType }) => {
|
|
||||||
const { servicesList } = useServicesContext();
|
|
||||||
const { onDragEnd, onDragStart } = useServicesDndContext();
|
|
||||||
|
|
||||||
const [expandedCategoryIds, setExpandedCategoryIds] = useState<number[]>(
|
const [expandedCategoryIds, setExpandedCategoryIds] = useState<number[]>(
|
||||||
[]
|
[]
|
||||||
@ -33,46 +22,14 @@ const ServicesTable: FC<Props> = ({ serviceType }) => {
|
|||||||
setExpandedCategoryIds,
|
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 (
|
return (
|
||||||
<DragDropContext
|
<ServicesTableDndWrapper>
|
||||||
onDragEnd={onDragEnd}
|
|
||||||
onDragStart={onDragStart}>
|
|
||||||
<BaseTable
|
<BaseTable
|
||||||
columns={outerColumns}
|
columns={outerColumns}
|
||||||
groups={undefined}
|
groups={undefined}
|
||||||
records={groupedServices}
|
records={groupedServices}
|
||||||
withTableBorder
|
withTableBorder
|
||||||
idAccessor={"category.id"}
|
idAccessor={"category.id"}
|
||||||
tableWrapper={({ children }) => (
|
|
||||||
<Droppable
|
|
||||||
droppableId={"categories"}
|
|
||||||
// isDropDisabled={
|
|
||||||
// dragState !== ServiceDragState.DRAG_CATEGORY
|
|
||||||
// }
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Droppable>
|
|
||||||
)}
|
|
||||||
rowExpansion={{
|
rowExpansion={{
|
||||||
allowMultiple: true,
|
allowMultiple: true,
|
||||||
expanded: {
|
expanded: {
|
||||||
@ -82,47 +39,21 @@ const ServicesTable: FC<Props> = ({ serviceType }) => {
|
|||||||
content: ({ record }) => (
|
content: ({ record }) => (
|
||||||
<InnerServicesTableDndWrapper
|
<InnerServicesTableDndWrapper
|
||||||
services={record.services}
|
services={record.services}
|
||||||
|
categoryId={record.category.id}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
styles={{ table: { tableLayout: "fixed" } }}
|
styles={{ table: { tableLayout: "fixed" } }}
|
||||||
rowFactory={({
|
rowFactory={({ record, children, ...props }) => (
|
||||||
record,
|
<DraggableTableRow
|
||||||
index,
|
draggableId={`category-${record.category.id}`}
|
||||||
rowProps,
|
rowElement={children}
|
||||||
children,
|
{...props}
|
||||||
expandedElement,
|
/>
|
||||||
}) => (
|
|
||||||
<>
|
|
||||||
<Draggable
|
|
||||||
key={record.category.id}
|
|
||||||
draggableId={`category-${record.category.id.toString()}`}
|
|
||||||
index={index}>
|
|
||||||
{(provided, snapshot) => (
|
|
||||||
<DataTableDraggableRow
|
|
||||||
isDragging={snapshot.isDragging}
|
|
||||||
{...rowProps}
|
|
||||||
className={clsx(
|
|
||||||
rowProps.className,
|
|
||||||
classes["draggable-row"]
|
|
||||||
)}
|
|
||||||
{...provided.draggableProps}>
|
|
||||||
<TableTd>
|
|
||||||
<Center
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
ref={provided.innerRef}>
|
|
||||||
<IconGripVertical />
|
|
||||||
</Center>
|
|
||||||
</TableTd>
|
|
||||||
{children}
|
|
||||||
</DataTableDraggableRow>
|
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
{expandedElement}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
mx={isMobile ? "xs" : 0}
|
||||||
/>
|
/>
|
||||||
</DragDropContext>
|
</ServicesTableDndWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,54 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
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 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 BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||||
import { ServiceSchema } from "@/lib/client";
|
import { ServiceSchema } from "@/lib/client";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
services: ServiceSchema[];
|
services: ServiceSchema[];
|
||||||
|
categoryId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InnerServicesTable: FC<Props> = ({ services }) => {
|
const InnerServicesTable: FC<Props> = ({ services, categoryId }) => {
|
||||||
const innerColumns = useServicesInnerTableColumns();
|
const innerColumns = useServicesInnerTableColumns();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseTable
|
<BaseTable
|
||||||
|
key={categoryId}
|
||||||
withTableBorder
|
withTableBorder
|
||||||
columns={innerColumns}
|
columns={innerColumns}
|
||||||
records={services}
|
records={services}
|
||||||
verticalSpacing={"md"}
|
verticalSpacing={"md"}
|
||||||
groups={undefined}
|
groups={undefined}
|
||||||
styles={{ table: { width: "100%" } }}
|
styles={{ table: { width: "100%" } }}
|
||||||
rowFactory={({ record, index, rowProps, children }) => (
|
rowFactory={({ record, children, ...props }) => (
|
||||||
<Draggable
|
<DraggableTableRow
|
||||||
key={record.id}
|
|
||||||
draggableId={`service-${record.id}`}
|
draggableId={`service-${record.id}`}
|
||||||
index={index}>
|
rowElement={children}
|
||||||
{(provided, snapshot) => (
|
disableDndOnMobile
|
||||||
<DataTableDraggableRow
|
{...props}
|
||||||
isDragging={snapshot.isDragging}
|
/>
|
||||||
{...rowProps}
|
|
||||||
className={clsx(
|
|
||||||
rowProps.className,
|
|
||||||
classes["draggable-row"]
|
|
||||||
)}
|
|
||||||
{...provided.draggableProps}>
|
|
||||||
<TableTd>
|
|
||||||
<Center
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
ref={provided.innerRef}>
|
|
||||||
<IconGripVertical />
|
|
||||||
</Center>
|
|
||||||
</TableTd>
|
|
||||||
{children}
|
|
||||||
</DataTableDraggableRow>
|
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import InnerServicesTable from "@/app/services/components/shared/ServicesTable/components/InnerServicesTable";
|
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 Droppable from "@/components/dnd-pangea/Droppable/Droppable";
|
||||||
import { ServiceSchema } from "@/lib/client";
|
import { ServiceSchema } from "@/lib/client";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
services: ServiceSchema[];
|
services: ServiceSchema[];
|
||||||
|
categoryId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InnerServicesTableDndWrapper: FC<Props> = ({ services }) => {
|
const InnerServicesTableDndWrapper: FC<Props> = ({ services, categoryId }) => {
|
||||||
|
const { dragState } = useServicesDndContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Droppable
|
<Droppable
|
||||||
droppableId={"services"}
|
droppableId={`services-${categoryId}`}
|
||||||
// isDropDisabled={dragState !== ServiceDragState.DRAG_SERVICE}
|
isDropDisabled={dragState !== ServiceDragState.DRAG_SERVICE}>
|
||||||
>
|
<InnerServicesTable
|
||||||
<InnerServicesTable services={services} />
|
services={services}
|
||||||
|
categoryId={categoryId}
|
||||||
|
/>
|
||||||
</Droppable>
|
</Droppable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -34,30 +34,30 @@ const useServicesInnerTableColumns = () => {
|
|||||||
return useMemo(
|
return useMemo(
|
||||||
() =>
|
() =>
|
||||||
[
|
[
|
||||||
{ accessor: "", hiddenContent: true, width: 0 },
|
{ accessor: "", hiddenContent: true, width: 2 },
|
||||||
{
|
{
|
||||||
accessor: "name",
|
accessor: "name",
|
||||||
title: "Название",
|
title: "Название",
|
||||||
width: 350,
|
width: isMobile ? 70 : 350,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: "price",
|
accessor: "price",
|
||||||
title: "Цена",
|
title: "Цена",
|
||||||
render: service => getPriceRow(service),
|
render: service => getPriceRow(service),
|
||||||
width: 200,
|
width: isMobile ? 40 : 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: "cost",
|
accessor: "cost",
|
||||||
title: isMobile ? "Себестоим." : "Себестоимость",
|
title: isMobile ? "Себестоим." : "Себестоимость",
|
||||||
render: service => `${service.cost?.toLocaleString("ru")}₽`,
|
render: service => `${service.cost?.toLocaleString("ru")}₽`,
|
||||||
width: 200,
|
width: isMobile ? 40 : 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: "actions",
|
accessor: "actions",
|
||||||
title: isMobile ? "" : "Действия",
|
title: isMobile ? "" : "Действия",
|
||||||
sortable: false,
|
sortable: false,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
width: 70,
|
width: "0%",
|
||||||
render: service => (
|
render: service => (
|
||||||
<Center>
|
<Center>
|
||||||
<UpdateDeleteTableActions
|
<UpdateDeleteTableActions
|
||||||
|
|||||||
@ -45,7 +45,7 @@ const useServicesOuterTableColumns = ({
|
|||||||
return useMemo(
|
return useMemo(
|
||||||
() =>
|
() =>
|
||||||
[
|
[
|
||||||
{ accessor: "", hiddenContent: true, width: 3 },
|
{ accessor: "", hiddenContent: true, width: isMobile ? 1 : 3 },
|
||||||
{
|
{
|
||||||
accessor: "category.name",
|
accessor: "category.name",
|
||||||
title: (
|
title: (
|
||||||
@ -58,9 +58,10 @@ const useServicesOuterTableColumns = ({
|
|||||||
Категория
|
Категория
|
||||||
</Group>
|
</Group>
|
||||||
),
|
),
|
||||||
noWrap: true,
|
|
||||||
render: ({ category: { id, name } }) => (
|
render: ({ category: { id, name } }) => (
|
||||||
<Group key={id}>
|
<Group
|
||||||
|
key={id}
|
||||||
|
wrap={"nowrap"}>
|
||||||
{expandedCategoryIds.includes(id) ? (
|
{expandedCategoryIds.includes(id) ? (
|
||||||
<IconChevronUp />
|
<IconChevronUp />
|
||||||
) : (
|
) : (
|
||||||
@ -69,13 +70,14 @@ const useServicesOuterTableColumns = ({
|
|||||||
<Text>{name}</Text>
|
<Text>{name}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
),
|
),
|
||||||
width: 450,
|
width: isMobile ? 100 : 450,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: "actions",
|
accessor: "actions",
|
||||||
title: isMobile ? "" : "Действия",
|
title: isMobile ? "" : "Действия",
|
||||||
sortable: false,
|
sortable: false,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
width: isMobile ? 2 : 50,
|
||||||
render: ({ category }) => (
|
render: ({ category }) => (
|
||||||
<Center>
|
<Center>
|
||||||
<UpdateDeleteTableActions
|
<UpdateDeleteTableActions
|
||||||
@ -87,7 +89,6 @@ const useServicesOuterTableColumns = ({
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
),
|
),
|
||||||
width: 50,
|
|
||||||
},
|
},
|
||||||
] as DataTableColumn<GroupedServices>[],
|
] as DataTableColumn<GroupedServices>[],
|
||||||
[expandedCategoryIds, categories]
|
[expandedCategoryIds, categories]
|
||||||
|
|||||||
@ -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<Props> = ({ children }) => {
|
||||||
|
const { onDragEnd, onDragStart, dragState } = useServicesDndContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragDropContext
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
onDragStart={onDragStart}>
|
||||||
|
<Droppable
|
||||||
|
droppableId={"categories"}
|
||||||
|
isDropDisabled={dragState !== ServiceDragState.DRAG_CATEGORY}>
|
||||||
|
{children}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesTableDndWrapper;
|
||||||
@ -1,5 +1,9 @@
|
|||||||
"use client";
|
"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 { ServiceCategorySchema } from "@/lib/client";
|
||||||
import makeContext from "@/lib/contextFactory/contextFactory";
|
import makeContext from "@/lib/contextFactory/contextFactory";
|
||||||
import {
|
import {
|
||||||
@ -22,6 +26,9 @@ import useServicesList, {
|
|||||||
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesList";
|
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesList";
|
||||||
|
|
||||||
type ServicesContextState = {
|
type ServicesContextState = {
|
||||||
|
servicesTab: ServicesTab;
|
||||||
|
setServicesTab: Dispatch<SetStateAction<ServicesTab>>;
|
||||||
|
groupedServices: GroupedServices[];
|
||||||
servicesList: ServicesList;
|
servicesList: ServicesList;
|
||||||
servicesCrud: ServicesCrud;
|
servicesCrud: ServicesCrud;
|
||||||
servicesKitList: ServicesKitsList;
|
servicesKitList: ServicesKitsList;
|
||||||
@ -31,6 +38,10 @@ type ServicesContextState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useServicesContextState = (): ServicesContextState => {
|
const useServicesContextState = (): ServicesContextState => {
|
||||||
|
const [servicesTab, setServicesTab] = useState<ServicesTab>(
|
||||||
|
ServicesTab.PRODUCT_SERVICE
|
||||||
|
);
|
||||||
|
|
||||||
const servicesList = useServicesList();
|
const servicesList = useServicesList();
|
||||||
const servicesCrud = useServicesCrud(servicesList);
|
const servicesCrud = useServicesCrud(servicesList);
|
||||||
|
|
||||||
@ -43,7 +54,15 @@ const useServicesContextState = (): ServicesContextState => {
|
|||||||
categories,
|
categories,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const groupedServices = useGroupedServices({
|
||||||
|
services: servicesList.services,
|
||||||
|
servicesTab,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
servicesTab,
|
||||||
|
setServicesTab,
|
||||||
|
groupedServices,
|
||||||
servicesList,
|
servicesList,
|
||||||
servicesCrud,
|
servicesCrud,
|
||||||
servicesKitList,
|
servicesKitList,
|
||||||
|
|||||||
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
import type { DragStart, DropResult } from "@hello-pangea/dnd";
|
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 ServiceDragState from "@/app/services/enums/DragState";
|
||||||
import makeContext from "@/lib/contextFactory/contextFactory";
|
import makeContext from "@/lib/contextFactory/contextFactory";
|
||||||
|
import { getNewDndLexorank } from "@/utils/lexorank/generation";
|
||||||
|
|
||||||
type ServicesDndContextState = {
|
type ServicesDndContextState = {
|
||||||
onDragStart: (start: DragStart) => void;
|
onDragStart: (start: DragStart) => void;
|
||||||
@ -13,36 +16,107 @@ type ServicesDndContextState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useServiceDndContextState = (): ServicesDndContextState => {
|
const useServiceDndContextState = (): ServicesDndContextState => {
|
||||||
|
const {
|
||||||
|
categories,
|
||||||
|
groupedServices,
|
||||||
|
servicesTab,
|
||||||
|
categoriesCrud,
|
||||||
|
servicesCrud,
|
||||||
|
servicesList,
|
||||||
|
} = useServicesContext();
|
||||||
|
|
||||||
const [dragState, setDragState] = useState<ServiceDragState>(
|
const [dragState, setDragState] = useState<ServiceDragState>(
|
||||||
ServiceDragState.DRAG_ENDED
|
ServiceDragState.DRAG_ENDED
|
||||||
);
|
);
|
||||||
|
|
||||||
const [value, setValue] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const onDragStart = (start: DragStart) => {
|
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) => {
|
const onDragEnd = (result: DropResult) => {
|
||||||
if (!result.destination) return;
|
if (!result.destination) return;
|
||||||
|
|
||||||
const sourceId = result.source.droppableId;
|
const sourceId = result.source.droppableId;
|
||||||
const destinationId = result.destination.droppableId;
|
|
||||||
|
|
||||||
console.log(destinationId);
|
if (sourceId === "categories") {
|
||||||
|
onCategoryDrag(result);
|
||||||
// const items = Array.from(records);
|
return;
|
||||||
// const sourceIndex = result.source.index;
|
}
|
||||||
// const destinationIndex = result.destination.index;
|
onServiceDrag(result);
|
||||||
// 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',
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
46
src/app/services/hooks/useGroupedServices.ts
Normal file
46
src/app/services/hooks/useGroupedServices.ts
Normal file
@ -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;
|
||||||
@ -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<Props> = ({
|
||||||
|
draggableId,
|
||||||
|
index,
|
||||||
|
rowElement,
|
||||||
|
expandedElement,
|
||||||
|
rowProps,
|
||||||
|
}) => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Draggable
|
||||||
|
key={draggableId}
|
||||||
|
draggableId={draggableId}
|
||||||
|
index={index}>
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<DataTableDraggableRow
|
||||||
|
isDragging={snapshot.isDragging}
|
||||||
|
{...rowProps}
|
||||||
|
className={classNames(
|
||||||
|
rowProps.className,
|
||||||
|
classes["draggable-row"]
|
||||||
|
)}
|
||||||
|
{...provided.draggableProps}>
|
||||||
|
<TableTd maw={isMobile ? 2 : "auto"}>
|
||||||
|
<Center
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
ref={provided.innerRef}>
|
||||||
|
<IconGripVertical />
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
{rowElement}
|
||||||
|
</DataTableDraggableRow>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
{expandedElement}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DraggableTableRow;
|
||||||
@ -13,14 +13,14 @@ import {
|
|||||||
restrictToVerticalAxis,
|
restrictToVerticalAxis,
|
||||||
} from "@dnd-kit/modifiers";
|
} from "@dnd-kit/modifiers";
|
||||||
import { SortableContext } from "@dnd-kit/sortable";
|
import { SortableContext } from "@dnd-kit/sortable";
|
||||||
import { LexoRank } from "lexorank";
|
|
||||||
import { FreeMode, Mousewheel, Scrollbar } from "swiper/modules";
|
import { FreeMode, Mousewheel, Scrollbar } from "swiper/modules";
|
||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import { Box, Flex } from "@mantine/core";
|
import { Box, Flex } from "@mantine/core";
|
||||||
import useDndSensors from "@/app/deals/hooks/useSensors";
|
import useDndSensors from "@/app/deals/hooks/useSensors";
|
||||||
import { SortableOverlay } from "@/components/dnd/SortableDnd/SortableOverlay";
|
import { SortableOverlay } from "@/components/dnd/SortableDnd/SortableOverlay";
|
||||||
import SortableItem from "@/components/dnd/SortableItem";
|
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";
|
import classes from "./SortableDnd.module.css";
|
||||||
|
|
||||||
type BaseItem = {
|
type BaseItem = {
|
||||||
@ -80,24 +80,12 @@ const SortableDnd = <T extends BaseItem>({
|
|||||||
({ id }) => id === activeItem.id
|
({ id }) => id === activeItem.id
|
||||||
);
|
);
|
||||||
|
|
||||||
let leftIndex = overIndex;
|
const newLexorank = getNewDndLexorank(
|
||||||
let rightIndex = overIndex + 1;
|
activeIndex,
|
||||||
if (overIndex < activeIndex) {
|
overIndex,
|
||||||
leftIndex = overIndex - 1;
|
items,
|
||||||
rightIndex = overIndex;
|
"lexorank"
|
||||||
}
|
);
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
items[activeIndex].lexorank = newLexorank;
|
items[activeIndex].lexorank = newLexorank;
|
||||||
onDragEnd(items[activeIndex].id, newLexorank);
|
onDragEnd(items[activeIndex].id, newLexorank);
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import { Text } from "@mantine/core";
|
|||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
|
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
|
||||||
import { HttpValidationError } from "@/lib/client";
|
import { HttpValidationError } from "@/lib/client";
|
||||||
import { sortByLexorank } from "@/utils/lexorank";
|
|
||||||
import {
|
import {
|
||||||
BaseEntity,
|
BaseEntity,
|
||||||
CreateMutationOptions,
|
CreateMutationOptions,
|
||||||
DeleteMutationOptions,
|
DeleteMutationOptions,
|
||||||
UpdateMutationOptions,
|
UpdateMutationOptions,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { sortByLexorank } from "@/utils/lexorank/sort";
|
||||||
|
|
||||||
type CrudOperations<
|
type CrudOperations<
|
||||||
TEntity,
|
TEntity,
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import {
|
|||||||
deleteBoardMutation,
|
deleteBoardMutation,
|
||||||
updateBoardMutation,
|
updateBoardMutation,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} 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 = {
|
type UseBoardsOperationsProps = {
|
||||||
boards: BoardSchema[];
|
boards: BoardSchema[];
|
||||||
|
|||||||
@ -11,7 +11,8 @@ import {
|
|||||||
deleteDealMutation,
|
deleteDealMutation,
|
||||||
updateDealMutation,
|
updateDealMutation,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} from "@/lib/client/@tanstack/react-query.gen";
|
||||||
import { getNewLexorank } from "@/utils/lexorank";
|
|
||||||
|
import { getNewLexorank } from "@/utils/lexorank/generation";
|
||||||
|
|
||||||
type UseDealsOperationsProps = {
|
type UseDealsOperationsProps = {
|
||||||
deals: DealSchema[];
|
deals: DealSchema[];
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import {
|
|||||||
deleteStatusMutation,
|
deleteStatusMutation,
|
||||||
updateStatusMutation,
|
updateStatusMutation,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} 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 = {
|
type Props = {
|
||||||
statuses: StatusSchema[];
|
statuses: StatusSchema[];
|
||||||
|
|||||||
@ -451,6 +451,10 @@ export type CreateServiceSchema = {
|
|||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
category: ServiceCategorySchema;
|
category: ServiceCategorySchema;
|
||||||
|
/**
|
||||||
|
* Categoryid
|
||||||
|
*/
|
||||||
|
categoryId?: number | null;
|
||||||
/**
|
/**
|
||||||
* Price
|
* Price
|
||||||
*/
|
*/
|
||||||
@ -1116,6 +1120,10 @@ export type ServiceSchema = {
|
|||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
category: ServiceCategorySchema;
|
category: ServiceCategorySchema;
|
||||||
|
/**
|
||||||
|
* Categoryid
|
||||||
|
*/
|
||||||
|
categoryId?: number | null;
|
||||||
/**
|
/**
|
||||||
* Price
|
* Price
|
||||||
*/
|
*/
|
||||||
@ -1485,19 +1493,15 @@ export type UpdateServiceCategorySchema = {
|
|||||||
/**
|
/**
|
||||||
* Name
|
* Name
|
||||||
*/
|
*/
|
||||||
name: string;
|
name?: string | null;
|
||||||
/**
|
/**
|
||||||
* Dealservicerank
|
* Dealservicerank
|
||||||
*/
|
*/
|
||||||
dealServiceRank: string;
|
dealServiceRank?: string | null;
|
||||||
/**
|
/**
|
||||||
* Productservicerank
|
* Productservicerank
|
||||||
*/
|
*/
|
||||||
productServiceRank: string;
|
productServiceRank?: string | null;
|
||||||
/**
|
|
||||||
* Id
|
|
||||||
*/
|
|
||||||
id: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1524,32 +1528,32 @@ export type UpdateServiceSchema = {
|
|||||||
/**
|
/**
|
||||||
* Name
|
* Name
|
||||||
*/
|
*/
|
||||||
name: string;
|
name?: string | null;
|
||||||
category: ServiceCategorySchema;
|
category?: ServiceCategorySchema | null;
|
||||||
|
/**
|
||||||
|
* Categoryid
|
||||||
|
*/
|
||||||
|
categoryId?: number | null;
|
||||||
/**
|
/**
|
||||||
* Price
|
* Price
|
||||||
*/
|
*/
|
||||||
price: number;
|
price?: number | null;
|
||||||
/**
|
/**
|
||||||
* Servicetype
|
* Servicetype
|
||||||
*/
|
*/
|
||||||
serviceType: number;
|
serviceType?: number | null;
|
||||||
/**
|
/**
|
||||||
* Priceranges
|
* Priceranges
|
||||||
*/
|
*/
|
||||||
priceRanges: Array<ServicePriceRangeSchema>;
|
priceRanges?: Array<ServicePriceRangeSchema> | null;
|
||||||
/**
|
/**
|
||||||
* Cost
|
* Cost
|
||||||
*/
|
*/
|
||||||
cost: number | null;
|
cost?: number | null;
|
||||||
/**
|
/**
|
||||||
* Lexorank
|
* Lexorank
|
||||||
*/
|
*/
|
||||||
lexorank: string;
|
lexorank?: string | null;
|
||||||
/**
|
|
||||||
* Id
|
|
||||||
*/
|
|
||||||
id: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -141,6 +141,7 @@ export const zServicePriceRangeSchema = z.object({
|
|||||||
export const zServiceSchema = z.object({
|
export const zServiceSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
category: zServiceCategorySchema,
|
category: zServiceCategorySchema,
|
||||||
|
categoryId: z.optional(z.union([z.int(), z.null()])),
|
||||||
price: z.number(),
|
price: z.number(),
|
||||||
serviceType: z.int(),
|
serviceType: z.int(),
|
||||||
priceRanges: z.array(zServicePriceRangeSchema),
|
priceRanges: z.array(zServicePriceRangeSchema),
|
||||||
@ -381,6 +382,7 @@ export const zCreateServiceCategoryResponse = z.object({
|
|||||||
export const zCreateServiceSchema = z.object({
|
export const zCreateServiceSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
category: zServiceCategorySchema,
|
category: zServiceCategorySchema,
|
||||||
|
categoryId: z.optional(z.union([z.int(), z.null()])),
|
||||||
price: z.number(),
|
price: z.number(),
|
||||||
serviceType: z.int(),
|
serviceType: z.int(),
|
||||||
priceRanges: z.array(zServicePriceRangeSchema),
|
priceRanges: z.array(zServicePriceRangeSchema),
|
||||||
@ -886,10 +888,9 @@ export const zUpdateProjectResponse = z.object({
|
|||||||
* UpdateServiceCategorySchema
|
* UpdateServiceCategorySchema
|
||||||
*/
|
*/
|
||||||
export const zUpdateServiceCategorySchema = z.object({
|
export const zUpdateServiceCategorySchema = z.object({
|
||||||
name: z.string(),
|
name: z.optional(z.union([z.string(), z.null()])),
|
||||||
dealServiceRank: z.string(),
|
dealServiceRank: z.optional(z.union([z.string(), z.null()])),
|
||||||
productServiceRank: z.string(),
|
productServiceRank: z.optional(z.union([z.string(), z.null()])),
|
||||||
id: z.int(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -910,14 +911,16 @@ export const zUpdateServiceCategoryResponse = z.object({
|
|||||||
* UpdateServiceSchema
|
* UpdateServiceSchema
|
||||||
*/
|
*/
|
||||||
export const zUpdateServiceSchema = z.object({
|
export const zUpdateServiceSchema = z.object({
|
||||||
name: z.string(),
|
name: z.optional(z.union([z.string(), z.null()])),
|
||||||
category: zServiceCategorySchema,
|
category: z.optional(z.union([zServiceCategorySchema, z.null()])),
|
||||||
price: z.number(),
|
categoryId: z.optional(z.union([z.int(), z.null()])),
|
||||||
serviceType: z.int(),
|
price: z.optional(z.union([z.number(), z.null()])),
|
||||||
priceRanges: z.array(zServicePriceRangeSchema),
|
serviceType: z.optional(z.union([z.int(), z.null()])),
|
||||||
cost: z.union([z.number(), z.null()]),
|
priceRanges: z.optional(
|
||||||
lexorank: z.string(),
|
z.union([z.array(zServicePriceRangeSchema), z.null()])
|
||||||
id: z.int(),
|
),
|
||||||
|
cost: z.optional(z.union([z.number(), z.null()])),
|
||||||
|
lexorank: z.optional(z.union([z.string(), z.null()])),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -11,7 +11,8 @@ import {
|
|||||||
updateServiceCategoryMutation,
|
updateServiceCategoryMutation,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} from "@/lib/client/@tanstack/react-query.gen";
|
||||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
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 = {
|
type UseServiceCategoryProps = {
|
||||||
queryKey: any[];
|
queryKey: any[];
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import {
|
|||||||
deleteServiceMutation,
|
deleteServiceMutation,
|
||||||
updateServiceMutation,
|
updateServiceMutation,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} 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 = {
|
type UseServicesProps = {
|
||||||
queryKey: any[];
|
queryKey: any[];
|
||||||
|
|||||||
@ -1,52 +0,0 @@
|
|||||||
import { LexoRank } from "lexorank";
|
|
||||||
|
|
||||||
type LexorankSortable = {
|
|
||||||
lexorank: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function compareByLexorank<T extends LexorankSortable>(
|
|
||||||
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<T extends LexorankSortable>(items: T[]): T[] {
|
|
||||||
return items.sort(compareByLexorank);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMaxByLexorank<T extends LexorankSortable>(
|
|
||||||
items: T[]
|
|
||||||
): T | null {
|
|
||||||
return items.reduce(
|
|
||||||
(max, item) => (!max || item.lexorank > max.lexorank ? item : max),
|
|
||||||
null as T | null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMaxLexorankByKey<T>(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();
|
|
||||||
}
|
|
||||||
22
src/utils/lexorank/compare.ts
Normal file
22
src/utils/lexorank/compare.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { LexorankSortable } from "@/utils/lexorank/types";
|
||||||
|
|
||||||
|
export function compareByLexorank<T extends LexorankSortable>(
|
||||||
|
a: T,
|
||||||
|
b: T
|
||||||
|
): -1 | 1 | 0 {
|
||||||
|
return compareByLexorankWithKey<T>(a, b, "lexorank");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareByLexorankWithKey<T>(
|
||||||
|
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;
|
||||||
|
}
|
||||||
38
src/utils/lexorank/generation.ts
Normal file
38
src/utils/lexorank/generation.ts
Normal file
@ -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 = <T>(
|
||||||
|
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();
|
||||||
|
};
|
||||||
17
src/utils/lexorank/max.ts
Normal file
17
src/utils/lexorank/max.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { LexorankSortable } from "@/utils/lexorank/types";
|
||||||
|
|
||||||
|
export function getMaxByLexorank<T extends LexorankSortable>(
|
||||||
|
items: T[]
|
||||||
|
): T | null {
|
||||||
|
return items.reduce(
|
||||||
|
(max, item) => (!max || item.lexorank > max.lexorank ? item : max),
|
||||||
|
null as T | null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMaxLexorankByKey<T>(items: T[], key: keyof T): T | null {
|
||||||
|
return items.reduce(
|
||||||
|
(max, item) => (!max || item[key] > max[key] ? item : max),
|
||||||
|
null as T | null
|
||||||
|
);
|
||||||
|
}
|
||||||
10
src/utils/lexorank/sort.ts
Normal file
10
src/utils/lexorank/sort.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { compareByLexorankWithKey } from "@/utils/lexorank/compare";
|
||||||
|
import { LexorankSortable } from "@/utils/lexorank/types";
|
||||||
|
|
||||||
|
export function sortByLexorank<T extends LexorankSortable>(items: T[]): T[] {
|
||||||
|
return sortByLexorankWithKey(items, "lexorank");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortByLexorankWithKey<T>(items: T[], key: keyof T): T[] {
|
||||||
|
return items.sort((a, b) => compareByLexorankWithKey(a, b, key));
|
||||||
|
}
|
||||||
3
src/utils/lexorank/types.ts
Normal file
3
src/utils/lexorank/types.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export type LexorankSortable = {
|
||||||
|
lexorank: string;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user