feat: services kits table in service page
This commit is contained in:
@ -15,6 +15,8 @@
|
|||||||
--dark-thick-shadow: 1px 1px 13px 1px var(--mantine-color-dark-5);
|
--dark-thick-shadow: 1px 1px 13px 1px var(--mantine-color-dark-5);
|
||||||
/* Heights */
|
/* Heights */
|
||||||
--page-height: calc(100vh - (var(--mantine-spacing-md) * 2));
|
--page-height: calc(100vh - (var(--mantine-spacing-md) * 2));
|
||||||
|
--mobile-footer-height: 70px;
|
||||||
|
--mobile-page-height: calc(100vh - var(--mobile-footer-height));
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { ButtonProps } from "@mantine/core";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
|
||||||
|
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||||
|
|
||||||
|
type Props = ButtonProps;
|
||||||
|
|
||||||
|
const CreateServiceKitButton: FC<Props> = () => {
|
||||||
|
const { servicesKitCrud } = useServicesContext();
|
||||||
|
|
||||||
|
const onCreateClick = () => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "servicesKitEditorModal",
|
||||||
|
title: "Создание набора услуг",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
onCreate: servicesKitCrud.onCreate,
|
||||||
|
isEditing: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return <InlineButton onClick={onCreateClick}>Создать набор</InlineButton>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateServiceKitButton;
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import { Box, Group } from "@mantine/core";
|
||||||
|
import CreateServiceKitButton from "@/app/services/components/desktop/CreateServiceKitButton/CreateServiceKitButton";
|
||||||
|
import ServiceTabSegmentedControl, {
|
||||||
|
ServicesTab,
|
||||||
|
} from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
serviceTab: ServicesTab;
|
||||||
|
onServiceTabChange: (serviceTab: ServicesTab) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServicesDesktopHeader: FC<Props> = ({
|
||||||
|
serviceTab,
|
||||||
|
onServiceTabChange,
|
||||||
|
}) => {
|
||||||
|
const getTabActions = () => {
|
||||||
|
switch (serviceTab) {
|
||||||
|
case ServicesTab.DEAL_SERVICE:
|
||||||
|
return <Box />;
|
||||||
|
case ServicesTab.PRODUCT_SERVICE:
|
||||||
|
return <Box />;
|
||||||
|
case ServicesTab.SERVICES_KITS:
|
||||||
|
return <CreateServiceKitButton />;
|
||||||
|
default:
|
||||||
|
return <Box />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
wrap={"nowrap"}
|
||||||
|
justify={"space-between"}>
|
||||||
|
{getTabActions()}
|
||||||
|
<ServiceTabSegmentedControl
|
||||||
|
value={serviceTab.toString()}
|
||||||
|
onChange={tab => onServiceTabChange(Number(tab))}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesDesktopHeader;
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import ServiceTabSegmentedControl, {
|
||||||
|
ServicesTab,
|
||||||
|
} from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
serviceTab: ServicesTab;
|
||||||
|
onServiceTabChange: (serviceTab: ServicesTab) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServicesMobileHeader: FC<Props> = ({
|
||||||
|
serviceTab,
|
||||||
|
onServiceTabChange,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ServiceTabSegmentedControl
|
||||||
|
value={serviceTab.toString()}
|
||||||
|
onChange={tab => onServiceTabChange(Number(tab))}
|
||||||
|
w={"100%"}
|
||||||
|
py={"md"}
|
||||||
|
px={"sm"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesMobileHeader;
|
||||||
63
src/app/services/components/shared/PageBody/PageBody.tsx
Normal file
63
src/app/services/components/shared/PageBody/PageBody.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
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 { ServicesTab } from "@/app/services/components/shared/ServiceTabSegmentedControl/ServiceTabSegmentedControl";
|
||||||
|
import PageBlock from "@/components/layout/PageBlock/PageBlock";
|
||||||
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
|
|
||||||
|
const PageBody = () => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const [servicesTab, setServicesTab] = useState<ServicesTab>(
|
||||||
|
ServicesTab.PRODUCT_SERVICE
|
||||||
|
);
|
||||||
|
|
||||||
|
const getPageBody = () => {
|
||||||
|
switch (servicesTab) {
|
||||||
|
case ServicesTab.PRODUCT_SERVICE:
|
||||||
|
return <></>;
|
||||||
|
case ServicesTab.DEAL_SERVICE:
|
||||||
|
return <></>;
|
||||||
|
case ServicesTab.SERVICES_KITS:
|
||||||
|
return <ServicesKitsTable />;
|
||||||
|
default:
|
||||||
|
return <>-</>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!isMobile && (
|
||||||
|
<PageBlock>
|
||||||
|
<ServicesDesktopHeader
|
||||||
|
serviceTab={servicesTab}
|
||||||
|
onServiceTabChange={setServicesTab}
|
||||||
|
/>
|
||||||
|
</PageBlock>
|
||||||
|
)}
|
||||||
|
<PageBlock fullHeight>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}>
|
||||||
|
{isMobile && (
|
||||||
|
<ServicesMobileHeader
|
||||||
|
serviceTab={servicesTab}
|
||||||
|
onServiceTabChange={setServicesTab}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div style={{ flex: 1, overflow: "auto" }}>
|
||||||
|
{getPageBody()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PageBlock>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageBody;
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||||
|
|
||||||
|
export enum ServicesTab {
|
||||||
|
DEAL_SERVICE,
|
||||||
|
PRODUCT_SERVICE,
|
||||||
|
SERVICES_KITS,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = Omit<SegmentedControlProps, "data">;
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
label: "Для товара",
|
||||||
|
value: ServicesTab.PRODUCT_SERVICE.toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Для сделки",
|
||||||
|
value: ServicesTab.DEAL_SERVICE.toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Наборы услуг",
|
||||||
|
value: ServicesTab.SERVICES_KITS.toString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ServiceTabSegmentedControl: FC<Props> = props => (
|
||||||
|
<SegmentedControl
|
||||||
|
data={data}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ServiceTabSegmentedControl;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||||
|
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
||||||
|
|
||||||
|
type Props = Omit<SegmentedControlProps, "data">;
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
label: "Для сделки",
|
||||||
|
value: ServiceType.DEAL_SERVICE.toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Для товара",
|
||||||
|
value: ServiceType.PRODUCT_SERVICE.toString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ServiceTypeSegmentedControl: FC<Props> = props => (
|
||||||
|
<SegmentedControl
|
||||||
|
data={data}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ServiceTypeSegmentedControl;
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import useServicesKitsTableColumns from "@/app/services/components/shared/ServicesKitTable/hooks/useServicesKitsTableColumns";
|
||||||
|
import { useServicesContext } from "@/app/services/contexts/ServicesContext";
|
||||||
|
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||||
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
|
import { ServicesKitSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
const ServicesKitsTable = () => {
|
||||||
|
const { servicesKitCrud, servicesKitList } = useServicesContext();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const onChange = (kit: ServicesKitSchema) => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "servicesKitEditorModal",
|
||||||
|
title: "Редактирование набора услуг",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
entity: kit,
|
||||||
|
onChange: value => servicesKitCrud.onUpdate(kit.id, value),
|
||||||
|
isEditing: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useServicesKitsTableColumns({
|
||||||
|
onChange,
|
||||||
|
onDelete: servicesKitCrud.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseTable
|
||||||
|
records={servicesKitList.servicesKits}
|
||||||
|
columns={columns}
|
||||||
|
groups={undefined}
|
||||||
|
withTableBorder
|
||||||
|
verticalSpacing={"xs"}
|
||||||
|
mx={isMobile ? "xs" : 0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesKitsTable;
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { DataTableColumn } from "mantine-datatable";
|
||||||
|
import { Flex } from "@mantine/core";
|
||||||
|
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||||
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
|
import { ServicesKitSchema } from "@/lib/client";
|
||||||
|
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onChange: (kit: ServicesKitSchema) => void;
|
||||||
|
onDelete: (kit: ServicesKitSchema) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useServicesKitsTableColumns = ({ onDelete, onChange }: Props) => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "Действия",
|
||||||
|
sortable: false,
|
||||||
|
textAlign: "center",
|
||||||
|
width: "0%",
|
||||||
|
render: kit => (
|
||||||
|
<Flex gap={isMobile ? "xs" : "md"}>
|
||||||
|
<ActionIconWithTip
|
||||||
|
onClick={() => onChange(kit)}
|
||||||
|
tipLabel={"Редактировать"}>
|
||||||
|
<IconEdit />
|
||||||
|
</ActionIconWithTip>
|
||||||
|
<ActionIconWithTip
|
||||||
|
color={"red"}
|
||||||
|
onClick={() => onDelete(kit)}
|
||||||
|
tipLabel={"Удалить"}>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIconWithTip>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "name",
|
||||||
|
title: "Название",
|
||||||
|
width: "60%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Кол-во услуг",
|
||||||
|
accessor: "services",
|
||||||
|
render: kit => kit.services.length,
|
||||||
|
width: "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Тип набора",
|
||||||
|
accessor: "serviceType",
|
||||||
|
render: kit =>
|
||||||
|
kit.serviceType === ServiceType.DEAL_SERVICE
|
||||||
|
? "Для сделок"
|
||||||
|
: "Для товаров",
|
||||||
|
width: "20%",
|
||||||
|
},
|
||||||
|
] as DataTableColumn<ServicesKitSchema>[],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useServicesKitsTableColumns;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import ObjectMultiSelect, {
|
||||||
|
ObjectMultiSelectProps,
|
||||||
|
} from "@/components/selects/ObjectMultiSelect/ObjectMultiSelect";
|
||||||
|
import { ServiceSchema } from "@/lib/client";
|
||||||
|
import useServicesList from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesList";
|
||||||
|
|
||||||
|
type Props = Omit<ObjectMultiSelectProps<ServiceSchema>, "data">;
|
||||||
|
|
||||||
|
const ServicesMultiselect: FC<Props> = (props: Props) => {
|
||||||
|
const { services } = useServicesList();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ObjectMultiSelect
|
||||||
|
data={services}
|
||||||
|
searchable
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesMultiselect;
|
||||||
45
src/app/services/contexts/ServicesContext.tsx
Normal file
45
src/app/services/contexts/ServicesContext.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import makeContext from "@/lib/contextFactory/contextFactory";
|
||||||
|
import {
|
||||||
|
ServicesCrud,
|
||||||
|
useServicesCrud,
|
||||||
|
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServicesCrud";
|
||||||
|
import {
|
||||||
|
ServicesKitsCrud,
|
||||||
|
useServicesKitsCrud,
|
||||||
|
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useServicesKitsCrud";
|
||||||
|
import useServicesKitsList, {
|
||||||
|
ServicesKitsList,
|
||||||
|
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesKitsList";
|
||||||
|
import useServicesList, {
|
||||||
|
ServicesList,
|
||||||
|
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesList";
|
||||||
|
|
||||||
|
type ServicesContextState = {
|
||||||
|
servicesList: ServicesList;
|
||||||
|
servicesCrud: ServicesCrud;
|
||||||
|
servicesKitList: ServicesKitsList;
|
||||||
|
servicesKitCrud: ServicesKitsCrud;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useFulfillmentBaseContextState = (): ServicesContextState => {
|
||||||
|
const servicesList = useServicesList();
|
||||||
|
const servicesCrud = useServicesCrud(servicesList);
|
||||||
|
|
||||||
|
const servicesKitList = useServicesKitsList();
|
||||||
|
const servicesKitCrud = useServicesKitsCrud(servicesKitList);
|
||||||
|
|
||||||
|
return {
|
||||||
|
servicesList,
|
||||||
|
servicesCrud,
|
||||||
|
servicesKitList,
|
||||||
|
servicesKitCrud,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const [ServicesContextProvider, useServicesContext] =
|
||||||
|
makeContext<ServicesContextState>(
|
||||||
|
useFulfillmentBaseContextState,
|
||||||
|
"Services"
|
||||||
|
);
|
||||||
70
src/app/services/modals/ServicesKitEditorModal.tsx
Normal file
70
src/app/services/modals/ServicesKitEditorModal.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { TextInput } from "@mantine/core";
|
||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import ServicesMultiselect from "@/app/services/components/shared/ServicesMultiselect/ServicesMultiselect";
|
||||||
|
import ServiceTypeSegmentedControl from "@/app/services/components/shared/ServiceTypeSegmentedControl/ServiceTypeSegmentedControl";
|
||||||
|
import { ServicesKitSchema } from "@/lib/client";
|
||||||
|
import BaseFormModal, {
|
||||||
|
CreateEditFormProps,
|
||||||
|
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||||
|
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
||||||
|
|
||||||
|
type Props = CreateEditFormProps<ServicesKitSchema>;
|
||||||
|
|
||||||
|
const ServicesKitEditorModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const initialValues: Partial<ServicesKitSchema> = innerProps.isEditing
|
||||||
|
? innerProps.entity
|
||||||
|
: {
|
||||||
|
name: "",
|
||||||
|
serviceType: ServiceType.DEAL_SERVICE,
|
||||||
|
services: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = useForm<Partial<ServicesKitSchema>>({
|
||||||
|
initialValues,
|
||||||
|
validate: {
|
||||||
|
name: name => !name && "Введите название",
|
||||||
|
services: services =>
|
||||||
|
(!services || services.length === 0) && "Выберите услуги",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseFormModal
|
||||||
|
{...innerProps}
|
||||||
|
form={form}
|
||||||
|
closeOnSubmit
|
||||||
|
onClose={() => context.closeContextModal(id)}>
|
||||||
|
<TextInput
|
||||||
|
label={"Название"}
|
||||||
|
placeholder={"Введите название набора услуг"}
|
||||||
|
{...form.getInputProps("name")}
|
||||||
|
/>
|
||||||
|
<ServiceTypeSegmentedControl
|
||||||
|
value={form.values.serviceType?.toString()}
|
||||||
|
onChange={tab => {
|
||||||
|
form.setFieldValue("serviceType", Number(tab));
|
||||||
|
form.setFieldValue("services", []);
|
||||||
|
}}
|
||||||
|
mt={"xs"}
|
||||||
|
/>
|
||||||
|
<ServicesMultiselect
|
||||||
|
label={"Услуги"}
|
||||||
|
placeholder={"Выберите услуги"}
|
||||||
|
filterBy={service =>
|
||||||
|
service.serviceType === form.values.serviceType
|
||||||
|
}
|
||||||
|
groupBy={service => service.category.name}
|
||||||
|
{...form.getInputProps("services")}
|
||||||
|
/>
|
||||||
|
</BaseFormModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesKitEditorModal;
|
||||||
1
src/app/services/modals/index.ts
Normal file
1
src/app/services/modals/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as ServicesKitEditorModal } from "@/app/services/modals/ServicesKitEditorModal";
|
||||||
22
src/app/services/page.tsx
Normal file
22
src/app/services/page.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import { Center, Loader } from "@mantine/core";
|
||||||
|
import PageBody from "@/app/services/components/shared/PageBody/PageBody";
|
||||||
|
import { ServicesContextProvider } from "@/app/services/contexts/ServicesContext";
|
||||||
|
import PageContainer from "@/components/layout/PageContainer/PageContainer";
|
||||||
|
|
||||||
|
export default async function ServicesPage() {
|
||||||
|
return (
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Center h="50vh">
|
||||||
|
<Loader size="lg" />
|
||||||
|
</Center>
|
||||||
|
}>
|
||||||
|
<ServicesContextProvider>
|
||||||
|
<PageContainer>
|
||||||
|
<PageBody />
|
||||||
|
</PageContainer>
|
||||||
|
</ServicesContextProvider>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
.container {
|
.container {
|
||||||
|
height: var(--mobile-footer-height);
|
||||||
|
|
||||||
@mixin light {
|
@mixin light {
|
||||||
border-top: solid gray 2px;
|
border-top: solid gray 2px;
|
||||||
}
|
}
|
||||||
@ -9,7 +11,6 @@
|
|||||||
|
|
||||||
.link {
|
.link {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: rem(50px);
|
|
||||||
border-radius: var(--mantine-radius-md);
|
border-radius: var(--mantine-radius-md);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { IconCircleDotted, IconLayoutKanban } from "@tabler/icons-react";
|
import { IconColumns, IconLayoutKanban } from "@tabler/icons-react";
|
||||||
import { Box, Flex } from "@mantine/core";
|
import { Box, Flex } from "@mantine/core";
|
||||||
import PageBlock from "@/components/layout/PageBlock/PageBlock";
|
import PageBlock from "@/components/layout/PageBlock/PageBlock";
|
||||||
import { ColorSchemeToggle } from "@/components/ui/ColorSchemeToggle/ColorSchemeToggle";
|
import { ColorSchemeToggle } from "@/components/ui/ColorSchemeToggle/ColorSchemeToggle";
|
||||||
@ -13,9 +13,9 @@ const linksData = [
|
|||||||
href: "/deals",
|
href: "/deals",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: IconCircleDotted,
|
icon: IconColumns,
|
||||||
label: "Назад",
|
label: "Услуги",
|
||||||
href: "/oiiai",
|
href: "/services",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,10 @@
|
|||||||
|
|
||||||
.container-full-height {
|
.container-full-height {
|
||||||
height: var(--page-height);
|
height: var(--page-height);
|
||||||
|
|
||||||
|
@media (max-width: 48em) {
|
||||||
|
height: var(--mobile-page-height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-no-border-radius {
|
.container-no-border-radius {
|
||||||
@ -28,8 +32,8 @@
|
|||||||
|
|
||||||
.container-full-screen-mobile {
|
.container-full-screen-mobile {
|
||||||
@media (max-width: 48em) {
|
@media (max-width: 48em) {
|
||||||
min-height: 100vh;
|
min-height: var(--mobile-page-height);
|
||||||
height: 100vh;
|
height: var(--mobile-page-height);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@ -93,7 +93,13 @@ const ObjectMultiSelect = <T,>(props: ObjectMultiSelectProps<T>) => {
|
|||||||
props.onChange(internalValue);
|
props.onChange(internalValue);
|
||||||
}, [internalValue]);
|
}, [internalValue]);
|
||||||
|
|
||||||
const restProps = omit(props, "getValueFn", "getLabelFn", "filterBy");
|
const restProps = omit(
|
||||||
|
props,
|
||||||
|
"getValueFn",
|
||||||
|
"getLabelFn",
|
||||||
|
"filterBy",
|
||||||
|
"groupBy"
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
PolymorphicComponentProps,
|
PolymorphicComponentProps,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
import style from "./ActionIconWithTip.module.css";
|
import style from "./ActionIconWithTip.module.css";
|
||||||
|
|
||||||
type Props = PolymorphicComponentProps<"button", ActionIconProps> & {
|
type Props = PolymorphicComponentProps<"button", ActionIconProps> & {
|
||||||
@ -15,20 +16,24 @@ const ActionIconWithTip: FC<PropsWithChildren<Props>> = ({
|
|||||||
children,
|
children,
|
||||||
tipLabel,
|
tipLabel,
|
||||||
...props
|
...props
|
||||||
}) => (
|
}) => {
|
||||||
<Tooltip
|
const isMobile = useIsMobile();
|
||||||
label={tipLabel}
|
|
||||||
hidden={!tipLabel}
|
return (
|
||||||
h={"max-content"}
|
<Tooltip
|
||||||
w={"max-content"}>
|
label={tipLabel}
|
||||||
<ActionIcon
|
hidden={!tipLabel}
|
||||||
variant={"default"}
|
h={"max-content"}
|
||||||
radius={"lg"}
|
w={"max-content"}>
|
||||||
className={style.container}
|
<ActionIcon
|
||||||
{...props}>
|
variant={isMobile ? "subtle" : "default"}
|
||||||
{children}
|
radius={"lg"}
|
||||||
</ActionIcon>
|
className={style.container}
|
||||||
</Tooltip>
|
{...props}>
|
||||||
);
|
{children}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default ActionIconWithTip;
|
export default ActionIconWithTip;
|
||||||
|
|||||||
@ -1,4 +1,13 @@
|
|||||||
|
|
||||||
|
.table-bg {
|
||||||
|
@mixin light {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
@mixin dark {
|
||||||
|
background-color: var(--mantine-color-dark-8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.table-border {
|
.table-border {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-radius: var(--mantine-radius-lg);
|
border-radius: var(--mantine-radius-lg);
|
||||||
|
|||||||
@ -9,7 +9,12 @@ function BaseTable<T>(props: DataTableProps<T>) {
|
|||||||
withRowBorders
|
withRowBorders
|
||||||
striped={false}
|
striped={false}
|
||||||
verticalAlign={"center"}
|
verticalAlign={"center"}
|
||||||
backgroundColor={"transparent"}
|
classNames={{
|
||||||
|
root: styles["table-bg"],
|
||||||
|
table: styles["table-bg"],
|
||||||
|
header: styles["table-bg"],
|
||||||
|
footer: styles["table-bg"],
|
||||||
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
props.withTableBorder && styles["table-border"]
|
props.withTableBorder && styles["table-border"]
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -90,6 +90,10 @@ export type BuiltInModuleTabSchema = {
|
|||||||
* Id
|
* Id
|
||||||
*/
|
*/
|
||||||
id: number;
|
id: number;
|
||||||
|
/**
|
||||||
|
* Key
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
/**
|
/**
|
||||||
* Label
|
* Label
|
||||||
*/
|
*/
|
||||||
@ -468,9 +472,9 @@ export type CreateServicesKitSchema = {
|
|||||||
*/
|
*/
|
||||||
serviceType: number;
|
serviceType: number;
|
||||||
/**
|
/**
|
||||||
* Servicesids
|
* Services
|
||||||
*/
|
*/
|
||||||
servicesIds: Array<number>;
|
services: Array<ServiceSchema>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1487,9 +1491,9 @@ export type UpdateServicesKitSchema = {
|
|||||||
*/
|
*/
|
||||||
serviceType: number;
|
serviceType: number;
|
||||||
/**
|
/**
|
||||||
* Servicesids
|
* Services
|
||||||
*/
|
*/
|
||||||
servicesIds: Array<number>;
|
services: Array<ServiceSchema>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const zBoardSchema = z.object({
|
|||||||
*/
|
*/
|
||||||
export const zBuiltInModuleTabSchema = z.object({
|
export const zBuiltInModuleTabSchema = z.object({
|
||||||
id: z.int(),
|
id: z.int(),
|
||||||
|
key: z.string(),
|
||||||
label: z.string(),
|
label: z.string(),
|
||||||
iconName: z.string(),
|
iconName: z.string(),
|
||||||
device: z.string(),
|
device: z.string(),
|
||||||
@ -385,7 +386,7 @@ export const zCreateServiceResponse = z.object({
|
|||||||
export const zCreateServicesKitSchema = z.object({
|
export const zCreateServicesKitSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
serviceType: z.int(),
|
serviceType: z.int(),
|
||||||
servicesIds: z.array(z.int()),
|
services: z.array(zServiceSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -878,7 +879,7 @@ export const zUpdateServiceResponse = z.object({
|
|||||||
export const zUpdateServicesKitSchema = z.object({
|
export const zUpdateServicesKitSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
serviceType: z.int(),
|
serviceType: z.int(),
|
||||||
servicesIds: z.array(z.int()),
|
services: z.array(zServiceSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal";
|
import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal";
|
||||||
import DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal";
|
import DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal";
|
||||||
import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal";
|
import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal";
|
||||||
|
import { ServicesKitEditorModal } from "@/app/services/modals";
|
||||||
import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal";
|
import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal";
|
||||||
import {
|
import {
|
||||||
DealProductEditorModal,
|
DealProductEditorModal,
|
||||||
@ -22,4 +23,5 @@ export const modals = {
|
|||||||
productServiceEditorModal: ProductServiceEditorModal,
|
productServiceEditorModal: ProductServiceEditorModal,
|
||||||
duplicateServicesModal: DuplicateServicesModal,
|
duplicateServicesModal: DuplicateServicesModal,
|
||||||
servicesKitSelectModal: ServicesKitSelectModal,
|
servicesKitSelectModal: ServicesKitSelectModal,
|
||||||
|
servicesKitEditorModal: ServicesKitEditorModal,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
||||||
|
import {
|
||||||
|
CreateServicesKitSchema,
|
||||||
|
ServicesKitSchema,
|
||||||
|
UpdateServicesKitSchema,
|
||||||
|
} from "@/lib/client";
|
||||||
|
import {
|
||||||
|
createServicesKitMutation,
|
||||||
|
deleteServicesKitMutation,
|
||||||
|
updateServicesKitMutation,
|
||||||
|
} from "@/lib/client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
queryKey: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ServicesKitsCrud = {
|
||||||
|
onCreate: (data: Partial<CreateServicesKitSchema>) => void;
|
||||||
|
onUpdate: (
|
||||||
|
servicesKitId: number,
|
||||||
|
servicesKit: UpdateServicesKitSchema
|
||||||
|
) => void;
|
||||||
|
onDelete: (servicesKit: ServicesKitSchema, onSuccess?: () => void) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useServicesKitsCrud = ({ queryKey }: Props): ServicesKitsCrud => {
|
||||||
|
return useCrudOperations<
|
||||||
|
ServicesKitSchema,
|
||||||
|
UpdateServicesKitSchema,
|
||||||
|
CreateServicesKitSchema
|
||||||
|
>({
|
||||||
|
key: "getServicesKits",
|
||||||
|
queryKey,
|
||||||
|
mutations: {
|
||||||
|
create: createServicesKitMutation(),
|
||||||
|
update: updateServicesKitMutation(),
|
||||||
|
delete: deleteServicesKitMutation(),
|
||||||
|
},
|
||||||
|
getCreateEntity: data => ({
|
||||||
|
name: data.name!,
|
||||||
|
serviceType: data.serviceType!,
|
||||||
|
services: data.services!,
|
||||||
|
}),
|
||||||
|
getUpdateEntity: (old, update) => ({
|
||||||
|
...old,
|
||||||
|
name: update.name ?? old.name,
|
||||||
|
serviceType: update.serviceType ?? old.serviceType,
|
||||||
|
}),
|
||||||
|
getDeleteConfirmTitle: () => "Удаление набора услуг",
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -5,7 +5,15 @@ import {
|
|||||||
getServicesKitsQueryKey,
|
getServicesKitsQueryKey,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} from "@/lib/client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
const useServicesKitsList = () => {
|
export type ServicesKitsList = {
|
||||||
|
servicesKits: ServicesKitSchema[];
|
||||||
|
setServicesKits: (servicesKits: ServicesKitSchema[]) => void;
|
||||||
|
refetch: () => void;
|
||||||
|
queryKey: any[];
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useServicesKitsList = (): ServicesKitsList => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data, refetch, isLoading } = useQuery(getServicesKitsOptions());
|
const { data, refetch, isLoading } = useQuery(getServicesKitsOptions());
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,15 @@ import {
|
|||||||
getServicesQueryKey,
|
getServicesQueryKey,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} from "@/lib/client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
|
export type ServicesList = {
|
||||||
|
services: ServiceSchema[];
|
||||||
|
setServices: (services: ServiceSchema[]) => void;
|
||||||
|
refetch: () => void;
|
||||||
|
queryKey: any[];
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const useServicesList = () => {
|
const useServicesList = (): ServicesList => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data, refetch, isLoading } = useQuery(getServicesOptions());
|
const { data, refetch, isLoading } = useQuery(getServicesOptions());
|
||||||
|
|
||||||
|
|||||||
10
src/theme.ts
10
src/theme.ts
@ -62,5 +62,15 @@ export const theme = createTheme({
|
|||||||
decimalSeparator: ",",
|
decimalSeparator: ",",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MultiSelect: {
|
||||||
|
defaultProps: {
|
||||||
|
radius,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SegmentedControl: {
|
||||||
|
defaultProps: {
|
||||||
|
radius,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user