v0.1
This commit is contained in:
		@@ -32,6 +32,8 @@ export type { DealDeleteProductRequest } from './models/DealDeleteProductRequest
 | 
			
		||||
export type { DealDeleteProductResponse } from './models/DealDeleteProductResponse';
 | 
			
		||||
export type { DealDeleteProductsRequest } from './models/DealDeleteProductsRequest';
 | 
			
		||||
export type { DealDeleteProductsResponse } from './models/DealDeleteProductsResponse';
 | 
			
		||||
export type { DealDeleteRequest } from './models/DealDeleteRequest';
 | 
			
		||||
export type { DealDeleteResponse } from './models/DealDeleteResponse';
 | 
			
		||||
export type { DealDeleteServiceRequest } from './models/DealDeleteServiceRequest';
 | 
			
		||||
export type { DealDeleteServiceResponse } from './models/DealDeleteServiceResponse';
 | 
			
		||||
export type { DealDeleteServicesRequest } from './models/DealDeleteServicesRequest';
 | 
			
		||||
@@ -46,7 +48,6 @@ export type { DealServiceSchema } from './models/DealServiceSchema';
 | 
			
		||||
export type { DealStatusHistorySchema } from './models/DealStatusHistorySchema';
 | 
			
		||||
export type { DealSummary } from './models/DealSummary';
 | 
			
		||||
export type { DealSummaryReorderRequest } from './models/DealSummaryReorderRequest';
 | 
			
		||||
export type { DealSummaryReorderResponse } from './models/DealSummaryReorderResponse';
 | 
			
		||||
export type { DealSummaryResponse } from './models/DealSummaryResponse';
 | 
			
		||||
export type { DealUpdateGeneralInfoRequest } from './models/DealUpdateGeneralInfoRequest';
 | 
			
		||||
export type { DealUpdateGeneralInfoResponse } from './models/DealUpdateGeneralInfoResponse';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/client/models/DealDeleteRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/client/models/DealDeleteRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do no edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type DealDeleteRequest = {
 | 
			
		||||
    dealId: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type DealSummaryReorderResponse = {
 | 
			
		||||
export type DealDeleteResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
};
 | 
			
		||||
@@ -8,6 +8,7 @@ export type DealStatusHistorySchema = {
 | 
			
		||||
    changedAt: string;
 | 
			
		||||
    fromStatus: number;
 | 
			
		||||
    toStatus: number;
 | 
			
		||||
    nextStatusDeadline: string;
 | 
			
		||||
    nextStatusDeadline: (string | null);
 | 
			
		||||
    comment?: (string | null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ export type DealSummary = {
 | 
			
		||||
    name: string;
 | 
			
		||||
    clientName: string;
 | 
			
		||||
    changedAt: string;
 | 
			
		||||
    deadline: string;
 | 
			
		||||
    status: number;
 | 
			
		||||
    totalPrice: number;
 | 
			
		||||
    rank: number;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type DealSummaryReorderRequest = {
 | 
			
		||||
    dealId: number;
 | 
			
		||||
    newStatus: number;
 | 
			
		||||
    rank: number;
 | 
			
		||||
    nextStatusDeadline: string;
 | 
			
		||||
    status: number;
 | 
			
		||||
    index: number;
 | 
			
		||||
    deadline: string;
 | 
			
		||||
    comment: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,8 @@ import type { DealDeleteProductRequest } from '../models/DealDeleteProductReques
 | 
			
		||||
import type { DealDeleteProductResponse } from '../models/DealDeleteProductResponse';
 | 
			
		||||
import type { DealDeleteProductsRequest } from '../models/DealDeleteProductsRequest';
 | 
			
		||||
import type { DealDeleteProductsResponse } from '../models/DealDeleteProductsResponse';
 | 
			
		||||
import type { DealDeleteRequest } from '../models/DealDeleteRequest';
 | 
			
		||||
import type { DealDeleteResponse } from '../models/DealDeleteResponse';
 | 
			
		||||
import type { DealDeleteServiceRequest } from '../models/DealDeleteServiceRequest';
 | 
			
		||||
import type { DealDeleteServiceResponse } from '../models/DealDeleteServiceResponse';
 | 
			
		||||
import type { DealDeleteServicesRequest } from '../models/DealDeleteServicesRequest';
 | 
			
		||||
@@ -24,7 +26,6 @@ import type { DealQuickCreateRequest } from '../models/DealQuickCreateRequest';
 | 
			
		||||
import type { DealQuickCreateResponse } from '../models/DealQuickCreateResponse';
 | 
			
		||||
import type { DealSchema } from '../models/DealSchema';
 | 
			
		||||
import type { DealSummaryReorderRequest } from '../models/DealSummaryReorderRequest';
 | 
			
		||||
import type { DealSummaryReorderResponse } from '../models/DealSummaryReorderResponse';
 | 
			
		||||
import type { DealSummaryResponse } from '../models/DealSummaryResponse';
 | 
			
		||||
import type { DealUpdateGeneralInfoRequest } from '../models/DealUpdateGeneralInfoRequest';
 | 
			
		||||
import type { DealUpdateGeneralInfoResponse } from '../models/DealUpdateGeneralInfoResponse';
 | 
			
		||||
@@ -56,6 +57,26 @@ export class DealService {
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete
 | 
			
		||||
     * @returns DealDeleteResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static deleteDeal({
 | 
			
		||||
        requestBody,
 | 
			
		||||
    }: {
 | 
			
		||||
        requestBody: DealDeleteRequest,
 | 
			
		||||
    }): CancelablePromise<DealDeleteResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/deal/delete',
 | 
			
		||||
            body: requestBody,
 | 
			
		||||
            mediaType: 'application/json',
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Quick Create
 | 
			
		||||
     * @returns DealQuickCreateResponse Successful Response
 | 
			
		||||
@@ -109,14 +130,14 @@ export class DealService {
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Reorder
 | 
			
		||||
     * @returns DealSummaryReorderResponse Successful Response
 | 
			
		||||
     * @returns DealSummaryResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static reorderDealSummaries({
 | 
			
		||||
        requestBody,
 | 
			
		||||
    }: {
 | 
			
		||||
        requestBody: DealSummaryReorderRequest,
 | 
			
		||||
    }): CancelablePromise<DealSummaryReorderResponse> {
 | 
			
		||||
    }): CancelablePromise<DealSummaryResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/deal/summaries/reorder',
 | 
			
		||||
 
 | 
			
		||||
@@ -6,23 +6,29 @@ import CreateDealButton from "../CreateDealButton/CreateDealButton.tsx";
 | 
			
		||||
import {DealSummary} from "../../../client";
 | 
			
		||||
import DealSummaryCard from "../DealSummaryCard/DealSummaryCard.tsx";
 | 
			
		||||
import classNames from "classnames";
 | 
			
		||||
import {getPluralForm} from "../../../shared/lib/utils.ts";
 | 
			
		||||
import {sum} from "lodash";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    droppableId: string;
 | 
			
		||||
    title: string;
 | 
			
		||||
    withCreateButton?: boolean;
 | 
			
		||||
    summaries: DealSummary[];
 | 
			
		||||
    color: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const Board: FC<Props> = ({droppableId, title, summaries, withCreateButton = false}) => {
 | 
			
		||||
 | 
			
		||||
export const Board: FC<Props> = ({droppableId, title, summaries, color, withCreateButton = false}) => {
 | 
			
		||||
    const getDealsText = () => {
 | 
			
		||||
        const pluralForm = getPluralForm(summaries.length, 'сделка', 'сделки', 'сделок');
 | 
			
		||||
        return `${summaries.length} ${pluralForm}: ${sum(summaries.map(summary => summary.totalPrice))}₽`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={styles["container"]}>
 | 
			
		||||
            <div className={styles["header"]}>
 | 
			
		||||
                <Title size={"h4"}>{title}</Title>
 | 
			
		||||
                <Text>12 сделок: 500р</Text>
 | 
			
		||||
                <Divider size={"xl"} my={10} color={"blue"}/>
 | 
			
		||||
                <Text>{getDealsText()}</Text>
 | 
			
		||||
                <Divider size={"xl"} my={10} color={color}/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <Droppable
 | 
			
		||||
                droppableId={droppableId}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import {Text, Transition} from '@mantine/core';
 | 
			
		||||
import CreateDealFrom from "../CreateDealForm/CreateDealFrom.tsx";
 | 
			
		||||
import {DealService} from "../../../client";
 | 
			
		||||
import {dateWithoutTimezone} from "../../../shared/lib/utils.ts";
 | 
			
		||||
import {useQueryClient} from "@tanstack/react-query";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    onClick: () => void;
 | 
			
		||||
@@ -12,6 +13,8 @@ type Props = {
 | 
			
		||||
const CreateDealButton: FC<Props> = () => {
 | 
			
		||||
    const [isCreating, setIsCreating] = useState(false);
 | 
			
		||||
    const [isTransitionEnded, setIsTransitionEnded] = useState(true);
 | 
			
		||||
    const queryClient = useQueryClient();
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={styles['container']}
 | 
			
		||||
             onClick={() => {
 | 
			
		||||
@@ -41,6 +44,9 @@ const CreateDealButton: FC<Props> = () => {
 | 
			
		||||
                                        ...quickDeal,
 | 
			
		||||
                                        acceptanceDate: dateWithoutTimezone(quickDeal.acceptanceDate)
 | 
			
		||||
                                    }
 | 
			
		||||
                                }).then(async () => {
 | 
			
		||||
                                    await queryClient.invalidateQueries({queryKey: ['getDealSummaries']});
 | 
			
		||||
                                    setIsCreating(false);
 | 
			
		||||
                                })
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,26 @@ const DealSummaryCard: FC<Props> = ({dealSummary}) => {
 | 
			
		||||
                setSelectedDeal(deal);
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
    const getDeadlineTextColor = (deadline: string) => {
 | 
			
		||||
        // generate three colors, yellow for 1 day, red for 0 days, green for more than 1 day
 | 
			
		||||
        const deadlineDate = new Date(deadline);
 | 
			
		||||
        const currentDate = new Date();
 | 
			
		||||
        const diff = deadlineDate.getTime() - currentDate.getTime();
 | 
			
		||||
        const diffDays = Math.ceil(diff / (1000 * 3600 * 24));
 | 
			
		||||
        if (diffDays === 1) {
 | 
			
		||||
            return 'yellow.8';
 | 
			
		||||
        }
 | 
			
		||||
        if (diffDays === 0) {
 | 
			
		||||
            return 'red.8';
 | 
			
		||||
        }
 | 
			
		||||
        return 'green.8';
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <div onClick={() => onDealSummaryClick()} className={styles['container']}>
 | 
			
		||||
            <div className={styles['flex-row']}>
 | 
			
		||||
                <div className={styles['flex-item']}>
 | 
			
		||||
                    <Text size={"sm"} c={"gray.6"}>
 | 
			
		||||
                        {dealSummary.clientName}
 | 
			
		||||
                        Клиент: {dealSummary.clientName}
 | 
			
		||||
                    </Text>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={styles['flex-item']}>
 | 
			
		||||
@@ -38,12 +52,12 @@ const DealSummaryCard: FC<Props> = ({dealSummary}) => {
 | 
			
		||||
            <div className={classNames(styles['flex-row'], styles['flex-row-right'])}>
 | 
			
		||||
                <div className={styles['flex-item']}>
 | 
			
		||||
                    <Text size={"sm"} c={"gray.6"}>
 | 
			
		||||
                        {new Date(dealSummary.changedAt).toLocaleString('ru-RU')}
 | 
			
		||||
                        {/*Создана: {new Date(dealSummary.changedAt).toLocaleString('ru-RU')}*/}
 | 
			
		||||
                    </Text>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={styles['flex-item']}>
 | 
			
		||||
                    <Text size={"sm"} c={"yellow.8"}>
 | 
			
		||||
                        Нет задач
 | 
			
		||||
                    <Text size={"sm"} c={getDeadlineTextColor(dealSummary.deadline)}>
 | 
			
		||||
                        {new Date(dealSummary.deadline).toLocaleString('ru-RU').slice(0, -3)}
 | 
			
		||||
                    </Text>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -30,11 +30,11 @@ function NavbarLink(props: NavbarLinkProps) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mockdata = [
 | 
			
		||||
    {
 | 
			
		||||
        icon: IconHome2,
 | 
			
		||||
        label: 'Главная',
 | 
			
		||||
        href: '/'
 | 
			
		||||
    },
 | 
			
		||||
    // {
 | 
			
		||||
    //     icon: IconHome2,
 | 
			
		||||
    //     label: 'Главная',
 | 
			
		||||
    //     href: '/'
 | 
			
		||||
    // },
 | 
			
		||||
    {
 | 
			
		||||
        icon: IconCash,
 | 
			
		||||
        label: 'Сделки',
 | 
			
		||||
@@ -83,7 +83,7 @@ export function Navbar() {
 | 
			
		||||
        <nav className={classes.navbar}>
 | 
			
		||||
            <Center>
 | 
			
		||||
                <Image
 | 
			
		||||
                    style={{filter: "drop-shadow(0 0 30px #fff)"}}
 | 
			
		||||
                    // style={{filter: "drop-shadow(0 0 30px #fff)"}}
 | 
			
		||||
                    src={colorScheme == "dark" ? "/icons/logo-light.png" : "/icons/logo.png"}
 | 
			
		||||
                />
 | 
			
		||||
            </Center>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,14 @@
 | 
			
		||||
.container {
 | 
			
		||||
    border-radius: rem(20);
 | 
			
		||||
    background-color: var(--mantine-color-body);
 | 
			
		||||
    padding: rem(10);
 | 
			
		||||
    padding: rem(15);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container-fluid {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container-full-height {
 | 
			
		||||
    height: calc(100vh - (rem(20) * 2));
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
import {FC, ReactNode} from "react";
 | 
			
		||||
import {CSSProperties, FC, ReactNode} from "react";
 | 
			
		||||
import styles from './PageBlock.module.css';
 | 
			
		||||
import classNames from "classnames";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    children: ReactNode
 | 
			
		||||
    fluid?: boolean;
 | 
			
		||||
    style?: CSSProperties;
 | 
			
		||||
    fullHeight?: boolean;
 | 
			
		||||
}
 | 
			
		||||
export const PageBlock: FC<Props> = ({children, fluid = true}) => {
 | 
			
		||||
export const PageBlock: FC<Props> = ({children, style, fluid = true, fullHeight = false}) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={classNames(styles['container'], fluid && styles['container-fluid'])}>
 | 
			
		||||
        <div style={style}
 | 
			
		||||
             className={classNames(styles['container'], fluid && styles['container-fluid'], fullHeight && styles['container-full-height'])}>
 | 
			
		||||
            {children}
 | 
			
		||||
        </div>
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,16 @@ import {Button, Flex, rem, Textarea} from "@mantine/core";
 | 
			
		||||
import {DateTimePicker, DateValue} from "@mantine/dates";
 | 
			
		||||
import {useForm} from "@mantine/form";
 | 
			
		||||
import {DealSummaryReorderRequest} from "../../client";
 | 
			
		||||
import {dateWithoutTimezone} from "../../shared/lib/utils.ts";
 | 
			
		||||
 | 
			
		||||
type Deadline = {
 | 
			
		||||
    datetime: DateValue,
 | 
			
		||||
    deadline: DateValue,
 | 
			
		||||
    comment: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    request:DealSummaryReorderRequest,
 | 
			
		||||
    request: Partial<DealSummaryReorderRequest>,
 | 
			
		||||
    onSubmit: (
 | 
			
		||||
        deadline: Deadline,
 | 
			
		||||
        request: DealSummaryReorderRequest,
 | 
			
		||||
    ) => void;
 | 
			
		||||
}
 | 
			
		||||
@@ -24,28 +24,37 @@ const EnterDeadlineModal = ({
 | 
			
		||||
                            }: ContextModalProps<Props>) => {
 | 
			
		||||
    const form = useForm<Deadline>({
 | 
			
		||||
        initialValues: {
 | 
			
		||||
            datetime: null,
 | 
			
		||||
            deadline: null,
 | 
			
		||||
            comment: '',
 | 
			
		||||
        },
 | 
			
		||||
        validate: {
 | 
			
		||||
            datetime: (datetime) => datetime !== null ? null : 'Необходимо ввести дедлайн',
 | 
			
		||||
            deadline: (datetime) => datetime !== null ? null : 'Необходимо ввести дедлайн',
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    const onCancelClick = () => {
 | 
			
		||||
        context.closeModal(id);
 | 
			
		||||
    }
 | 
			
		||||
    const onSubmit = (values: Deadline) => {
 | 
			
		||||
        innerProps.onSubmit(values, innerProps.request);
 | 
			
		||||
        const {deadline, comment} = values;
 | 
			
		||||
        if (!deadline) return;
 | 
			
		||||
        innerProps.onSubmit({
 | 
			
		||||
            ...innerProps.request,
 | 
			
		||||
            deadline: dateWithoutTimezone(deadline),
 | 
			
		||||
            comment
 | 
			
		||||
        } as unknown as DealSummaryReorderRequest);
 | 
			
		||||
        context.closeModal(id);
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <form onSubmit={form.onSubmit((values) => onSubmit(values))}>
 | 
			
		||||
 | 
			
		||||
            <Flex direction={'column'} gap={10}>
 | 
			
		||||
                <Flex direction={'column'} gap={rem(10)}>
 | 
			
		||||
                    <DateTimePicker
 | 
			
		||||
                        required
 | 
			
		||||
                        label={'Дата и время'}
 | 
			
		||||
                        placeholder={'Введите дату и время'}
 | 
			
		||||
                        {...form.getInputProps('date')}
 | 
			
		||||
                        minDate={new Date()}
 | 
			
		||||
                        {...form.getInputProps('deadline')}
 | 
			
		||||
                    />
 | 
			
		||||
                    <Textarea
 | 
			
		||||
                        label={'Коментарий'}
 | 
			
		||||
@@ -59,7 +68,9 @@ const EnterDeadlineModal = ({
 | 
			
		||||
                        variant={'default'}
 | 
			
		||||
                        onClick={onCancelClick}
 | 
			
		||||
                    >Отменить</Button>
 | 
			
		||||
                    <Button>Сохранить</Button>
 | 
			
		||||
                    <Button
 | 
			
		||||
                        type={'submit'}
 | 
			
		||||
                    >Сохранить</Button>
 | 
			
		||||
                </Flex>
 | 
			
		||||
            </Flex>
 | 
			
		||||
        </form>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ const DealStatusChangeTable: FC<Props> = (props: Props) => {
 | 
			
		||||
                enableBottomToolbar: false,
 | 
			
		||||
                enableColumnFilters: false,
 | 
			
		||||
                enableColumnVisibilityToggle: false,
 | 
			
		||||
                layoutMode:"grid"
 | 
			
		||||
            }}
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import {useMemo} from "react";
 | 
			
		||||
import {MRT_ColumnDef} from "mantine-react-table";
 | 
			
		||||
import {DealStatusHistorySchema} from "../../../../client";
 | 
			
		||||
import {DealStatus, DealStatusDictionary} from "../../../../shared/enums/DealStatus.ts";
 | 
			
		||||
import {Spoiler, Text} from "@mantine/core";
 | 
			
		||||
 | 
			
		||||
export const useDealStatusChangeTableColumns = () => {
 | 
			
		||||
    return useMemo<MRT_ColumnDef<DealStatusHistorySchema>[]>(() => [
 | 
			
		||||
@@ -23,6 +24,17 @@ export const useDealStatusChangeTableColumns = () => {
 | 
			
		||||
            accessorKey: "toStatus",
 | 
			
		||||
            header: "В статус",
 | 
			
		||||
            accessorFn: (row) => DealStatusDictionary[row.toStatus as DealStatus],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            accessorKey: "comment",
 | 
			
		||||
            header: "Комментарий",
 | 
			
		||||
            Cell: ({row}) =>
 | 
			
		||||
            <Spoiler onDoubleClick={()=>{console.log("double click")}} maxHeight={80} showLabel={"Показать весь"} hideLabel={"Скрыть"}>
 | 
			
		||||
                <Text style={{wordWrap: "break-word", wordBreak: "break-all", whiteSpace: "normal"}} span>
 | 
			
		||||
                    {row.original.comment}<br/>
 | 
			
		||||
                </Text>
 | 
			
		||||
            </Spoiler>
 | 
			
		||||
            ,
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    ], []);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import {useQuery} from "@tanstack/react-query";
 | 
			
		||||
import {DealService, DealSummary} from "../../../client";
 | 
			
		||||
import {DealService} from "../../../client";
 | 
			
		||||
 | 
			
		||||
export const useDealSummaries = (): DealSummary[] => {
 | 
			
		||||
    const {data: summaries = []} = useQuery({
 | 
			
		||||
export const useDealSummaries = () => {
 | 
			
		||||
    const {data: summariesRaw = [], refetch} = useQuery({
 | 
			
		||||
        queryKey: ['getDealSummaries'],
 | 
			
		||||
        queryFn: DealService.getDealSummaries,
 | 
			
		||||
        select: data => data.summaries || [] // Трансформируем полученные данные
 | 
			
		||||
@@ -11,5 +11,5 @@ export const useDealSummaries = (): DealSummary[] => {
 | 
			
		||||
    // Теперь summaries будет содержать либо трансформированные данные, либо пустой массив по умолчанию
 | 
			
		||||
    // isLoading и isError могут быть использованы для отображения индикаторов загрузки или ошибки
 | 
			
		||||
 | 
			
		||||
    return summaries;
 | 
			
		||||
    return {summariesRaw, refetch};
 | 
			
		||||
}
 | 
			
		||||
@@ -20,3 +20,22 @@
 | 
			
		||||
    padding-right: 5%;
 | 
			
		||||
    padding-left: 5%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.delete {
 | 
			
		||||
 | 
			
		||||
    @mixin light {
 | 
			
		||||
        border-color: var(--mantine-color-gray-1);
 | 
			
		||||
    }
 | 
			
		||||
    @mixin dark {
 | 
			
		||||
        border-color: var(--mantine-color-dark-5);
 | 
			
		||||
    }
 | 
			
		||||
    border: dashed var(--item-border-size) var(--mantine-color-default-border);
 | 
			
		||||
    border-radius: var(--item-border-radius);
 | 
			
		||||
    padding: rem(30);
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.delete-hidden {
 | 
			
		||||
    border: none;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,77 @@
 | 
			
		||||
import {FC, useEffect, useState} from "react";
 | 
			
		||||
import styles from './LeadsPage.module.css';
 | 
			
		||||
import Board from "../../../components/Dnd/Board/Board.tsx";
 | 
			
		||||
import {DragDropContext, DropResult, OnDragEndResponder, ResponderProvided} from "@hello-pangea/dnd";
 | 
			
		||||
import {DragDropContext, Droppable, DropResult} from "@hello-pangea/dnd";
 | 
			
		||||
import {useDealSummaries} from "../hooks/useDealSummaries.tsx";
 | 
			
		||||
import {DealStatus} from "../../../shared/enums/DealStatus.ts";
 | 
			
		||||
import {DealStatus, getDealStatusByName} from "../../../shared/enums/DealStatus.ts";
 | 
			
		||||
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
 | 
			
		||||
import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx";
 | 
			
		||||
import {DealPageContextProvider} from "../contexts/DealPageContext.tsx";
 | 
			
		||||
import {modals} from "@mantine/modals";
 | 
			
		||||
import {DealSummaryReorderRequest} from "../../../client";
 | 
			
		||||
import {DealService, DealSummaryReorderRequest} from "../../../client";
 | 
			
		||||
import {Flex} from "@mantine/core";
 | 
			
		||||
import classNames from "classnames";
 | 
			
		||||
import {notifications} from "../../../shared/lib/notifications.ts";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const LeadsPage: FC = () => {
 | 
			
		||||
    const summariesRaw = useDealSummaries();
 | 
			
		||||
    const {summariesRaw, refetch} = useDealSummaries();
 | 
			
		||||
    const [summaries, setSummaries] = useState(summariesRaw);
 | 
			
		||||
    const [isDragEnded, setIsDragEnded] = useState(true);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setSummaries(summariesRaw);
 | 
			
		||||
    }, [summariesRaw]);
 | 
			
		||||
    const onDragEnd = async (result: DropResult, provided: ResponderProvided) => {
 | 
			
		||||
        const dealId = parseInt(result.draggableId);
 | 
			
		||||
 | 
			
		||||
        const request: DealSummaryReorderRequest = {}
 | 
			
		||||
    const onDelete = (dealId: number) => {
 | 
			
		||||
        const summary = summaries.find(summary => summary.id == dealId);
 | 
			
		||||
        if (!summary) return;
 | 
			
		||||
        modals.openConfirmModal({
 | 
			
		||||
            title: "Удаление сделки",
 | 
			
		||||
            children:
 | 
			
		||||
                <Flex>
 | 
			
		||||
                    Вы действительно хотите удалить сделку {summary.name}?
 | 
			
		||||
                </Flex>,
 | 
			
		||||
            onConfirm: () => {
 | 
			
		||||
                DealService.deleteDeal({requestBody: {dealId: dealId}})
 | 
			
		||||
                    .then(async ({ok, message}) => {
 | 
			
		||||
                        notifications.guess(ok, {message});
 | 
			
		||||
                        if (!ok) return;
 | 
			
		||||
                        await refetch();
 | 
			
		||||
                    })
 | 
			
		||||
            },
 | 
			
		||||
            labels: {
 | 
			
		||||
                confirm: "Удалить",
 | 
			
		||||
                cancel: "Отмена"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    const onDragEnd = async (result: DropResult) => {
 | 
			
		||||
        setIsDragEnded(true);
 | 
			
		||||
        if (!result.destination || result.destination == result.source) return;
 | 
			
		||||
        const dealId = parseInt(result.draggableId);
 | 
			
		||||
        if (isNaN(dealId)) return;
 | 
			
		||||
 | 
			
		||||
        const droppableId = result.destination.droppableId;
 | 
			
		||||
        if (droppableId === 'DELETE') {
 | 
			
		||||
            onDelete(dealId);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const request: Partial<DealSummaryReorderRequest> = {
 | 
			
		||||
            dealId: dealId,
 | 
			
		||||
            index: result.destination.index,
 | 
			
		||||
            status: getDealStatusByName(droppableId)
 | 
			
		||||
        }
 | 
			
		||||
        modals.openContextModal({
 | 
			
		||||
            modal: 'enterDeadline',
 | 
			
		||||
            title: "Необходимо указать дедлайн",
 | 
			
		||||
            innerProps: {
 | 
			
		||||
                onSubmit: (event) => console.log(event)
 | 
			
		||||
                onSubmit: (event) => DealService.reorderDealSummaries({requestBody: event})
 | 
			
		||||
                    .then(async response => {
 | 
			
		||||
                        setSummaries(response.summaries);
 | 
			
		||||
                        await refetch();
 | 
			
		||||
                    }),
 | 
			
		||||
                request: request
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -34,43 +80,89 @@ export const LeadsPage: FC = () => {
 | 
			
		||||
        <>
 | 
			
		||||
            <DealPageContextProvider>
 | 
			
		||||
 | 
			
		||||
                <PageBlock>
 | 
			
		||||
                <PageBlock fullHeight>
 | 
			
		||||
                    <div className={styles['container']}>
 | 
			
		||||
                        <div className={styles['boards']}>
 | 
			
		||||
                            <DragDropContext onDragEnd={onDragEnd}>
 | 
			
		||||
                        <DragDropContext
 | 
			
		||||
                            onDragStart={() => {
 | 
			
		||||
                                setIsDragEnded(false);
 | 
			
		||||
                            }}
 | 
			
		||||
                            onDragEnd={onDragEnd}>
 | 
			
		||||
                            <div className={styles['boards']}>
 | 
			
		||||
                                <Board
 | 
			
		||||
                                    withCreateButton
 | 
			
		||||
                                    summaries={summaries
 | 
			
		||||
                                        .filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
 | 
			
		||||
                                    title={"Ожидает приемки"}
 | 
			
		||||
                                    droppableId={"AWAITING_ACCEPTANCE"}
 | 
			
		||||
                                    color={'#4A90E2'}
 | 
			
		||||
                                />
 | 
			
		||||
                                <Board
 | 
			
		||||
                                    summaries={summaries
 | 
			
		||||
                                        .filter(summary => summary.status == DealStatus.PACKAGING)}
 | 
			
		||||
                                    title={"Упаковка"}
 | 
			
		||||
                                    droppableId={"PACKAGING"}
 | 
			
		||||
                                    color={'#F5A623'}
 | 
			
		||||
 | 
			
		||||
                                />
 | 
			
		||||
                                <Board
 | 
			
		||||
                                    summaries={summaries
 | 
			
		||||
                                        .filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
 | 
			
		||||
                                    title={"Ожидает отгрузки"}
 | 
			
		||||
                                    droppableId={"AWAITING_SHIPMENT"}
 | 
			
		||||
                                    color={'#7ED321'}
 | 
			
		||||
 | 
			
		||||
                                />
 | 
			
		||||
                                <Board
 | 
			
		||||
                                    summaries={summaries
 | 
			
		||||
                                        .filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
 | 
			
		||||
                                    title={"Ожидает оплаты"}
 | 
			
		||||
                                    droppableId={"AWAITING_PAYMENT"}
 | 
			
		||||
                                    color={'#D0021B'}
 | 
			
		||||
 | 
			
		||||
                                />
 | 
			
		||||
                                <Board
 | 
			
		||||
                                    summaries={summaries
 | 
			
		||||
                                        .filter(summary => summary.status == DealStatus.COMPLETED)}
 | 
			
		||||
                                    title={"Завершена"}
 | 
			
		||||
                                    droppableId={"COMPLETED"}
 | 
			
		||||
                                    color={'#417505'}
 | 
			
		||||
                                />
 | 
			
		||||
                            </DragDropContext>
 | 
			
		||||
                        </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <Flex justify={"flex-end"}>
 | 
			
		||||
                                <div
 | 
			
		||||
                                    className={
 | 
			
		||||
                                        classNames(
 | 
			
		||||
                                            styles['delete'],
 | 
			
		||||
                                            isDragEnded && styles['delete-hidden']
 | 
			
		||||
                                        )
 | 
			
		||||
                                    }
 | 
			
		||||
                                >
 | 
			
		||||
                                    <Droppable droppableId={"DELETE"}>
 | 
			
		||||
                                        {(provided) => (
 | 
			
		||||
                                            <>
 | 
			
		||||
                                                <div
 | 
			
		||||
                                                    {...provided.droppableProps}
 | 
			
		||||
                                                    ref={provided.innerRef}
 | 
			
		||||
                                                >
 | 
			
		||||
                                                    {!isDragEnded &&
 | 
			
		||||
                                                        <span>
 | 
			
		||||
                                                    Удалить
 | 
			
		||||
                                                    </span>
 | 
			
		||||
 | 
			
		||||
                                                    }
 | 
			
		||||
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                                {provided.placeholder}
 | 
			
		||||
                                            </>
 | 
			
		||||
 | 
			
		||||
                                        )}
 | 
			
		||||
                                    </Droppable>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            </Flex>
 | 
			
		||||
                        </DragDropContext>
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                </PageBlock>
 | 
			
		||||
                <DealEditDrawer
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ const LoginPage = () => {
 | 
			
		||||
    const authState = useSelector((state: RootState) => state.auth);
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    if (authState.isAuthorized) {
 | 
			
		||||
        navigate({to: "/"})
 | 
			
		||||
        navigate({to: "/leads"})
 | 
			
		||||
        return (<></>)
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import {FC} from "react";
 | 
			
		||||
import {useNavigate} from "@tanstack/react-router";
 | 
			
		||||
 | 
			
		||||
const MainPage: FC = () => {
 | 
			
		||||
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    navigate({to: '/leads'});
 | 
			
		||||
    return (<>
 | 
			
		||||
 | 
			
		||||
        </>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,5 +9,4 @@
 | 
			
		||||
 | 
			
		||||
.container {
 | 
			
		||||
    padding: rem(20);
 | 
			
		||||
    display: flex;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,10 +13,10 @@ const PageWrapper: FC<Props> = ({children}) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <AppShell
 | 
			
		||||
            layout={"alt"}
 | 
			
		||||
            navbar={{
 | 
			
		||||
            navbar={authState.isAuthorized ? {
 | 
			
		||||
                width: '5%',
 | 
			
		||||
                breakpoint: "sm"
 | 
			
		||||
            }}
 | 
			
		||||
            } : undefined}
 | 
			
		||||
        >
 | 
			
		||||
 | 
			
		||||
            <AppShell.Navbar>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.body-container {
 | 
			
		||||
    padding: rem(5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.top-panel {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,3 +10,18 @@ export const getDigitsCount = (num: number): number => {
 | 
			
		||||
    if (num === 0) return 1;
 | 
			
		||||
    return Math.floor(Math.log10(Math.abs(num))) + 1;
 | 
			
		||||
};
 | 
			
		||||
export const getPluralForm = (amount: number, one: string, twoFour: string, many: string): string => {
 | 
			
		||||
    if (amount % 100 >= 11 && amount % 100 <= 14) {
 | 
			
		||||
        return many;
 | 
			
		||||
    }
 | 
			
		||||
    switch (amount % 10) {
 | 
			
		||||
        case 1:
 | 
			
		||||
            return one;
 | 
			
		||||
        case 2:
 | 
			
		||||
        case 3:
 | 
			
		||||
        case 4:
 | 
			
		||||
            return twoFour;
 | 
			
		||||
        default:
 | 
			
		||||
            return many;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user