feat: services table, base segmented control

This commit is contained in:
2025-09-27 18:24:22 +04:00
parent 14140826a7
commit 47533ad7f5
29 changed files with 1489 additions and 44 deletions

View File

@ -1,11 +1,15 @@
"use client";
import { FC } from "react";
import { Box, Group } from "@mantine/core";
import { IconPlus } from "@tabler/icons-react";
import { Box, Group, Text } from "@mantine/core";
import CreateServiceKitButton from "@/app/services/components/desktop/CreateServiceKitButton/CreateServiceKitButton";
import ServiceTabSegmentedControl, {
ServicesTab,
} from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl";
import useCategoriesActions from "@/app/services/hooks/useCategoriesActions";
import useServicesActions from "@/app/services/hooks/useServicesActions";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
type Props = {
serviceTab: ServicesTab;
@ -16,12 +20,25 @@ const ServicesDesktopHeader: FC<Props> = ({
serviceTab,
onServiceTabChange,
}) => {
const { onCreateService } = useServicesActions();
const { onCreateCategory } = useCategoriesActions();
const getTabActions = () => {
switch (serviceTab) {
case ServicesTab.DEAL_SERVICE:
return <Box />;
case ServicesTab.PRODUCT_SERVICE:
return <Box />;
return (
<Group>
<InlineButton onClick={onCreateService}>
<IconPlus />
<Text>Услуга</Text>
</InlineButton>
<InlineButton onClick={onCreateCategory}>
<IconPlus />
<Text>Категория</Text>
</InlineButton>
</Group>
);
case ServicesTab.SERVICES_KITS:
return <CreateServiceKitButton />;
default:
@ -35,8 +52,8 @@ const ServicesDesktopHeader: FC<Props> = ({
justify={"space-between"}>
{getTabActions()}
<ServiceTabSegmentedControl
value={serviceTab.toString()}
onChange={tab => onServiceTabChange(Number(tab))}
value={serviceTab}
onChange={onServiceTabChange}
/>
</Group>
);

View File

@ -16,8 +16,8 @@ const ServicesMobileHeader: FC<Props> = ({
}) => {
return (
<ServiceTabSegmentedControl
value={serviceTab.toString()}
onChange={tab => onServiceTabChange(Number(tab))}
value={serviceTab}
onChange={onServiceTabChange}
w={"100%"}
py={"md"}
px={"sm"}

View File

@ -1,12 +1,15 @@
"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 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();
@ -18,9 +21,11 @@ const PageBody = () => {
const getPageBody = () => {
switch (servicesTab) {
case ServicesTab.PRODUCT_SERVICE:
return <></>;
return (
<ServicesTable serviceType={ServiceType.PRODUCT_SERVICE} />
);
case ServicesTab.DEAL_SERVICE:
return <></>;
return <ServicesTable serviceType={ServiceType.DEAL_SERVICE} />;
case ServicesTab.SERVICES_KITS:
return <ServicesKitsTable />;
default:
@ -29,7 +34,7 @@ const PageBody = () => {
};
return (
<>
<Stack h={"100%"}>
{!isMobile && (
<PageBlock>
<ServicesDesktopHeader
@ -38,7 +43,7 @@ const PageBody = () => {
/>
</PageBlock>
)}
<PageBlock fullHeight>
<PageBlock style={{ flex: 1, minHeight: 0 }}>
<div
style={{
height: "100%",
@ -56,7 +61,7 @@ const PageBody = () => {
</div>
</div>
</PageBlock>
</>
</Stack>
);
};

View File

@ -0,0 +1,24 @@
import { FC } from "react";
import ObjectSelect, {
ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect";
import { ServiceCategorySchema } from "@/lib/client";
import useServiceCategoriesList from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServiceCategoriesList";
type Props = Omit<
ObjectSelectProps<ServiceCategorySchema | null>,
"data" | "getLabelFn" | "getValueFn"
>;
const ServiceCategorySelect: FC<Props> = props => {
const { categories } = useServiceCategoriesList();
return (
<ObjectSelect
data={categories}
{...props}
/>
);
};
export default ServiceCategorySelect;

View File

@ -1,5 +1,7 @@
import { FC } from "react";
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
import BaseSegmentedControl, {
BaseSegmentedControlProps,
} from "@/components/ui/BaseSegmentedControl/BaseSegmentedControl";
export enum ServicesTab {
DEAL_SERVICE,
@ -7,25 +9,25 @@ export enum ServicesTab {
SERVICES_KITS,
}
type Props = Omit<SegmentedControlProps, "data">;
type Props = Omit<BaseSegmentedControlProps<ServicesTab>, "data">;
const data = [
{
label: "Для товара",
value: ServicesTab.PRODUCT_SERVICE.toString(),
value: ServicesTab.PRODUCT_SERVICE,
},
{
label: "Для сделки",
value: ServicesTab.DEAL_SERVICE.toString(),
value: ServicesTab.DEAL_SERVICE,
},
{
label: "Наборы услуг",
value: ServicesTab.SERVICES_KITS.toString(),
value: ServicesTab.SERVICES_KITS,
},
];
const ServiceTabSegmentedControl: FC<Props> = props => (
<SegmentedControl
<BaseSegmentedControl
data={data}
{...props}
/>

View File

@ -1,22 +1,24 @@
import { FC } from "react";
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
import BaseSegmentedControl, {
BaseSegmentedControlProps,
} from "@/components/ui/BaseSegmentedControl/BaseSegmentedControl";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
type Props = Omit<SegmentedControlProps, "data">;
type Props = Omit<BaseSegmentedControlProps<ServiceType>, "data">;
const data = [
{
label: "Для сделки",
value: ServiceType.DEAL_SERVICE.toString(),
value: ServiceType.DEAL_SERVICE,
},
{
label: "Для товара",
value: ServiceType.PRODUCT_SERVICE.toString(),
value: ServiceType.PRODUCT_SERVICE,
},
];
const ServiceTypeSegmentedControl: FC<Props> = props => (
<SegmentedControl
<BaseSegmentedControl
data={data}
{...props}
/>

View File

@ -0,0 +1,76 @@
"use client";
import { FC, useMemo, useState } from "react";
import useServicesInnerTableColumns from "@/app/services/components/shared/ServicesTable/hooks/servicesInnerTableColumns";
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 BaseTable from "@/components/ui/BaseTable/BaseTable";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
type Props = {
serviceType: ServiceType;
};
const ServicesTable: FC<Props> = ({ serviceType }) => {
const { servicesList } = useServicesContext();
const [expandedCategoryIds, setExpandedCategoryIds] = useState<number[]>(
[]
);
const innerColumns = useServicesInnerTableColumns();
const outerColumns = useServicesOuterTableColumns({
expandedCategoryIds,
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 (
<BaseTable
withTableBorder
columns={outerColumns}
records={groupedServices}
verticalSpacing={"md"}
groups={undefined}
idAccessor={"category.id"}
rowExpansion={{
allowMultiple: true,
expanded: {
recordIds: expandedCategoryIds,
onRecordIdsChange: setExpandedCategoryIds,
},
content: ({ record }) => (
<BaseTable
withTableBorder
columns={innerColumns}
records={record.services}
verticalSpacing={"md"}
groups={undefined}
/>
),
}}
/>
);
};
export default ServicesTable;

View File

@ -0,0 +1,46 @@
import { useMemo } from "react";
import { DataTableColumn } from "mantine-datatable";
import { List, Text } from "@mantine/core";
import { ServiceSchema } from "@/lib/client";
const useServicesInnerTableColumns = () => {
const getPriceRow = (service: ServiceSchema) => {
if (service.priceRanges.length === 0) {
return <>{service.price.toLocaleString("ru")}</>;
}
return (
<List>
{service.priceRanges.map(range => (
<List.Item key={range.id}>
<Text>
{`${range.fromQuantity} - ${range.toQuantity}: ${range.price.toLocaleString("ru")}`}
</Text>
</List.Item>
))}
</List>
);
};
return useMemo(
() =>
[
{
accessor: "name",
title: "Название",
},
{
accessor: "price",
title: "Цена",
render: service => getPriceRow(service),
},
{
accessor: "cost",
title: "Себестоимость",
render: service => `${service.cost?.toLocaleString("ru")}`,
},
] as DataTableColumn<ServiceSchema>[],
[]
);
};
export default useServicesInnerTableColumns;

View File

@ -0,0 +1,72 @@
import { useMemo } from "react";
import {
IconChevronDown,
IconChevronsDown,
IconChevronsRight,
IconChevronsUp,
IconChevronUp,
} from "@tabler/icons-react";
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";
type Props = {
expandedCategoryIds: number[];
setExpandedCategoryIds: (ids: number[]) => void;
};
const useServicesOuterTableColumns = ({
expandedCategoryIds,
setExpandedCategoryIds,
}: Props) => {
const { categoriesList } = useServicesContext();
const onExpandAllClick = () => {
if (expandedCategoryIds.length !== categoriesList.categories.length) {
setExpandedCategoryIds(categoriesList.categories.map(c => c.id));
return;
}
setExpandedCategoryIds([]);
};
const getExpandAllIcon = () => {
if (expandedCategoryIds.length === categoriesList.categories.length)
return <IconChevronsUp />;
if (expandedCategoryIds.length === 0) return <IconChevronsDown />;
return <IconChevronsRight />;
};
return useMemo(
() =>
[
{
accessor: "name",
title: (
<Group>
<Box
style={{ cursor: "pointer" }}
onClick={onExpandAllClick}>
{getExpandAllIcon()}
</Box>
Категория
</Group>
),
noWrap: true,
render: ({ category: { id, name } }) => (
<Group key={id}>
{expandedCategoryIds.includes(id) ? (
<IconChevronUp />
) : (
<IconChevronDown />
)}
<Text>{name}</Text>
</Group>
),
},
] as DataTableColumn<GroupedServices>[],
[expandedCategoryIds, categoriesList.categories]
);
};
export default useServicesOuterTableColumns;

View File

@ -0,0 +1,6 @@
import { ServiceCategorySchema, ServiceSchema } from "@/lib/client";
export type GroupedServices = {
category: ServiceCategorySchema;
services: ServiceSchema[];
};

View File

@ -1,6 +1,10 @@
"use client";
import makeContext from "@/lib/contextFactory/contextFactory";
import {
ServiceCategoriesCrud,
useServiceCategoriesCrud,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServiceCategoriesCrud";
import {
ServicesCrud,
useServicesCrud,
@ -9,6 +13,9 @@ 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";
@ -21,6 +28,8 @@ type ServicesContextState = {
servicesCrud: ServicesCrud;
servicesKitList: ServicesKitsList;
servicesKitCrud: ServicesKitsCrud;
categoriesList: ServiceCategoriesList;
categoriesCrud: ServiceCategoriesCrud;
};
const useFulfillmentBaseContextState = (): ServicesContextState => {
@ -30,11 +39,16 @@ const useFulfillmentBaseContextState = (): ServicesContextState => {
const servicesKitList = useServicesKitsList();
const servicesKitCrud = useServicesKitsCrud(servicesKitList);
const categoriesList = useServiceCategoriesList();
const categoriesCrud = useServiceCategoriesCrud(categoriesList);
return {
servicesList,
servicesCrud,
servicesKitList,
servicesKitCrud,
categoriesList,
categoriesCrud,
};
};

View File

@ -0,0 +1,39 @@
import { modals } from "@mantine/modals";
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
import { ServiceCategorySchema } from "@/lib/client";
const useCategoriesActions = () => {
const { categoriesCrud } = useServicesContext();
const onChangeCategory = (category: ServiceCategorySchema) => {
modals.openContextModal({
modal: "serviceCategoryEditorModal",
title: "Создание категории",
withCloseButton: false,
innerProps: {
onChange: value => categoriesCrud.onUpdate(category.id, value),
entity: category,
isEditing: true,
},
});
};
const onCreateCategory = () => {
modals.openContextModal({
modal: "serviceCategoryEditorModal",
title: "Создание категории",
withCloseButton: false,
innerProps: {
onCreate: categoriesCrud.onCreate,
isEditing: false,
},
});
};
return {
onCreateCategory,
onChangeCategory,
};
};
export default useCategoriesActions;

View File

@ -0,0 +1,39 @@
import { modals } from "@mantine/modals";
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
import { ServiceSchema } from "@/lib/client";
const useServicesActions = () => {
const { servicesCrud } = useServicesContext();
const onChangeService = (service: ServiceSchema) => {
modals.openContextModal({
modal: "serviceEditorModal",
title: "Редактирование услуги",
withCloseButton: false,
innerProps: {
onChange: value => servicesCrud.onUpdate(service.id, value),
entity: service,
isEditing: true,
},
});
};
const onCreateService = () => {
modals.openContextModal({
modal: "serviceEditorModal",
title: "Создание услуги",
withCloseButton: false,
innerProps: {
onCreate: servicesCrud.onCreate,
isEditing: false,
},
});
};
return {
onCreateService,
onChangeService,
};
};
export default useServicesActions;

View File

@ -0,0 +1,64 @@
"use client";
import { Flex, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import {
CreateServiceCategorySchema,
ServiceCategorySchema,
UpdateServiceCategorySchema,
} from "@/lib/client";
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
type Props = CreateEditFormProps<
CreateServiceCategorySchema,
UpdateServiceCategorySchema,
ServiceCategorySchema
>;
const ServiceCategoryEditorModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const initialValues = innerProps.isEditing
? innerProps.entity
: {
name: "",
dealServiceRank: "",
productServiceRank: "",
};
const form = useForm<Partial<ServiceCategorySchema>>({
initialValues,
validate: {
name: name =>
(!name || name.trim() === "") &&
"Необходимо ввести название категории",
},
});
const onClose = () => context.closeContextModal(id);
return (
<BaseFormModal
{...innerProps}
form={form}
closeOnSubmit
onClose={onClose}>
<Flex
gap={"xs"}
direction={"column"}>
<TextInput
placeholder={"Введите название категори"}
label={"Название категории"}
{...form.getInputProps("name")}
/>
</Flex>
</BaseFormModal>
);
};
export default ServiceCategoryEditorModal;

View File

@ -0,0 +1,141 @@
"use client";
import { useState } from "react";
import { Fieldset, Flex, NumberInput, Stack, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import ServiceCategorySelect from "@/app/services/components/shared/ServiceCategorySelect/ServiceCategorySelect";
import ServiceTypeSegmentedControl from "@/app/services/components/shared/ServiceTypeSegmentedControl/ServiceTypeSegmentedControl";
import RangePriceInput, {
PriceRangeInputType,
} from "@/app/services/modals/ServiceEditorModal/components/RangePriceInput";
import ServicePriceTypeSegmentedControl from "@/app/services/modals/ServiceEditorModal/components/ServicePriceTypeSegmentedControl";
import {
CreateServiceSchema,
ServiceSchema,
UpdateServiceSchema,
} from "@/lib/client";
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
import {
ServicePriceType,
ServiceType,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
type Props = CreateEditFormProps<
CreateServiceSchema,
UpdateServiceSchema,
ServiceSchema
>;
const ServiceEditorModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const [priceType, setPriceType] = useState<ServicePriceType>(
ServicePriceType.DEFAULT
);
const initialValues = innerProps.isEditing
? innerProps.entity
: {
name: "",
price: 0,
cost: 0,
serviceType: ServiceType.DEAL_SERVICE,
priceRanges: [],
lexorank: "",
};
const form = useForm<Partial<ServiceSchema>>({
initialValues,
validate: {
name: name =>
(!name || name.trim() === "") &&
"Необходимо ввести название услуги",
category: category => !category && "Необходимо выбрать категорию",
priceRanges: (value, values) =>
(!value || value.length === 0) &&
(!values.price || values.price <= 0) &&
"Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги",
price: (value, values) =>
(!value || value === 0) &&
(!values.priceRanges || values.priceRanges.length === 0) &&
"Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги",
cost: cost => (!cost || cost < 0) && "Введите себестоимость",
},
});
const getPriceBody = () => {
if (priceType === ServicePriceType.DEFAULT)
return (
<NumberInput
placeholder={"Введите стоимость услуги"}
label={"Cтоимость услуги"}
hideControls
{...form.getInputProps("price")}
/>
);
return (
<RangePriceInput
{...(form.getInputProps("priceRanges") as PriceRangeInputType)}
/>
);
};
const onCancel = () => context.closeContextModal(id);
return (
<BaseFormModal
{...innerProps}
closeOnSubmit
form={form}
onClose={onCancel}>
<Fieldset
legend={"Общие параметры"}
bdrs={"lg"}>
<Stack>
<ServiceCategorySelect
placeholder={"Выберите категорию"}
label={"Категория услуги"}
{...form.getInputProps("category")}
/>
<TextInput
placeholder={"Введите название услуги"}
label={"Название услуги"}
{...form.getInputProps("name")}
/>
<NumberInput
placeholder={"Введите себестоимость услуги"}
label={"Себестоимость услуги"}
hideControls
{...form.getInputProps("cost")}
allowNegative={false}
/>
<ServiceTypeSegmentedControl
{...form.getInputProps("serviceType")}
/>
</Stack>
</Fieldset>
<Fieldset
legend={"Стоимость"}
bdrs={"lg"}>
<Flex
direction={"column"}
gap={"xs"}
justify={"center"}>
<ServicePriceTypeSegmentedControl
value={priceType}
onChange={setPriceType}
/>
{getPriceBody()}
</Flex>
</Fieldset>
</BaseFormModal>
);
};
export default ServiceEditorModal;

View File

@ -0,0 +1,105 @@
import { FC } from "react";
import { IconTrash } from "@tabler/icons-react";
import { isNumber } from "lodash";
import { ActionIcon, Flex, Input, NumberInput, rem } from "@mantine/core";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
import { ServicePriceRangeSchema } from "@/lib/client";
import BaseFormInputProps from "@/utils/baseFormInputProps";
export type PriceRangeInputType = BaseFormInputProps<ServicePriceRangeSchema[]>;
const RangePriceInput: FC<PriceRangeInputType> = props => {
const onAddRange = () => {
const newRange = {
fromQuantity: 0,
toQuantity: 0,
price: 0,
id: null,
};
props.onChange([...props.value, newRange]);
};
const onDeleteRange = (idx: number) => {
const newRanges = props.value.filter((_, i) => i !== idx);
props.onChange(newRanges);
};
const onChangeRange = (
idx: number,
values: Partial<ServicePriceRangeSchema>
) => {
const newRanges = props.value.map((item, i) =>
i === idx
? {
...item,
...values,
}
: item
);
props.onChange(newRanges);
};
return (
<Input.Wrapper
error={props.error}
label={"Диапазон цен"}>
<Flex
direction={"column"}
gap={"xs"}>
{props.value.map((range, idx) => (
<Flex
key={idx}
gap={rem(10)}
align={"flex-end"}>
<ActionIcon
onClick={() => onDeleteRange(idx)}
variant={"default"}>
<IconTrash />
</ActionIcon>
<NumberInput
label={"От количества"}
placeholder={"От"}
hideControls
value={range.fromQuantity}
onChange={val =>
val &&
isNumber(val) &&
onChangeRange(idx, { fromQuantity: val })
}
allowNegative={false}
/>
<NumberInput
label={"До количества"}
placeholder={"До"}
hideControls
value={range.toQuantity}
onChange={val =>
val &&
isNumber(val) &&
onChangeRange(idx, { toQuantity: val })
}
allowNegative={false}
/>
<NumberInput
label={"Цена"}
placeholder={"Цена"}
hideControls
value={range.price}
onChange={val =>
val &&
isNumber(val) &&
onChangeRange(idx, { price: val })
}
allowNegative={false}
/>
</Flex>
))}
<InlineButton onClick={onAddRange}>
Добавить диапазон
</InlineButton>
</Flex>
</Input.Wrapper>
);
};
export default RangePriceInput;

View File

@ -0,0 +1,33 @@
import { FC } from "react";
import BaseSegmentedControl, {
BaseSegmentedControlProps,
} from "@/components/ui/BaseSegmentedControl/BaseSegmentedControl";
export enum ServicePriceType {
DEFAULT,
BY_RANGE,
}
type Props = Omit<BaseSegmentedControlProps<ServicePriceType>, "data">;
const ServicePriceTypeSegmentedControl: FC<Props> = props => {
const data = [
{
label: "По умолчанию",
value: ServicePriceType.DEFAULT,
},
{
label: "По диапазону",
value: ServicePriceType.BY_RANGE,
},
];
return (
<BaseSegmentedControl
data={data}
{...props}
/>
);
};
export default ServicePriceTypeSegmentedControl;

View File

@ -1 +1,3 @@
export { default as ServicesKitEditorModal } from "@/app/services/modals/ServicesKitEditorModal";
export { default as ServiceCategoryEditorModal } from "@/app/services/modals/ServiceCategoryEditorModal";
export { default as ServiceEditorModal } from "@/app/services/modals/ServiceEditorModal/ServiceEditorModal";