feat: products page
This commit is contained in:
@ -1,4 +1,9 @@
|
|||||||
import { IconColumns, IconFileBarcode, IconUsers } from "@tabler/icons-react";
|
import {
|
||||||
|
IconBox,
|
||||||
|
IconColumns,
|
||||||
|
IconFileBarcode,
|
||||||
|
IconUsers,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
import { ModuleNames } from "@/modules/modules";
|
import { ModuleNames } from "@/modules/modules";
|
||||||
import LinkData from "@/types/LinkData";
|
import LinkData from "@/types/LinkData";
|
||||||
|
|
||||||
@ -15,6 +20,12 @@ const mobileButtonsData: LinkData[] = [
|
|||||||
href: "/services",
|
href: "/services",
|
||||||
moduleName: ModuleNames.FULFILLMENT_BASE,
|
moduleName: ModuleNames.FULFILLMENT_BASE,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: IconBox,
|
||||||
|
label: "Товары",
|
||||||
|
href: "/products",
|
||||||
|
moduleName: ModuleNames.FULFILLMENT_BASE,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: IconFileBarcode,
|
icon: IconFileBarcode,
|
||||||
label: "Шаблоны штрихкодов",
|
label: "Шаблоны штрихкодов",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { FC, useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { IconMoodSad } from "@tabler/icons-react";
|
import { IconMoodSad } from "@tabler/icons-react";
|
||||||
import { Group, Pagination, Stack, Text } from "@mantine/core";
|
import { Group, Pagination, Stack, Text } from "@mantine/core";
|
||||||
import useDealsTableColumns from "@/app/deals/components/desktop/DealsTable/useDealsTableColumns";
|
import useDealsTableColumns from "@/app/deals/components/shared/DealsTable/useDealsTableColumns";
|
||||||
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
|
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
|
||||||
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
|
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
|
||||||
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||||
@ -1,3 +1,3 @@
|
|||||||
import DealsTable from "@/app/deals/components/desktop/DealsTable/DealsTable";
|
import DealsTable from "../DealsTable/DealsTable";
|
||||||
|
|
||||||
export const TableView = () => <DealsTable />;
|
export const TableView = () => <DealsTable />;
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Flex, Group, TextInput } from "@mantine/core";
|
||||||
|
import { useProductsContext } from "@/app/products/contexts/ProductsContext";
|
||||||
|
import useProductsActions from "@/app/products/hooks/useProductsActions";
|
||||||
|
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||||
|
import ClientSelect from "@/modules/dealModularEditorTabs/Clients/components/ClientSelect";
|
||||||
|
|
||||||
|
const ProductsDesktopHeader = () => {
|
||||||
|
const { productsFiltersForm } = useProductsContext();
|
||||||
|
const { onCreateClick } = useProductsActions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group justify={"space-between"}>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<ClientSelect
|
||||||
|
{...productsFiltersForm.getInputProps("client")}
|
||||||
|
/>
|
||||||
|
<InlineButton
|
||||||
|
onClick={onCreateClick}
|
||||||
|
disabled={!productsFiltersForm.values.client}>
|
||||||
|
Создать
|
||||||
|
</InlineButton>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<TextInput
|
||||||
|
placeholder={"Артикул, название, шк"}
|
||||||
|
{...productsFiltersForm.getInputProps("searchInput")}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsDesktopHeader;
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Flex, Stack, TextInput } from "@mantine/core";
|
||||||
|
import { useProductsContext } from "@/app/products/contexts/ProductsContext";
|
||||||
|
import useProductsActions from "@/app/products/hooks/useProductsActions";
|
||||||
|
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||||
|
import ClientSelect from "@/modules/dealModularEditorTabs/Clients/components/ClientSelect";
|
||||||
|
|
||||||
|
const ProductsMobileHeader = () => {
|
||||||
|
const { productsFiltersForm } = useProductsContext();
|
||||||
|
const { onCreateClick } = useProductsActions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
gap={"xs"}
|
||||||
|
justify={"space-between"}
|
||||||
|
mt={"xs"}
|
||||||
|
mx={"xs"}>
|
||||||
|
<Flex
|
||||||
|
gap={"xs"}
|
||||||
|
flex={2}>
|
||||||
|
<ClientSelect
|
||||||
|
{...productsFiltersForm.getInputProps("client")}
|
||||||
|
flex={1}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder={"Артикул, название, шк"}
|
||||||
|
{...productsFiltersForm.getInputProps("searchInput")}
|
||||||
|
flex={1}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<InlineButton
|
||||||
|
onClick={onCreateClick}
|
||||||
|
disabled={!productsFiltersForm.values.client}>
|
||||||
|
Создать
|
||||||
|
</InlineButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsMobileHeader;
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import useBarcodeTemplatesList from "@/app/barcode-templates/hooks/useBarcodeTemplatesList";
|
||||||
|
import ObjectSelect, {
|
||||||
|
ObjectSelectProps,
|
||||||
|
} from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||||
|
import { BarcodeTemplateSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
type Props = Omit<ObjectSelectProps<BarcodeTemplateSchema>, "data">;
|
||||||
|
|
||||||
|
const BarcodeTemplateSelect: FC<Props> = props => {
|
||||||
|
const { barcodeTemplates } = useBarcodeTemplatesList();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ObjectSelect
|
||||||
|
data={barcodeTemplates}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BarcodeTemplateSelect;
|
||||||
36
src/app/products/components/shared/PageBody/PageBody.tsx
Normal file
36
src/app/products/components/shared/PageBody/PageBody.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
import ProductsDesktopHeader from "@/app/products/components/desktop/ProductsDesktopHeader/ProductsDesktopHeader";
|
||||||
|
import ProductsMobileHeader from "@/app/products/components/mobile/ProductsMobileHeader/ProductsMobileHeader";
|
||||||
|
import PageBlock from "@/components/layout/PageBlock/PageBlock";
|
||||||
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
|
import ProductsTable from "@/app/products/components/shared/ProductsTable/ProductsTable";
|
||||||
|
|
||||||
|
const PageBody = () => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack h={"100%"}>
|
||||||
|
{!isMobile && (
|
||||||
|
<PageBlock>
|
||||||
|
<ProductsDesktopHeader />
|
||||||
|
</PageBlock>
|
||||||
|
)}
|
||||||
|
<PageBlock
|
||||||
|
style={{ flex: 1, minHeight: 0 }}
|
||||||
|
fullScreenMobile>
|
||||||
|
<Stack
|
||||||
|
gap={"xs"}
|
||||||
|
h={"100%"}>
|
||||||
|
{isMobile && <ProductsMobileHeader />}
|
||||||
|
<div style={{ flex: 1, overflow: "auto" }}>
|
||||||
|
<ProductsTable />
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</PageBlock>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageBody;
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
import { IconMoodSad } from "@tabler/icons-react";
|
||||||
|
import { Group, Pagination, Stack, Text } from "@mantine/core";
|
||||||
|
import { useProductsTableColumns } from "@/app/products/components/shared/ProductsTable/columns";
|
||||||
|
import { useProductsContext } from "@/app/products/contexts/ProductsContext";
|
||||||
|
import useProductsActions from "@/app/products/hooks/useProductsActions";
|
||||||
|
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||||
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
|
|
||||||
|
const ProductsTable = () => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const { productsCrud, products, productsFiltersForm, paginationInfo } =
|
||||||
|
useProductsContext();
|
||||||
|
const { onChangeClick } = useProductsActions();
|
||||||
|
|
||||||
|
const columns = useProductsTableColumns({
|
||||||
|
onChange: onChangeClick,
|
||||||
|
onDelete: productsCrud.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
h={"100%"}
|
||||||
|
pb={"xs"}
|
||||||
|
px={isMobile ? "xs" : ""}>
|
||||||
|
<BaseTable
|
||||||
|
withTableBorder
|
||||||
|
records={products}
|
||||||
|
columns={columns}
|
||||||
|
groups={undefined}
|
||||||
|
style={{
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
emptyState={
|
||||||
|
productsFiltersForm.values.client ? (
|
||||||
|
<Group
|
||||||
|
align={"center"}
|
||||||
|
gap={"xs"}>
|
||||||
|
<Text>Нет товаров</Text>
|
||||||
|
<IconMoodSad />
|
||||||
|
</Group>
|
||||||
|
) : (
|
||||||
|
<Text>Выберите клиента для вывода товаров</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verticalSpacing={"md"}
|
||||||
|
/>
|
||||||
|
{paginationInfo && paginationInfo.totalPages > 1 && (
|
||||||
|
<Group justify={"end"}>
|
||||||
|
<Pagination
|
||||||
|
withEdges
|
||||||
|
total={paginationInfo?.totalPages}
|
||||||
|
{...productsFiltersForm.getInputProps("page")}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsTable;
|
||||||
106
src/app/products/components/shared/ProductsTable/columns.tsx
Normal file
106
src/app/products/components/shared/ProductsTable/columns.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { DataTableColumn } from "mantine-datatable";
|
||||||
|
import { Center, Flex, List, Spoiler, useMantineTheme } from "@mantine/core";
|
||||||
|
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||||
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
|
import { ProductSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onChange: (product: ProductSchema) => void;
|
||||||
|
onDelete: (product: ProductSchema) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useProductsTableColumns = ({ onChange, onDelete }: Props) => {
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: <Center>Действия</Center>,
|
||||||
|
width: "0%",
|
||||||
|
render: product => (
|
||||||
|
<Flex gap={isMobile ? "sm" : "md"}>
|
||||||
|
{/*<ActionIconWithTip*/}
|
||||||
|
{/* tipLabel={"Печать штрихкода"}*/}
|
||||||
|
{/* onClick={() => onPrintBarcodeClick(product)}>*/}
|
||||||
|
{/* <IconBarcode />*/}
|
||||||
|
{/*</ActionIconWithTip>*/}
|
||||||
|
<ActionIconWithTip
|
||||||
|
tipLabel={"Редактировать"}
|
||||||
|
onClick={() => onChange(product)}>
|
||||||
|
<IconEdit />
|
||||||
|
</ActionIconWithTip>
|
||||||
|
<ActionIconWithTip
|
||||||
|
tipLabel={"Удалить"}
|
||||||
|
onClick={() => onDelete(product)}>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIconWithTip>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "article",
|
||||||
|
title: "Артикул",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "factoryArticle",
|
||||||
|
title: "Складской артикул",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "name",
|
||||||
|
title: "Название",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "barcodes",
|
||||||
|
title: "Штрихкоды",
|
||||||
|
render: product => {
|
||||||
|
return (
|
||||||
|
<List size={"sm"}>
|
||||||
|
<Spoiler
|
||||||
|
maxHeight={
|
||||||
|
parseFloat(theme.lineHeights.sm) * 30
|
||||||
|
}
|
||||||
|
showLabel={"Показать все"}
|
||||||
|
hideLabel={"Скрыть"}>
|
||||||
|
{product.barcodes.map(barcode => (
|
||||||
|
<List.Item key={barcode}>
|
||||||
|
{barcode}
|
||||||
|
</List.Item>
|
||||||
|
))}
|
||||||
|
</Spoiler>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "barcodeTemplate.name",
|
||||||
|
title: "Шаблон ШК",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "brand",
|
||||||
|
title: "Бренд",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "composition",
|
||||||
|
title: "Состав",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "color",
|
||||||
|
title: "Цвет",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "size",
|
||||||
|
title: "Размер",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "additionalInfo",
|
||||||
|
title: "Доп. информация",
|
||||||
|
},
|
||||||
|
] as DataTableColumn<ProductSchema>[],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
};
|
||||||
57
src/app/products/contexts/ProductsContext.tsx
Normal file
57
src/app/products/contexts/ProductsContext.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useForm, UseFormReturnType } from "@mantine/form";
|
||||||
|
import { useDebouncedValue } from "@mantine/hooks";
|
||||||
|
import { ClientSchema, PaginationInfoSchema, ProductSchema } from "@/lib/client";
|
||||||
|
import makeContext from "@/lib/contextFactory/contextFactory";
|
||||||
|
import {
|
||||||
|
ProductsCrud,
|
||||||
|
useProductsCrud,
|
||||||
|
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useProductsCrud";
|
||||||
|
import useProductsList from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useProductsList";
|
||||||
|
|
||||||
|
export type ProductsFiltersForm = {
|
||||||
|
searchInput: string;
|
||||||
|
client: ClientSchema | null;
|
||||||
|
page: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProductsContextState = {
|
||||||
|
productsFiltersForm: UseFormReturnType<ProductsFiltersForm>;
|
||||||
|
products: ProductSchema[];
|
||||||
|
productsCrud: ProductsCrud;
|
||||||
|
paginationInfo?: PaginationInfoSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useProductsContextState = (): ProductsContextState => {
|
||||||
|
const productsFiltersForm = useForm<ProductsFiltersForm>({
|
||||||
|
initialValues: {
|
||||||
|
searchInput: "",
|
||||||
|
client: null,
|
||||||
|
page: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [debouncedSearchInput] = useDebouncedValue(
|
||||||
|
productsFiltersForm.values.searchInput,
|
||||||
|
500
|
||||||
|
);
|
||||||
|
|
||||||
|
const { products, paginationInfo, queryKey } = useProductsList({
|
||||||
|
clientId: productsFiltersForm.values.client?.id,
|
||||||
|
searchInput: debouncedSearchInput,
|
||||||
|
page: productsFiltersForm.values.page,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
});
|
||||||
|
const productsCrud = useProductsCrud({ queryKey });
|
||||||
|
|
||||||
|
return {
|
||||||
|
productsFiltersForm,
|
||||||
|
products,
|
||||||
|
productsCrud,
|
||||||
|
paginationInfo,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const [ProductsContextProvider, useProductsContext] =
|
||||||
|
makeContext<ProductsContextState>(useProductsContextState, "Products");
|
||||||
59
src/app/products/hooks/useProductsActions.ts
Normal file
59
src/app/products/hooks/useProductsActions.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { useProductsContext } from "@/app/products/contexts/ProductsContext";
|
||||||
|
import { ProductSchema } from "@/lib/client";
|
||||||
|
import { notifications } from "@/lib/notifications";
|
||||||
|
|
||||||
|
const useProductsActions = () => {
|
||||||
|
const { productsCrud, productsFiltersForm } = useProductsContext();
|
||||||
|
|
||||||
|
const onCreateClick = () => {
|
||||||
|
if (!productsFiltersForm.values.client) {
|
||||||
|
notifications.error({ message: "Выберите клиента" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "productEditorModal",
|
||||||
|
title: "Создание товара",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
onCreate: productsCrud.onCreate,
|
||||||
|
clientId: productsFiltersForm.values.client.id,
|
||||||
|
isEditing: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeClick = (product: ProductSchema) => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "productEditorModal",
|
||||||
|
title: "Редактирование товара",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
onChange: updated => productsCrud.onUpdate(product.id, updated),
|
||||||
|
clientId: product.clientId,
|
||||||
|
entity: product,
|
||||||
|
isEditing: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPrintBarcodeClick = (product: ProductSchema) => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "printBarcode",
|
||||||
|
title: "Печать штрихкода", // TODO
|
||||||
|
withCloseButton: true,
|
||||||
|
innerProps: {
|
||||||
|
productId: product.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
onCreateClick,
|
||||||
|
onChangeClick,
|
||||||
|
onPrintBarcodeClick,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useProductsActions;
|
||||||
20
src/app/products/hooks/useProductsList.tsx
Normal file
20
src/app/products/hooks/useProductsList.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { ProductService } from "../../../client";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
clientId: number;
|
||||||
|
page?: number;
|
||||||
|
itemsPerPage?: number;
|
||||||
|
searchInput: string;
|
||||||
|
};
|
||||||
|
const useProductsList = (props: Props) => {
|
||||||
|
const { clientId, page, itemsPerPage, searchInput } = props;
|
||||||
|
const { data, refetch, isLoading } = useQuery({
|
||||||
|
queryKey: ["getAllServices", clientId, page, itemsPerPage, searchInput],
|
||||||
|
queryFn: () => ProductService.getProductsByClientId(props),
|
||||||
|
});
|
||||||
|
const products = !data ? [] : data.products;
|
||||||
|
const paginationInfo = data?.paginationInfo;
|
||||||
|
return { products, paginationInfo, refetch, isLoading };
|
||||||
|
};
|
||||||
|
export default useProductsList;
|
||||||
22
src/app/products/page.tsx
Normal file
22
src/app/products/page.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import { Center, Loader } from "@mantine/core";
|
||||||
|
import PageBody from "@/app/products/components/shared/PageBody/PageBody";
|
||||||
|
import { ProductsContextProvider } from "@/app/products/contexts/ProductsContext";
|
||||||
|
import PageContainer from "@/components/layout/PageContainer/PageContainer";
|
||||||
|
|
||||||
|
export default async function ProductsPage() {
|
||||||
|
return (
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Center h="50vh">
|
||||||
|
<Loader size="lg" />
|
||||||
|
</Center>
|
||||||
|
}>
|
||||||
|
<PageContainer>
|
||||||
|
<ProductsContextProvider>
|
||||||
|
<PageBody />
|
||||||
|
</ProductsContextProvider>
|
||||||
|
</PageContainer>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
IconBox,
|
||||||
IconColumns,
|
IconColumns,
|
||||||
IconFileBarcode,
|
IconFileBarcode,
|
||||||
IconLayoutKanban,
|
IconLayoutKanban,
|
||||||
@ -26,6 +27,12 @@ const linksData: LinkData[] = [
|
|||||||
href: "/services",
|
href: "/services",
|
||||||
moduleName: ModuleNames.FULFILLMENT_BASE,
|
moduleName: ModuleNames.FULFILLMENT_BASE,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: IconBox,
|
||||||
|
label: "Товары",
|
||||||
|
href: "/products",
|
||||||
|
moduleName: ModuleNames.FULFILLMENT_BASE,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: IconFileBarcode,
|
icon: IconFileBarcode,
|
||||||
label: "Шаблоны штрихкодов",
|
label: "Шаблоны штрихкодов",
|
||||||
|
|||||||
@ -487,6 +487,14 @@ export type CreateProductSchema = {
|
|||||||
* Factoryarticle
|
* Factoryarticle
|
||||||
*/
|
*/
|
||||||
factoryArticle: string;
|
factoryArticle: string;
|
||||||
|
/**
|
||||||
|
* Clientid
|
||||||
|
*/
|
||||||
|
clientId: number;
|
||||||
|
/**
|
||||||
|
* Barcodetemplateid
|
||||||
|
*/
|
||||||
|
barcodeTemplateId: number;
|
||||||
/**
|
/**
|
||||||
* Brand
|
* Brand
|
||||||
*/
|
*/
|
||||||
@ -507,6 +515,10 @@ export type CreateProductSchema = {
|
|||||||
* Additionalinfo
|
* Additionalinfo
|
||||||
*/
|
*/
|
||||||
additionalInfo: string | null;
|
additionalInfo: string | null;
|
||||||
|
/**
|
||||||
|
* Barcodes
|
||||||
|
*/
|
||||||
|
barcodes: Array<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1098,6 +1110,7 @@ export type GetProductsResponse = {
|
|||||||
* Items
|
* Items
|
||||||
*/
|
*/
|
||||||
items: Array<ProductSchema>;
|
items: Array<ProductSchema>;
|
||||||
|
paginationInfo: PaginationInfoSchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1218,6 +1231,14 @@ export type ProductSchema = {
|
|||||||
* Factoryarticle
|
* Factoryarticle
|
||||||
*/
|
*/
|
||||||
factoryArticle: string;
|
factoryArticle: string;
|
||||||
|
/**
|
||||||
|
* Clientid
|
||||||
|
*/
|
||||||
|
clientId: number;
|
||||||
|
/**
|
||||||
|
* Barcodetemplateid
|
||||||
|
*/
|
||||||
|
barcodeTemplateId: number;
|
||||||
/**
|
/**
|
||||||
* Brand
|
* Brand
|
||||||
*/
|
*/
|
||||||
@ -1238,10 +1259,15 @@ export type ProductSchema = {
|
|||||||
* Additionalinfo
|
* Additionalinfo
|
||||||
*/
|
*/
|
||||||
additionalInfo: string | null;
|
additionalInfo: string | null;
|
||||||
|
/**
|
||||||
|
* Barcodes
|
||||||
|
*/
|
||||||
|
barcodes: Array<string>;
|
||||||
/**
|
/**
|
||||||
* Id
|
* Id
|
||||||
*/
|
*/
|
||||||
id: number;
|
id: number;
|
||||||
|
barcodeTemplate: BarcodeTemplateSchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1704,6 +1730,10 @@ export type UpdateProductSchema = {
|
|||||||
* Factoryarticle
|
* Factoryarticle
|
||||||
*/
|
*/
|
||||||
factoryArticle?: string | null;
|
factoryArticle?: string | null;
|
||||||
|
/**
|
||||||
|
* Barcodetemplateid
|
||||||
|
*/
|
||||||
|
barcodeTemplateId?: number | null;
|
||||||
/**
|
/**
|
||||||
* Brand
|
* Brand
|
||||||
*/
|
*/
|
||||||
@ -1724,6 +1754,10 @@ export type UpdateProductSchema = {
|
|||||||
* Additionalinfo
|
* Additionalinfo
|
||||||
*/
|
*/
|
||||||
additionalInfo?: string | null;
|
additionalInfo?: string | null;
|
||||||
|
/**
|
||||||
|
* Barcodes
|
||||||
|
*/
|
||||||
|
barcodes?: Array<string> | null;
|
||||||
/**
|
/**
|
||||||
* Images
|
* Images
|
||||||
*/
|
*/
|
||||||
@ -3218,6 +3252,10 @@ export type GetProductsData = {
|
|||||||
body?: never;
|
body?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
query?: {
|
query?: {
|
||||||
|
/**
|
||||||
|
* Clientid
|
||||||
|
*/
|
||||||
|
clientId?: number | null;
|
||||||
/**
|
/**
|
||||||
* Searchinput
|
* Searchinput
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -208,12 +208,16 @@ export const zProductSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
article: z.string(),
|
article: z.string(),
|
||||||
factoryArticle: z.string(),
|
factoryArticle: z.string(),
|
||||||
|
clientId: z.int(),
|
||||||
|
barcodeTemplateId: z.int(),
|
||||||
brand: z.union([z.string(), z.null()]),
|
brand: z.union([z.string(), z.null()]),
|
||||||
color: z.union([z.string(), z.null()]),
|
color: z.union([z.string(), z.null()]),
|
||||||
composition: z.union([z.string(), z.null()]),
|
composition: z.union([z.string(), z.null()]),
|
||||||
size: z.union([z.string(), z.null()]),
|
size: z.union([z.string(), z.null()]),
|
||||||
additionalInfo: z.union([z.string(), z.null()]),
|
additionalInfo: z.union([z.string(), z.null()]),
|
||||||
|
barcodes: z.array(z.string()),
|
||||||
id: z.int(),
|
id: z.int(),
|
||||||
|
barcodeTemplate: zBarcodeTemplateSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -377,11 +381,14 @@ export const zCreateProductSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
article: z.string(),
|
article: z.string(),
|
||||||
factoryArticle: z.string(),
|
factoryArticle: z.string(),
|
||||||
|
clientId: z.int(),
|
||||||
|
barcodeTemplateId: z.int(),
|
||||||
brand: z.union([z.string(), z.null()]),
|
brand: z.union([z.string(), z.null()]),
|
||||||
color: z.union([z.string(), z.null()]),
|
color: z.union([z.string(), z.null()]),
|
||||||
composition: z.union([z.string(), z.null()]),
|
composition: z.union([z.string(), z.null()]),
|
||||||
size: z.union([z.string(), z.null()]),
|
size: z.union([z.string(), z.null()]),
|
||||||
additionalInfo: z.union([z.string(), z.null()]),
|
additionalInfo: z.union([z.string(), z.null()]),
|
||||||
|
barcodes: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -765,6 +772,7 @@ export const zGetDealsResponse = z.object({
|
|||||||
*/
|
*/
|
||||||
export const zGetProductsResponse = z.object({
|
export const zGetProductsResponse = z.object({
|
||||||
items: z.array(zProductSchema),
|
items: z.array(zProductSchema),
|
||||||
|
paginationInfo: zPaginationInfoSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1014,11 +1022,13 @@ export const zUpdateProductSchema = z.object({
|
|||||||
name: z.optional(z.union([z.string(), z.null()])),
|
name: z.optional(z.union([z.string(), z.null()])),
|
||||||
article: z.optional(z.union([z.string(), z.null()])),
|
article: z.optional(z.union([z.string(), z.null()])),
|
||||||
factoryArticle: z.optional(z.union([z.string(), z.null()])),
|
factoryArticle: z.optional(z.union([z.string(), z.null()])),
|
||||||
|
barcodeTemplateId: z.optional(z.union([z.int(), z.null()])),
|
||||||
brand: z.optional(z.union([z.string(), z.null()])),
|
brand: z.optional(z.union([z.string(), z.null()])),
|
||||||
color: z.optional(z.union([z.string(), z.null()])),
|
color: z.optional(z.union([z.string(), z.null()])),
|
||||||
composition: z.optional(z.union([z.string(), z.null()])),
|
composition: z.optional(z.union([z.string(), z.null()])),
|
||||||
size: z.optional(z.union([z.string(), z.null()])),
|
size: z.optional(z.union([z.string(), z.null()])),
|
||||||
additionalInfo: z.optional(z.union([z.string(), z.null()])),
|
additionalInfo: z.optional(z.union([z.string(), z.null()])),
|
||||||
|
barcodes: z.optional(z.union([z.array(z.string()), z.null()])),
|
||||||
images: z.optional(z.union([z.array(zProductImageSchema), z.null()])),
|
images: z.optional(z.union([z.array(zProductImageSchema), z.null()])),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1718,6 +1728,7 @@ export const zGetProductsData = z.object({
|
|||||||
path: z.optional(z.never()),
|
path: z.optional(z.never()),
|
||||||
query: z.optional(
|
query: z.optional(
|
||||||
z.object({
|
z.object({
|
||||||
|
clientId: z.optional(z.union([z.int(), z.null()])),
|
||||||
searchInput: z.optional(z.union([z.string(), z.null()])),
|
searchInput: z.optional(z.union([z.string(), z.null()])),
|
||||||
page: z.optional(z.union([z.int(), z.null()])),
|
page: z.optional(z.union([z.int(), z.null()])),
|
||||||
itemsPerPage: z.optional(z.union([z.int(), z.null()])),
|
itemsPerPage: z.optional(z.union([z.int(), z.null()])),
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { FC } from "react";
|
|||||||
import { Button, Flex } from "@mantine/core";
|
import { Button, Flex } from "@mantine/core";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
import { notifications } from "@/lib/notifications";
|
||||||
|
|
||||||
const ProductsActions: FC = () => {
|
const ProductsActions: FC = () => {
|
||||||
const { deal, dealProductsList, productsCrud, dealProductsCrud } =
|
const { deal, dealProductsList, productsCrud, dealProductsCrud } =
|
||||||
@ -20,6 +21,11 @@ const ProductsActions: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onCreateDealProductClick = () => {
|
const onCreateDealProductClick = () => {
|
||||||
|
if (!deal.client) {
|
||||||
|
notifications.error({ message: "Выберите клиента для сделки" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const productIdsToExclude = dealProductsList.dealProducts.map(
|
const productIdsToExclude = dealProductsList.dealProducts.map(
|
||||||
product => product.product.id
|
product => product.product.id
|
||||||
);
|
);
|
||||||
@ -33,7 +39,7 @@ const ProductsActions: FC = () => {
|
|||||||
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
|
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
|
||||||
productIdsToExclude,
|
productIdsToExclude,
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
clientId: 0, // TODO add clients
|
clientId: deal.client.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IconPlus } from "@tabler/icons-react";
|
|||||||
import { ButtonProps, Text } from "@mantine/core";
|
import { ButtonProps, Text } from "@mantine/core";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||||
|
import { notifications } from "@/lib/notifications";
|
||||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
|
||||||
|
|
||||||
type Props = ButtonProps;
|
type Props = ButtonProps;
|
||||||
@ -12,6 +13,11 @@ const AddDealProductButton: FC<Props> = props => {
|
|||||||
useFulfillmentBaseContext();
|
useFulfillmentBaseContext();
|
||||||
|
|
||||||
const onCreateClick = () => {
|
const onCreateClick = () => {
|
||||||
|
if (!deal.client) {
|
||||||
|
notifications.error({ message: "Выберите клиента для сделки" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const productIdsToExclude = dealProductsList.dealProducts.map(
|
const productIdsToExclude = dealProductsList.dealProducts.map(
|
||||||
product => product.product.id
|
product => product.product.id
|
||||||
);
|
);
|
||||||
@ -25,7 +31,7 @@ const AddDealProductButton: FC<Props> = props => {
|
|||||||
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
|
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
|
||||||
productIdsToExclude,
|
productIdsToExclude,
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
clientId: 0, // TODO add clients
|
clientId: deal.client.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { DealProductSchema } from "@/lib/client";
|
import { DealProductSchema } from "@/lib/client";
|
||||||
|
import { notifications } from "@/lib/notifications";
|
||||||
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/components/ProductFieldsList";
|
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/components/ProductFieldsList";
|
||||||
import ProductMenu from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductMenu/ProductMenu";
|
import ProductMenu from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductMenu/ProductMenu";
|
||||||
import ProductServicesTable from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductServicesTable/ProductServicesTable";
|
import ProductServicesTable from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/ProductServicesTable/ProductServicesTable";
|
||||||
@ -22,9 +23,14 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DealProductView: FC<Props> = ({ dealProduct }) => {
|
const DealProductView: FC<Props> = ({ dealProduct }) => {
|
||||||
const { dealProductsCrud } = useFulfillmentBaseContext();
|
const { dealProductsCrud, deal } = useFulfillmentBaseContext();
|
||||||
|
|
||||||
const onChangeDealProductClick = () => {
|
const onChangeDealProductClick = () => {
|
||||||
|
if (!deal.client) {
|
||||||
|
notifications.error({ message: "Выберите клиента для сделки" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
modal: "dealProductEditorModal",
|
modal: "dealProductEditorModal",
|
||||||
title: "Добавление товара",
|
title: "Добавление товара",
|
||||||
@ -38,7 +44,7 @@ const DealProductView: FC<Props> = ({ dealProduct }) => {
|
|||||||
),
|
),
|
||||||
entity: dealProduct,
|
entity: dealProduct,
|
||||||
isEditing: true,
|
isEditing: true,
|
||||||
clientId: 0, // TODO add clients
|
clientId: deal.client.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const ProductSelect: FC<Props> = (props: Props) => {
|
|||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const [debounced] = useDebouncedValue(searchValue, 500);
|
const [debounced] = useDebouncedValue(searchValue, 500);
|
||||||
const { products, isLoading } = useProductsList({
|
const { products, isLoading } = useProductsList({
|
||||||
// clientId: props.clientId,
|
clientId: props.clientId,
|
||||||
searchInput: debounced,
|
searchInput: debounced,
|
||||||
page: 0,
|
page: 0,
|
||||||
itemsPerPage: MAX_PRODUCTS,
|
itemsPerPage: MAX_PRODUCTS,
|
||||||
|
|||||||
@ -40,7 +40,9 @@ type Props = {
|
|||||||
const useFulfillmentBaseContextState = ({
|
const useFulfillmentBaseContextState = ({
|
||||||
deal,
|
deal,
|
||||||
}: Props): FulfillmentBaseContextState => {
|
}: Props): FulfillmentBaseContextState => {
|
||||||
const productQueryKey = getProductsQueryKey();
|
const productQueryKey = getProductsQueryKey({
|
||||||
|
query: { clientId: deal.client?.id },
|
||||||
|
});
|
||||||
const productsCrud = useProductsCrud({ queryKey: productQueryKey });
|
const productsCrud = useProductsCrud({ queryKey: productQueryKey });
|
||||||
|
|
||||||
const dealProductsList = useDealProductsList({ dealId: deal.id });
|
const dealProductsList = useDealProductsList({ dealId: deal.id });
|
||||||
|
|||||||
@ -1,22 +1,40 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { ProductSchema } from "@/lib/client";
|
import { PaginationInfoSchema, ProductSchema } from "@/lib/client";
|
||||||
import {
|
import {
|
||||||
getProductsOptions,
|
getProductsOptions,
|
||||||
getProductsQueryKey,
|
getProductsQueryKey,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} from "@/lib/client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
clientId?: number;
|
||||||
searchInput: string;
|
searchInput: string;
|
||||||
page?: number;
|
page?: number;
|
||||||
itemsPerPage?: number;
|
itemsPerPage?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useProductsList = ({ searchInput, page, itemsPerPage }: Props) => {
|
type ReturnType = {
|
||||||
|
products: ProductSchema[];
|
||||||
|
paginationInfo?: PaginationInfoSchema;
|
||||||
|
setProducts: (products: ProductSchema[]) => void;
|
||||||
|
refetch: () => void;
|
||||||
|
queryKey: any[];
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useProductsList = ({
|
||||||
|
clientId,
|
||||||
|
searchInput,
|
||||||
|
page,
|
||||||
|
itemsPerPage,
|
||||||
|
}: Props): ReturnType => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const options = {
|
const options = {
|
||||||
query: { searchInput, page, itemsPerPage },
|
query: { clientId, searchInput, page, itemsPerPage },
|
||||||
};
|
};
|
||||||
const { data, refetch, isLoading } = useQuery(getProductsOptions(options));
|
const { data, refetch, isLoading } = useQuery({
|
||||||
|
...getProductsOptions(options),
|
||||||
|
enabled: !!clientId,
|
||||||
|
});
|
||||||
|
|
||||||
const queryKey = getProductsQueryKey(options);
|
const queryKey = getProductsQueryKey(options);
|
||||||
|
|
||||||
@ -32,6 +50,7 @@ const useProductsList = ({ searchInput, page, itemsPerPage }: Props) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
products: data?.items ?? [],
|
products: data?.items ?? [],
|
||||||
|
paginationInfo: data?.paginationInfo,
|
||||||
setProducts,
|
setProducts,
|
||||||
refetch,
|
refetch,
|
||||||
queryKey,
|
queryKey,
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Fieldset, Flex, TextInput } from "@mantine/core";
|
import { Fieldset, Flex, Stack, TagsInput, TextInput } from "@mantine/core";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { ContextModalProps } from "@mantine/modals";
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import BarcodeTemplateSelect from "@/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect";
|
||||||
import {
|
import {
|
||||||
|
BarcodeTemplateSchema,
|
||||||
CreateProductSchema,
|
CreateProductSchema,
|
||||||
ProductSchema,
|
ProductSchema,
|
||||||
UpdateProductSchema,
|
UpdateProductSchema,
|
||||||
@ -18,6 +20,14 @@ type Props = CreateEditFormProps<
|
|||||||
CreateProductSchema,
|
CreateProductSchema,
|
||||||
UpdateProductSchema,
|
UpdateProductSchema,
|
||||||
ProductSchema
|
ProductSchema
|
||||||
|
> & {
|
||||||
|
clientId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProductForm = Partial<
|
||||||
|
ProductSchema & {
|
||||||
|
barcodeTemplate: BarcodeTemplateSchema;
|
||||||
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const ProductEditorModal = ({
|
const ProductEditorModal = ({
|
||||||
@ -27,7 +37,7 @@ const ProductEditorModal = ({
|
|||||||
}: ContextModalProps<Props>) => {
|
}: ContextModalProps<Props>) => {
|
||||||
const isEditing = "entity" in innerProps;
|
const isEditing = "entity" in innerProps;
|
||||||
|
|
||||||
const initialValues: Partial<ProductSchema> = isEditing
|
const initialValues: ProductForm = isEditing
|
||||||
? innerProps.entity!
|
? innerProps.entity!
|
||||||
: {
|
: {
|
||||||
name: "",
|
name: "",
|
||||||
@ -38,6 +48,9 @@ const ProductEditorModal = ({
|
|||||||
color: "",
|
color: "",
|
||||||
size: "",
|
size: "",
|
||||||
additionalInfo: "",
|
additionalInfo: "",
|
||||||
|
clientId: innerProps.clientId,
|
||||||
|
barcodeTemplate: undefined,
|
||||||
|
barcodeTemplateId: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm<Partial<ProductSchema>>({
|
const form = useForm<Partial<ProductSchema>>({
|
||||||
@ -50,18 +63,17 @@ const ProductEditorModal = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClose = () => context.closeContextModal(id);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseFormModal
|
<BaseFormModal
|
||||||
{...innerProps}
|
{...innerProps}
|
||||||
form={form}
|
form={form}
|
||||||
closeOnSubmit
|
closeOnSubmit
|
||||||
onClose={onClose}>
|
onClose={() => context.closeContextModal(id)}>
|
||||||
<Flex
|
<Flex
|
||||||
gap={"xs"}
|
gap={"xs"}
|
||||||
direction={"column"}>
|
direction={"column"}>
|
||||||
<Fieldset legend={"Основные характеристики"}>
|
<Fieldset legend={"Основные характеристики"}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={"Введите название товара"}
|
placeholder={"Введите название товара"}
|
||||||
label={"Название товара"}
|
label={"Название товара"}
|
||||||
@ -77,8 +89,31 @@ const ProductEditorModal = ({
|
|||||||
label={"Складской артикул"}
|
label={"Складской артикул"}
|
||||||
{...form.getInputProps("factoryArticle")}
|
{...form.getInputProps("factoryArticle")}
|
||||||
/>
|
/>
|
||||||
|
<BarcodeTemplateSelect
|
||||||
|
placeholder={"Выберите шаблон штрихкода"}
|
||||||
|
label={"Шаблон штрихкода"}
|
||||||
|
{...form.getInputProps("barcodeTemplate")}
|
||||||
|
onChange={template => {
|
||||||
|
form.setFieldValue("barcodeTemplate", template);
|
||||||
|
form.setFieldValue(
|
||||||
|
"barcodeTemplateId",
|
||||||
|
template?.id
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TagsInput
|
||||||
|
placeholder={
|
||||||
|
!form.values.barcodes?.length
|
||||||
|
? "Добавьте штрихкоды к товару"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
label={"Штрихкоды"}
|
||||||
|
{...form.getInputProps("barcodes")}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Fieldset legend={"Дополнительные характеристики"}>
|
<Fieldset legend={"Дополнительные характеристики"}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={"Введите бренд"}
|
placeholder={"Введите бренд"}
|
||||||
label={"Бренд"}
|
label={"Бренд"}
|
||||||
@ -104,6 +139,7 @@ const ProductEditorModal = ({
|
|||||||
label={"Доп. информация"}
|
label={"Доп. информация"}
|
||||||
{...form.getInputProps("additionalInfo")}
|
{...form.getInputProps("additionalInfo")}
|
||||||
/>
|
/>
|
||||||
|
</Stack>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<ProductImageDropzone
|
<ProductImageDropzone
|
||||||
|
|||||||
@ -77,5 +77,10 @@ export const theme = createTheme({
|
|||||||
radius,
|
radius,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TagsInput: {
|
||||||
|
defaultProps: {
|
||||||
|
radius,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user