feat: filling deals from excel file

This commit is contained in:
2024-12-25 21:16:10 +04:00
parent db21801cc1
commit 71cab887d2
29 changed files with 878 additions and 101 deletions

View File

@@ -0,0 +1,33 @@
.container {
display: flex;
flex-direction: row;
gap: rem(10);
max-height: 95vh;
}
.deal-container {
display: flex;
flex-direction: column;
gap: rem(10);
flex: 1;
}
.deal-container-wrapper {
border: dashed var(--item-border-size) var(--mantine-color-default-border);
border-radius: var(--item-border-radius);
padding: rem(10);
}
.deal-container-buttons {
gap: rem(10);
display: flex;
flex-direction: column;
margin-top: auto;
width: 100%;
}
.top-panel {
padding-bottom: rem(9);
gap: rem(10);
display: flex;
}

View File

@@ -0,0 +1,47 @@
import { Drawer, rem } from "@mantine/core";
import ExcelDropzone from "../../../../components/ExcelDropzone/ExcelDropzone.tsx";
import styles from "../PrefillDealWithExcelDrawer/PrefillDealsWithExcelDrawer.module.css";
import { usePrefillDealsWithExcelContext } from "../../contexts/PrefillDealsWithExcelContext.tsx";
import ProductsPreview from "./components/ProductsPreview.tsx";
const PrefillDealsWithExcelDrawer = () => {
const {
prefillWithExcelOpened,
prefillWithExcelOnClose,
barcodeProductsMap,
onDrop,
excelDropzone,
} = usePrefillDealsWithExcelContext();
const getBody = () => {
if (barcodeProductsMap?.size === 0) {
return <ExcelDropzone dropzone={excelDropzone} onDrop={onDrop} />;
}
return <ProductsPreview />;
};
return (
<Drawer
size={"calc(77vw)"}
position={"right"}
onClose={prefillWithExcelOnClose}
removeScrollProps={{ allowPinchZoom: true }}
withCloseButton={false}
opened={prefillWithExcelOpened}
styles={{
body: {
height: "100%",
display: "flex",
flexDirection: "row",
gap: rem(20),
},
}}
>
<div className={styles["deal-container"]}>
{getBody()}
</div>
</Drawer>
);
};
export default PrefillDealsWithExcelDrawer;

View File

@@ -0,0 +1,28 @@
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
import { ParsedCityBreakdownSchema } from "../../../../../client";
import { useBreakdownByCityTableColumns } from "../hooks/useBreakdownByCityTableColumns.tsx";
type Props = {
breakdowns: ParsedCityBreakdownSchema[];
}
const BreakdownByCityTable = ({ breakdowns }: Props) => {
const columns = useBreakdownByCityTableColumns();
return (
<BaseTable
data={breakdowns}
columns={columns}
w={"30%"}
restProps={{
enableSorting: false,
enableRowActions: false,
enableTopToolbar: false,
enableColumnActions: false,
}}
/>
);
};
export default BreakdownByCityTable;

View File

@@ -0,0 +1,30 @@
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { Text, Tooltip } from "@mantine/core";
import { IconAlertCircle, IconCircleCheck } from "@tabler/icons-react";
const ParsingResultsTooltip = () => {
const { errors } = usePrefillDealsWithExcelContext();
const isError = errors.length !== 0;
const errorLines = errors.map((error, i) => <Text key={i}>{error}</Text>);
const tooltipData = isError ? errorLines : "Ошибок при обработке нет";
const color = isError ? "red" : "grey";
return (
<Tooltip
label={tooltipData}
multiline
w={350}
withArrow
color={color}
>
{isError ? (
<IconAlertCircle color={"red"}/>
) : (
<IconCircleCheck color={"green"}/>
)}
</Tooltip>
);
};
export default ParsingResultsTooltip;

View File

@@ -0,0 +1,68 @@
import styles from "../PrefillDealsWithExcelDrawer.module.css";
import ProductsTable from "./ProductsTable.tsx";
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { Box, Button, Flex, Group, Stack, Title } from "@mantine/core";
import { ProductExcelData } from "../types.tsx";
import BreakdownByCityTable from "./BreakdownByCityTable.tsx";
import ClientSelect from "../../../../../components/Selects/ClientSelect/ClientSelect.tsx";
import ParsingResultsTooltip from "./ParsingResultsTooltip.tsx";
const ProductsPreview = () => {
const { barcodeProductsMap, createDeals, form } = usePrefillDealsWithExcelContext();
const getTitle = (barcode: string, productsData: ProductExcelData) => {
if (productsData.products.length === 1) {
return `Товар со штрихкодом ${barcode}`;
}
return `Товары со штрихкодом ${barcode}`;
};
const getProductsData = () => {
return barcodeProductsMap.entries().map(([barcode, productsData]) => (
<div key={barcode} className={styles["deal-container-wrapper"]}>
<Stack>
<Title order={5}>
{getTitle(barcode, productsData)}
</Title>
<Flex direction={"row"} gap={"md"} flex={10}>
<Box flex={7}>
<ProductsTable barcode={barcode} productsData={productsData} />
</Box>
<Box flex={3}>
<BreakdownByCityTable breakdowns={productsData.breakdowns} />
</Box>
</Flex>
</Stack>
</div>
),
).toArray();
};
return (
<Stack gap={"md"}>
<Title order={3}>Предпросмотр</Title>
<form onSubmit={form.onSubmit((values) => createDeals(values))}>
<ClientSelect
{...form.getInputProps("client")}
inputContainer={(children) => (
<Group align={"flex-start"}>
{children}
<Group>
<Button
variant="outline"
type="submit"
>
Создать сделки
</Button>
<ParsingResultsTooltip />
</Group>
</Group>
)}
/>
</form>
{getProductsData()}
</Stack>
);
};
export default ProductsPreview;

View File

@@ -0,0 +1,29 @@
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
import { useProductsTableColumns } from "../hooks/useProductsTableColumns.tsx";
import { ProductExcelData } from "../types.tsx";
type Props = {
barcode: string;
productsData: ProductExcelData;
}
const ProductsTable = ({ barcode, productsData }: Props) => {
const columns = useProductsTableColumns({ barcode });
return (
<BaseTable
data={productsData.products}
columns={columns}
w={"100%"}
restProps={{
enableSorting: false,
enableRowActions: false,
enableTopToolbar: false,
enableColumnActions: false,
}}
/>
);
};
export default ProductsTable;

View File

@@ -0,0 +1,34 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { ParsedCityBreakdownSchema } from "../../../../../client";
import { ActionIcon, Image } from "@mantine/core";
export const useBreakdownByCityTableColumns = () => {
return useMemo<MRT_ColumnDef<ParsedCityBreakdownSchema>[]>(
() => [
{
accessorKey: "baseMarketplace.iconUrl",
header: "Маркетплейс",
Cell: ({ cell }) => (
<ActionIcon
radius={"md"}
variant={"transparent"}>
<Image src={cell.getValue()} />
</ActionIcon>
),
size: 10,
},
{
accessorKey: "shippingWarehouse.name",
header: "Склад отгрузки",
size: 10,
},
{
accessorKey: "quantity",
header: "Количество",
size: 10,
},
],
[],
);
};

View File

@@ -0,0 +1,63 @@
import { useEffect, useMemo, useState } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { ProductSchema } from "../../../../../client";
import { Radio } from "@mantine/core";
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { ProductExcelData } from "../types.tsx";
type Props = {
barcode: string;
}
export const useProductsTableColumns = ({ barcode }: Props) => {
const { onProductSelectChange, barcodeProductsMap } = usePrefillDealsWithExcelContext();
const [productData, setProductData] = useState<ProductExcelData>();
useEffect(() => {
setProductData(barcodeProductsMap.get(barcode));
}, [barcodeProductsMap]);
return useMemo<MRT_ColumnDef<ProductSchema>[]>(
() => [
{
header: "Выбор",
size: 10,
Cell: ({ row }) => {
return (
<Radio
checked={productData?.selectedProduct?.id === row.original.id}
onChange={() => onProductSelectChange(barcode, row.original)}
/>
);
},
},
{
accessorKey: "article",
header: "Артикул",
size: 20,
},
{
accessorKey: "name",
header: "Название",
},
{
accessorKey: "brand",
header: "Бренд",
size: 30,
},
{
accessorKey: "color",
header: "Цвет",
size: 30,
},
{
accessorKey: "size",
header: "Размер",
size: 10,
},
],
[productData],
).filter(columnDef => (
!(columnDef.header === "Выбор" && (productData?.products.length ?? 0) === 1)
));
};

View File

@@ -0,0 +1,11 @@
import { ClientSchema, type ParsedCityBreakdownSchema, ProductSchema } from "../../../../client";
export type ProductExcelData = {
products: ProductSchema[];
breakdowns: ParsedCityBreakdownSchema[];
selectedProduct?: ProductSchema;
}
export type DealsWithExcelForm = {
client?: ClientSchema;
}