crap
This commit is contained in:
		@@ -13,14 +13,21 @@ export type { ClientDetailsSchema } from './models/ClientDetailsSchema';
 | 
			
		||||
export type { ClientGetAllResponse } from './models/ClientGetAllResponse';
 | 
			
		||||
export type { ClientSchema } from './models/ClientSchema';
 | 
			
		||||
export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest';
 | 
			
		||||
export type { DealAddServicesRequest } from './models/DealAddServicesRequest';
 | 
			
		||||
export type { DealAddServicesResponse } from './models/DealAddServicesResponse';
 | 
			
		||||
export type { DealChangeStatusRequest } from './models/DealChangeStatusRequest';
 | 
			
		||||
export type { DealChangeStatusResponse } from './models/DealChangeStatusResponse';
 | 
			
		||||
export type { DealCreateRequest } from './models/DealCreateRequest';
 | 
			
		||||
export type { DealQuickCreateRequest } from './models/DealQuickCreateRequest';
 | 
			
		||||
export type { DealQuickCreateResponse } from './models/DealQuickCreateResponse';
 | 
			
		||||
export type { DealServiceSchema } from './models/DealServiceSchema';
 | 
			
		||||
export type { DealSummary } from './models/DealSummary';
 | 
			
		||||
export type { DealSummaryResponse } from './models/DealSummaryResponse';
 | 
			
		||||
export type { HTTPValidationError } from './models/HTTPValidationError';
 | 
			
		||||
export type { ProductCreateRequest } from './models/ProductCreateRequest';
 | 
			
		||||
export type { ProductCreateResponse } from './models/ProductCreateResponse';
 | 
			
		||||
export type { ProductGetResponse } from './models/ProductGetResponse';
 | 
			
		||||
export type { ProductSchema } from './models/ProductSchema';
 | 
			
		||||
export type { ServiceCategorySchema } from './models/ServiceCategorySchema';
 | 
			
		||||
export type { ServiceCreateCategoryRequest } from './models/ServiceCreateCategoryRequest';
 | 
			
		||||
export type { ServiceCreateCategoryResponse } from './models/ServiceCreateCategoryResponse';
 | 
			
		||||
@@ -34,4 +41,5 @@ export type { ValidationError } from './models/ValidationError';
 | 
			
		||||
export { AuthService } from './services/AuthService';
 | 
			
		||||
export { ClientService } from './services/ClientService';
 | 
			
		||||
export { DealService } from './services/DealService';
 | 
			
		||||
export { ProductService } from './services/ProductService';
 | 
			
		||||
export { ServiceService } from './services/ServiceService';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/client/models/DealAddServicesRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/models/DealAddServicesRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do no edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { DealServiceSchema } from './DealServiceSchema';
 | 
			
		||||
export type DealAddServicesRequest = {
 | 
			
		||||
    deal_id: number;
 | 
			
		||||
    services: Array<DealServiceSchema>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/DealAddServicesResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/DealAddServicesResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do no edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type DealAddServicesResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/DealServiceSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/DealServiceSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do no edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type DealServiceSchema = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    quantity: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -8,5 +8,6 @@ export type DealSummary = {
 | 
			
		||||
    client_name: string;
 | 
			
		||||
    changed_at: string;
 | 
			
		||||
    status: number;
 | 
			
		||||
    total_price: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/client/models/ProductCreateRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/models/ProductCreateRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do no edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type ProductCreateRequest = {
 | 
			
		||||
    name: string;
 | 
			
		||||
    article: string;
 | 
			
		||||
    client_id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								src/client/models/ProductCreateResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/client/models/ProductCreateResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do no edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type ProductCreateResponse = {
 | 
			
		||||
    product_id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/ProductGetResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/ProductGetResponse.ts
									
									
									
									
									
										Normal 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 ProductGetResponse = {
 | 
			
		||||
    products: Array<ProductSchema>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								src/client/models/ProductSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/client/models/ProductSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do no edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type ProductSchema = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    name: string;
 | 
			
		||||
    article: string;
 | 
			
		||||
    client_id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -2,6 +2,8 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { DealAddServicesRequest } from '../models/DealAddServicesRequest';
 | 
			
		||||
import type { DealAddServicesResponse } from '../models/DealAddServicesResponse';
 | 
			
		||||
import type { DealChangeStatusRequest } from '../models/DealChangeStatusRequest';
 | 
			
		||||
import type { DealChangeStatusResponse } from '../models/DealChangeStatusResponse';
 | 
			
		||||
import type { DealCreateRequest } from '../models/DealCreateRequest';
 | 
			
		||||
@@ -83,4 +85,24 @@ export class DealService {
 | 
			
		||||
            url: '/deal/summaries',
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Services Add
 | 
			
		||||
     * @returns DealAddServicesResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static servicesAddDealServicesAddPost({
 | 
			
		||||
        requestBody,
 | 
			
		||||
    }: {
 | 
			
		||||
        requestBody: DealAddServicesRequest,
 | 
			
		||||
    }): CancelablePromise<DealAddServicesResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/deal/services/add',
 | 
			
		||||
            body: requestBody,
 | 
			
		||||
            mediaType: 'application/json',
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								src/client/services/ProductService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/client/services/ProductService.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do no edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { ProductCreateRequest } from '../models/ProductCreateRequest';
 | 
			
		||||
import type { ProductCreateResponse } from '../models/ProductCreateResponse';
 | 
			
		||||
import type { ProductGetResponse } from '../models/ProductGetResponse';
 | 
			
		||||
import type { CancelablePromise } from '../core/CancelablePromise';
 | 
			
		||||
import { OpenAPI } from '../core/OpenAPI';
 | 
			
		||||
import { request as __request } from '../core/request';
 | 
			
		||||
export class ProductService {
 | 
			
		||||
    /**
 | 
			
		||||
     * Create Product
 | 
			
		||||
     * @returns ProductCreateResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static createProductProductCreatePost({
 | 
			
		||||
        requestBody,
 | 
			
		||||
    }: {
 | 
			
		||||
        requestBody: ProductCreateRequest,
 | 
			
		||||
    }): CancelablePromise<ProductCreateResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/product/create',
 | 
			
		||||
            body: requestBody,
 | 
			
		||||
            mediaType: 'application/json',
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Get Product
 | 
			
		||||
     * @returns ProductGetResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static getProductsByClientId({
 | 
			
		||||
        clientId,
 | 
			
		||||
        page,
 | 
			
		||||
        itemsPerPage,
 | 
			
		||||
    }: {
 | 
			
		||||
        clientId: number,
 | 
			
		||||
        page: number,
 | 
			
		||||
        itemsPerPage: number,
 | 
			
		||||
    }): CancelablePromise<ProductGetResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
            url: '/product/get',
 | 
			
		||||
            query: {
 | 
			
		||||
                'client_id': clientId,
 | 
			
		||||
                'page': page,
 | 
			
		||||
                'items_per_page': itemsPerPage,
 | 
			
		||||
            },
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,7 @@ import {QuickDeal} from "../../../types/QuickDeal.ts";
 | 
			
		||||
import {FC} from "react";
 | 
			
		||||
import {useForm} from "@mantine/form";
 | 
			
		||||
import styles from './CreateDealForm.module.css';
 | 
			
		||||
import ClientSelect from "../../Selects/ClientSelect/ClientSelect.tsx";
 | 
			
		||||
import ClientAutocomplete from "../../Selects/ClientAutocomplete/ClientAutocomplete.tsx";
 | 
			
		||||
import {DateTimePicker} from "@mantine/dates";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
@@ -41,7 +41,7 @@ const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => {
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div className={styles['inputs']}>
 | 
			
		||||
                    <ClientSelect
 | 
			
		||||
                    <ClientAutocomplete
 | 
			
		||||
                        withAddress
 | 
			
		||||
                        nameRestProps={form.getInputProps('client_name')}
 | 
			
		||||
                        addressRestProps={form.getInputProps('client_address')}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ const DealSummaryCard: FC<Props> = ({dealSummary}) => {
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={styles['flex-item']}>
 | 
			
		||||
                    <Text size={"sm"} c={"gray.6"}>
 | 
			
		||||
                        228 руб
 | 
			
		||||
                        {dealSummary.total_price.toLocaleString('ru-RU')} руб
 | 
			
		||||
                    </Text>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import {Center, Image, rem, Stack, Tooltip, UnstyledButton, useMantineColorScheme} from '@mantine/core';
 | 
			
		||||
import {IconBox, IconCash, IconHome2, IconLogout, IconMan, IconMoon, IconSun,} from '@tabler/icons-react';
 | 
			
		||||
import {IconBarcode, IconBox, IconCash, IconHome2, IconLogout, IconMan, IconMoon, IconSun,} from '@tabler/icons-react';
 | 
			
		||||
import classes from './Navbar.module.css';
 | 
			
		||||
import {useAppDispatch} from "../../redux/store.ts";
 | 
			
		||||
import {logout} from "../../features/authSlice.ts";
 | 
			
		||||
@@ -50,6 +50,11 @@ const mockdata = [
 | 
			
		||||
        label: 'Услуги',
 | 
			
		||||
        href: '/services'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        icon: IconBarcode,
 | 
			
		||||
        label: 'Товары',
 | 
			
		||||
        href: '/products'
 | 
			
		||||
    }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export function Navbar() {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,5 +2,8 @@
 | 
			
		||||
    border-radius: rem(20);
 | 
			
		||||
    background-color: var(--mantine-color-body);
 | 
			
		||||
    padding: rem(10);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container-fluid {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
import {FC, ReactNode} from "react";
 | 
			
		||||
import styles from './PageBlock.module.css';
 | 
			
		||||
import classNames from "classnames";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    children: ReactNode
 | 
			
		||||
    fluid?: boolean;
 | 
			
		||||
}
 | 
			
		||||
export const PageBlock: FC<Props> = ({children}) => {
 | 
			
		||||
export const PageBlock: FC<Props> = ({children, fluid = true}) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={styles['container']}>
 | 
			
		||||
        <div className={classNames(styles['container'], fluid && styles['container-fluid'])}>
 | 
			
		||||
            {children}
 | 
			
		||||
        </div>
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,93 @@
 | 
			
		||||
import {useDebouncedValue} from "@mantine/hooks";
 | 
			
		||||
import {Autocomplete, AutocompleteProps, TextInput, TextInputProps} from "@mantine/core";
 | 
			
		||||
import {FC, useEffect, useState} from "react";
 | 
			
		||||
import {Client} from "../../../types/Client.ts";
 | 
			
		||||
import {ClientService} from "../../../client";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    onSelect?: (client: Client) => void;
 | 
			
		||||
    withAddress?: boolean;
 | 
			
		||||
    nameRestProps?: AutocompleteProps;
 | 
			
		||||
    addressRestProps?: TextInputProps;
 | 
			
		||||
}
 | 
			
		||||
const ClientAutocomplete: FC<Props> = ({onSelect, addressRestProps, nameRestProps, withAddress = false}) => {
 | 
			
		||||
    const [value, setValue] = useState('');
 | 
			
		||||
    const [debouncedValue] = useDebouncedValue(value, 200);
 | 
			
		||||
 | 
			
		||||
    // const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
    const [clients, setClients] = useState<Client[]>([])
 | 
			
		||||
    const [selectedClient, selectClient] = useState<Client>();
 | 
			
		||||
 | 
			
		||||
    const handleChange = (value: string) => {
 | 
			
		||||
        setClients([]);
 | 
			
		||||
        setValue(value);
 | 
			
		||||
    }
 | 
			
		||||
    const handleDebouncedChange = () => {
 | 
			
		||||
        if (!value.trim()) return;
 | 
			
		||||
        // setIsLoading(true);
 | 
			
		||||
        ClientService.searchClients({name: value}).then(({clients}) => {
 | 
			
		||||
            setClients(clients);
 | 
			
		||||
            // setIsLoading(false);
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        handleDebouncedChange();
 | 
			
		||||
    }, [debouncedValue]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        selectClient(clients.find(client =>
 | 
			
		||||
                client.name.toLowerCase().trim() == value.toLowerCase().trim())
 | 
			
		||||
            ||
 | 
			
		||||
            {
 | 
			
		||||
                name: value,
 | 
			
		||||
                id: -1,
 | 
			
		||||
                address: ""
 | 
			
		||||
            });
 | 
			
		||||
    }, [value]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!selectedClient) return;
 | 
			
		||||
        if (onSelect) onSelect(selectedClient);
 | 
			
		||||
        if (nameRestProps?.onChange) nameRestProps.onChange(selectedClient.name);
 | 
			
		||||
        if (addressRestProps?.onChange) {
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            addressRestProps.onChange(selectedClient.address);
 | 
			
		||||
        }
 | 
			
		||||
    }, [selectedClient]);
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <Autocomplete
 | 
			
		||||
                {...nameRestProps}
 | 
			
		||||
                placeholder={'Клиент: название'}
 | 
			
		||||
                onChange={handleChange}
 | 
			
		||||
                value={value}
 | 
			
		||||
                data={clients.map(client => client.name)}
 | 
			
		||||
                styles={withAddress ? {
 | 
			
		||||
                    input: {
 | 
			
		||||
                        borderBottomLeftRadius: 0,
 | 
			
		||||
                        borderBottomRightRadius: 0
 | 
			
		||||
                    }
 | 
			
		||||
                } : {}}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            {withAddress &&
 | 
			
		||||
                <TextInput
 | 
			
		||||
                    placeholder={'Клиент: адрес'}
 | 
			
		||||
                    styles={{
 | 
			
		||||
                        input: {
 | 
			
		||||
                            borderTopLeftRadius: 0,
 | 
			
		||||
                            borderTopRightRadius: 0,
 | 
			
		||||
                        }
 | 
			
		||||
                    }}
 | 
			
		||||
                    value={selectedClient?.address || ''}
 | 
			
		||||
                    onChange={event => {
 | 
			
		||||
                        selectClient(prevState => prevState && {...prevState, address: event.target.value})
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
            }
 | 
			
		||||
        </>
 | 
			
		||||
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
export default ClientAutocomplete;
 | 
			
		||||
@@ -1,93 +1,31 @@
 | 
			
		||||
import {useDebouncedValue} from "@mantine/hooks";
 | 
			
		||||
import {Autocomplete, AutocompleteProps, TextInput, TextInputProps} from "@mantine/core";
 | 
			
		||||
import {FC, useEffect, useState} from "react";
 | 
			
		||||
import {Client} from "../../../types/Client.ts";
 | 
			
		||||
import {ClientService} from "../../../client";
 | 
			
		||||
import {FC} from "react";
 | 
			
		||||
import {Select} from "@mantine/core";
 | 
			
		||||
import {ClientSchema} from "../../../client";
 | 
			
		||||
import useClientsList from "../../../pages/ClientsPage/hooks/useClientsList.tsx";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    onSelect?: (client: Client) => void;
 | 
			
		||||
    withAddress?: boolean;
 | 
			
		||||
    nameRestProps?: AutocompleteProps;
 | 
			
		||||
    addressRestProps?: TextInputProps;
 | 
			
		||||
    value?: ClientSchema;
 | 
			
		||||
    onChange: (client: ClientSchema) => void;
 | 
			
		||||
    withLabel?: boolean;
 | 
			
		||||
}
 | 
			
		||||
const ClientSelect: FC<Props> = ({onSelect, addressRestProps, nameRestProps, withAddress = false}) => {
 | 
			
		||||
    const [value, setValue] = useState('');
 | 
			
		||||
    const [debouncedValue] = useDebouncedValue(value, 200);
 | 
			
		||||
 | 
			
		||||
    // const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
    const [clients, setClients] = useState<Client[]>([])
 | 
			
		||||
    const [selectedClient, selectClient] = useState<Client>();
 | 
			
		||||
 | 
			
		||||
    const handleChange = (value: string) => {
 | 
			
		||||
        setClients([]);
 | 
			
		||||
        setValue(value);
 | 
			
		||||
    }
 | 
			
		||||
    const handleDebouncedChange = () => {
 | 
			
		||||
        if (!value.trim()) return;
 | 
			
		||||
        // setIsLoading(true);
 | 
			
		||||
        ClientService.searchClients({name: value}).then(({clients}) => {
 | 
			
		||||
            setClients(clients);
 | 
			
		||||
            // setIsLoading(false);
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        handleDebouncedChange();
 | 
			
		||||
    }, [debouncedValue]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        selectClient(clients.find(client =>
 | 
			
		||||
                client.name.toLowerCase().trim() == value.toLowerCase().trim())
 | 
			
		||||
            ||
 | 
			
		||||
            {
 | 
			
		||||
                name: value,
 | 
			
		||||
                id: -1,
 | 
			
		||||
                address: ""
 | 
			
		||||
            });
 | 
			
		||||
    }, [value]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!selectedClient) return;
 | 
			
		||||
        if (onSelect) onSelect(selectedClient);
 | 
			
		||||
        if (nameRestProps?.onChange) nameRestProps.onChange(selectedClient.name);
 | 
			
		||||
        if (addressRestProps?.onChange) {
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            addressRestProps.onChange(selectedClient.address);
 | 
			
		||||
        }
 | 
			
		||||
    }, [selectedClient]);
 | 
			
		||||
const ClientSelect: FC<Props> = ({value, onChange, withLabel = false}) => {
 | 
			
		||||
    const {clients} = useClientsList();
 | 
			
		||||
    const options = clients.map(client => ({label: client.name, value: client.id.toString()}))
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <Autocomplete
 | 
			
		||||
                {...nameRestProps}
 | 
			
		||||
                placeholder={'Клиент: название'}
 | 
			
		||||
                onChange={handleChange}
 | 
			
		||||
                value={value}
 | 
			
		||||
                data={clients.map(client => client.name)}
 | 
			
		||||
                styles={withAddress ? {
 | 
			
		||||
                    input: {
 | 
			
		||||
                        borderBottomLeftRadius: 0,
 | 
			
		||||
                        borderBottomRightRadius: 0
 | 
			
		||||
                    }
 | 
			
		||||
                } : {}}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            {withAddress &&
 | 
			
		||||
                <TextInput
 | 
			
		||||
                    placeholder={'Клиент: адрес'}
 | 
			
		||||
                    styles={{
 | 
			
		||||
                        input: {
 | 
			
		||||
                            borderTopLeftRadius: 0,
 | 
			
		||||
                            borderTopRightRadius: 0,
 | 
			
		||||
                        }
 | 
			
		||||
                    }}
 | 
			
		||||
                    value={selectedClient?.address || ''}
 | 
			
		||||
                    onChange={event => {
 | 
			
		||||
                        selectClient(prevState => prevState && {...prevState, address: event.target.value})
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
            }
 | 
			
		||||
        </>
 | 
			
		||||
        <Select
 | 
			
		||||
 | 
			
		||||
            placeholder={"Выберите клиента"}
 | 
			
		||||
            value={value && options.find(client => client.value == value.id.toString())?.value}
 | 
			
		||||
            onChange={event => {
 | 
			
		||||
                if (!event) return;
 | 
			
		||||
                const client = clients.find(client => client.id == parseInt(event));
 | 
			
		||||
                if (!client) return;
 | 
			
		||||
                onChange(client);
 | 
			
		||||
            }}
 | 
			
		||||
            data={options}
 | 
			
		||||
            label={withLabel && "Клиент"}
 | 
			
		||||
        />
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
export default ClientSelect;
 | 
			
		||||
@@ -10,5 +10,5 @@
 | 
			
		||||
.container {
 | 
			
		||||
    padding: rem(20);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    min-height: 100vh;
 | 
			
		||||
    //min-height: 100vh;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
import {ProductSchema} from "../../../../client";
 | 
			
		||||
import {FC, RefObject} from "react";
 | 
			
		||||
import {BaseTable, BaseTableRef} from "../../../../components/BaseTable/BaseTable.tsx";
 | 
			
		||||
import {MRT_TableOptions} from "mantine-react-table";
 | 
			
		||||
import {useProductsTableColumns} from "./columns.tsx";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    products: ProductSchema[];
 | 
			
		||||
    tableRef?: RefObject<BaseTableRef<ProductSchema>>
 | 
			
		||||
}
 | 
			
		||||
const ProductsTable: FC<Props> = ({products, tableRef}) => {
 | 
			
		||||
    const columns = useProductsTableColumns();
 | 
			
		||||
    return (
 | 
			
		||||
        <BaseTable
 | 
			
		||||
            ref={tableRef}
 | 
			
		||||
            data={products}
 | 
			
		||||
            columns={columns}
 | 
			
		||||
            restProps={{
 | 
			
		||||
                enableColumnActions: false,
 | 
			
		||||
                manualPagination: true,
 | 
			
		||||
                enableBottomToolbar: true,
 | 
			
		||||
                enablePagination: true
 | 
			
		||||
            } as MRT_TableOptions<ProductSchema>}
 | 
			
		||||
 | 
			
		||||
        />
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ProductsTable;
 | 
			
		||||
							
								
								
									
										20
									
								
								src/pages/ProductsPage/components/ProductsTable/columns.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/pages/ProductsPage/components/ProductsTable/columns.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
import {useMemo} from "react";
 | 
			
		||||
import {MRT_ColumnDef} from "mantine-react-table";
 | 
			
		||||
import {ProductSchema} from "../../../../client";
 | 
			
		||||
 | 
			
		||||
export const useProductsTableColumns = () => {
 | 
			
		||||
    return useMemo<MRT_ColumnDef<ProductSchema>[]>(() => [
 | 
			
		||||
        {
 | 
			
		||||
            accessorKey: "article",
 | 
			
		||||
            header: "Артикул",
 | 
			
		||||
            enableSorting: false,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            accessorKey: "name",
 | 
			
		||||
            header: "Название",
 | 
			
		||||
            enableSorting: false,
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
    ], []);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/pages/ProductsPage/hooks/useProductsList.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/pages/ProductsPage/hooks/useProductsList.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import {useQuery} from "@tanstack/react-query";
 | 
			
		||||
import {ProductService} from "../../../client";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    clientId: number,
 | 
			
		||||
    page: number,
 | 
			
		||||
    itemsPerPage: number,
 | 
			
		||||
}
 | 
			
		||||
const useServicesList = (props: Props) => {
 | 
			
		||||
    const {clientId, page, itemsPerPage} = props;
 | 
			
		||||
    const {isPending, error, data, refetch} = useQuery({
 | 
			
		||||
        queryKey: ['getAllServices', clientId, page, itemsPerPage],
 | 
			
		||||
        queryFn: () => ProductService.getProductsByClientId({clientId, page, itemsPerPage})
 | 
			
		||||
    });
 | 
			
		||||
    const products = isPending || error || !data ? [] : data.products;
 | 
			
		||||
 | 
			
		||||
    return {products, refetch}
 | 
			
		||||
}
 | 
			
		||||
export default useServicesList;
 | 
			
		||||
							
								
								
									
										1
									
								
								src/pages/ProductsPage/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/pages/ProductsPage/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export {ProductsPage} from './ui/ProductsPage';
 | 
			
		||||
							
								
								
									
										17
									
								
								src/pages/ProductsPage/ui/ProductsPage.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/pages/ProductsPage/ui/ProductsPage.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
.container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    gap: rem(10);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.body-container {
 | 
			
		||||
    padding: rem(5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.top-panel {
 | 
			
		||||
    padding: rem(5);
 | 
			
		||||
    gap: rem(10);
 | 
			
		||||
    display: flex;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								src/pages/ProductsPage/ui/ProductsPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/pages/ProductsPage/ui/ProductsPage.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
 | 
			
		||||
import {FC, useState} from "react";
 | 
			
		||||
import styles from './ProductsPage.module.css';
 | 
			
		||||
import {Drawer} from "@mantine/core";
 | 
			
		||||
import {useDisclosure} from "@mantine/hooks";
 | 
			
		||||
import ClientSelect from "../../../components/Selects/ClientSelect/ClientSelect.tsx";
 | 
			
		||||
import ProductsTable from "../components/ProductsTable/ProductsTable.tsx";
 | 
			
		||||
import useProductsList from "../hooks/useProductsList.tsx";
 | 
			
		||||
 | 
			
		||||
export const ProductsPage: FC = () => {
 | 
			
		||||
    const [opened, {open, close}] = useDisclosure(false);
 | 
			
		||||
    const [clientId, setClientId] = useState(-1);
 | 
			
		||||
    const {products} = useProductsList({clientId, page: 0, itemsPerPage: 10});
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <Drawer
 | 
			
		||||
                opened={opened}
 | 
			
		||||
                onClose={close}
 | 
			
		||||
                position={"right"}
 | 
			
		||||
                size={"100%"}
 | 
			
		||||
            />
 | 
			
		||||
            <div className={styles['container']}>
 | 
			
		||||
 | 
			
		||||
                <PageBlock>
 | 
			
		||||
                    <div className={styles['top-panel']}>
 | 
			
		||||
 | 
			
		||||
                        <ClientSelect onChange={event => setClientId(event.id)}/>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                </PageBlock>
 | 
			
		||||
                <PageBlock>
 | 
			
		||||
                    <div className={styles['body-container']}>
 | 
			
		||||
                        <ProductsTable products={products}/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </PageBlock>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        </>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@@ -18,6 +18,7 @@ import { Route as rootRoute } from './routes/__root'
 | 
			
		||||
 | 
			
		||||
const TestLazyImport = createFileRoute('/test')()
 | 
			
		||||
const ServicesLazyImport = createFileRoute('/services')()
 | 
			
		||||
const ProductsLazyImport = createFileRoute('/products')()
 | 
			
		||||
const LoginLazyImport = createFileRoute('/login')()
 | 
			
		||||
const LeadsLazyImport = createFileRoute('/leads')()
 | 
			
		||||
const ClientsLazyImport = createFileRoute('/clients')()
 | 
			
		||||
@@ -35,6 +36,11 @@ const ServicesLazyRoute = ServicesLazyImport.update({
 | 
			
		||||
  getParentRoute: () => rootRoute,
 | 
			
		||||
} as any).lazy(() => import('./routes/services.lazy').then((d) => d.Route))
 | 
			
		||||
 | 
			
		||||
const ProductsLazyRoute = ProductsLazyImport.update({
 | 
			
		||||
  path: '/products',
 | 
			
		||||
  getParentRoute: () => rootRoute,
 | 
			
		||||
} as any).lazy(() => import('./routes/products.lazy').then((d) => d.Route))
 | 
			
		||||
 | 
			
		||||
const LoginLazyRoute = LoginLazyImport.update({
 | 
			
		||||
  path: '/login',
 | 
			
		||||
  getParentRoute: () => rootRoute,
 | 
			
		||||
@@ -75,6 +81,10 @@ declare module '@tanstack/react-router' {
 | 
			
		||||
      preLoaderRoute: typeof LoginLazyImport
 | 
			
		||||
      parentRoute: typeof rootRoute
 | 
			
		||||
    }
 | 
			
		||||
    '/products': {
 | 
			
		||||
      preLoaderRoute: typeof ProductsLazyImport
 | 
			
		||||
      parentRoute: typeof rootRoute
 | 
			
		||||
    }
 | 
			
		||||
    '/services': {
 | 
			
		||||
      preLoaderRoute: typeof ServicesLazyImport
 | 
			
		||||
      parentRoute: typeof rootRoute
 | 
			
		||||
@@ -93,6 +103,7 @@ export const routeTree = rootRoute.addChildren([
 | 
			
		||||
  ClientsLazyRoute,
 | 
			
		||||
  LeadsLazyRoute,
 | 
			
		||||
  LoginLazyRoute,
 | 
			
		||||
  ProductsLazyRoute,
 | 
			
		||||
  ServicesLazyRoute,
 | 
			
		||||
  TestLazyRoute,
 | 
			
		||||
])
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								src/routes/products.lazy.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/routes/products.lazy.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import {createLazyFileRoute} from "@tanstack/react-router";
 | 
			
		||||
import {ProductsPage} from "../pages/ProductsPage";
 | 
			
		||||
 | 
			
		||||
export const Route = createLazyFileRoute('/products')({
 | 
			
		||||
    component: ProductsPage
 | 
			
		||||
})
 | 
			
		||||
		Reference in New Issue
	
	Block a user