diff --git a/src/app/actions/data/mobileButtonsData.tsx b/src/app/actions/data/mobileButtonsData.tsx
index b01b2bc..a09b95e 100644
--- a/src/app/actions/data/mobileButtonsData.tsx
+++ b/src/app/actions/data/mobileButtonsData.tsx
@@ -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 LinkData from "@/types/LinkData";
@@ -15,6 +20,12 @@ const mobileButtonsData: LinkData[] = [
href: "/services",
moduleName: ModuleNames.FULFILLMENT_BASE,
},
+ {
+ icon: IconBox,
+ label: "Товары",
+ href: "/products",
+ moduleName: ModuleNames.FULFILLMENT_BASE,
+ },
{
icon: IconFileBarcode,
label: "Шаблоны штрихкодов",
diff --git a/src/app/deals/components/desktop/DealsTable/DealsTable.tsx b/src/app/deals/components/shared/DealsTable/DealsTable.tsx
similarity index 96%
rename from src/app/deals/components/desktop/DealsTable/DealsTable.tsx
rename to src/app/deals/components/shared/DealsTable/DealsTable.tsx
index dcc91e3..70720e0 100644
--- a/src/app/deals/components/desktop/DealsTable/DealsTable.tsx
+++ b/src/app/deals/components/shared/DealsTable/DealsTable.tsx
@@ -1,7 +1,7 @@
import { FC, useCallback } from "react";
import { IconMoodSad } from "@tabler/icons-react";
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 { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
diff --git a/src/app/deals/components/desktop/DealsTable/useDealsTableColumns.tsx b/src/app/deals/components/shared/DealsTable/useDealsTableColumns.tsx
similarity index 100%
rename from src/app/deals/components/desktop/DealsTable/useDealsTableColumns.tsx
rename to src/app/deals/components/shared/DealsTable/useDealsTableColumns.tsx
diff --git a/src/app/deals/components/shared/views/TableView.tsx b/src/app/deals/components/shared/views/TableView.tsx
index 51279d8..8ae908e 100644
--- a/src/app/deals/components/shared/views/TableView.tsx
+++ b/src/app/deals/components/shared/views/TableView.tsx
@@ -1,3 +1,3 @@
-import DealsTable from "@/app/deals/components/desktop/DealsTable/DealsTable";
+import DealsTable from "../DealsTable/DealsTable";
export const TableView = () => ;
diff --git a/src/app/products/components/desktop/ProductsDesktopHeader/ProductsDesktopHeader.tsx b/src/app/products/components/desktop/ProductsDesktopHeader/ProductsDesktopHeader.tsx
new file mode 100644
index 0000000..5b4291c
--- /dev/null
+++ b/src/app/products/components/desktop/ProductsDesktopHeader/ProductsDesktopHeader.tsx
@@ -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 (
+
+
+
+
+ Создать
+
+
+
+
+
+
+ );
+};
+
+export default ProductsDesktopHeader;
diff --git a/src/app/products/components/mobile/ProductsMobileHeader/ProductsMobileHeader.tsx b/src/app/products/components/mobile/ProductsMobileHeader/ProductsMobileHeader.tsx
new file mode 100644
index 0000000..1299b18
--- /dev/null
+++ b/src/app/products/components/mobile/ProductsMobileHeader/ProductsMobileHeader.tsx
@@ -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 (
+
+
+
+
+
+
+ Создать
+
+
+ );
+};
+
+export default ProductsMobileHeader;
diff --git a/src/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx b/src/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx
new file mode 100644
index 0000000..9b0092e
--- /dev/null
+++ b/src/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx
@@ -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, "data">;
+
+const BarcodeTemplateSelect: FC = props => {
+ const { barcodeTemplates } = useBarcodeTemplatesList();
+
+ return (
+
+ );
+};
+
+export default BarcodeTemplateSelect;
diff --git a/src/app/products/components/shared/PageBody/PageBody.tsx b/src/app/products/components/shared/PageBody/PageBody.tsx
new file mode 100644
index 0000000..e888b5e
--- /dev/null
+++ b/src/app/products/components/shared/PageBody/PageBody.tsx
@@ -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 (
+
+ {!isMobile && (
+
+
+
+ )}
+
+
+ {isMobile && }
+
+
+
+
+ );
+};
+
+export default PageBody;
diff --git a/src/app/products/components/shared/ProductsTable/ProductsTable.tsx b/src/app/products/components/shared/ProductsTable/ProductsTable.tsx
new file mode 100644
index 0000000..a22c90a
--- /dev/null
+++ b/src/app/products/components/shared/ProductsTable/ProductsTable.tsx
@@ -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 (
+
+
+ Нет товаров
+
+
+ ) : (
+ Выберите клиента для вывода товаров
+ )
+ }
+ verticalSpacing={"md"}
+ />
+ {paginationInfo && paginationInfo.totalPages > 1 && (
+
+
+
+ )}
+
+ );
+};
+
+export default ProductsTable;
diff --git a/src/app/products/components/shared/ProductsTable/columns.tsx b/src/app/products/components/shared/ProductsTable/columns.tsx
new file mode 100644
index 0000000..b8d2290
--- /dev/null
+++ b/src/app/products/components/shared/ProductsTable/columns.tsx
@@ -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: Действия,
+ width: "0%",
+ render: product => (
+
+ {/* onPrintBarcodeClick(product)}>*/}
+ {/* */}
+ {/**/}
+ onChange(product)}>
+
+
+ onDelete(product)}>
+
+
+
+ ),
+ },
+ {
+ accessor: "article",
+ title: "Артикул",
+ },
+ {
+ accessor: "factoryArticle",
+ title: "Складской артикул",
+ },
+ {
+ accessor: "name",
+ title: "Название",
+ },
+ {
+ accessor: "barcodes",
+ title: "Штрихкоды",
+ render: product => {
+ return (
+
+
+ {product.barcodes.map(barcode => (
+
+ {barcode}
+
+ ))}
+
+
+ );
+ },
+ },
+ {
+ accessor: "barcodeTemplate.name",
+ title: "Шаблон ШК",
+ },
+ {
+ accessor: "brand",
+ title: "Бренд",
+ },
+ {
+ accessor: "composition",
+ title: "Состав",
+ },
+ {
+ accessor: "color",
+ title: "Цвет",
+ },
+ {
+ accessor: "size",
+ title: "Размер",
+ },
+ {
+ accessor: "additionalInfo",
+ title: "Доп. информация",
+ },
+ ] as DataTableColumn[],
+ []
+ );
+};
diff --git a/src/app/products/contexts/ProductsContext.tsx b/src/app/products/contexts/ProductsContext.tsx
new file mode 100644
index 0000000..815b888
--- /dev/null
+++ b/src/app/products/contexts/ProductsContext.tsx
@@ -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;
+ products: ProductSchema[];
+ productsCrud: ProductsCrud;
+ paginationInfo?: PaginationInfoSchema;
+};
+
+const useProductsContextState = (): ProductsContextState => {
+ const productsFiltersForm = useForm({
+ 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(useProductsContextState, "Products");
diff --git a/src/app/products/hooks/useProductsActions.ts b/src/app/products/hooks/useProductsActions.ts
new file mode 100644
index 0000000..4a7b0d6
--- /dev/null
+++ b/src/app/products/hooks/useProductsActions.ts
@@ -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;
diff --git a/src/app/products/hooks/useProductsList.tsx b/src/app/products/hooks/useProductsList.tsx
new file mode 100644
index 0000000..7ce21a1
--- /dev/null
+++ b/src/app/products/hooks/useProductsList.tsx
@@ -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;
diff --git a/src/app/products/page.tsx b/src/app/products/page.tsx
new file mode 100644
index 0000000..0b38ed9
--- /dev/null
+++ b/src/app/products/page.tsx
@@ -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 (
+
+
+
+ }>
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/layout/Navbar/data/linksData.ts b/src/components/layout/Navbar/data/linksData.ts
index fa03ef2..a51c1f8 100644
--- a/src/components/layout/Navbar/data/linksData.ts
+++ b/src/components/layout/Navbar/data/linksData.ts
@@ -1,4 +1,5 @@
import {
+ IconBox,
IconColumns,
IconFileBarcode,
IconLayoutKanban,
@@ -26,6 +27,12 @@ const linksData: LinkData[] = [
href: "/services",
moduleName: ModuleNames.FULFILLMENT_BASE,
},
+ {
+ icon: IconBox,
+ label: "Товары",
+ href: "/products",
+ moduleName: ModuleNames.FULFILLMENT_BASE,
+ },
{
icon: IconFileBarcode,
label: "Шаблоны штрихкодов",
diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts
index c0ee4c3..b083c7e 100644
--- a/src/lib/client/types.gen.ts
+++ b/src/lib/client/types.gen.ts
@@ -487,6 +487,14 @@ export type CreateProductSchema = {
* Factoryarticle
*/
factoryArticle: string;
+ /**
+ * Clientid
+ */
+ clientId: number;
+ /**
+ * Barcodetemplateid
+ */
+ barcodeTemplateId: number;
/**
* Brand
*/
@@ -507,6 +515,10 @@ export type CreateProductSchema = {
* Additionalinfo
*/
additionalInfo: string | null;
+ /**
+ * Barcodes
+ */
+ barcodes: Array;
};
/**
@@ -1098,6 +1110,7 @@ export type GetProductsResponse = {
* Items
*/
items: Array;
+ paginationInfo: PaginationInfoSchema;
};
/**
@@ -1218,6 +1231,14 @@ export type ProductSchema = {
* Factoryarticle
*/
factoryArticle: string;
+ /**
+ * Clientid
+ */
+ clientId: number;
+ /**
+ * Barcodetemplateid
+ */
+ barcodeTemplateId: number;
/**
* Brand
*/
@@ -1238,10 +1259,15 @@ export type ProductSchema = {
* Additionalinfo
*/
additionalInfo: string | null;
+ /**
+ * Barcodes
+ */
+ barcodes: Array;
/**
* Id
*/
id: number;
+ barcodeTemplate: BarcodeTemplateSchema;
};
/**
@@ -1704,6 +1730,10 @@ export type UpdateProductSchema = {
* Factoryarticle
*/
factoryArticle?: string | null;
+ /**
+ * Barcodetemplateid
+ */
+ barcodeTemplateId?: number | null;
/**
* Brand
*/
@@ -1724,6 +1754,10 @@ export type UpdateProductSchema = {
* Additionalinfo
*/
additionalInfo?: string | null;
+ /**
+ * Barcodes
+ */
+ barcodes?: Array | null;
/**
* Images
*/
@@ -3218,6 +3252,10 @@ export type GetProductsData = {
body?: never;
path?: never;
query?: {
+ /**
+ * Clientid
+ */
+ clientId?: number | null;
/**
* Searchinput
*/
diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts
index 01adaa2..e7dfe7b 100644
--- a/src/lib/client/zod.gen.ts
+++ b/src/lib/client/zod.gen.ts
@@ -208,12 +208,16 @@ export const zProductSchema = z.object({
name: z.string(),
article: z.string(),
factoryArticle: z.string(),
+ clientId: z.int(),
+ barcodeTemplateId: z.int(),
brand: z.union([z.string(), z.null()]),
color: z.union([z.string(), z.null()]),
composition: z.union([z.string(), z.null()]),
size: z.union([z.string(), z.null()]),
additionalInfo: z.union([z.string(), z.null()]),
+ barcodes: z.array(z.string()),
id: z.int(),
+ barcodeTemplate: zBarcodeTemplateSchema,
});
/**
@@ -377,11 +381,14 @@ export const zCreateProductSchema = z.object({
name: z.string(),
article: z.string(),
factoryArticle: z.string(),
+ clientId: z.int(),
+ barcodeTemplateId: z.int(),
brand: z.union([z.string(), z.null()]),
color: z.union([z.string(), z.null()]),
composition: z.union([z.string(), z.null()]),
size: 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({
items: z.array(zProductSchema),
+ paginationInfo: zPaginationInfoSchema,
});
/**
@@ -1014,11 +1022,13 @@ export const zUpdateProductSchema = z.object({
name: 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()])),
+ barcodeTemplateId: z.optional(z.union([z.int(), z.null()])),
brand: 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()])),
size: 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()])),
});
@@ -1718,6 +1728,7 @@ export const zGetProductsData = z.object({
path: z.optional(z.never()),
query: z.optional(
z.object({
+ clientId: z.optional(z.union([z.int(), z.null()])),
searchInput: z.optional(z.union([z.string(), z.null()])),
page: z.optional(z.union([z.int(), z.null()])),
itemsPerPage: z.optional(z.union([z.int(), z.null()])),
diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealInfoView/components/ProductsActions/ProductsActions.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealInfoView/components/ProductsActions/ProductsActions.tsx
index 3d0b753..9dd25eb 100644
--- a/src/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealInfoView/components/ProductsActions/ProductsActions.tsx
+++ b/src/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealInfoView/components/ProductsActions/ProductsActions.tsx
@@ -2,6 +2,7 @@ import { FC } from "react";
import { Button, Flex } from "@mantine/core";
import { modals } from "@mantine/modals";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
+import { notifications } from "@/lib/notifications";
const ProductsActions: FC = () => {
const { deal, dealProductsList, productsCrud, dealProductsCrud } =
@@ -20,6 +21,11 @@ const ProductsActions: FC = () => {
};
const onCreateDealProductClick = () => {
+ if (!deal.client) {
+ notifications.error({ message: "Выберите клиента для сделки" });
+ return;
+ }
+
const productIdsToExclude = dealProductsList.dealProducts.map(
product => product.product.id
);
@@ -33,7 +39,7 @@ const ProductsActions: FC = () => {
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
productIdsToExclude,
isEditing: false,
- clientId: 0, // TODO add clients
+ clientId: deal.client.id,
},
});
};
diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/AddDealProductButton/AddDealProductButton.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/AddDealProductButton/AddDealProductButton.tsx
index ebe4f23..d2b9892 100644
--- a/src/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/AddDealProductButton/AddDealProductButton.tsx
+++ b/src/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/AddDealProductButton/AddDealProductButton.tsx
@@ -3,6 +3,7 @@ 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 { notifications } from "@/lib/notifications";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
type Props = ButtonProps;
@@ -12,6 +13,11 @@ const AddDealProductButton: FC = props => {
useFulfillmentBaseContext();
const onCreateClick = () => {
+ if (!deal.client) {
+ notifications.error({ message: "Выберите клиента для сделки" });
+ return;
+ }
+
const productIdsToExclude = dealProductsList.dealProducts.map(
product => product.product.id
);
@@ -25,7 +31,7 @@ const AddDealProductButton: FC = props => {
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
productIdsToExclude,
isEditing: false,
- clientId: 0, // TODO add clients
+ clientId: deal.client.id,
},
});
};
diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/DealProductView/DealProductView.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/DealProductView/DealProductView.tsx
index 6f2eb05..c740962 100644
--- a/src/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/DealProductView/DealProductView.tsx
+++ b/src/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/DealProductView/DealProductView.tsx
@@ -11,6 +11,7 @@ import {
} from "@mantine/core";
import { modals } from "@mantine/modals";
import { DealProductSchema } from "@/lib/client";
+import { notifications } from "@/lib/notifications";
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";
@@ -22,9 +23,14 @@ type Props = {
};
const DealProductView: FC = ({ dealProduct }) => {
- const { dealProductsCrud } = useFulfillmentBaseContext();
+ const { dealProductsCrud, deal } = useFulfillmentBaseContext();
const onChangeDealProductClick = () => {
+ if (!deal.client) {
+ notifications.error({ message: "Выберите клиента для сделки" });
+ return;
+ }
+
modals.openContextModal({
modal: "dealProductEditorModal",
title: "Добавление товара",
@@ -38,7 +44,7 @@ const DealProductView: FC = ({ dealProduct }) => {
),
entity: dealProduct,
isEditing: true,
- clientId: 0, // TODO add clients
+ clientId: deal.client.id,
},
});
};
diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/components/ProductSelect/ProductSelect.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/components/ProductSelect/ProductSelect.tsx
index f951d37..d367505 100644
--- a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/components/ProductSelect/ProductSelect.tsx
+++ b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/components/ProductSelect/ProductSelect.tsx
@@ -21,7 +21,7 @@ const ProductSelect: FC = (props: Props) => {
const [searchValue, setSearchValue] = useState("");
const [debounced] = useDebouncedValue(searchValue, 500);
const { products, isLoading } = useProductsList({
- // clientId: props.clientId,
+ clientId: props.clientId,
searchInput: debounced,
page: 0,
itemsPerPage: MAX_PRODUCTS,
diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext.tsx
index 47cc095..6b2a193 100644
--- a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext.tsx
+++ b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext.tsx
@@ -40,7 +40,9 @@ type Props = {
const useFulfillmentBaseContextState = ({
deal,
}: Props): FulfillmentBaseContextState => {
- const productQueryKey = getProductsQueryKey();
+ const productQueryKey = getProductsQueryKey({
+ query: { clientId: deal.client?.id },
+ });
const productsCrud = useProductsCrud({ queryKey: productQueryKey });
const dealProductsList = useDealProductsList({ dealId: deal.id });
diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useProductsList.ts b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useProductsList.ts
index 2a5a7c4..6fb2d1a 100644
--- a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useProductsList.ts
+++ b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useProductsList.ts
@@ -1,22 +1,40 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
-import { ProductSchema } from "@/lib/client";
+import { PaginationInfoSchema, ProductSchema } from "@/lib/client";
import {
getProductsOptions,
getProductsQueryKey,
} from "@/lib/client/@tanstack/react-query.gen";
type Props = {
+ clientId?: number;
searchInput: string;
page?: 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 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);
@@ -32,6 +50,7 @@ const useProductsList = ({ searchInput, page, itemsPerPage }: Props) => {
return {
products: data?.items ?? [],
+ paginationInfo: data?.paginationInfo,
setProducts,
refetch,
queryKey,
diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductEditorModal/ProductEditorModal.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductEditorModal/ProductEditorModal.tsx
index 07736a4..0cdf4d9 100644
--- a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductEditorModal/ProductEditorModal.tsx
+++ b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductEditorModal/ProductEditorModal.tsx
@@ -1,9 +1,11 @@
"use client";
-import { Fieldset, Flex, TextInput } from "@mantine/core";
+import { Fieldset, Flex, Stack, TagsInput, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
+import BarcodeTemplateSelect from "@/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect";
import {
+ BarcodeTemplateSchema,
CreateProductSchema,
ProductSchema,
UpdateProductSchema,
@@ -18,6 +20,14 @@ type Props = CreateEditFormProps<
CreateProductSchema,
UpdateProductSchema,
ProductSchema
+> & {
+ clientId: number;
+};
+
+type ProductForm = Partial<
+ ProductSchema & {
+ barcodeTemplate: BarcodeTemplateSchema;
+ }
>;
const ProductEditorModal = ({
@@ -27,7 +37,7 @@ const ProductEditorModal = ({
}: ContextModalProps) => {
const isEditing = "entity" in innerProps;
- const initialValues: Partial = isEditing
+ const initialValues: ProductForm = isEditing
? innerProps.entity!
: {
name: "",
@@ -38,6 +48,9 @@ const ProductEditorModal = ({
color: "",
size: "",
additionalInfo: "",
+ clientId: innerProps.clientId,
+ barcodeTemplate: undefined,
+ barcodeTemplateId: undefined,
};
const form = useForm>({
@@ -50,60 +63,83 @@ const ProductEditorModal = ({
},
});
- const onClose = () => context.closeContextModal(id);
-
return (
+ onClose={() => context.closeContextModal(id)}>
{isEditing && (