feat: price by category
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
|
||||
import { ServicePriceCategorySchema } from "../../../../client";
|
||||
import { FC } from "react";
|
||||
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import useServicePriceCategoryTableColumns from "./columns.tsx";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
|
||||
type Props = CRUDTableProps<ServicePriceCategorySchema>
|
||||
|
||||
const ServicePriceCategoryTable: FC<Props> = ({ items, onChange, onDelete }) => {
|
||||
const columns = useServicePriceCategoryTableColumns();
|
||||
return (
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={{
|
||||
enableRowActions: true,
|
||||
renderRowActions: ({ row }) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onChange && onChange(row.original)}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => onDelete && onDelete(row.original)} variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<ServicePriceCategorySchema>}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicePriceCategoryTable;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { ServicePriceCategorySchema } from "../../../../client";
|
||||
|
||||
const useServicePriceCategoryTableColumns = () => {
|
||||
|
||||
return useMemo<MRT_ColumnDef<ServicePriceCategorySchema>[]>(() => [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Название",
|
||||
enableColumnActions: false,
|
||||
enableSorting: false,
|
||||
},
|
||||
], []);
|
||||
};
|
||||
|
||||
export default useServicePriceCategoryTableColumns;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import type { ServiceCategoryPriceSchema, ServicePriceCategorySchema } from "../../../../client";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { Flex, NumberInput, rem } from "@mantine/core";
|
||||
import ServicePriceCategorySelect
|
||||
from "../../../../components/Selects/ServicePriceCategorySelect/ServicePriceCategorySelect.tsx";
|
||||
|
||||
export type PriceCategoryInputProps = BaseFormInputProps<ServiceCategoryPriceSchema[]>
|
||||
|
||||
const PriceCategoryInput: FC<PriceCategoryInputProps> = (props: PriceCategoryInputProps) => {
|
||||
const [innerState, setInnerState] = useState<ServiceCategoryPriceSchema[]>(props.value || []);
|
||||
const [category, setCategory] = useState<ServicePriceCategorySchema | undefined>(undefined);
|
||||
|
||||
const getValue = (): number | undefined | string => {
|
||||
if (category === undefined) return undefined;
|
||||
const value = innerState.find(item => item.category.id === category.id);
|
||||
|
||||
return value?.price || "";
|
||||
};
|
||||
const handleChange = (value: number | string) => {
|
||||
// remove value if is string
|
||||
if (typeof value === "string") {
|
||||
setInnerState(innerState.filter(item => item.category.id !== category?.id));
|
||||
return;
|
||||
}
|
||||
const newValue = {
|
||||
category: category as ServicePriceCategorySchema,
|
||||
price: value,
|
||||
};
|
||||
const newInnerState = innerState.filter(item => item.category.id !== category?.id);
|
||||
setInnerState([...newInnerState, newValue]);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (props.value === innerState) return;
|
||||
props.onChange(innerState);
|
||||
}, [innerState]);
|
||||
return (
|
||||
<Flex direction={"column"} gap={rem(10)}>
|
||||
<ServicePriceCategorySelect
|
||||
label={"Категория цены"}
|
||||
placeholder={"Выберите категорию цены"}
|
||||
onChange={setCategory}
|
||||
/>
|
||||
<NumberInput
|
||||
label={"Цена"}
|
||||
placeholder={"Введите цену"}
|
||||
value={getValue()}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default PriceCategoryInput;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||
import { FC } from "react";
|
||||
import { omit } from "lodash";
|
||||
|
||||
export enum ServicePriceType {
|
||||
DEFAULT,
|
||||
BY_RANGE,
|
||||
BY_CATEGORY,
|
||||
}
|
||||
|
||||
type RestProps = {
|
||||
onChange: (value: ServicePriceType) => void;
|
||||
value?: ServicePriceType;
|
||||
}
|
||||
type Props = Omit<SegmentedControlProps, "data" | "value" | "onChange"> & RestProps;
|
||||
const ServicePriceTypeSegmentedControl: FC<Props> = (props) => {
|
||||
const { onChange, value } = props;
|
||||
|
||||
const data = [
|
||||
{
|
||||
label: "По умолчанию",
|
||||
value: ServicePriceType.DEFAULT.toString(),
|
||||
},
|
||||
{
|
||||
label: "По диапазону",
|
||||
value: ServicePriceType.BY_RANGE.toString(),
|
||||
},
|
||||
{
|
||||
label: "По категории",
|
||||
value: ServicePriceType.BY_CATEGORY.toString(),
|
||||
},
|
||||
];
|
||||
const handleChange = (value: string) => {
|
||||
onChange(Number(value));
|
||||
|
||||
};
|
||||
|
||||
const restProps = omit(props, ["onChange", "value"]);
|
||||
return (
|
||||
<SegmentedControl
|
||||
data={data}
|
||||
value={value?.toString()}
|
||||
onChange={handleChange}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicePriceTypeSegmentedControl;
|
||||
@@ -1,27 +1,33 @@
|
||||
import {FC} from "react";
|
||||
import {SegmentedControl, SegmentedControlProps} from "@mantine/core";
|
||||
import { FC } from "react";
|
||||
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||
|
||||
export enum ServicesTab {
|
||||
DEAL_SERVICE,
|
||||
PRODUCT_SERVICE,
|
||||
SERVICES_KITS
|
||||
SERVICES_KITS,
|
||||
SERVICES_PRICE_CATEGORIES
|
||||
}
|
||||
|
||||
|
||||
type Props = Omit<SegmentedControlProps, 'data'>;
|
||||
type Props = Omit<SegmentedControlProps, "data">;
|
||||
const data = [
|
||||
{
|
||||
label: 'Для товара',
|
||||
value: ServicesTab.PRODUCT_SERVICE.toString()
|
||||
label: "Для товара",
|
||||
value: ServicesTab.PRODUCT_SERVICE.toString(),
|
||||
},
|
||||
{
|
||||
label: 'Для сделки',
|
||||
value: ServicesTab.DEAL_SERVICE.toString()
|
||||
label: "Для сделки",
|
||||
value: ServicesTab.DEAL_SERVICE.toString(),
|
||||
},
|
||||
{
|
||||
label: 'Наборы услуг',
|
||||
value: ServicesTab.SERVICES_KITS.toString()
|
||||
}
|
||||
]
|
||||
label: "Наборы услуг",
|
||||
value: ServicesTab.SERVICES_KITS.toString(),
|
||||
},
|
||||
{
|
||||
label: "Категории цен",
|
||||
value: ServicesTab.SERVICES_PRICE_CATEGORIES.toString(),
|
||||
},
|
||||
];
|
||||
const ServiceTypeSegmentedControl: FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
@@ -29,6 +35,6 @@ const ServiceTypeSegmentedControl: FC<Props> = (props) => {
|
||||
data={data}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default ServiceTypeSegmentedControl
|
||||
);
|
||||
};
|
||||
export default ServiceTypeSegmentedControl;
|
||||
@@ -0,0 +1,10 @@
|
||||
import ObjectList from "../../../hooks/objectList.tsx";
|
||||
import { ServiceService } from "../../../client";
|
||||
|
||||
const useServicePriceCategoriesList = () => ObjectList({
|
||||
queryFn: ServiceService.getAllPriceCategories,
|
||||
getObjectsFn: (response) => response.priceCategories,
|
||||
queryKey: "getAllPriceCategories",
|
||||
});
|
||||
|
||||
export default useServicePriceCategoriesList;
|
||||
@@ -0,0 +1,80 @@
|
||||
import UseObjectState from "../../../types/UseObjectState.ts";
|
||||
import { type ServicePriceCategorySchema, ServiceService } from "../../../client";
|
||||
import useServicePriceCategoriesList from "./useServicePriceCategoriesList.tsx";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||
|
||||
const useServicePriceCategoryState = (): UseObjectState<ServicePriceCategorySchema> => {
|
||||
const { objects, refetch } = useServicePriceCategoriesList();
|
||||
const onCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "servicePriceCategoryForm",
|
||||
title: "Создание категории цен",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate,
|
||||
},
|
||||
});
|
||||
};
|
||||
const onCreate = (values: ServicePriceCategorySchema) => {
|
||||
console.log(ServiceService);
|
||||
ServiceService.createPriceCategory({
|
||||
requestBody: {
|
||||
name: values.name,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
});
|
||||
};
|
||||
const onDelete = (item: ServicePriceCategorySchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление категории",
|
||||
children: "Вы уверены, что хотите удалить категорию?",
|
||||
onConfirm: () => {
|
||||
|
||||
ServiceService.deletePriceCategory({
|
||||
requestBody: {
|
||||
id: item.id,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
const onChange = (item: ServicePriceCategorySchema) => {
|
||||
modals.openContextModal({
|
||||
modal: "servicePriceCategoryForm",
|
||||
title: "Изменение категории цен",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onChange: (values: ServicePriceCategorySchema) => {
|
||||
ServiceService.updatePriceCategory({
|
||||
requestBody: {
|
||||
id: item.id,
|
||||
name: values.name,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
});
|
||||
},
|
||||
element: item,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateClick,
|
||||
onCreate,
|
||||
onDelete,
|
||||
onChange,
|
||||
objects,
|
||||
};
|
||||
};
|
||||
export default useServicePriceCategoryState;
|
||||
57
src/pages/ServicesPage/hooks/useServicesKitsState.tsx
Normal file
57
src/pages/ServicesPage/hooks/useServicesKitsState.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { GetServiceKitSchema, ServiceService } from "../../../client";
|
||||
import { omit } from "lodash";
|
||||
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||
import { modals } from "@mantine/modals";
|
||||
import useServicesKitsList from "./useServicesKitsList.tsx";
|
||||
|
||||
const useServicesKitsState = () => {
|
||||
const { objects: servicesKits, refetch: refetchKits } = useServicesKitsList();
|
||||
|
||||
const onKitCreate = (kit: GetServiceKitSchema) => {
|
||||
ServiceService.createServicesKit({
|
||||
requestBody: {
|
||||
data: {
|
||||
...omit(kit, ["services", "id"]),
|
||||
servicesIds: kit.services.map(service => service.id),
|
||||
},
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
if (!ok) return;
|
||||
await refetchKits();
|
||||
});
|
||||
};
|
||||
|
||||
const onKitCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "serviceKitModalForm",
|
||||
title: "Создание набора услуг",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: onKitCreate,
|
||||
},
|
||||
});
|
||||
};
|
||||
const onKitUpdate = (kit: GetServiceKitSchema) => {
|
||||
ServiceService.updateServicesKit({
|
||||
requestBody: {
|
||||
data: {
|
||||
...omit(kit, ["services"]),
|
||||
servicesIds: kit.services.map(service => service.id),
|
||||
},
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
if (!ok) return;
|
||||
await refetchKits();
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onKitCreateClick,
|
||||
onKitUpdate,
|
||||
servicesKits,
|
||||
};
|
||||
};
|
||||
|
||||
export default useServicesKitsState;
|
||||
89
src/pages/ServicesPage/hooks/useServicesState.tsx
Normal file
89
src/pages/ServicesPage/hooks/useServicesState.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { modals } from "@mantine/modals";
|
||||
import { ServiceCategorySchema, ServiceSchema, ServiceService } from "../../../client";
|
||||
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||
import useServicesList from "./useServicesList.tsx";
|
||||
import { Text } from "@mantine/core";
|
||||
|
||||
const useServicesState = () => {
|
||||
const { services, refetch } = useServicesList();
|
||||
|
||||
const onCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "createService",
|
||||
title: "Создание услуги",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate,
|
||||
},
|
||||
});
|
||||
};
|
||||
const onCreate = (values: ServiceSchema) => {
|
||||
ServiceService.createService({ requestBody: { service: values } })
|
||||
.then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
});
|
||||
};
|
||||
|
||||
const onCreateCategoryClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "createServiceCategory",
|
||||
title: "Создание категории",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: onCategoryCreate,
|
||||
},
|
||||
});
|
||||
};
|
||||
const onCategoryCreate = (category: ServiceCategorySchema) => {
|
||||
ServiceService.createServiceCategory({ requestBody: { category: category } })
|
||||
.then(({ ok, message }) =>
|
||||
notifications.guess(ok, { message: message }));
|
||||
};
|
||||
|
||||
const onServiceDelete = (service: ServiceSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление услуги",
|
||||
children: (<Text>
|
||||
Вы уверены, что хотите удалить услугу "{service.name}"?
|
||||
</Text>),
|
||||
onConfirm: () => {
|
||||
ServiceService.deleteService({ requestBody: { serviceId: service.id } })
|
||||
.then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
});
|
||||
},
|
||||
labels: {
|
||||
confirm: "Удалить",
|
||||
cancel: "Отмена",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onServiceUpdate = (service: ServiceSchema) => {
|
||||
ServiceService
|
||||
.updateService({
|
||||
requestBody: {
|
||||
data: service,
|
||||
},
|
||||
})
|
||||
.then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
services,
|
||||
onCreateClick,
|
||||
onServiceDelete,
|
||||
onServiceUpdate,
|
||||
onCreateCategoryClick,
|
||||
};
|
||||
};
|
||||
|
||||
export default useServicesState;
|
||||
@@ -1,12 +1,17 @@
|
||||
import {ServicePriceRangeSchema, ServiceSchema} from "../../../client";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {ContextModalProps} from "@mantine/modals";
|
||||
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import {NumberInput, TextInput} from "@mantine/core";
|
||||
import { ServicePriceRangeSchema, ServiceSchema } from "../../../client";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import BaseFormModal, { CreateEditFormProps } from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import { Fieldset, Flex, rem, TextInput } from "@mantine/core";
|
||||
import ServiceCategorySelect from "../components/ServiceCategorySelect/ServiceCategorySelect.tsx";
|
||||
import ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx";
|
||||
import ServicePriceInput from "../components/ServicePriceInput/ServicePriceInput.tsx";
|
||||
import {PriceRangeInputType} from "../components/ServicePriceInput/RangePriceInput.tsx";
|
||||
import ServicePriceTypeSegmentedControl, {
|
||||
ServicePriceType,
|
||||
} from "../components/ServicePriceTypeSegmentedControl/ServicePriceTypeSegmentedControl.tsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import RangePriceInput, { PriceRangeInputType } from "../components/ServicePriceInput/RangePriceInput.tsx";
|
||||
import SinglePriceInput from "../components/ServicePriceInput/SinglePriceInput.tsx";
|
||||
import PriceCategoryInput, { PriceCategoryInputProps } from "../components/ServicePriceInput/PriceCategoryInput.tsx";
|
||||
|
||||
type Props = CreateEditFormProps<ServiceSchema>
|
||||
const CreateServiceModal = ({
|
||||
@@ -14,40 +19,61 @@ const CreateServiceModal = ({
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
|
||||
const isEditing = 'onChange' in innerProps;
|
||||
const [priceType, setPriceType] = useState<ServicePriceType>(ServicePriceType.DEFAULT);
|
||||
const isEditing = "onChange" in innerProps;
|
||||
const initialValues: ServiceSchema = isEditing ? innerProps.element : {
|
||||
id: -1,
|
||||
name: '',
|
||||
name: "",
|
||||
price: 0,
|
||||
category: {
|
||||
id: -1,
|
||||
name: ''
|
||||
name: "",
|
||||
},
|
||||
serviceType: -1,
|
||||
priceRanges: [] as ServicePriceRangeSchema[],
|
||||
cost: null
|
||||
}
|
||||
cost: null,
|
||||
categoryPrices: [],
|
||||
};
|
||||
|
||||
const form = useForm<ServiceSchema>({
|
||||
initialValues: initialValues,
|
||||
validate: {
|
||||
name: (name: string) => name.trim() !== '' ? null : "Необходимо ввести название услуги",
|
||||
name: (name: string) => name.trim() !== "" ? null : "Необходимо ввести название услуги",
|
||||
category: (category: {
|
||||
id: number,
|
||||
name: string
|
||||
}) => category.id !== -1 ? null : "Необходимо выбрать категорию",
|
||||
serviceType: (serviceType: number) => serviceType !== -1 ? null : "Необходимо выбрать тип услуги",
|
||||
priceRanges: (value, values) => value.length > 0 || values.price > 0 ? null : "Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги",
|
||||
price: (value, values) => value > 0 || values.priceRanges.length > 0 ? null : "Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги"
|
||||
price: (value, values) => value > 0 || values.priceRanges.length > 0 ? null : "Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги",
|
||||
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
console.log(form.values.categoryPrices);
|
||||
}, [form.values]);
|
||||
const getPriceBody = () => {
|
||||
switch (priceType) {
|
||||
case ServicePriceType.DEFAULT:
|
||||
return <SinglePriceInput
|
||||
placeholder={"Введите стоимость услуги"}
|
||||
label={"Cтоимость услуги"}
|
||||
hideControls
|
||||
{...form.getInputProps("cost")}
|
||||
/>;
|
||||
case ServicePriceType.BY_RANGE:
|
||||
return <RangePriceInput
|
||||
{...form.getInputProps("priceRanges") as PriceRangeInputType}
|
||||
/>;
|
||||
case ServicePriceType.BY_CATEGORY:
|
||||
return <PriceCategoryInput
|
||||
{...form.getInputProps("categoryPrices") as PriceCategoryInputProps}
|
||||
/>;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
};
|
||||
const onCancelClick = () => {
|
||||
context.closeContextModal(id);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
@@ -57,43 +83,39 @@ const CreateServiceModal = ({
|
||||
>
|
||||
<BaseFormModal.Body>
|
||||
<>
|
||||
<ServiceCategorySelect
|
||||
placeholder={"Выберите категорию"}
|
||||
label={"Категория услуги"}
|
||||
{...form.getInputProps('category')}
|
||||
/>
|
||||
<TextInput
|
||||
<Fieldset legend={"Общие параметры"}>
|
||||
<ServiceCategorySelect
|
||||
placeholder={"Выберите категорию"}
|
||||
label={"Категория услуги"}
|
||||
{...form.getInputProps("category")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите название услуги"}
|
||||
label={"Название услуги"}
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
<ServiceTypeSelect
|
||||
placeholder={"Выберите тип услуги"}
|
||||
label={"Тип услуги"}
|
||||
{...form.getInputProps("serviceType")}
|
||||
/>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Стоимость"}>
|
||||
<Flex direction={"column"} gap={rem(10)} justify={"center"}>
|
||||
<ServicePriceTypeSegmentedControl
|
||||
value={priceType}
|
||||
onChange={setPriceType}
|
||||
/>
|
||||
{getPriceBody()}
|
||||
|
||||
</Flex>
|
||||
|
||||
</Fieldset>
|
||||
|
||||
placeholder={"Введите название услуги"}
|
||||
label={"Название услуги"}
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<ServiceTypeSelect
|
||||
placeholder={"Выберите тип услуги"}
|
||||
label={"Тип услуги"}
|
||||
{...form.getInputProps('serviceType')}
|
||||
/>
|
||||
<NumberInput
|
||||
placeholder={"Введите себестоимость услуги"}
|
||||
label={"Себестоимость услуги"}
|
||||
hideControls
|
||||
{...form.getInputProps('cost')}
|
||||
/>
|
||||
<ServicePriceInput
|
||||
singlePriceInputProps={{
|
||||
hideControls: true,
|
||||
label: "Цена за единицу услуги",
|
||||
placeholder: "Введите цену за одну услугу",
|
||||
...form.getInputProps('price'),
|
||||
}}
|
||||
priceRangeInputProps={{
|
||||
...form.getInputProps('priceRanges')
|
||||
} as PriceRangeInputType}
|
||||
/>
|
||||
</>
|
||||
</BaseFormModal.Body>
|
||||
</BaseFormModal>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateServiceModal;
|
||||
38
src/pages/ServicesPage/modals/ServicePriceCategoryForm.tsx
Normal file
38
src/pages/ServicesPage/modals/ServicePriceCategoryForm.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { ServicePriceCategorySchema } from "../../../client";
|
||||
import BaseFormModal, { CreateEditFormProps } from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { TextInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
|
||||
type Props = CreateEditFormProps<ServicePriceCategorySchema>;
|
||||
|
||||
const ServicePriceCategoryForm = ({ context, id, innerProps }: ContextModalProps<Props>) => {
|
||||
const isEditing = "element" in innerProps;
|
||||
const initialValues: Partial<ServicePriceCategorySchema> = isEditing ? innerProps.element : {
|
||||
name: "",
|
||||
};
|
||||
const form = useForm<Partial<ServicePriceCategorySchema>>({
|
||||
initialValues,
|
||||
});
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={() => context.closeContextModal(id)}
|
||||
>
|
||||
<BaseFormModal.Body>
|
||||
<>
|
||||
<TextInput
|
||||
label={"Название"}
|
||||
placeholder={"Введите название категории"}
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
</>
|
||||
</BaseFormModal.Body>
|
||||
</BaseFormModal>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default ServicePriceCategoryForm;
|
||||
@@ -1,9 +1,9 @@
|
||||
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import {GetServiceKitSchema} from "../../../client";
|
||||
import {ContextModalProps} from "@mantine/modals";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {ServiceType} from "../../../shared/enums/ServiceType.ts";
|
||||
import {TextInput} from "@mantine/core";
|
||||
import BaseFormModal, { CreateEditFormProps } from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import { GetServiceKitSchema } from "../../../client";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ServiceType } from "../../../shared/enums/ServiceType.ts";
|
||||
import { TextInput } from "@mantine/core";
|
||||
import ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx";
|
||||
import ServicesMultiselect from "../../../components/Selects/ServicesMultiselect/ServicesMultiselect.tsx";
|
||||
|
||||
@@ -13,17 +13,17 @@ const ServiceKitModalForm = ({
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const isEditing = 'element' in innerProps;
|
||||
const isEditing = "element" in innerProps;
|
||||
const initialValues: Partial<GetServiceKitSchema> = isEditing ? innerProps.element : {
|
||||
name: "",
|
||||
serviceType: ServiceType.DEAL_SERVICE,
|
||||
services: []
|
||||
}
|
||||
services: [],
|
||||
};
|
||||
|
||||
const form = useForm<Partial<GetServiceKitSchema>>(
|
||||
{
|
||||
initialValues
|
||||
}
|
||||
initialValues,
|
||||
},
|
||||
);
|
||||
return (
|
||||
<BaseFormModal
|
||||
@@ -54,7 +54,7 @@ const ServiceKitModalForm = ({
|
||||
</>
|
||||
</BaseFormModal.Body>
|
||||
</BaseFormModal>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceKitModalForm;
|
||||
@@ -1,173 +1,90 @@
|
||||
import {FC, useState} from "react";
|
||||
import { FC, useState } from "react";
|
||||
import ServicesTable from "../components/ServicesTable/ServicesTable.tsx";
|
||||
import useServicesList from "../hooks/useServicesList.tsx";
|
||||
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
|
||||
import styles from './ServicesPage.module.css';
|
||||
import {Button, Text} from "@mantine/core";
|
||||
import {GetServiceKitSchema, ServiceCategorySchema, ServiceSchema, ServiceService} from "../../../client";
|
||||
import {notifications} from "../../../shared/lib/notifications.ts";
|
||||
import {modals} from "@mantine/modals";
|
||||
import styles from "./ServicesPage.module.css";
|
||||
import { Button } from "@mantine/core";
|
||||
import ServiceTypeSegmentedControl, {
|
||||
ServicesTab
|
||||
ServicesTab,
|
||||
} from "../components/ServiceTypeSegmentedControl/ServiceTypeSegmentedControl.tsx";
|
||||
import useServicesKitsList from "../hooks/useServicesKitsList.tsx";
|
||||
import ServicesKitsTable from "../components/ServicesKitsTable/ServicesKitsTable.tsx";
|
||||
import {omit} from "lodash";
|
||||
import ServicePriceCategoryTable from "../components/ServicePriceCategoryTable/ServicePriceCategoryTable.tsx";
|
||||
import useServicesState from "../hooks/useServicesState.tsx";
|
||||
import useServicesKitsState from "../hooks/useServicesKitsState.tsx";
|
||||
import useServicePriceCategoryState from "../hooks/useServicePriceCategoryState.tsx";
|
||||
import { ObjectStateToTableProps } from "../../../types/utils.ts";
|
||||
|
||||
|
||||
export const ServicesPage: FC = () => {
|
||||
const {services, refetch} = useServicesList();
|
||||
const {objects: servicesKits, refetch: refetchKits} = useServicesKitsList();
|
||||
const [serviceType, setServiceType] = useState(ServicesTab.DEAL_SERVICE)
|
||||
// region Service create
|
||||
const onCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: 'createService',
|
||||
title: 'Создание услуги',
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate
|
||||
}
|
||||
})
|
||||
}
|
||||
const onCreate = (values: ServiceSchema) => {
|
||||
ServiceService.createService({requestBody: {service: values}})
|
||||
.then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message: message});
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
})
|
||||
}
|
||||
// endregion
|
||||
const [serviceType, setServiceType] = useState(ServicesTab.DEAL_SERVICE);
|
||||
const { services, onServiceDelete, onServiceUpdate, onCreateClick, onCreateCategoryClick } = useServicesState();
|
||||
const { servicesKits, onKitUpdate, onKitCreateClick } = useServicesKitsState();
|
||||
const { onCreateClick: onCreatePriceCategoryClick, ...priceCategoryRestProps } = useServicePriceCategoryState();
|
||||
const getBody = () => {
|
||||
switch (serviceType) {
|
||||
case ServicesTab.SERVICES_KITS:
|
||||
return (
|
||||
<ServicesKitsTable
|
||||
items={servicesKits}
|
||||
onChange={onKitUpdate}
|
||||
/>
|
||||
);
|
||||
case ServicesTab.DEAL_SERVICE:
|
||||
case ServicesTab.PRODUCT_SERVICE:
|
||||
return (
|
||||
<ServicesTable
|
||||
onDelete={onServiceDelete}
|
||||
onChange={onServiceUpdate}
|
||||
items={services.filter(service => service.serviceType == serviceType)}
|
||||
/>
|
||||
);
|
||||
case ServicesTab.SERVICES_PRICE_CATEGORIES:
|
||||
return (
|
||||
<ServicePriceCategoryTable
|
||||
{...ObjectStateToTableProps(priceCategoryRestProps)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// region Category create
|
||||
const onCreateCategoryClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "createServiceCategory",
|
||||
title: 'Создание категории',
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: onCategoryCreate
|
||||
}
|
||||
})
|
||||
}
|
||||
const onCategoryCreate = (category: ServiceCategorySchema) => {
|
||||
ServiceService.createServiceCategory({requestBody: {category: category}})
|
||||
.then(({ok, message}) =>
|
||||
notifications.guess(ok, {message: message}))
|
||||
}
|
||||
// endregion
|
||||
|
||||
const onServiceDelete = (service: ServiceSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: 'Удаление услуги',
|
||||
children: (<Text>
|
||||
Вы уверены, что хотите удалить услугу "{service.name}"?
|
||||
</Text>),
|
||||
onConfirm: () => {
|
||||
ServiceService.deleteService({requestBody: {serviceId: service.id}})
|
||||
.then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message: message});
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
})
|
||||
},
|
||||
labels: {
|
||||
confirm: 'Удалить',
|
||||
cancel: 'Отмена'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const onServiceUpdate = (service: ServiceSchema) => {
|
||||
ServiceService
|
||||
.updateService({
|
||||
requestBody: {
|
||||
data: service
|
||||
}
|
||||
})
|
||||
.then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message: message});
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
})
|
||||
}
|
||||
|
||||
const onKitCreate = (kit: GetServiceKitSchema) => {
|
||||
ServiceService.createServicesKit({
|
||||
requestBody: {
|
||||
data: {
|
||||
...omit(kit, ["services", "id"]),
|
||||
servicesIds: kit.services.map(service => service.id)
|
||||
}
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message: message});
|
||||
if (!ok) return;
|
||||
await refetchKits();
|
||||
})
|
||||
}
|
||||
|
||||
const onKitCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: 'serviceKitModalForm',
|
||||
title: 'Создание набора услуг',
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: onKitCreate
|
||||
}
|
||||
})
|
||||
}
|
||||
const onKitUpdate = (kit: GetServiceKitSchema) => {
|
||||
ServiceService.updateServicesKit({
|
||||
requestBody: {
|
||||
data: {
|
||||
...omit(kit, ["services"]),
|
||||
servicesIds: kit.services.map(service => service.id)
|
||||
}
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message: message});
|
||||
if (!ok) return;
|
||||
await refetchKits();
|
||||
})
|
||||
}
|
||||
const getControls = () => {
|
||||
switch (serviceType) {
|
||||
case ServicesTab.SERVICES_KITS:
|
||||
return (
|
||||
<Button onClick={onKitCreateClick} variant={"default"}>Создать набор</Button>
|
||||
);
|
||||
case ServicesTab.DEAL_SERVICE:
|
||||
case ServicesTab.PRODUCT_SERVICE:
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCreateClick} variant={"default"}>Создать услугу</Button>
|
||||
<Button onClick={onCreateCategoryClick} variant={"default"}>Создать категорию</Button>
|
||||
</>
|
||||
);
|
||||
case ServicesTab.SERVICES_PRICE_CATEGORIES:
|
||||
return (
|
||||
<Button variant={"default"} onClick={() => {
|
||||
if (onCreatePriceCategoryClick) {
|
||||
onCreatePriceCategoryClick();
|
||||
}
|
||||
}}>Создать категорию</Button>
|
||||
);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<div className={styles["container"]}>
|
||||
<PageBlock>
|
||||
<div className={styles['top-panel']}>
|
||||
{
|
||||
serviceType === ServicesTab.SERVICES_KITS ?
|
||||
<Button onClick={onKitCreateClick} variant={"default"}>Создать набор</Button> :
|
||||
<>
|
||||
<Button onClick={onCreateClick} variant={"default"}>Создать услугу</Button>
|
||||
<Button onClick={onCreateCategoryClick} variant={"default"}>Создать
|
||||
категорию</Button>
|
||||
</>
|
||||
}
|
||||
|
||||
<div className={styles["top-panel"]}>
|
||||
{getControls()}
|
||||
<ServiceTypeSegmentedControl
|
||||
className={styles['top-panel-last-item']}
|
||||
className={styles["top-panel-last-item"]}
|
||||
value={serviceType.toString()}
|
||||
onChange={(event) => setServiceType(parseInt(event))}
|
||||
/>
|
||||
</div>
|
||||
</PageBlock>
|
||||
<PageBlock>
|
||||
{
|
||||
serviceType === ServicesTab.SERVICES_KITS ?
|
||||
<ServicesKitsTable
|
||||
items={servicesKits}
|
||||
onChange={onKitUpdate}
|
||||
/>
|
||||
:
|
||||
<ServicesTable
|
||||
onDelete={onServiceDelete}
|
||||
onChange={onServiceUpdate}
|
||||
items={services.filter(service => service.serviceType == serviceType)}
|
||||
/>
|
||||
}
|
||||
{getBody()}
|
||||
</PageBlock>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user