services table dnd to fix

This commit is contained in:
2025-09-30 23:21:34 +04:00
parent b51467cbf6
commit f3a0179467
13 changed files with 334 additions and 58 deletions

View File

@ -0,0 +1,4 @@
.draggable-row[data-is-dragging='true'] td {
opacity: 0.75;
border: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}

View File

@ -1,13 +1,20 @@
"use client";
import { FC, useMemo, useState } from "react";
import useServicesInnerTableColumns from "@/app/services/components/shared/ServicesTable/hooks/servicesInnerTableColumns";
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 useServicesOuterTableColumns from "@/app/services/components/shared/ServicesTable/hooks/servicesOuterTableColumns";
import { GroupedServices } from "@/app/services/components/shared/ServicesTable/types/GroupedServices";
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
import { useServicesDndContext } from "@/app/services/contexts/ServicesDndContext";
import Droppable from "@/components/dnd-pangea/Droppable/Droppable";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
import classes from "./ServicesTable.module.css";
type Props = {
serviceType: ServiceType;
@ -15,13 +22,12 @@ type Props = {
const ServicesTable: FC<Props> = ({ serviceType }) => {
const { servicesList } = useServicesContext();
const isMobile = useIsMobile();
const { onDragEnd, onDragStart } = useServicesDndContext();
const [expandedCategoryIds, setExpandedCategoryIds] = useState<number[]>(
[]
);
const innerColumns = useServicesInnerTableColumns();
const outerColumns = useServicesOuterTableColumns({
expandedCategoryIds,
setExpandedCategoryIds,
@ -48,36 +54,75 @@ const ServicesTable: FC<Props> = ({ serviceType }) => {
}, [servicesList.services, serviceType]);
return (
<BaseTable
withTableBorder
columns={outerColumns}
records={groupedServices}
verticalSpacing={"md"}
groups={undefined}
idAccessor={"category.id"}
styles={{
header: { zIndex: 100 },
}}
rowExpansion={{
allowMultiple: true,
expanded: {
recordIds: expandedCategoryIds,
onRecordIdsChange: setExpandedCategoryIds,
},
content: ({ record }) => (
<BaseTable
withTableBorder
columns={innerColumns}
records={record.services}
verticalSpacing={"md"}
groups={undefined}
/>
),
}}
style={{
marginInline: isMobile ? "var(--mantine-spacing-xs)" : "0",
}}
/>
<DragDropContext
onDragEnd={onDragEnd}
onDragStart={onDragStart}>
<BaseTable
columns={outerColumns}
groups={undefined}
records={groupedServices}
withTableBorder
idAccessor={"category.id"}
tableWrapper={({ children }) => (
<Droppable
droppableId={"categories"}
// isDropDisabled={
// dragState !== ServiceDragState.DRAG_CATEGORY
// }
>
{children}
</Droppable>
)}
rowExpansion={{
allowMultiple: true,
expanded: {
recordIds: expandedCategoryIds,
onRecordIdsChange: setExpandedCategoryIds,
},
content: ({ record }) => (
<InnerServicesTableDndWrapper
services={record.services}
/>
),
}}
styles={{ table: { tableLayout: "fixed" } }}
rowFactory={({
record,
index,
rowProps,
children,
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}
</>
)}
/>
</DragDropContext>
);
};

View File

@ -0,0 +1,57 @@
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 BaseTable from "@/components/ui/BaseTable/BaseTable";
import { ServiceSchema } from "@/lib/client";
type Props = {
services: ServiceSchema[];
};
const InnerServicesTable: FC<Props> = ({ services }) => {
const innerColumns = useServicesInnerTableColumns();
return (
<BaseTable
withTableBorder
columns={innerColumns}
records={services}
verticalSpacing={"md"}
groups={undefined}
styles={{ table: { width: "100%" } }}
rowFactory={({ record, index, rowProps, children }) => (
<Draggable
key={record.id}
draggableId={`service-${record.id}`}
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>
)}
/>
);
};
export default InnerServicesTable;

View File

@ -0,0 +1,21 @@
import { FC } from "react";
import InnerServicesTable from "@/app/services/components/shared/ServicesTable/components/InnerServicesTable";
import Droppable from "@/components/dnd-pangea/Droppable/Droppable";
import { ServiceSchema } from "@/lib/client";
type Props = {
services: ServiceSchema[];
};
const InnerServicesTableDndWrapper: FC<Props> = ({ services }) => {
return (
<Droppable
droppableId={"services"}
// isDropDisabled={dragState !== ServiceDragState.DRAG_SERVICE}
>
<InnerServicesTable services={services} />
</Droppable>
);
};
export default InnerServicesTableDndWrapper;

View File

@ -2,7 +2,7 @@
import { useMemo } from "react";
import { DataTableColumn } from "mantine-datatable";
import { List, Text } from "@mantine/core";
import { Center, List, Text } from "@mantine/core";
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
import useServicesActions from "@/app/services/hooks/useServicesActions";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
@ -34,32 +34,38 @@ const useServicesInnerTableColumns = () => {
return useMemo(
() =>
[
{ accessor: "", hiddenContent: true, width: 0 },
{
accessor: "name",
title: "Название",
width: 350,
},
{
accessor: "price",
title: "Цена",
render: service => getPriceRow(service),
width: 200,
},
{
accessor: "cost",
title: isMobile ? "Себестоим." : "Себестоимость",
render: service => `${service.cost?.toLocaleString("ru")}`,
width: 200,
},
{
accessor: "actions",
title: isMobile ? "" : "Действия",
sortable: false,
textAlign: "center",
width: "0%",
width: 70,
render: service => (
<UpdateDeleteTableActions
onDelete={() => servicesCrud.onDelete(service)}
onChange={() => onChangeService(service)}
dotsForMobile
/>
<Center>
<UpdateDeleteTableActions
onDelete={() => servicesCrud.onDelete(service)}
onChange={() => onChangeService(service)}
dotsForMobile
/>
</Center>
),
},
] as DataTableColumn<ServiceSchema>[],

View File

@ -7,11 +7,12 @@ import {
IconChevronUp,
} from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { Box, Group, Text } from "@mantine/core";
import { Box, Center, Group, Text } from "@mantine/core";
import { GroupedServices } from "@/app/services/components/shared/ServicesTable/types/GroupedServices";
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
import useCategoriesActions from "@/app/services/hooks/useCategoriesActions";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import useIsMobile from "@/hooks/utils/useIsMobile";
type Props = {
expandedCategoryIds: number[];
@ -22,6 +23,7 @@ const useServicesOuterTableColumns = ({
expandedCategoryIds,
setExpandedCategoryIds,
}: Props) => {
const isMobile = useIsMobile();
const { onChangeCategory } = useCategoriesActions();
const { categoriesCrud, categories } = useServicesContext();
@ -43,8 +45,9 @@ const useServicesOuterTableColumns = ({
return useMemo(
() =>
[
{ accessor: "", hiddenContent: true, width: 3 },
{
accessor: "name",
accessor: "category.name",
title: (
<Group>
<Box
@ -66,19 +69,25 @@ const useServicesOuterTableColumns = ({
<Text>{name}</Text>
</Group>
),
width: 450,
},
{
accessor: "actions",
title: "Действия",
title: isMobile ? "" : "Действия",
sortable: false,
textAlign: "center",
width: "0%",
render: ({ category }) => (
<UpdateDeleteTableActions
onDelete={() => categoriesCrud.onDelete(category)}
onChange={() => onChangeCategory(category)}
/>
<Center>
<UpdateDeleteTableActions
onDelete={() =>
categoriesCrud.onDelete(category)
}
onChange={() => onChangeCategory(category)}
dotsForMobile
/>
</Center>
),
width: 50,
},
] as DataTableColumn<GroupedServices>[],
[expandedCategoryIds, categories]

View File

@ -30,7 +30,7 @@ type ServicesContextState = {
categories: ServiceCategorySchema[];
};
const useFulfillmentBaseContextState = (): ServicesContextState => {
const useServicesContextState = (): ServicesContextState => {
const servicesList = useServicesList();
const servicesCrud = useServicesCrud(servicesList);
@ -54,7 +54,4 @@ const useFulfillmentBaseContextState = (): ServicesContextState => {
};
export const [ServicesContextProvider, useServicesContext] =
makeContext<ServicesContextState>(
useFulfillmentBaseContextState,
"Services"
);
makeContext<ServicesContextState>(useServicesContextState, "Services");

View File

@ -0,0 +1,60 @@
"use client";
import { Dispatch, SetStateAction, useState } from "react";
import type { DragStart, DropResult } from "@hello-pangea/dnd";
import ServiceDragState from "@/app/services/enums/DragState";
import makeContext from "@/lib/contextFactory/contextFactory";
type ServicesDndContextState = {
onDragStart: (start: DragStart) => void;
onDragEnd: (result: DropResult) => void;
dragState: ServiceDragState;
setDragState: Dispatch<SetStateAction<ServiceDragState>>;
};
const useServiceDndContextState = (): ServicesDndContextState => {
const [dragState, setDragState] = useState<ServiceDragState>(
ServiceDragState.DRAG_ENDED
);
const [value, setValue] = useState<boolean>(false);
const onDragStart = (start: DragStart) => {
setValue(prev => !prev);
};
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',
// });
};
return {
dragState,
setDragState,
onDragStart,
onDragEnd,
};
};
export const [ServicesDndContextProvider, useServicesDndContext] =
makeContext<ServicesDndContextState>(
useServiceDndContextState,
"ServicesDnd"
);

View File

@ -0,0 +1,7 @@
enum ServiceDragState {
DRAG_ENDED,
DRAG_SERVICE,
DRAG_CATEGORY,
}
export default ServiceDragState;

View File

@ -2,6 +2,7 @@ import { Suspense } from "react";
import { Center, Loader } from "@mantine/core";
import PageBody from "@/app/services/components/shared/PageBody/PageBody";
import { ServicesContextProvider } from "@/app/services/contexts/ServicesContext";
import { ServicesDndContextProvider } from "@/app/services/contexts/ServicesDndContext";
import PageContainer from "@/components/layout/PageContainer/PageContainer";
export default async function ServicesPage() {
@ -13,9 +14,11 @@ export default async function ServicesPage() {
</Center>
}>
<ServicesContextProvider>
<PageContainer>
<PageBody />
</PageContainer>
<ServicesDndContextProvider>
<PageContainer>
<PageBody />
</PageContainer>
</ServicesDndContextProvider>
</ServicesContextProvider>
</Suspense>
);

View File

@ -0,0 +1,26 @@
"use client";
import { FC, ReactNode } from "react";
import {
Droppable as DroppablePangea,
DroppableProps,
} from "@hello-pangea/dnd";
type Props = Omit<DroppableProps, "children"> & {
children: ReactNode;
};
const Droppable: FC<Props> = ({ children, ...restProps }) => (
<DroppablePangea {...restProps}>
{provided => (
<div
{...provided.droppableProps}
ref={provided.innerRef}>
{children}
{provided.placeholder}
</div>
)}
</DroppablePangea>
);
export default Droppable;