feat: services table, base segmented control
This commit is contained in:
@ -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>
|
||||
);
|
||||
|
||||
@ -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"}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -0,0 +1,6 @@
|
||||
import { ServiceCategorySchema, ServiceSchema } from "@/lib/client";
|
||||
|
||||
export type GroupedServices = {
|
||||
category: ServiceCategorySchema;
|
||||
services: ServiceSchema[];
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
39
src/app/services/hooks/useCategoriesActions.ts
Normal file
39
src/app/services/hooks/useCategoriesActions.ts
Normal 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;
|
||||
39
src/app/services/hooks/useServicesActions.ts
Normal file
39
src/app/services/hooks/useServicesActions.ts
Normal 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;
|
||||
64
src/app/services/modals/ServiceCategoryEditorModal.tsx
Normal file
64
src/app/services/modals/ServiceCategoryEditorModal.tsx
Normal 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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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";
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||
|
||||
export type BaseSegmentedControlProps<T> = Omit<
|
||||
SegmentedControlProps,
|
||||
"onChange" | "value" | "data"
|
||||
> & {
|
||||
onChange?: (value: T) => void;
|
||||
value?: T;
|
||||
data: { label: string; value: T }[];
|
||||
};
|
||||
|
||||
const BaseSegmentedControl = <T extends string | number>(
|
||||
props: BaseSegmentedControlProps<T>
|
||||
) => {
|
||||
const handleChange = (value: string) => {
|
||||
const numValue = Number(value);
|
||||
const convertedValue = isNaN(numValue) ? (value as T) : (numValue as T);
|
||||
props.onChange?.(convertedValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<SegmentedControl
|
||||
{...props}
|
||||
onChange={handleChange}
|
||||
value={String(props.value)}
|
||||
data={props.data.map(item => ({
|
||||
...item,
|
||||
value: item.value.toString(),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseSegmentedControl;
|
||||
@ -19,6 +19,7 @@ import {
|
||||
createProduct,
|
||||
createProject,
|
||||
createService,
|
||||
createServiceCategory,
|
||||
createServicesKit,
|
||||
createStatus,
|
||||
deleteBoard,
|
||||
@ -29,6 +30,7 @@ import {
|
||||
deleteProduct,
|
||||
deleteProject,
|
||||
deleteService,
|
||||
deleteServiceCategory,
|
||||
deleteServicesKit,
|
||||
deleteStatus,
|
||||
duplicateProductServices,
|
||||
@ -39,6 +41,7 @@ import {
|
||||
getDealServices,
|
||||
getProducts,
|
||||
getProjects,
|
||||
getServiceCategories,
|
||||
getServices,
|
||||
getServicesKits,
|
||||
getStatuses,
|
||||
@ -51,6 +54,7 @@ import {
|
||||
updateProduct,
|
||||
updateProject,
|
||||
updateService,
|
||||
updateServiceCategory,
|
||||
updateServicesKit,
|
||||
updateStatus,
|
||||
type Options,
|
||||
@ -83,6 +87,9 @@ import type {
|
||||
CreateProjectData,
|
||||
CreateProjectError,
|
||||
CreateProjectResponse2,
|
||||
CreateServiceCategoryData,
|
||||
CreateServiceCategoryError,
|
||||
CreateServiceCategoryResponse2,
|
||||
CreateServiceData,
|
||||
CreateServiceError,
|
||||
CreateServiceResponse2,
|
||||
@ -113,6 +120,9 @@ import type {
|
||||
DeleteProjectData,
|
||||
DeleteProjectError,
|
||||
DeleteProjectResponse2,
|
||||
DeleteServiceCategoryData,
|
||||
DeleteServiceCategoryError,
|
||||
DeleteServiceCategoryResponse2,
|
||||
DeleteServiceData,
|
||||
DeleteServiceError,
|
||||
DeleteServiceResponse2,
|
||||
@ -136,6 +146,7 @@ import type {
|
||||
GetProductsError,
|
||||
GetProductsResponse2,
|
||||
GetProjectsData,
|
||||
GetServiceCategoriesData,
|
||||
GetServicesData,
|
||||
GetServicesKitsData,
|
||||
GetStatusesData,
|
||||
@ -161,6 +172,9 @@ import type {
|
||||
UpdateProjectData,
|
||||
UpdateProjectError,
|
||||
UpdateProjectResponse2,
|
||||
UpdateServiceCategoryData,
|
||||
UpdateServiceCategoryError,
|
||||
UpdateServiceCategoryResponse2,
|
||||
UpdateServiceData,
|
||||
UpdateServiceError,
|
||||
UpdateServiceResponse2,
|
||||
@ -1644,6 +1658,135 @@ export const updateServiceMutation = (
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
export const getServiceCategoriesQueryKey = (
|
||||
options?: Options<GetServiceCategoriesData>
|
||||
) => createQueryKey("getServiceCategories", options);
|
||||
|
||||
/**
|
||||
* Get Services Categories
|
||||
*/
|
||||
export const getServiceCategoriesOptions = (
|
||||
options?: Options<GetServiceCategoriesData>
|
||||
) => {
|
||||
return queryOptions({
|
||||
queryFn: async ({ queryKey, signal }) => {
|
||||
const { data } = await getServiceCategories({
|
||||
...options,
|
||||
...queryKey[0],
|
||||
signal,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
queryKey: getServiceCategoriesQueryKey(options),
|
||||
});
|
||||
};
|
||||
|
||||
export const createServiceCategoryQueryKey = (
|
||||
options: Options<CreateServiceCategoryData>
|
||||
) => createQueryKey("createServiceCategory", options);
|
||||
|
||||
/**
|
||||
* Create Service Category
|
||||
*/
|
||||
export const createServiceCategoryOptions = (
|
||||
options: Options<CreateServiceCategoryData>
|
||||
) => {
|
||||
return queryOptions({
|
||||
queryFn: async ({ queryKey, signal }) => {
|
||||
const { data } = await createServiceCategory({
|
||||
...options,
|
||||
...queryKey[0],
|
||||
signal,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
queryKey: createServiceCategoryQueryKey(options),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Service Category
|
||||
*/
|
||||
export const createServiceCategoryMutation = (
|
||||
options?: Partial<Options<CreateServiceCategoryData>>
|
||||
): UseMutationOptions<
|
||||
CreateServiceCategoryResponse2,
|
||||
AxiosError<CreateServiceCategoryError>,
|
||||
Options<CreateServiceCategoryData>
|
||||
> => {
|
||||
const mutationOptions: UseMutationOptions<
|
||||
CreateServiceCategoryResponse2,
|
||||
AxiosError<CreateServiceCategoryError>,
|
||||
Options<CreateServiceCategoryData>
|
||||
> = {
|
||||
mutationFn: async localOptions => {
|
||||
const { data } = await createServiceCategory({
|
||||
...options,
|
||||
...localOptions,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Service Category
|
||||
*/
|
||||
export const deleteServiceCategoryMutation = (
|
||||
options?: Partial<Options<DeleteServiceCategoryData>>
|
||||
): UseMutationOptions<
|
||||
DeleteServiceCategoryResponse2,
|
||||
AxiosError<DeleteServiceCategoryError>,
|
||||
Options<DeleteServiceCategoryData>
|
||||
> => {
|
||||
const mutationOptions: UseMutationOptions<
|
||||
DeleteServiceCategoryResponse2,
|
||||
AxiosError<DeleteServiceCategoryError>,
|
||||
Options<DeleteServiceCategoryData>
|
||||
> = {
|
||||
mutationFn: async localOptions => {
|
||||
const { data } = await deleteServiceCategory({
|
||||
...options,
|
||||
...localOptions,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Service Category
|
||||
*/
|
||||
export const updateServiceCategoryMutation = (
|
||||
options?: Partial<Options<UpdateServiceCategoryData>>
|
||||
): UseMutationOptions<
|
||||
UpdateServiceCategoryResponse2,
|
||||
AxiosError<UpdateServiceCategoryError>,
|
||||
Options<UpdateServiceCategoryData>
|
||||
> => {
|
||||
const mutationOptions: UseMutationOptions<
|
||||
UpdateServiceCategoryResponse2,
|
||||
AxiosError<UpdateServiceCategoryError>,
|
||||
Options<UpdateServiceCategoryData>
|
||||
> = {
|
||||
mutationFn: async localOptions => {
|
||||
const { data } = await updateServiceCategory({
|
||||
...options,
|
||||
...localOptions,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
export const getServicesKitsQueryKey = (
|
||||
options?: Options<GetServicesKitsData>
|
||||
) => createQueryKey("getServicesKits", options);
|
||||
|
||||
@ -30,6 +30,9 @@ import type {
|
||||
CreateProjectData,
|
||||
CreateProjectErrors,
|
||||
CreateProjectResponses,
|
||||
CreateServiceCategoryData,
|
||||
CreateServiceCategoryErrors,
|
||||
CreateServiceCategoryResponses,
|
||||
CreateServiceData,
|
||||
CreateServiceErrors,
|
||||
CreateServiceResponses,
|
||||
@ -60,6 +63,9 @@ import type {
|
||||
DeleteProjectData,
|
||||
DeleteProjectErrors,
|
||||
DeleteProjectResponses,
|
||||
DeleteServiceCategoryData,
|
||||
DeleteServiceCategoryErrors,
|
||||
DeleteServiceCategoryResponses,
|
||||
DeleteServiceData,
|
||||
DeleteServiceErrors,
|
||||
DeleteServiceResponses,
|
||||
@ -91,6 +97,8 @@ import type {
|
||||
GetProductsResponses,
|
||||
GetProjectsData,
|
||||
GetProjectsResponses,
|
||||
GetServiceCategoriesData,
|
||||
GetServiceCategoriesResponses,
|
||||
GetServicesData,
|
||||
GetServicesKitsData,
|
||||
GetServicesKitsResponses,
|
||||
@ -122,6 +130,9 @@ import type {
|
||||
UpdateProjectData,
|
||||
UpdateProjectErrors,
|
||||
UpdateProjectResponses,
|
||||
UpdateServiceCategoryData,
|
||||
UpdateServiceCategoryErrors,
|
||||
UpdateServiceCategoryResponses,
|
||||
UpdateServiceData,
|
||||
UpdateServiceErrors,
|
||||
UpdateServiceResponses,
|
||||
@ -151,6 +162,8 @@ import {
|
||||
zCreateProductResponse2,
|
||||
zCreateProjectData,
|
||||
zCreateProjectResponse2,
|
||||
zCreateServiceCategoryData,
|
||||
zCreateServiceCategoryResponse2,
|
||||
zCreateServiceData,
|
||||
zCreateServiceResponse2,
|
||||
zCreateServicesKitData,
|
||||
@ -171,6 +184,8 @@ import {
|
||||
zDeleteProductResponse2,
|
||||
zDeleteProjectData,
|
||||
zDeleteProjectResponse2,
|
||||
zDeleteServiceCategoryData,
|
||||
zDeleteServiceCategoryResponse2,
|
||||
zDeleteServiceData,
|
||||
zDeleteServiceResponse2,
|
||||
zDeleteServicesKitData,
|
||||
@ -193,6 +208,8 @@ import {
|
||||
zGetProductsResponse2,
|
||||
zGetProjectsData,
|
||||
zGetProjectsResponse2,
|
||||
zGetServiceCategoriesData,
|
||||
zGetServiceCategoriesResponse2,
|
||||
zGetServicesData,
|
||||
zGetServicesKitsData,
|
||||
zGetServicesKitsResponse,
|
||||
@ -215,6 +232,8 @@ import {
|
||||
zUpdateProductResponse2,
|
||||
zUpdateProjectData,
|
||||
zUpdateProjectResponse2,
|
||||
zUpdateServiceCategoryData,
|
||||
zUpdateServiceCategoryResponse2,
|
||||
zUpdateServiceData,
|
||||
zUpdateServiceResponse2,
|
||||
zUpdateServicesKitData,
|
||||
@ -1244,6 +1263,106 @@ export const updateService = <ThrowOnError extends boolean = false>(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Services Categories
|
||||
*/
|
||||
export const getServiceCategories = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<GetServiceCategoriesData, ThrowOnError>
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).get<
|
||||
GetServiceCategoriesResponses,
|
||||
unknown,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zGetServiceCategoriesData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zGetServiceCategoriesResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/service-category/",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Service Category
|
||||
*/
|
||||
export const createServiceCategory = <ThrowOnError extends boolean = false>(
|
||||
options: Options<CreateServiceCategoryData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
CreateServiceCategoryResponses,
|
||||
CreateServiceCategoryErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zCreateServiceCategoryData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zCreateServiceCategoryResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/service-category/",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Service Category
|
||||
*/
|
||||
export const deleteServiceCategory = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteServiceCategoryData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<
|
||||
DeleteServiceCategoryResponses,
|
||||
DeleteServiceCategoryErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDeleteServiceCategoryData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDeleteServiceCategoryResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/service-category/{pk}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Service Category
|
||||
*/
|
||||
export const updateServiceCategory = <ThrowOnError extends boolean = false>(
|
||||
options: Options<UpdateServiceCategoryData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).patch<
|
||||
UpdateServiceCategoryResponses,
|
||||
UpdateServiceCategoryErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zUpdateServiceCategoryData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zUpdateServiceCategoryResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/service-category/{pk}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Services Kits
|
||||
*/
|
||||
|
||||
@ -388,6 +388,42 @@ export type CreateProjectSchema = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CreateServiceCategoryRequest
|
||||
*/
|
||||
export type CreateServiceCategoryRequest = {
|
||||
entity: CreateServiceCategorySchema;
|
||||
};
|
||||
|
||||
/**
|
||||
* CreateServiceCategoryResponse
|
||||
*/
|
||||
export type CreateServiceCategoryResponse = {
|
||||
/**
|
||||
* Message
|
||||
*/
|
||||
message: string;
|
||||
entity: ServiceCategorySchema;
|
||||
};
|
||||
|
||||
/**
|
||||
* CreateServiceCategorySchema
|
||||
*/
|
||||
export type CreateServiceCategorySchema = {
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Dealservicerank
|
||||
*/
|
||||
dealServiceRank: string;
|
||||
/**
|
||||
* Productservicerank
|
||||
*/
|
||||
productServiceRank: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CreateServiceRequest
|
||||
*/
|
||||
@ -410,10 +446,6 @@ export type CreateServiceResponse = {
|
||||
* CreateServiceSchema
|
||||
*/
|
||||
export type CreateServiceSchema = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
@ -713,6 +745,16 @@ export type DeleteProjectResponse = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* DeleteServiceCategoryResponse
|
||||
*/
|
||||
export type DeleteServiceCategoryResponse = {
|
||||
/**
|
||||
* Message
|
||||
*/
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* DeleteServiceResponse
|
||||
*/
|
||||
@ -814,6 +856,16 @@ export type GetProjectsResponse = {
|
||||
items: Array<ProjectSchema>;
|
||||
};
|
||||
|
||||
/**
|
||||
* GetServiceCategoriesResponse
|
||||
*/
|
||||
export type GetServiceCategoriesResponse = {
|
||||
/**
|
||||
* Items
|
||||
*/
|
||||
items: Array<ServiceCategorySchema>;
|
||||
};
|
||||
|
||||
/**
|
||||
* GetServicesKitResponse
|
||||
*/
|
||||
@ -1015,10 +1067,6 @@ export type ProjectSchema = {
|
||||
* ServiceCategorySchema
|
||||
*/
|
||||
export type ServiceCategorySchema = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
@ -1031,6 +1079,10 @@ export type ServiceCategorySchema = {
|
||||
* Productservicerank
|
||||
*/
|
||||
productServiceRank: string;
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1059,10 +1111,6 @@ export type ServicePriceRangeSchema = {
|
||||
* ServiceSchema
|
||||
*/
|
||||
export type ServiceSchema = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
@ -1088,6 +1136,10 @@ export type ServiceSchema = {
|
||||
* Lexorank
|
||||
*/
|
||||
lexorank: string;
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1409,6 +1461,45 @@ export type UpdateProjectSchema = {
|
||||
builtInModules?: Array<BuiltInModuleSchemaInput>;
|
||||
};
|
||||
|
||||
/**
|
||||
* UpdateServiceCategoryRequest
|
||||
*/
|
||||
export type UpdateServiceCategoryRequest = {
|
||||
entity: UpdateServiceCategorySchema;
|
||||
};
|
||||
|
||||
/**
|
||||
* UpdateServiceCategoryResponse
|
||||
*/
|
||||
export type UpdateServiceCategoryResponse = {
|
||||
/**
|
||||
* Message
|
||||
*/
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* UpdateServiceCategorySchema
|
||||
*/
|
||||
export type UpdateServiceCategorySchema = {
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Dealservicerank
|
||||
*/
|
||||
dealServiceRank: string;
|
||||
/**
|
||||
* Productservicerank
|
||||
*/
|
||||
productServiceRank: string;
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* UpdateServiceRequest
|
||||
*/
|
||||
@ -1430,10 +1521,6 @@ export type UpdateServiceResponse = {
|
||||
* UpdateServiceSchema
|
||||
*/
|
||||
export type UpdateServiceSchema = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
@ -1459,6 +1546,10 @@ export type UpdateServiceSchema = {
|
||||
* Lexorank
|
||||
*/
|
||||
lexorank: string;
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2767,6 +2858,114 @@ export type UpdateServiceResponses = {
|
||||
export type UpdateServiceResponse2 =
|
||||
UpdateServiceResponses[keyof UpdateServiceResponses];
|
||||
|
||||
export type GetServiceCategoriesData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: "/modules/fulfillment-base/service-category/";
|
||||
};
|
||||
|
||||
export type GetServiceCategoriesResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: GetServiceCategoriesResponse;
|
||||
};
|
||||
|
||||
export type GetServiceCategoriesResponse2 =
|
||||
GetServiceCategoriesResponses[keyof GetServiceCategoriesResponses];
|
||||
|
||||
export type CreateServiceCategoryData = {
|
||||
body: CreateServiceCategoryRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: "/modules/fulfillment-base/service-category/";
|
||||
};
|
||||
|
||||
export type CreateServiceCategoryErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type CreateServiceCategoryError =
|
||||
CreateServiceCategoryErrors[keyof CreateServiceCategoryErrors];
|
||||
|
||||
export type CreateServiceCategoryResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: CreateServiceCategoryResponse;
|
||||
};
|
||||
|
||||
export type CreateServiceCategoryResponse2 =
|
||||
CreateServiceCategoryResponses[keyof CreateServiceCategoryResponses];
|
||||
|
||||
export type DeleteServiceCategoryData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Pk
|
||||
*/
|
||||
pk: number;
|
||||
};
|
||||
query?: never;
|
||||
url: "/modules/fulfillment-base/service-category/{pk}";
|
||||
};
|
||||
|
||||
export type DeleteServiceCategoryErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type DeleteServiceCategoryError =
|
||||
DeleteServiceCategoryErrors[keyof DeleteServiceCategoryErrors];
|
||||
|
||||
export type DeleteServiceCategoryResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: DeleteServiceCategoryResponse;
|
||||
};
|
||||
|
||||
export type DeleteServiceCategoryResponse2 =
|
||||
DeleteServiceCategoryResponses[keyof DeleteServiceCategoryResponses];
|
||||
|
||||
export type UpdateServiceCategoryData = {
|
||||
body: UpdateServiceCategoryRequest;
|
||||
path: {
|
||||
/**
|
||||
* Pk
|
||||
*/
|
||||
pk: number;
|
||||
};
|
||||
query?: never;
|
||||
url: "/modules/fulfillment-base/service-category/{pk}";
|
||||
};
|
||||
|
||||
export type UpdateServiceCategoryErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type UpdateServiceCategoryError =
|
||||
UpdateServiceCategoryErrors[keyof UpdateServiceCategoryErrors];
|
||||
|
||||
export type UpdateServiceCategoryResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: UpdateServiceCategoryResponse;
|
||||
};
|
||||
|
||||
export type UpdateServiceCategoryResponse2 =
|
||||
UpdateServiceCategoryResponses[keyof UpdateServiceCategoryResponses];
|
||||
|
||||
export type GetServicesKitsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@ -119,10 +119,10 @@ export const zProductSchema = z.object({
|
||||
* ServiceCategorySchema
|
||||
*/
|
||||
export const zServiceCategorySchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
dealServiceRank: z.string(),
|
||||
productServiceRank: z.string(),
|
||||
id: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -139,7 +139,6 @@ export const zServicePriceRangeSchema = z.object({
|
||||
* ServiceSchema
|
||||
*/
|
||||
export const zServiceSchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
category: zServiceCategorySchema,
|
||||
price: z.number(),
|
||||
@ -147,6 +146,7 @@ export const zServiceSchema = z.object({
|
||||
priceRanges: z.array(zServicePriceRangeSchema),
|
||||
cost: z.union([z.number(), z.null()]),
|
||||
lexorank: z.string(),
|
||||
id: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -351,11 +351,34 @@ export const zCreateProjectResponse = z.object({
|
||||
entity: zProjectSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServiceCategorySchema
|
||||
*/
|
||||
export const zCreateServiceCategorySchema = z.object({
|
||||
name: z.string(),
|
||||
dealServiceRank: z.string(),
|
||||
productServiceRank: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServiceCategoryRequest
|
||||
*/
|
||||
export const zCreateServiceCategoryRequest = z.object({
|
||||
entity: zCreateServiceCategorySchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServiceCategoryResponse
|
||||
*/
|
||||
export const zCreateServiceCategoryResponse = z.object({
|
||||
message: z.string(),
|
||||
entity: zServiceCategorySchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServiceSchema
|
||||
*/
|
||||
export const zCreateServiceSchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
category: zServiceCategorySchema,
|
||||
price: z.number(),
|
||||
@ -518,6 +541,13 @@ export const zDeleteProjectResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteServiceCategoryResponse
|
||||
*/
|
||||
export const zDeleteServiceCategoryResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteServiceResponse
|
||||
*/
|
||||
@ -597,6 +627,13 @@ export const zGetProjectsResponse = z.object({
|
||||
items: z.array(zProjectSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetServiceCategoriesResponse
|
||||
*/
|
||||
export const zGetServiceCategoriesResponse = z.object({
|
||||
items: z.array(zServiceCategorySchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetServicesKitResponse
|
||||
*/
|
||||
@ -845,11 +882,34 @@ export const zUpdateProjectResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServiceCategorySchema
|
||||
*/
|
||||
export const zUpdateServiceCategorySchema = z.object({
|
||||
name: z.string(),
|
||||
dealServiceRank: z.string(),
|
||||
productServiceRank: z.string(),
|
||||
id: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServiceCategoryRequest
|
||||
*/
|
||||
export const zUpdateServiceCategoryRequest = z.object({
|
||||
entity: zUpdateServiceCategorySchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServiceCategoryResponse
|
||||
*/
|
||||
export const zUpdateServiceCategoryResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServiceSchema
|
||||
*/
|
||||
export const zUpdateServiceSchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
category: zServiceCategorySchema,
|
||||
price: z.number(),
|
||||
@ -857,6 +917,7 @@ export const zUpdateServiceSchema = z.object({
|
||||
priceRanges: z.array(zServicePriceRangeSchema),
|
||||
cost: z.union([z.number(), z.null()]),
|
||||
lexorank: z.string(),
|
||||
id: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -1431,6 +1492,54 @@ export const zUpdateServiceData = z.object({
|
||||
*/
|
||||
export const zUpdateServiceResponse2 = zUpdateServiceResponse;
|
||||
|
||||
export const zGetServiceCategoriesData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zGetServiceCategoriesResponse2 = zGetServiceCategoriesResponse;
|
||||
|
||||
export const zCreateServiceCategoryData = z.object({
|
||||
body: zCreateServiceCategoryRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zCreateServiceCategoryResponse2 = zCreateServiceCategoryResponse;
|
||||
|
||||
export const zDeleteServiceCategoryData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDeleteServiceCategoryResponse2 = zDeleteServiceCategoryResponse;
|
||||
|
||||
export const zUpdateServiceCategoryData = z.object({
|
||||
body: zUpdateServiceCategoryRequest,
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUpdateServiceCategoryResponse2 = zUpdateServiceCategoryResponse;
|
||||
|
||||
export const zGetServicesKitsData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal";
|
||||
import DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal";
|
||||
import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal";
|
||||
import { ServicesKitEditorModal } from "@/app/services/modals";
|
||||
import { ServiceCategoryEditorModal, ServiceEditorModal, ServicesKitEditorModal } from "@/app/services/modals";
|
||||
import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal";
|
||||
import {
|
||||
DealProductEditorModal,
|
||||
@ -24,4 +24,6 @@ export const modals = {
|
||||
duplicateServicesModal: DuplicateServicesModal,
|
||||
servicesKitSelectModal: ServicesKitSelectModal,
|
||||
servicesKitEditorModal: ServicesKitEditorModal,
|
||||
serviceCategoryEditorModal: ServiceCategoryEditorModal,
|
||||
serviceEditorModal: ServiceEditorModal,
|
||||
};
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
import { LexoRank } from "lexorank";
|
||||
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
||||
import {
|
||||
CreateServiceCategorySchema,
|
||||
ServiceCategorySchema,
|
||||
UpdateServiceCategorySchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createServiceCategoryMutation,
|
||||
deleteServiceCategoryMutation,
|
||||
updateServiceCategoryMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
||||
import { getMaxLexorankByKey, getNewLexorank } from "@/utils/lexorank";
|
||||
|
||||
type UseServiceCategoryProps = {
|
||||
queryKey: any[];
|
||||
categories: ServiceCategorySchema[];
|
||||
};
|
||||
|
||||
export type ServiceCategoriesCrud = {
|
||||
onCreate: (category: CreateServiceCategorySchema) => void;
|
||||
onUpdate: (
|
||||
categoryId: number,
|
||||
category: UpdateServiceCategorySchema,
|
||||
onSuccess?: () => void
|
||||
) => void;
|
||||
onDelete: (service: ServiceCategorySchema) => void;
|
||||
};
|
||||
|
||||
export const useServiceCategoriesCrud = ({
|
||||
queryKey,
|
||||
categories,
|
||||
}: UseServiceCategoryProps): ServiceCategoriesCrud => {
|
||||
const getCategoryRank = (serviceType: ServiceType): string => {
|
||||
const lastCategory = getMaxLexorankByKey(
|
||||
categories,
|
||||
serviceType === ServiceType.DEAL_SERVICE
|
||||
? "dealServiceRank"
|
||||
: "productServiceRank"
|
||||
);
|
||||
|
||||
if (!lastCategory) return LexoRank.middle().toString();
|
||||
|
||||
const rank =
|
||||
serviceType === ServiceType.DEAL_SERVICE
|
||||
? lastCategory.dealServiceRank
|
||||
: lastCategory?.productServiceRank;
|
||||
|
||||
return getNewLexorank(LexoRank.parse(rank)).toString();
|
||||
};
|
||||
|
||||
return useCrudOperations<
|
||||
ServiceCategorySchema,
|
||||
UpdateServiceCategorySchema,
|
||||
CreateServiceCategorySchema
|
||||
>({
|
||||
key: "getServiceCategories",
|
||||
queryKey,
|
||||
mutations: {
|
||||
create: createServiceCategoryMutation(),
|
||||
update: updateServiceCategoryMutation(),
|
||||
delete: deleteServiceCategoryMutation(),
|
||||
},
|
||||
getCreateEntity: category => {
|
||||
const dealServiceRank = getCategoryRank(ServiceType.DEAL_SERVICE);
|
||||
const productServiceRank = getCategoryRank(
|
||||
ServiceType.PRODUCT_SERVICE
|
||||
);
|
||||
|
||||
return {
|
||||
dealServiceRank,
|
||||
productServiceRank,
|
||||
name: category.name!,
|
||||
};
|
||||
},
|
||||
getUpdateEntity: (old, update) =>
|
||||
({
|
||||
...old,
|
||||
...update,
|
||||
}) as ServiceCategorySchema,
|
||||
getDeleteConfirmTitle: () => "Удаление категории услуг",
|
||||
});
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
import { LexoRank } from "lexorank";
|
||||
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
||||
import {
|
||||
CreateServiceSchema,
|
||||
@ -9,9 +10,11 @@ import {
|
||||
deleteServiceMutation,
|
||||
updateServiceMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { getMaxByLexorank, getNewLexorank } from "@/utils/lexorank";
|
||||
|
||||
type UseServicesProps = {
|
||||
queryKey: any[];
|
||||
services: ServiceSchema[];
|
||||
};
|
||||
|
||||
export type ServicesCrud = {
|
||||
@ -26,6 +29,7 @@ export type ServicesCrud = {
|
||||
|
||||
export const useServicesCrud = ({
|
||||
queryKey,
|
||||
services,
|
||||
}: UseServicesProps): ServicesCrud => {
|
||||
return useCrudOperations<
|
||||
ServiceSchema,
|
||||
@ -39,6 +43,15 @@ export const useServicesCrud = ({
|
||||
update: updateServiceMutation(),
|
||||
delete: deleteServiceMutation(),
|
||||
},
|
||||
getCreateEntity: service => {
|
||||
const maxRankStr = getMaxByLexorank(services)?.lexorank;
|
||||
const maxRank = maxRankStr ? LexoRank.parse(maxRankStr) : null;
|
||||
|
||||
return {
|
||||
...(service as CreateServiceSchema),
|
||||
lexorank: getNewLexorank(maxRank).toString(),
|
||||
};
|
||||
},
|
||||
getUpdateEntity: (old, update) =>
|
||||
({
|
||||
...old,
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ServiceCategorySchema } from "@/lib/client";
|
||||
import {
|
||||
getServiceCategoriesOptions,
|
||||
getServiceCategoriesQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
export type ServiceCategoriesList = {
|
||||
categories: ServiceCategorySchema[];
|
||||
setCategories: (categories: ServiceCategorySchema[]) => void;
|
||||
refetch: () => void;
|
||||
queryKey: any[];
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const useServiceCategoriesList = (): ServiceCategoriesList => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data, refetch, isLoading } = useQuery(
|
||||
getServiceCategoriesOptions()
|
||||
);
|
||||
|
||||
const queryKey = getServiceCategoriesQueryKey();
|
||||
|
||||
const setCategories = (categories: ServiceCategorySchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: ServiceCategorySchema[] }) => ({
|
||||
...old,
|
||||
items: categories,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
categories: data?.items ?? [],
|
||||
setCategories,
|
||||
refetch,
|
||||
queryKey,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export default useServiceCategoriesList;
|
||||
@ -2,3 +2,8 @@ export enum ServiceType {
|
||||
DEAL_SERVICE,
|
||||
PRODUCT_SERVICE,
|
||||
}
|
||||
|
||||
export enum ServicePriceType {
|
||||
DEFAULT,
|
||||
BY_RANGE,
|
||||
}
|
||||
|
||||
@ -30,6 +30,13 @@ export function getMaxByLexorank<T extends LexorankSortable>(
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user