feat: products and services tabs for mobile
This commit is contained in:
@ -97,6 +97,7 @@ const DealEditorBody: FC<Props> = props => {
|
|||||||
orientation={isMobile ? "horizontal" : "vertical"}
|
orientation={isMobile ? "horizontal" : "vertical"}
|
||||||
h={isMobile ? "min-content" : "97vh"}
|
h={isMobile ? "min-content" : "97vh"}
|
||||||
mih={isMobile ? "min-content" : "97vh"}
|
mih={isMobile ? "min-content" : "97vh"}
|
||||||
|
keepMounted={false}
|
||||||
classNames={{ tab: styles.tab }}>
|
classNames={{ tab: styles.tab }}>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
{getTab("general", "Общая информация", <IconEdit />)}
|
{getTab("general", "Общая информация", <IconEdit />)}
|
||||||
|
|||||||
@ -4,9 +4,31 @@
|
|||||||
border-radius: var(--mantine-radius-lg);
|
border-radius: var(--mantine-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
@mixin light {
|
||||||
|
box-shadow: var(--light-shadow);
|
||||||
|
}
|
||||||
|
@mixin dark {
|
||||||
|
box-shadow: var(--dark-shadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-height: rem(250);
|
max-height: rem(250);
|
||||||
max-width: rem(250);
|
max-width: rem(250);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.products-swiper :global(.swiper-pagination) {
|
||||||
|
@mixin light {
|
||||||
|
background-color: var(--mantine-color-gray-2);
|
||||||
|
}
|
||||||
|
@mixin dark {
|
||||||
|
background-color: var(--mantine-color-dark-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
border: 1px solid var(--mantine-color-default-border);
|
||||||
|
border-radius: var(--mantine-radius-lg);
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
import { FC } from "react";
|
import { FC, useEffect, useRef, useState } from "react";
|
||||||
|
import { isNull } from "lodash";
|
||||||
|
import type { Swiper as SwiperClass } from "swiper/types";
|
||||||
import { DealSchema } from "@/lib/client";
|
import { DealSchema } from "@/lib/client";
|
||||||
|
import ProductsTabSlider from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductsTabSlider/ProductsTabSlider";
|
||||||
|
import ProductsTabTable from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductsTabTable/ProductsTabTable";
|
||||||
|
import ProductsTabViewAffix, {
|
||||||
|
ProductsTabView,
|
||||||
|
} from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductsTabViewAffix/ProductsTabViewAffix";
|
||||||
import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -7,9 +14,36 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ProductsTab: FC<Props> = ({ value }) => {
|
const ProductsTab: FC<Props> = ({ value }) => {
|
||||||
|
const [view, setView] = useState<ProductsTabView>(ProductsTabView.SLIDER);
|
||||||
|
const swiperRef = useRef<SwiperClass | null>(null);
|
||||||
|
const targetSlideIndex = useRef<number>(null);
|
||||||
|
|
||||||
|
const onProductRowSelect = async (productIndex: number) => {
|
||||||
|
targetSlideIndex.current = productIndex;
|
||||||
|
setView(ProductsTabView.SLIDER);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
view === ProductsTabView.SLIDER &&
|
||||||
|
!isNull(targetSlideIndex.current)
|
||||||
|
) {
|
||||||
|
swiperRef.current?.slideTo(targetSlideIndex.current);
|
||||||
|
targetSlideIndex.current = null;
|
||||||
|
}
|
||||||
|
}, [view]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FulfillmentBaseContextProvider deal={value}>
|
<FulfillmentBaseContextProvider deal={value}>
|
||||||
<></>
|
{view === ProductsTabView.SLIDER ? (
|
||||||
|
<ProductsTabSlider swiperRef={swiperRef} />
|
||||||
|
) : (
|
||||||
|
<ProductsTabTable onSelect={onProductRowSelect} />
|
||||||
|
)}
|
||||||
|
<ProductsTabViewAffix
|
||||||
|
value={view}
|
||||||
|
onChange={value => setView(value as ProductsTabView)}
|
||||||
|
/>
|
||||||
</FulfillmentBaseContextProvider>
|
</FulfillmentBaseContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
|
import { ButtonProps, Text } from "@mantine/core";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||||
|
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
|
||||||
|
type Props = ButtonProps;
|
||||||
|
|
||||||
|
const AddDealProductButton: FC<Props> = props => {
|
||||||
|
const { dealProductsList, dealProductsCrud, deal } =
|
||||||
|
useFulfillmentBaseContext();
|
||||||
|
|
||||||
|
const onCreateClick = () => {
|
||||||
|
const productIdsToExclude = dealProductsList.dealProducts.map(
|
||||||
|
product => product.product.id
|
||||||
|
);
|
||||||
|
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "dealProductEditorModal",
|
||||||
|
title: "Добавление товара",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
onCreate: values =>
|
||||||
|
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
|
||||||
|
productIdsToExclude,
|
||||||
|
isEditing: false,
|
||||||
|
clientId: 0, // TODO add clients
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineButton
|
||||||
|
{...props}
|
||||||
|
onClick={onCreateClick}>
|
||||||
|
<IconPlus />
|
||||||
|
<Text>Добавить товар</Text>
|
||||||
|
</InlineButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddDealProductButton;
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { DealProductSchema } from "@/lib/client";
|
||||||
|
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/components/ProductFieldsList";
|
||||||
|
import ProductMenu from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductMenu/ProductMenu";
|
||||||
|
import ProductServicesTable from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductServicesTable/ProductServicesTable";
|
||||||
|
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
import styles from "../../../FulfillmentBase.module.css";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dealProduct: DealProductSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DealProductView: FC<Props> = ({ dealProduct }) => {
|
||||||
|
const { dealProductsCrud } = useFulfillmentBaseContext();
|
||||||
|
|
||||||
|
const onChangeDealProductClick = () => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "dealProductEditorModal",
|
||||||
|
title: "Добавление товара",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
onChange: values =>
|
||||||
|
dealProductsCrud.onUpdate(
|
||||||
|
dealProduct.dealId,
|
||||||
|
dealProduct.productId,
|
||||||
|
values
|
||||||
|
),
|
||||||
|
entity: dealProduct,
|
||||||
|
isEditing: true,
|
||||||
|
clientId: 0, // TODO add clients
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
p={"md"}
|
||||||
|
h={"100%"}
|
||||||
|
style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "var(--mantine-spacing-sm)",
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 0,
|
||||||
|
}}
|
||||||
|
className={styles.shadow}
|
||||||
|
withBorder>
|
||||||
|
<Flex
|
||||||
|
gap={"sm"}
|
||||||
|
wrap={"nowrap"}
|
||||||
|
align={"start"}>
|
||||||
|
<Image
|
||||||
|
src="https://placehold.co/400x500?text=Placeholder"
|
||||||
|
alt={dealProduct.product.name}
|
||||||
|
w={"40%"}
|
||||||
|
bdrs={"md"}
|
||||||
|
/>
|
||||||
|
<Stack
|
||||||
|
gap={0}
|
||||||
|
w={"100%"}>
|
||||||
|
<Group
|
||||||
|
wrap={"nowrap"}
|
||||||
|
justify={"space-between"}>
|
||||||
|
<Title order={3}>{dealProduct.product.name}</Title>
|
||||||
|
<ProductMenu
|
||||||
|
value={dealProduct}
|
||||||
|
onChange={onChangeDealProductClick}
|
||||||
|
onDelete={dealProductsCrud.onDelete}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Text>Количество: {dealProduct.quantity}</Text>
|
||||||
|
<ProductFieldsList product={dealProduct.product} />
|
||||||
|
</Stack>
|
||||||
|
</Flex>
|
||||||
|
<ProductServicesTable dealProduct={dealProduct} />
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DealProductView;
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { FC, useMemo } from "react";
|
||||||
|
import { Title, TitleProps } from "@mantine/core";
|
||||||
|
import { DealProductSchema } from "@/lib/client";
|
||||||
|
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
|
||||||
|
type Props = TitleProps;
|
||||||
|
|
||||||
|
const DealProductsTotalLabel: FC<Props> = ({ order = 4, ...props }) => {
|
||||||
|
const { dealProductsList } = useFulfillmentBaseContext();
|
||||||
|
|
||||||
|
const getProductTotal = (dealProduct: DealProductSchema) =>
|
||||||
|
dealProduct.productServices.reduce(
|
||||||
|
(acc, service) => acc + dealProduct.quantity * service.price,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const total = useMemo(
|
||||||
|
() =>
|
||||||
|
dealProductsList.dealProducts.reduce(
|
||||||
|
(acc, product) => acc + getProductTotal(product),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
[dealProductsList.dealProducts]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Title
|
||||||
|
order={order}
|
||||||
|
{...props}>
|
||||||
|
Итог: {total.toLocaleString("ru")}₽
|
||||||
|
</Title>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DealProductsTotalLabel;
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import React, { FC } from "react";
|
||||||
|
import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { Box, Group, Menu, Text } from "@mantine/core";
|
||||||
|
import ThemeIcon from "@/components/ui/ThemeIcon/ThemeIcon";
|
||||||
|
import { DealProductSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: DealProductSchema;
|
||||||
|
onChange: (dealProduct: DealProductSchema) => void;
|
||||||
|
onDelete: (dealProduct: DealProductSchema) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProductMenu: FC<Props> = ({ value, onChange, onDelete }) => {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<Menu.Target>
|
||||||
|
<Box
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={e => e.stopPropagation()}>
|
||||||
|
<ThemeIcon
|
||||||
|
bd={0}
|
||||||
|
variant={"default"}
|
||||||
|
size={"sm"}>
|
||||||
|
<IconDotsVertical />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Box>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onChange(value);
|
||||||
|
}}>
|
||||||
|
<Group wrap={"nowrap"}>
|
||||||
|
<IconEdit />
|
||||||
|
<Text>Редактировать</Text>
|
||||||
|
</Group>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete(value);
|
||||||
|
}}>
|
||||||
|
<Group wrap={"nowrap"}>
|
||||||
|
<IconTrash />
|
||||||
|
<Text>Удалить</Text>
|
||||||
|
</Group>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductMenu;
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { IconMoodSad } from "@tabler/icons-react";
|
||||||
|
import { Button, Flex, Group, ScrollArea, Text } from "@mantine/core";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||||
|
import { DealProductSchema, ProductServiceSchema } from "@/lib/client";
|
||||||
|
import useProductServicesTableColumns from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductServicesTable/useProductServicesTableColumns";
|
||||||
|
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dealProduct: DealProductSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProductServicesTable: FC<Props> = ({ dealProduct }) => {
|
||||||
|
const { productServiceCrud, dealProductsList } =
|
||||||
|
useFulfillmentBaseContext();
|
||||||
|
|
||||||
|
const onChange = (item: ProductServiceSchema) => {
|
||||||
|
const excludeServiceIds = dealProduct.productServices.map(
|
||||||
|
productService => productService.service.id
|
||||||
|
);
|
||||||
|
const totalQuantity = dealProductsList.dealProducts.reduce(
|
||||||
|
(sum, prod) => prod.quantity + sum,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "productServiceEditorModal",
|
||||||
|
innerProps: {
|
||||||
|
entity: item,
|
||||||
|
onChange: values =>
|
||||||
|
productServiceCrud.onUpdate(
|
||||||
|
item.dealId,
|
||||||
|
item.productId,
|
||||||
|
item.serviceId,
|
||||||
|
values
|
||||||
|
),
|
||||||
|
excludeServiceIds,
|
||||||
|
quantity: totalQuantity,
|
||||||
|
isEditing: true,
|
||||||
|
},
|
||||||
|
withCloseButton: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useProductServicesTableColumns({
|
||||||
|
data: dealProduct.productServices,
|
||||||
|
quantity: dealProduct.quantity,
|
||||||
|
onDelete: productServiceCrud.onDelete,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCreateClick = () => {
|
||||||
|
const excludeServiceIds = dealProduct.productServices.map(
|
||||||
|
productService => productService.service.id
|
||||||
|
);
|
||||||
|
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "productServiceEditorModal",
|
||||||
|
innerProps: {
|
||||||
|
onCreate: values =>
|
||||||
|
productServiceCrud.onCreate({ ...dealProduct, ...values }),
|
||||||
|
excludeServiceIds,
|
||||||
|
quantity: dealProduct.quantity,
|
||||||
|
isEditing: false,
|
||||||
|
},
|
||||||
|
withCloseButton: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEmptyTable = dealProduct.productServices.length === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
flex={1}
|
||||||
|
gap={"xs"}
|
||||||
|
direction={"column"}
|
||||||
|
justify={"space-between"}
|
||||||
|
style={{ minHeight: 0 }}>
|
||||||
|
<ScrollArea
|
||||||
|
flex={10}
|
||||||
|
scrollbarSize={10}
|
||||||
|
onTouchStart={e => e.stopPropagation()}
|
||||||
|
onTouchMove={e => e.stopPropagation()}>
|
||||||
|
<BaseTable
|
||||||
|
records={dealProduct.productServices}
|
||||||
|
columns={columns}
|
||||||
|
groups={undefined}
|
||||||
|
idAccessor={"serviceId"}
|
||||||
|
verticalSpacing={"md"}
|
||||||
|
withTableBorder
|
||||||
|
style={{
|
||||||
|
height: isEmptyTable ? "8rem" : "auto",
|
||||||
|
}}
|
||||||
|
emptyState={
|
||||||
|
<Group
|
||||||
|
gap={"xs"}
|
||||||
|
mt={isEmptyTable ? "xl" : 0}>
|
||||||
|
<Text>Нет услуг</Text>
|
||||||
|
<IconMoodSad />
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ScrollArea>
|
||||||
|
<Button
|
||||||
|
flex={1}
|
||||||
|
py={"xs"}
|
||||||
|
onClick={onCreateClick}
|
||||||
|
variant={"default"}>
|
||||||
|
Добавить услугу
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ProductServicesTable;
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { DataTableColumn } from "mantine-datatable";
|
||||||
|
import { ActionIcon, Flex, Text } from "@mantine/core";
|
||||||
|
import { ProductServiceSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: ProductServiceSchema[];
|
||||||
|
quantity: number;
|
||||||
|
onChange: (dealProductService: ProductServiceSchema) => void;
|
||||||
|
onDelete: (dealProductService: ProductServiceSchema) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useProductServicesTableColumns = ({
|
||||||
|
data,
|
||||||
|
quantity,
|
||||||
|
onChange,
|
||||||
|
onDelete,
|
||||||
|
}: Props) => {
|
||||||
|
const totalPrice = useMemo(
|
||||||
|
() => data.reduce((acc, row) => acc + row.price * quantity, 0),
|
||||||
|
[data, quantity]
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "Действия",
|
||||||
|
textAlign: "center",
|
||||||
|
width: "0%",
|
||||||
|
render: dealProductService => (
|
||||||
|
<Flex gap="md">
|
||||||
|
<ActionIcon
|
||||||
|
variant={"subtle"}
|
||||||
|
color={"red"}
|
||||||
|
onClick={() => onDelete(dealProductService)}>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"subtle"}
|
||||||
|
onClick={() => onChange(dealProductService)}>
|
||||||
|
<IconEdit />
|
||||||
|
</ActionIcon>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "service.name",
|
||||||
|
title: "Услуга",
|
||||||
|
width: "70%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "price",
|
||||||
|
title: "Цена",
|
||||||
|
width: "30%",
|
||||||
|
render: productService =>
|
||||||
|
productService.price.toLocaleString("ru"),
|
||||||
|
footer: data.length > 0 && (
|
||||||
|
<Text fw={700}>
|
||||||
|
Итог: {totalPrice.toLocaleString("ru")}₽
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as DataTableColumn<ProductServiceSchema>[],
|
||||||
|
[totalPrice]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useProductServicesTableColumns;
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import React, { FC, RefObject } from "react";
|
||||||
|
import { Pagination } from "swiper/modules";
|
||||||
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
|
import type { Swiper as SwiperClass } from "swiper/types";
|
||||||
|
import { Box } from "@mantine/core";
|
||||||
|
import AddDealProductButton from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/AddDealProductButton/AddDealProductButton";
|
||||||
|
import DealProductView from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/DealProductView/DealProductView";
|
||||||
|
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
import classes from "../../../FulfillmentBase.module.css";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
swiperRef: RefObject<SwiperClass | null>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProductsTabSlider: FC<Props> = ({ swiperRef }) => {
|
||||||
|
const { dealProductsList } = useFulfillmentBaseContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Swiper
|
||||||
|
onSwiper={swiper => (swiperRef.current = swiper)}
|
||||||
|
spaceBetween={15}
|
||||||
|
modules={[Pagination]}
|
||||||
|
direction={"vertical"}
|
||||||
|
style={{ maxHeight: "calc(100vh - 110px)" }}
|
||||||
|
className={classes["products-swiper"]}
|
||||||
|
pagination={{ enabled: true, clickable: true }}>
|
||||||
|
{dealProductsList.dealProducts.map(dealProduct => (
|
||||||
|
<SwiperSlide>
|
||||||
|
<DealProductView dealProduct={dealProduct} />
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
<SwiperSlide>
|
||||||
|
<Box
|
||||||
|
p={"md"}
|
||||||
|
h={"100%"}>
|
||||||
|
<AddDealProductButton
|
||||||
|
fullWidth
|
||||||
|
h={"100%"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</SwiperSlide>
|
||||||
|
</Swiper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsTabSlider;
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { Flex, Stack } from "@mantine/core";
|
||||||
|
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||||
|
import AddDealProductButton from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/AddDealProductButton/AddDealProductButton";
|
||||||
|
import DealProductsTotalLabel from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/DealProductsTotalLabel/DealProductsTotalLabel";
|
||||||
|
import useDealServicesTableColumns from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductsTabTable/hooks/useDealProductsTableColumns";
|
||||||
|
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onSelect: (index: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProductsTabTable: FC<Props> = ({ onSelect }) => {
|
||||||
|
const { dealProductsList } = useFulfillmentBaseContext();
|
||||||
|
const columns = useDealServicesTableColumns();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
gap={"xs"}
|
||||||
|
m={"xs"}>
|
||||||
|
<Flex
|
||||||
|
p={"sm"}
|
||||||
|
bd={"1px solid var(--mantine-color-default-border)"}
|
||||||
|
bdrs={"lg"}>
|
||||||
|
<DealProductsTotalLabel />
|
||||||
|
</Flex>
|
||||||
|
<BaseTable
|
||||||
|
records={dealProductsList.dealProducts}
|
||||||
|
columns={columns}
|
||||||
|
groups={undefined}
|
||||||
|
idAccessor={"productId"}
|
||||||
|
verticalSpacing={"md"}
|
||||||
|
withTableBorder
|
||||||
|
onRowClick={event => onSelect(event.index)}
|
||||||
|
/>
|
||||||
|
<AddDealProductButton size={"lg"} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsTabTable;
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { DataTableColumn } from "mantine-datatable";
|
||||||
|
import { DealProductSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
const useDealServicesTableColumns = () => {
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
accessor: "product.name",
|
||||||
|
title: "Название",
|
||||||
|
width: "60%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "quantity",
|
||||||
|
title: "Кол-во",
|
||||||
|
width: "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "product.size",
|
||||||
|
title: "Размер",
|
||||||
|
width: "20%",
|
||||||
|
},
|
||||||
|
] as DataTableColumn<DealProductSchema>[],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDealServicesTableColumns;
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { IconLayoutDistributeVertical, IconTable } from "@tabler/icons-react";
|
||||||
|
import {
|
||||||
|
Affix,
|
||||||
|
Center,
|
||||||
|
SegmentedControl,
|
||||||
|
SegmentedControlProps,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import styles from "../../../FulfillmentBase.module.css";
|
||||||
|
|
||||||
|
export enum ProductsTabView {
|
||||||
|
TABLE = "table",
|
||||||
|
SLIDER = "slider",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = Omit<SegmentedControlProps, "data">;
|
||||||
|
|
||||||
|
const ProductsTabViewAffix: FC<Props> = props => {
|
||||||
|
return (
|
||||||
|
<Affix
|
||||||
|
bdrs={"xl"}
|
||||||
|
bd={"1px solid var(--mantine-color-default-border"}
|
||||||
|
className={styles.shadow}
|
||||||
|
position={{ bottom: 10, right: 10 }}>
|
||||||
|
<Center>
|
||||||
|
<SegmentedControl
|
||||||
|
bdrs={"xl"}
|
||||||
|
radius={"lg"}
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
value: ProductsTabView.SLIDER,
|
||||||
|
label: <IconLayoutDistributeVertical />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ProductsTabView.TABLE,
|
||||||
|
label: <IconTable />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Affix>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsTabViewAffix;
|
||||||
@ -12,7 +12,7 @@ import BaseFormModal, {
|
|||||||
CreateEditFormProps,
|
CreateEditFormProps,
|
||||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||||
import ProductSelect
|
import ProductSelect
|
||||||
from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductSelect/ProductSelect";
|
from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/components/ProductSelect/ProductSelect";
|
||||||
|
|
||||||
type RestProps = {
|
type RestProps = {
|
||||||
clientId: number;
|
clientId: number;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { ObjectSelectProps } from "@/components/selects/ObjectSelect/ObjectSelect";
|
import { ObjectSelectProps } from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||||
import { ServiceSchema } from "@/lib/client";
|
import { ServiceSchema } from "@/lib/client";
|
||||||
import ServiceSelect from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ServiceSelect/ServiceSelect";
|
import ServiceSelect from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/components/ServiceSelect/ServiceSelect";
|
||||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
|
||||||
|
|
||||||
type ServiceProps = Omit<ObjectSelectProps<ServiceSchema>, "data">;
|
type ServiceProps = Omit<ObjectSelectProps<ServiceSchema>, "data">;
|
||||||
|
|||||||
Reference in New Issue
Block a user