feat: actions for services and categories

This commit is contained in:
2025-09-28 12:46:57 +04:00
parent 47533ad7f5
commit 61f0a9069b
12 changed files with 178 additions and 88 deletions

View File

@ -43,7 +43,9 @@ const PageBody = () => {
/>
</PageBlock>
)}
<PageBlock style={{ flex: 1, minHeight: 0 }}>
<PageBlock
style={{ flex: 1, minHeight: 0 }}
fullScreenMobile>
<div
style={{
height: "100%",

View File

@ -1,9 +1,6 @@
import { useMemo } from "react";
import { IconEdit, IconTrash } from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { Flex } from "@mantine/core";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import useIsMobile from "@/hooks/utils/useIsMobile";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import { ServicesKitSchema } from "@/lib/client";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
@ -13,8 +10,6 @@ type Props = {
};
const useServicesKitsTableColumns = ({ onDelete, onChange }: Props) => {
const isMobile = useIsMobile();
return useMemo(
() =>
[
@ -25,19 +20,10 @@ const useServicesKitsTableColumns = ({ onDelete, onChange }: Props) => {
textAlign: "center",
width: "0%",
render: kit => (
<Flex gap={isMobile ? "xs" : "md"}>
<ActionIconWithTip
onClick={() => onChange(kit)}
tipLabel={"Редактировать"}>
<IconEdit />
</ActionIconWithTip>
<ActionIconWithTip
color={"red"}
onClick={() => onDelete(kit)}
tipLabel={"Удалить"}>
<IconTrash />
</ActionIconWithTip>
</Flex>
<UpdateDeleteTableActions
onDelete={() => onDelete(kit)}
onChange={() => onChange(kit)}
/>
),
},
{

View File

@ -6,6 +6,7 @@ import useServicesOuterTableColumns from "@/app/services/components/shared/Servi
import { GroupedServices } from "@/app/services/components/shared/ServicesTable/types/GroupedServices";
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
type Props = {
@ -14,6 +15,7 @@ type Props = {
const ServicesTable: FC<Props> = ({ serviceType }) => {
const { servicesList } = useServicesContext();
const isMobile = useIsMobile();
const [expandedCategoryIds, setExpandedCategoryIds] = useState<number[]>(
[]
@ -53,6 +55,9 @@ const ServicesTable: FC<Props> = ({ serviceType }) => {
verticalSpacing={"md"}
groups={undefined}
idAccessor={"category.id"}
styles={{
header: { zIndex: 1000 },
}}
rowExpansion={{
allowMultiple: true,
expanded: {
@ -69,6 +74,9 @@ const ServicesTable: FC<Props> = ({ serviceType }) => {
/>
),
}}
style={{
marginInline: isMobile ? "var(--mantine-spacing-xs)" : "0",
}}
/>
);
};

View File

@ -1,9 +1,19 @@
"use client";
import { useMemo } from "react";
import { DataTableColumn } from "mantine-datatable";
import { 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";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { ServiceSchema } from "@/lib/client";
const useServicesInnerTableColumns = () => {
const { onChangeService } = useServicesActions();
const { servicesCrud } = useServicesContext();
const isMobile = useIsMobile();
const getPriceRow = (service: ServiceSchema) => {
if (service.priceRanges.length === 0) {
return <>{service.price.toLocaleString("ru")}</>;
@ -35,11 +45,25 @@ const useServicesInnerTableColumns = () => {
},
{
accessor: "cost",
title: "Себестоимость",
title: isMobile ? "Себестоим." : "Себестоимость",
render: service => `${service.cost?.toLocaleString("ru")}`,
},
{
accessor: "actions",
title: isMobile ? "" : "Действия",
sortable: false,
textAlign: "center",
width: "0%",
render: service => (
<UpdateDeleteTableActions
onDelete={() => servicesCrud.onDelete(service)}
onChange={() => onChangeService(service)}
dotsForMobile
/>
),
},
] as DataTableColumn<ServiceSchema>[],
[]
[isMobile]
);
};

View File

@ -10,6 +10,8 @@ import { DataTableColumn } from "mantine-datatable";
import { Box, 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";
type Props = {
expandedCategoryIds: number[];
@ -20,18 +22,19 @@ const useServicesOuterTableColumns = ({
expandedCategoryIds,
setExpandedCategoryIds,
}: Props) => {
const { categoriesList } = useServicesContext();
const { onChangeCategory } = useCategoriesActions();
const { categoriesCrud, categories } = useServicesContext();
const onExpandAllClick = () => {
if (expandedCategoryIds.length !== categoriesList.categories.length) {
setExpandedCategoryIds(categoriesList.categories.map(c => c.id));
if (expandedCategoryIds.length !== categories.length) {
setExpandedCategoryIds(categories.map(c => c.id));
return;
}
setExpandedCategoryIds([]);
};
const getExpandAllIcon = () => {
if (expandedCategoryIds.length === categoriesList.categories.length)
if (expandedCategoryIds.length === categories.length)
return <IconChevronsUp />;
if (expandedCategoryIds.length === 0) return <IconChevronsDown />;
return <IconChevronsRight />;
@ -64,8 +67,21 @@ const useServicesOuterTableColumns = ({
</Group>
),
},
{
accessor: "actions",
title: "Действия",
sortable: false,
textAlign: "center",
width: "0%",
render: ({ category }) => (
<UpdateDeleteTableActions
onDelete={() => categoriesCrud.onDelete(category)}
onChange={() => onChangeCategory(category)}
/>
),
},
] as DataTableColumn<GroupedServices>[],
[expandedCategoryIds, categoriesList.categories]
[expandedCategoryIds, categories]
);
};

View File

@ -1,5 +1,6 @@
"use client";
import { ServiceCategorySchema } from "@/lib/client";
import makeContext from "@/lib/contextFactory/contextFactory";
import {
ServiceCategoriesCrud,
@ -13,9 +14,6 @@ import {
ServicesKitsCrud,
useServicesKitsCrud,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServicesKitsCrud";
import useServiceCategoriesList, {
ServiceCategoriesList,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServiceCategoriesList";
import useServicesKitsList, {
ServicesKitsList,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesKitsList";
@ -28,8 +26,8 @@ type ServicesContextState = {
servicesCrud: ServicesCrud;
servicesKitList: ServicesKitsList;
servicesKitCrud: ServicesKitsCrud;
categoriesList: ServiceCategoriesList;
categoriesCrud: ServiceCategoriesCrud;
categories: ServiceCategorySchema[];
};
const useFulfillmentBaseContextState = (): ServicesContextState => {
@ -39,16 +37,19 @@ const useFulfillmentBaseContextState = (): ServicesContextState => {
const servicesKitList = useServicesKitsList();
const servicesKitCrud = useServicesKitsCrud(servicesKitList);
const categoriesList = useServiceCategoriesList();
const categoriesCrud = useServiceCategoriesCrud(categoriesList);
const categories = [...new Set(servicesList.services.map(s => s.category))];
const categoriesCrud = useServiceCategoriesCrud({
queryKey: servicesList.queryKey,
categories,
});
return {
servicesList,
servicesCrud,
servicesKitList,
servicesKitCrud,
categoriesList,
categoriesCrud,
categories,
};
};

View File

@ -1,7 +1,8 @@
import { FC } from "react";
import { IconTrash } from "@tabler/icons-react";
import { isNumber } from "lodash";
import { ActionIcon, Flex, Input, NumberInput, rem } from "@mantine/core";
import { Flex, Input, NumberInput, rem } from "@mantine/core";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
import { ServicePriceRangeSchema } from "@/lib/client";
import BaseFormInputProps from "@/utils/baseFormInputProps";
@ -51,11 +52,9 @@ const RangePriceInput: FC<PriceRangeInputType> = props => {
key={idx}
gap={rem(10)}
align={"flex-end"}>
<ActionIcon
onClick={() => onDeleteRange(idx)}
variant={"default"}>
<ActionIconWithTip onClick={() => onDeleteRange(idx)}>
<IconTrash />
</ActionIcon>
</ActionIconWithTip>
<NumberInput
label={"От количества"}
placeholder={"От"}

View File

@ -0,0 +1,84 @@
import React, { CSSProperties, FC } from "react";
import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react";
import { Box, Flex, Group, Menu, Text } from "@mantine/core";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import ThemeIcon from "@/components/ui/ThemeIcon/ThemeIcon";
import useIsMobile from "@/hooks/utils/useIsMobile";
type Props = {
onChange: () => void;
onDelete: () => void;
dotsForMobile?: boolean;
style?: CSSProperties;
};
const UpdateDeleteTableActions: FC<Props> = ({
onChange,
onDelete,
style,
dotsForMobile = false,
}) => {
const isMobile = useIsMobile();
if (dotsForMobile && isMobile) {
return (
<Menu>
<Menu.Target>
<Box onClick={e => e.stopPropagation()}>
<ThemeIcon size={"sm"}>
<IconDotsVertical />
</ThemeIcon>
</Box>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
onClick={e => {
e.stopPropagation();
onChange();
}}>
<Group wrap={"nowrap"}>
<IconEdit />
<Text>Редактировать</Text>
</Group>
</Menu.Item>
<Menu.Item
onClick={e => {
e.stopPropagation();
onDelete();
}}>
<Group wrap={"nowrap"}>
<IconTrash />
<Text>Удалить</Text>
</Group>
</Menu.Item>
</Menu.Dropdown>
</Menu>
);
}
return (
<Flex
gap={isMobile ? "xs" : "md"}
style={style}>
<ActionIconWithTip
onClick={e => {
e.stopPropagation();
onChange();
}}
tipLabel={"Редактировать"}>
<IconEdit />
</ActionIconWithTip>
<ActionIconWithTip
color={"red"}
onClick={e => {
e.stopPropagation();
onDelete();
}}
tipLabel={"Удалить"}>
<IconTrash />
</ActionIconWithTip>
</Flex>
);
};
export default UpdateDeleteTableActions;

View File

@ -1,8 +1,7 @@
import { useMemo } from "react";
import { IconEdit, IconTrash } from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { Box, Flex, Text } from "@mantine/core";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import { Box, Text } from "@mantine/core";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import { ProductServiceSchema } from "@/lib/client";
type Props = {
@ -32,21 +31,11 @@ const useProductServicesTableColumns = ({
textAlign: "center",
width: "0%",
render: dealProductService => (
<Flex
gap="md"
px={"sm"}
my={"sm"}>
<ActionIconWithTip
tipLabel={"Удалить услугу"}
onClick={() => onDelete(dealProductService)}>
<IconTrash />
</ActionIconWithTip>
<ActionIconWithTip
tipLabel="Редактировать"
onClick={() => onChange(dealProductService)}>
<IconEdit />
</ActionIconWithTip>
</Flex>
<UpdateDeleteTableActions
onDelete={() => onDelete(dealProductService)}
onChange={() => onChange(dealProductService)}
style={{ padding: "var(--mantine-spacing-xs)" }}
/>
),
},
{

View File

@ -1,7 +1,6 @@
import { useMemo } from "react";
import { IconEdit, IconTrash } from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { ActionIcon, Flex } from "@mantine/core";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import { DealServiceSchema } from "@/lib/client";
type Props = {
@ -36,19 +35,10 @@ const useDealServicesTableColumns = ({ onChange, onDelete }: Props) => {
textAlign: "center",
width: "0%",
render: dealService => (
<Flex gap={"sm"}>
<ActionIcon
variant={"subtle"}
c={"red"}
onClick={() => onDelete(dealService)}>
<IconTrash />
</ActionIcon>
<ActionIcon
variant={"subtle"}
onClick={() => onChange(dealService)}>
<IconEdit />
</ActionIcon>
</Flex>
<UpdateDeleteTableActions
onDelete={() => onDelete(dealService)}
onChange={() => onChange(dealService)}
/>
),
},
] as DataTableColumn<DealServiceSchema>[],

View File

@ -1,7 +1,7 @@
import { useMemo } from "react";
import { IconEdit, IconTrash } from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { ActionIcon, Flex, Text } from "@mantine/core";
import { Text } from "@mantine/core";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import { ProductServiceSchema } from "@/lib/client";
type Props = {
@ -31,19 +31,10 @@ const useProductServicesTableColumns = ({
textAlign: "center",
width: "0%",
render: dealProductService => (
<Flex gap="md">
<ActionIcon
variant={"subtle"}
color={"red"}
onClick={() => onDelete(dealProductService)}>
<IconTrash />
</ActionIcon>
<ActionIcon
variant={"subtle"}
onClick={() => onChange(dealProductService)}>
<IconEdit />
</ActionIcon>
</Flex>
<UpdateDeleteTableActions
onDelete={() => onDelete(dealProductService)}
onChange={() => onChange(dealProductService)}
/>
),
},
{

View File

@ -55,7 +55,7 @@ export const useServiceCategoriesCrud = ({
UpdateServiceCategorySchema,
CreateServiceCategorySchema
>({
key: "getServiceCategories",
key: "getServices",
queryKey,
mutations: {
create: createServiceCategoryMutation(),