This commit is contained in:
2024-03-31 07:36:24 +03:00
parent 98a6dee996
commit 6328ac877a
38 changed files with 385 additions and 106 deletions

View File

@@ -27,8 +27,12 @@ export type { HTTPValidationError } from './models/HTTPValidationError';
export type { PaginationInfoSchema } from './models/PaginationInfoSchema'; export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
export type { ProductCreateRequest } from './models/ProductCreateRequest'; export type { ProductCreateRequest } from './models/ProductCreateRequest';
export type { ProductCreateResponse } from './models/ProductCreateResponse'; export type { ProductCreateResponse } from './models/ProductCreateResponse';
export type { ProductDeleteRequest } from './models/ProductDeleteRequest';
export type { ProductDeleteResponse } from './models/ProductDeleteResponse';
export type { ProductGetResponse } from './models/ProductGetResponse'; export type { ProductGetResponse } from './models/ProductGetResponse';
export type { ProductSchema } from './models/ProductSchema'; export type { ProductSchema } from './models/ProductSchema';
export type { ProductUpdateRequest } from './models/ProductUpdateRequest';
export type { ProductUpdateResponse } from './models/ProductUpdateResponse';
export type { ServiceCategorySchema } from './models/ServiceCategorySchema'; export type { ServiceCategorySchema } from './models/ServiceCategorySchema';
export type { ServiceCreateCategoryRequest } from './models/ServiceCreateCategoryRequest'; export type { ServiceCreateCategoryRequest } from './models/ServiceCreateCategoryRequest';
export type { ServiceCreateCategoryResponse } from './models/ServiceCreateCategoryResponse'; export type { ServiceCreateCategoryResponse } from './models/ServiceCreateCategoryResponse';

View File

@@ -3,6 +3,6 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export type AuthLoginResponse = { export type AuthLoginResponse = {
access_token: string; accessToken: string;
}; };

View File

@@ -4,7 +4,7 @@
/* eslint-disable */ /* eslint-disable */
export type ClientDetailsSchema = { export type ClientDetailsSchema = {
address?: (string | null); address?: (string | null);
phone_number?: (string | null); phoneNumber?: (string | null);
inn?: (number | null); inn?: (number | null);
email?: (string | null); email?: (string | null);
}; };

View File

@@ -4,7 +4,7 @@
/* eslint-disable */ /* eslint-disable */
import type { ClientDetailsSchema } from './ClientDetailsSchema'; import type { ClientDetailsSchema } from './ClientDetailsSchema';
export type ClientUpdateDetailsRequest = { export type ClientUpdateDetailsRequest = {
client_id: number; clientId: number;
details: ClientDetailsSchema; details: ClientDetailsSchema;
}; };

View File

@@ -4,7 +4,7 @@
/* eslint-disable */ /* eslint-disable */
import type { DealServiceSchema } from './DealServiceSchema'; import type { DealServiceSchema } from './DealServiceSchema';
export type DealAddServicesRequest = { export type DealAddServicesRequest = {
deal_id: number; dealId: number;
services: Array<DealServiceSchema>; services: Array<DealServiceSchema>;
}; };

View File

@@ -3,7 +3,7 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export type DealChangeStatusRequest = { export type DealChangeStatusRequest = {
deal_id: number; dealId: number;
new_status: number; newStatus: number;
}; };

View File

@@ -4,9 +4,9 @@
/* eslint-disable */ /* eslint-disable */
export type DealQuickCreateRequest = { export type DealQuickCreateRequest = {
name: string; name: string;
client_name: string; clientName: string;
client_address: string; clientAddress: string;
comment: string; comment: string;
acceptance_date: string; acceptanceDate: string;
}; };

View File

@@ -3,6 +3,6 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export type DealQuickCreateResponse = { export type DealQuickCreateResponse = {
deal_id: number; dealId: number;
}; };

View File

@@ -5,9 +5,9 @@
export type DealSummary = { export type DealSummary = {
id: number; id: number;
name: string; name: string;
client_name: string; clientName: string;
changed_at: string; changedAt: string;
status: number; status: number;
total_price: number; totalPrice: number;
}; };

View File

@@ -3,7 +3,7 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export type PaginationInfoSchema = { export type PaginationInfoSchema = {
total_pages: number; totalPages: number;
total_items: number; totalItems: number;
}; };

View File

@@ -5,6 +5,7 @@
export type ProductCreateRequest = { export type ProductCreateRequest = {
name: string; name: string;
article: string; article: string;
client_id: number; clientId: number;
barcodes: Array<string>;
}; };

View File

@@ -3,6 +3,8 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export type ProductCreateResponse = { export type ProductCreateResponse = {
product_id: number; ok: boolean;
message: string;
productId?: (number | null);
}; };

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ProductDeleteRequest = {
productId: number;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ProductDeleteResponse = {
ok: boolean;
message: string;
};

View File

@@ -6,6 +6,6 @@ import type { PaginationInfoSchema } from './PaginationInfoSchema';
import type { ProductSchema } from './ProductSchema'; import type { ProductSchema } from './ProductSchema';
export type ProductGetResponse = { export type ProductGetResponse = {
products: Array<ProductSchema>; products: Array<ProductSchema>;
pagination_info: PaginationInfoSchema; paginationInfo: PaginationInfoSchema;
}; };

View File

@@ -6,6 +6,7 @@ export type ProductSchema = {
id: number; id: number;
name: string; name: string;
article: string; article: string;
client_id: number; clientId: number;
barcodes: Array<string>;
}; };

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ProductSchema } from './ProductSchema';
export type ProductUpdateRequest = {
product: ProductSchema;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ProductUpdateResponse = {
ok: boolean;
message: string;
};

View File

@@ -4,7 +4,11 @@
/* eslint-disable */ /* eslint-disable */
import type { ProductCreateRequest } from '../models/ProductCreateRequest'; import type { ProductCreateRequest } from '../models/ProductCreateRequest';
import type { ProductCreateResponse } from '../models/ProductCreateResponse'; import type { ProductCreateResponse } from '../models/ProductCreateResponse';
import type { ProductDeleteRequest } from '../models/ProductDeleteRequest';
import type { ProductDeleteResponse } from '../models/ProductDeleteResponse';
import type { ProductGetResponse } from '../models/ProductGetResponse'; import type { ProductGetResponse } from '../models/ProductGetResponse';
import type { ProductUpdateRequest } from '../models/ProductUpdateRequest';
import type { ProductUpdateResponse } from '../models/ProductUpdateResponse';
import type { CancelablePromise } from '../core/CancelablePromise'; import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI'; import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request'; import { request as __request } from '../core/request';
@@ -14,7 +18,7 @@ export class ProductService {
* @returns ProductCreateResponse Successful Response * @returns ProductCreateResponse Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static createProductProductCreatePost({ public static createProduct({
requestBody, requestBody,
}: { }: {
requestBody: ProductCreateRequest, requestBody: ProductCreateRequest,
@@ -29,6 +33,46 @@ export class ProductService {
}, },
}); });
} }
/**
* Delete Product
* @returns ProductDeleteResponse Successful Response
* @throws ApiError
*/
public static deleteProduct({
requestBody,
}: {
requestBody: ProductDeleteRequest,
}): CancelablePromise<ProductDeleteResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/product/delete',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Delete Product
* @returns ProductUpdateResponse Successful Response
* @throws ApiError
*/
public static updateProduct({
requestBody,
}: {
requestBody: ProductUpdateRequest,
}): CancelablePromise<ProductUpdateResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/product/update',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/** /**
* Get Product * Get Product
* @returns ProductGetResponse Successful Response * @returns ProductGetResponse Successful Response

View File

@@ -2,13 +2,12 @@ import {
MantineReactTable, MantineReactTable,
MRT_ColumnDef, MRT_ColumnDef,
MRT_RowData, MRT_RowData,
MRT_Table,
MRT_TableInstance, MRT_TableInstance,
MRT_TableOptions, MRT_TableOptions,
useMantineReactTable useMantineReactTable
} from "mantine-react-table"; } from "mantine-react-table";
import {MRT_Localization_RU} from "mantine-react-table/locales/ru/index.cjs"; import {MRT_Localization_RU} from "mantine-react-table/locales/ru/index.cjs";
import {forwardRef, useEffect, useImperativeHandle} from 'react'; import {forwardRef, useImperativeHandle} from 'react';
type Props<T extends Record<string, any>, K extends keyof T> = { type Props<T extends Record<string, any>, K extends keyof T> = {
data: T[], data: T[],

View File

@@ -39,7 +39,7 @@ const CreateDealButton: FC<Props> = () => {
DealService.quickCreateDealQuickCreatePost({ DealService.quickCreateDealQuickCreatePost({
requestBody: { requestBody: {
...quickDeal, ...quickDeal,
acceptance_date: dateWithoutTimezone(quickDeal.acceptance_date) acceptanceDate: dateWithoutTimezone(quickDeal.acceptanceDate)
} }
}) })
}} }}

View File

@@ -14,10 +14,10 @@ const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
const form = useForm({ const form = useForm({
initialValues: { initialValues: {
name: '', name: '',
client_name: '', clientName: '',
client_address: '', clientAddress: '',
comment: '', comment: '',
acceptance_date: new Date() acceptanceDate: new Date()
} }
}); });
return ( return (
@@ -43,8 +43,8 @@ const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
<div className={styles['inputs']}> <div className={styles['inputs']}>
<ClientAutocomplete <ClientAutocomplete
withAddress withAddress
nameRestProps={form.getInputProps('client_name')} nameRestProps={form.getInputProps('clientName')}
addressRestProps={form.getInputProps('client_address')} addressRestProps={form.getInputProps('clientAddress')}
/> />
</div> </div>
@@ -60,7 +60,7 @@ const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
<div className={styles['inputs']}> <div className={styles['inputs']}>
<DateTimePicker <DateTimePicker
placeholder={'Дата приемки'} placeholder={'Дата приемки'}
{...form.getInputProps('acceptance_date')} {...form.getInputProps('acceptanceDate')}
/> />
</div> </div>

View File

@@ -15,7 +15,7 @@ const DealSummaryCard: FC<Props> = ({dealSummary}) => {
<div className={styles['flex-row']}> <div className={styles['flex-row']}>
<div className={styles['flex-item']}> <div className={styles['flex-item']}>
<Text size={"sm"} c={"gray.6"}> <Text size={"sm"} c={"gray.6"}>
{dealSummary.client_name} {dealSummary.clientName}
</Text> </Text>
</div> </div>
<div className={styles['flex-item']}> <div className={styles['flex-item']}>
@@ -23,14 +23,14 @@ const DealSummaryCard: FC<Props> = ({dealSummary}) => {
</div> </div>
<div className={styles['flex-item']}> <div className={styles['flex-item']}>
<Text size={"sm"} c={"gray.6"}> <Text size={"sm"} c={"gray.6"}>
{dealSummary.total_price.toLocaleString('ru-RU')} руб {dealSummary.totalPrice.toLocaleString('ru-RU')} руб
</Text> </Text>
</div> </div>
</div> </div>
<div className={classNames(styles['flex-row'], styles['flex-row-right'])}> <div className={classNames(styles['flex-row'], styles['flex-row-right'])}>
<div className={styles['flex-item']}> <div className={styles['flex-item']}>
<Text size={"sm"} c={"gray.6"}> <Text size={"sm"} c={"gray.6"}>
{new Date(dealSummary.changed_at).toLocaleString('ru-RU')} {new Date(dealSummary.changedAt).toLocaleString('ru-RU')}
</Text> </Text>
</div> </div>
<div className={styles['flex-item']}> <div className={styles['flex-item']}>

View File

@@ -1,26 +0,0 @@
import {ContextModalProps} from "@mantine/modals";
import {Button, Text} from "@mantine/core";
import {useForm} from "@mantine/form";
const CreateProductModal = ({
context,
id,
innerProps,
}: ContextModalProps<{ clientId: number }>) => {
const form = useForm({
initialValues: {
name: '',
article: '',
barcode: ''
}
})
return (
<>
<Button fullWidth mt="md" onClick={() => context.closeModal(id)}>
Close modal
</Button>
</>
)
};
export default CreateProductModal;

View File

@@ -1,7 +1,7 @@
import EnterDeadlineModal from "./EnterDeadlineModal/EnterDeadlineModal.tsx"; import EnterDeadlineModal from "./EnterDeadlineModal/EnterDeadlineModal.tsx";
import CreateServiceCategoryModal from "../pages/ServicesPage/modals/CreateServiceCategoryModal.tsx"; import CreateServiceCategoryModal from "../pages/ServicesPage/modals/CreateServiceCategoryModal.tsx";
import CreateServiceModal from "../pages/ServicesPage/modals/CreateServiceModal.tsx"; import CreateServiceModal from "../pages/ServicesPage/modals/CreateServiceModal.tsx";
import createProductModal from "./CreateProductModal/CreateProductModal.tsx"; import createProductModal from "../pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx";
export const modals = { export const modals = {
enterDeadline: EnterDeadlineModal, enterDeadline: EnterDeadlineModal,

View File

@@ -1,4 +1,4 @@
import React, {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import styles from './LeadsPage.module.css'; import styles from './LeadsPage.module.css';
import Board from "../../../components/Dnd/Board/Board.tsx"; import Board from "../../../components/Dnd/Board/Board.tsx";
import {DragDropContext} from "@hello-pangea/dnd"; import {DragDropContext} from "@hello-pangea/dnd";

View File

@@ -39,8 +39,8 @@ const LoginPage = () => {
}, },
(data: TelegramUser) => { (data: TelegramUser) => {
AuthService.loginAuthLoginPost({requestBody: data}) AuthService.loginAuthLoginPost({requestBody: data})
.then(({access_token}) => { .then(({accessToken}) => {
dispatch(login({accessToken: access_token})); dispatch(login({accessToken: accessToken}));
navigate({to: "/"}).then(() => { navigate({to: "/"}).then(() => {
notifications.success({message: "Вы успешно вошли!"}) notifications.success({message: "Вы успешно вошли!"})
}) })

View File

@@ -10,5 +10,4 @@
.container { .container {
padding: rem(20); padding: rem(20);
display: flex; display: flex;
//min-height: 100vh;
} }

View File

@@ -14,20 +14,17 @@ const PageWrapper: FC<Props> = ({children}) => {
<AppShell <AppShell
layout={"alt"} layout={"alt"}
navbar={{width: rem('80px'), breakpoint: "sm"}} navbar={{width: rem('80px'), breakpoint: "sm"}}
// header={{height:rem(60)}}
> >
{/*<AppShell.Header>*/}
{/* <Header/>*/}
{/*</AppShell.Header>*/}
<AppShell.Navbar> <AppShell.Navbar>
<Navbar/> {authState.isAuthorized &&
<Navbar/>
}
</AppShell.Navbar> </AppShell.Navbar>
<AppShell.Main className={styles['main-container']}> <AppShell.Main className={styles['main-container']}>
<div className={styles['container']}> <div className={styles['container']}>
{children} {children}
</div> </div>
</AppShell.Main> </AppShell.Main>
</AppShell> </AppShell>
) )

View File

@@ -1,24 +1,56 @@
import {ProductSchema} from "../../../../client"; import {ProductSchema} from "../../../../client";
import {FC, RefObject} from "react"; import {FC} from "react";
import {BaseTable, BaseTableRef} from "../../../../components/BaseTable/BaseTable.tsx"; import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
import {MRT_TableOptions} from "mantine-react-table"; import {MRT_TableOptions} from "mantine-react-table";
import {useProductsTableColumns} from "./columns.tsx"; import {useProductsTableColumns} from "./columns.tsx";
import {ActionIcon, Flex, Tooltip} from "@mantine/core";
import {IconEdit, IconTrash} from "@tabler/icons-react";
import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
import {modals} from "@mantine/modals";
type Props = {
products: ProductSchema[]; const ProductsTable: FC<CRUDTableProps<ProductSchema>> = ({items, onDelete, onChange, tableRef}) => {
tableRef?: RefObject<BaseTableRef<ProductSchema>>
}
const ProductsTable: FC<Props> = ({products, tableRef}) => {
const columns = useProductsTableColumns(); const columns = useProductsTableColumns();
const onEditClick = (product: ProductSchema) => {
if (!onChange) return;
modals.openContextModal({
modal: "createProduct",
title: 'Создание товара',
withCloseButton: false,
innerProps: {
onChange: (newProduct) => onChange(newProduct),
product: product,
}
})
}
return ( return (
<BaseTable <BaseTable
ref={tableRef} ref={tableRef}
data={products} data={items}
columns={columns} columns={columns}
restProps={{ restProps={{
enableColumnActions: false, enableColumnActions: false,
} as MRT_TableOptions<ProductSchema>} enableRowActions: true,
/> renderRowActions: ({row}) => (
<Flex gap="md">
<Tooltip label="Редактировать">
<ActionIcon
onClick={() => onEditClick(row.original)}
variant={"default"}>
<IconEdit/>
</ActionIcon>
</Tooltip>
<Tooltip label="Удалить">
<ActionIcon onClick={() => {
if (onDelete) onDelete(row.original);
}} variant={"default"}>
<IconTrash/>
</ActionIcon>
</Tooltip>
</Flex>
)
} as MRT_TableOptions<ProductSchema>}
/>
) )
} }

View File

@@ -1,8 +1,10 @@
import {useMemo} from "react"; import {useMemo} from "react";
import {MRT_ColumnDef} from "mantine-react-table"; import {MRT_ColumnDef} from "mantine-react-table";
import {ProductSchema} from "../../../../client"; import {ProductSchema} from "../../../../client";
import {List, Spoiler, useMantineTheme} from "@mantine/core";
export const useProductsTableColumns = () => { export const useProductsTableColumns = () => {
const theme = useMantineTheme();
return useMemo<MRT_ColumnDef<ProductSchema>[]>(() => [ return useMemo<MRT_ColumnDef<ProductSchema>[]>(() => [
{ {
accessorKey: "article", accessorKey: "article",
@@ -13,8 +15,27 @@ export const useProductsTableColumns = () => {
accessorKey: "name", accessorKey: "name",
header: "Название", header: "Название",
enableSorting: false, enableSorting: false,
}, },
{
accessorKey: "barcodes",
header: "Штрихкоды",
Cell: ({cell}) => {
return (
<List size={"sm"}>
<Spoiler maxHeight={parseFloat(theme.lineHeights.sm) * 25}
showLabel={"Показать все"}
hideLabel={"Скрыть"}>
{cell.getValue<string[]>().map(barcode => (
<List.Item key={barcode}>
{barcode}
</List.Item>
))}
</Spoiler>
</List>
)
}
}
], []); ], []);
} }

View File

@@ -8,12 +8,12 @@ type Props = {
} }
const useServicesList = (props: Props) => { const useServicesList = (props: Props) => {
const {clientId, page, itemsPerPage} = props; const {clientId, page, itemsPerPage} = props;
const {isPending, error, data, refetch} = useQuery({ const {data, refetch} = useQuery({
queryKey: ['getAllServices', clientId, page, itemsPerPage], queryKey: ['getAllServices', clientId, page, itemsPerPage],
queryFn: () => ProductService.getProductsByClientId({clientId, page, itemsPerPage}) queryFn: () => ProductService.getProductsByClientId({clientId, page, itemsPerPage})
}); });
const products = isPending || error || !data ? [] : data.products; const products = !data ? [] : data.products;
const paginationInfo = data?.pagination_info; const paginationInfo = data?.paginationInfo;
return {products, paginationInfo, refetch} return {products, paginationInfo, refetch}
} }
export default useServicesList; export default useServicesList;

View File

@@ -0,0 +1,97 @@
import {ContextModalProps} from "@mantine/modals";
import {Button, Flex, rem, TagsInput, TextInput} from "@mantine/core";
import {useForm} from "@mantine/form";
import {BaseProduct, CreateProductRequest} from "../../types.ts";
import {ProductSchema} from "../../../../client";
type CreateProps = {
clientId: number;
onCreate: (values: CreateProductRequest) => void
}
type EditProps = {
product: ProductSchema,
onChange: (values: ProductSchema) => void
}
type Props = CreateProps | EditProps;
const CreateProductModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const isEditProps = 'product' in innerProps;
const isCreatingProps = 'clientId' in innerProps;
const initialValues = isEditProps ? {
name: innerProps.product.name,
article: innerProps.product.article,
barcodes: innerProps.product.barcodes
} : {
name: '',
article: '',
barcodes: []
};
const form = useForm({
initialValues: initialValues,
validate: {
name: (name) => name.trim() !== '' ? null : "Необходимо ввести название товара",
article: (article) => article.trim() !== '' ? null : "Необходимо ввести артикул",
}
})
const onCancelClick = () => {
context.closeContextModal(id);
}
const onSubmit = (values: BaseProduct) => {
if (isEditProps) innerProps.onChange({...innerProps.product, ...values})
if (isCreatingProps) {
innerProps.onCreate({...values, clientId: innerProps.clientId});
form.reset();
}
}
return (
<>
<form onSubmit={form.onSubmit((values) => onSubmit(values))}>
<Flex gap={rem(10)} direction={"column"}>
<TextInput
placeholder={"Введите название товара"}
label={"Название товара"}
{...form.getInputProps('name')}
/>
<TextInput
placeholder={"Введите артикул"}
label={"Артикул"}
{...form.getInputProps('article')}
/>
<TagsInput
placeholder={!form.values.barcodes.length ? "Добавьте штрихкоды к товару" : ""}
label={"Штрихкоды"}
{...form.getInputProps('barcodes')}
/>
<Flex justify={"flex-end"} mt={rem(5)} gap={rem(10)}>
<Button onClick={() => onCancelClick()} variant={"subtle"}>Отменить</Button>
{isEditProps ?
<Button
onClick={() => context.closeContextModal(id)}
type={"submit"}
variant={"default"}
>Сохранить и закрыть</Button> :
<>
<Button
onClick={() => context.closeContextModal(id)}
type={"submit"}
variant={"default"}
>Создать и закрыть</Button>
<Button
type={"submit"}
variant={"default"}
>Создать</Button>
</>}
</Flex>
</Flex>
</form>
</>
)
};
export default CreateProductModal;

View File

@@ -0,0 +1,6 @@
export type BaseProduct = {
name: string;
article: string;
barcodes: string[];
}
export type CreateProductRequest = BaseProduct & { clientId: number }

View File

@@ -1,37 +1,83 @@
import PageBlock from "../../../components/PageBlock/PageBlock.tsx"; import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import styles from './ProductsPage.module.css'; import styles from './ProductsPage.module.css';
import {Button, Drawer, Pagination} from "@mantine/core"; import {Button, Text, Pagination} from "@mantine/core";
import {useDisclosure, usePagination} from "@mantine/hooks";
import ClientSelect from "../../../components/Selects/ClientSelect/ClientSelect.tsx"; import ClientSelect from "../../../components/Selects/ClientSelect/ClientSelect.tsx";
import ProductsTable from "../components/ProductsTable/ProductsTable.tsx"; import ProductsTable from "../components/ProductsTable/ProductsTable.tsx";
import useProductsList from "../hooks/useProductsList.tsx"; import useProductsList from "../hooks/useProductsList.tsx";
import {notifications} from "../../../shared/lib/notifications.ts";
import {modals} from "@mantine/modals"; import {modals} from "@mantine/modals";
import {notifications} from "../../../shared/lib/notifications.ts";
import {CreateProductRequest} from "../types.ts";
import {ProductSchema, ProductService} from "../../../client";
export const ProductsPage: FC = () => { export const ProductsPage: FC = () => {
// const [opened, {open, close}] = useDisclosure(false);
const [clientId, setClientId] = useState(-1); const [clientId, setClientId] = useState(-1);
const [totalPages, setTotalPages] = useState(1); const [totalPages, setTotalPages] = useState(1);
const pagination = usePagination({total: totalPages}); const [currentPage, setCurrentPage] = useState(1);
const {products, paginationInfo} = useProductsList({
const {products, paginationInfo, refetch} = useProductsList({
clientId, clientId,
page: pagination.active - 1, page: currentPage - 1,
itemsPerPage: 10 itemsPerPage: 10
}); });
const onProductCreate = (request: CreateProductRequest) => {
ProductService.createProduct({requestBody: request})
.then(async ({ok, message}) => {
notifications.guess(ok, {message: message});
if (!ok) return;
await refetch();
});
}
const onProductChange = (product: ProductSchema) => {
ProductService.updateProduct({requestBody: {product}})
.then(async ({ok, message}) => {
notifications.guess(ok, {message});
if (!ok) return;
await refetch();
})
}
const onCreateProductClick = () => { const onCreateProductClick = () => {
if (clientId < 0) {
notifications.error({message: "Необходимо выбрать клиента"});
return
}
modals.openContextModal({ modals.openContextModal({
modal: "createProduct", modal: "createProduct",
title: 'Создание категории', title: 'Создание товара',
withCloseButton: false, withCloseButton: false,
innerProps: { innerProps: {
clientId clientId,
onCreate: onProductCreate
} }
}) })
} }
const onDeleteClick = (product: ProductSchema) => {
modals.openConfirmModal({
title: 'Удаление товара',
centered: true,
children: (
<Text size="sm">
Вы уверены что хотите удалить товар {product.name}
</Text>
),
labels: {confirm: 'Да', cancel: "Нет"},
confirmProps: {color: 'red'},
onConfirm: () =>
ProductService.deleteProduct({requestBody: {productId: product.id}})
.then(async ({ok, message}) => {
notifications.guess(ok, {message: message});
if (!ok) return;
await refetch();
})
});
}
useEffect(() => { useEffect(() => {
if (!paginationInfo) return; if (!paginationInfo) return;
setTotalPages(paginationInfo.total_pages); setTotalPages(paginationInfo.totalPages);
}, [paginationInfo]); }, [paginationInfo]);
@@ -50,8 +96,18 @@ export const ProductsPage: FC = () => {
<PageBlock> <PageBlock>
<div className={styles['body-container']}> <div className={styles['body-container']}>
<div className={styles['table-container']}> <div className={styles['table-container']}>
<ProductsTable products={products}/> <ProductsTable
<Pagination className={styles['table-pagination']} total={100}/> onChange={onProductChange}
onDelete={onDeleteClick}
items={products}
/>
<Pagination
className={styles['table-pagination']}
withEdges
onChange={event => setCurrentPage(event)}
total={totalPages}
value={currentPage}
/>
</div> </div>
</div> </div>
</PageBlock> </PageBlock>

View File

@@ -26,10 +26,10 @@ export const ServicesPage: FC = () => {
} }
const onCreate = (values: ServiceSchema) => { const onCreate = (values: ServiceSchema) => {
ServiceService.createService({requestBody: {service: values}}) ServiceService.createService({requestBody: {service: values}})
.then(({ok, message}) => { .then(async ({ok, message}) => {
notifications.guess(ok, {message: message}); notifications.guess(ok, {message: message});
if (!ok) return; if (!ok) return;
refetch(); await refetch();
}) })
} }
const onCreateCategoryClick = () => { const onCreateCategoryClick = () => {

11
src/types/CRUDTable.tsx Normal file
View File

@@ -0,0 +1,11 @@
import {RefObject} from "react";
import {BaseTableRef} from "../components/BaseTable/BaseTable.tsx";
import {MRT_RowData} from "mantine-react-table";
export interface CRUDTableProps<T extends MRT_RowData> {
items: T[];
onCreate?: (item: T) => void;
onDelete?: (item: T) => void;
onChange?: (item: T) => void;
tableRef?: RefObject<BaseTableRef<T>>
}

View File

@@ -1,7 +1,7 @@
export type QuickDeal = { export type QuickDeal = {
name: string name: string
client_name: string clientName: string
client_address: string clientAddress: string
comment:string comment:string
acceptance_date: Date acceptanceDate: Date
} }