feat: cancel deal bill, date range in timetable
This commit is contained in:
		@@ -27,6 +27,8 @@ export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
 | 
			
		||||
export type { BillPaymentStatus } from './models/BillPaymentStatus';
 | 
			
		||||
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
 | 
			
		||||
export type { Body_upload_product_image } from './models/Body_upload_product_image';
 | 
			
		||||
export type { CancelDealBillRequest } from './models/CancelDealBillRequest';
 | 
			
		||||
export type { CancelDealBillResponse } from './models/CancelDealBillResponse';
 | 
			
		||||
export type { ClientCreateRequest } from './models/ClientCreateRequest';
 | 
			
		||||
export type { ClientCreateResponse } from './models/ClientCreateResponse';
 | 
			
		||||
export type { ClientDeleteRequest } from './models/ClientDeleteRequest';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/client/models/CancelDealBillRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/client/models/CancelDealBillRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type CancelDealBillRequest = {
 | 
			
		||||
    dealId: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/CancelDealBillResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/CancelDealBillResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type CancelDealBillResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { BillStatusUpdateRequest } from '../models/BillStatusUpdateRequest';
 | 
			
		||||
import type { CancelDealBillRequest } from '../models/CancelDealBillRequest';
 | 
			
		||||
import type { CancelDealBillResponse } from '../models/CancelDealBillResponse';
 | 
			
		||||
import type { CreateDealBillRequest } from '../models/CreateDealBillRequest';
 | 
			
		||||
import type { CreateDealBillResponse } from '../models/CreateDealBillResponse';
 | 
			
		||||
import type { GetDealBillById } from '../models/GetDealBillById';
 | 
			
		||||
@@ -50,6 +52,26 @@ export class BillingService {
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Create Deal Bill
 | 
			
		||||
     * @returns CancelDealBillResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static cancelDealBill({
 | 
			
		||||
        requestBody,
 | 
			
		||||
    }: {
 | 
			
		||||
        requestBody: CancelDealBillRequest,
 | 
			
		||||
    }): CancelablePromise<CancelDealBillResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/billing/cancel-deal-bill',
 | 
			
		||||
            body: requestBody,
 | 
			
		||||
            mediaType: 'application/json',
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Get Deal Bill By Id
 | 
			
		||||
     * @returns GetDealBillById Successful Response
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +1,75 @@
 | 
			
		||||
import {useMemo} from "react";
 | 
			
		||||
import {MRT_ColumnDef} from "mantine-react-table";
 | 
			
		||||
import {getDatesInMonth, getDayOfWeek} from "../../../../../shared/lib/date.ts";
 | 
			
		||||
import {Box, Flex, NumberInput} from "@mantine/core";
 | 
			
		||||
import {isNumber} from "lodash";
 | 
			
		||||
import {getDayOfWeek} from "../../../../../shared/lib/date.ts";
 | 
			
		||||
import {Box, Flex, NumberInput, rem} from "@mantine/core";
 | 
			
		||||
import {isNumber, last} from "lodash";
 | 
			
		||||
import {processSelectedCells} from "../../../../../shared/lib/interpolateCells.ts";
 | 
			
		||||
import {TimeTrackingData} from "../../../../../client";
 | 
			
		||||
import dayjs from "dayjs";
 | 
			
		||||
 | 
			
		||||
export type EmployeeData = {
 | 
			
		||||
    name: string;
 | 
			
		||||
    userId: number;
 | 
			
		||||
    totalAmount: number;
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | 
			
		||||
    // @ts-expect-error
 | 
			
		||||
    data?: TimeTrackingData[],
 | 
			
		||||
    [key: string]: number | string;
 | 
			
		||||
};
 | 
			
		||||
type Props = {
 | 
			
		||||
    month: Date;
 | 
			
		||||
    data: EmployeeData[];
 | 
			
		||||
    onUpdate: (date: Date, userId: number, value: number) => void
 | 
			
		||||
    onUpdate: (date: Date, userId: number, value: number) => void,
 | 
			
		||||
    selectedCells: string[];
 | 
			
		||||
    setSelectedCells: (cells: string[]) => void
 | 
			
		||||
    selectedBoundaries: [Date | null, Date | null];
 | 
			
		||||
    range: dayjs.Dayjs[];
 | 
			
		||||
}
 | 
			
		||||
const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
 | 
			
		||||
    const totalAmount = useMemo(() => data.reduce((acc, value) => acc + value.totalAmount, 0), [data]);
 | 
			
		||||
const useWorkTableColumns = ({
 | 
			
		||||
                                 month,
 | 
			
		||||
                                 onUpdate,
 | 
			
		||||
                                 data,
 | 
			
		||||
                                 selectedCells,
 | 
			
		||||
                                 setSelectedCells,
 | 
			
		||||
                                 selectedBoundaries,
 | 
			
		||||
                                 range
 | 
			
		||||
                             }: Props) => {
 | 
			
		||||
    const totalAmount = useMemo(() => {
 | 
			
		||||
        return data.reduce((acc, value) => {
 | 
			
		||||
            if (value.data) {
 | 
			
		||||
                const sum = value.data.reduce((innerAcc, item) => innerAcc + item.amount, 0);
 | 
			
		||||
                return acc + sum;
 | 
			
		||||
            }
 | 
			
		||||
            return acc;
 | 
			
		||||
        }, 0);
 | 
			
		||||
    }, [data, month, selectedCells, selectedBoundaries]);
 | 
			
		||||
    const getBorderStyles = (cellId: string) => {
 | 
			
		||||
        if (selectedCells.length <= 1) return {}
 | 
			
		||||
        if (selectedCells[0] === cellId)
 | 
			
		||||
            return {
 | 
			
		||||
                borderTopLeftRadius: rem(20),
 | 
			
		||||
                borderBottomLeftRadius: rem(20),
 | 
			
		||||
            }
 | 
			
		||||
        else if (last(selectedCells) === cellId)
 | 
			
		||||
            return {
 | 
			
		||||
                borderTopRightRadius: rem(20),
 | 
			
		||||
                borderBottomRightRadius: rem(20),
 | 
			
		||||
            }
 | 
			
		||||
        return {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return useMemo<MRT_ColumnDef<EmployeeData>[]>(() => [
 | 
			
		||||
        {
 | 
			
		||||
            accessorKey: "name",
 | 
			
		||||
            header: "ФИО"
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        ...getDatesInMonth(month).map(date => ({
 | 
			
		||||
        ...range.map(date => ({
 | 
			
		||||
            size: 80,
 | 
			
		||||
            accessorKey: date.date().toString(),
 | 
			
		||||
            header: date.date().toString(),
 | 
			
		||||
            enableSorting: false,
 | 
			
		||||
            enableColumnActions: false,
 | 
			
		||||
 | 
			
		||||
            Header: (
 | 
			
		||||
                <Flex
 | 
			
		||||
                    align={"center"}
 | 
			
		||||
@@ -44,6 +84,19 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
 | 
			
		||||
            ),
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            mantineTableBodyCellProps: ({cell}) => ({
 | 
			
		||||
                style: selectedCells.includes(cell.id) ? {
 | 
			
		||||
                    backgroundColor: "var(--mantine-primary-color-filled)",
 | 
			
		||||
                    ...getBorderStyles(cell.id)
 | 
			
		||||
                } : {},
 | 
			
		||||
                onClick: () => {
 | 
			
		||||
                    const result = processSelectedCells(selectedCells, cell.id);
 | 
			
		||||
                    setSelectedCells(result);
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
            }),
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            Cell: ({cell, row}) => {
 | 
			
		||||
                return (
 | 
			
		||||
                    <Flex direction={"column"}>
 | 
			
		||||
@@ -71,9 +124,13 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
 | 
			
		||||
        {
 | 
			
		||||
            accessorKey: "totalAmount",
 | 
			
		||||
            header: "Итоговая сумма заработка",
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | 
			
		||||
            // @ts-expect-error
 | 
			
		||||
            Cell: ({cell}) => cell.renderValue().toLocaleString("ru-RU"),
 | 
			
		||||
 | 
			
		||||
            Cell: ({row}) => {
 | 
			
		||||
                return (row.original.data || []).reduce((acc, value) => {
 | 
			
		||||
                    acc += value.amount;
 | 
			
		||||
                    return acc;
 | 
			
		||||
                }, 0);
 | 
			
		||||
            },
 | 
			
		||||
            Footer: (
 | 
			
		||||
                <Flex>
 | 
			
		||||
                    Всего: {totalAmount.toLocaleString("ru-RU")}
 | 
			
		||||
@@ -82,7 +139,7 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ], [month]);
 | 
			
		||||
    ], [month, selectedCells, selectedBoundaries, totalAmount]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default useWorkTableColumns;
 | 
			
		||||
@@ -1,10 +1,16 @@
 | 
			
		||||
import {useEffect, useState} from "react";
 | 
			
		||||
import {TimeTrackingRecord, TimeTrackingService} from "../../../../../client";
 | 
			
		||||
import {dateWithoutTimezone} from "../../../../../shared/lib/date.ts";
 | 
			
		||||
import {dateWithoutTimezone, getDatesInMonth} from "../../../../../shared/lib/date.ts";
 | 
			
		||||
import {last} from "lodash";
 | 
			
		||||
 | 
			
		||||
const getDateBoundaries = (month: Date) => {
 | 
			
		||||
    return [getDatesInMonth(month)[0].toDate(), last(getDatesInMonth(month))?.toDate()]
 | 
			
		||||
}
 | 
			
		||||
const useWorkTableState = () => {
 | 
			
		||||
    const [month, setMonth] = useState<Date>(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
 | 
			
		||||
    const [trackingRecords, setTrackingRecords] = useState<TimeTrackingRecord[]>([]);
 | 
			
		||||
    const [dateBoundaries, setDateBoundaries] = useState(getDateBoundaries(month));
 | 
			
		||||
 | 
			
		||||
    const refetch = async () => {
 | 
			
		||||
        return TimeTrackingService.getTimeTrackingRecords({
 | 
			
		||||
            requestBody: {
 | 
			
		||||
@@ -13,16 +19,20 @@ const useWorkTableState = () => {
 | 
			
		||||
            }
 | 
			
		||||
        }).then((response) => setTrackingRecords(response.records));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
        refetch().then(_ => {
 | 
			
		||||
            setDateBoundaries(getDateBoundaries(month));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }, [month])
 | 
			
		||||
    return {
 | 
			
		||||
        month,
 | 
			
		||||
        setMonth,
 | 
			
		||||
        refetch,
 | 
			
		||||
        trackingRecords
 | 
			
		||||
        trackingRecords,
 | 
			
		||||
        dateBoundaries
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,58 @@
 | 
			
		||||
import {ActionIcon, Flex, MultiSelect, rem, Tooltip} from "@mantine/core";
 | 
			
		||||
import {MonthPickerInput} from "@mantine/dates";
 | 
			
		||||
import {DatePickerInput, MonthPickerInput} from "@mantine/dates";
 | 
			
		||||
import useWorkTableState from "../hooks/useWorkTableState.tsx";
 | 
			
		||||
import {BaseTable} from "../../../../../components/BaseTable/BaseTable.tsx";
 | 
			
		||||
import {useEffect, useState} from "react";
 | 
			
		||||
import useWorkTableColumns, {EmployeeData} from "../hooks/useWorkTableColumns.tsx";
 | 
			
		||||
import {TimeTrackingRecord, TimeTrackingService, UserSchema} from "../../../../../client";
 | 
			
		||||
import {dateWithoutTimezone, getDatesInMonth} from "../../../../../shared/lib/date.ts";
 | 
			
		||||
import {dateWithoutTimezone, getDatesBetween, getDatesInMonth} from "../../../../../shared/lib/date.ts";
 | 
			
		||||
import useUsersList from "../../../hooks/useUsersList.tsx";
 | 
			
		||||
import {notifications} from "../../../../../shared/lib/notifications.ts";
 | 
			
		||||
import {PaySchemeType} from "../../../../../shared/enums/PaySchemeType.ts";
 | 
			
		||||
import {IconEyeOff} from "@tabler/icons-react";
 | 
			
		||||
import {MRT_TableOptions} from "mantine-react-table";
 | 
			
		||||
import {difference, omit} from "lodash";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const WorkTimeTable = () => {
 | 
			
		||||
    const [data, setData] = useState<EmployeeData[]>([]);
 | 
			
		||||
    const {
 | 
			
		||||
        dateBoundaries,
 | 
			
		||||
        month,
 | 
			
		||||
        setMonth,
 | 
			
		||||
        trackingRecords,
 | 
			
		||||
        refetch
 | 
			
		||||
    } = useWorkTableState();
 | 
			
		||||
    const [hiddenUsers, setHiddenUsers] = useState<UserSchema[]>([]);
 | 
			
		||||
    const [selectedBoundaries, setSelectedBoundaries] = useState<[Date | null, Date | null]>([null, null]);
 | 
			
		||||
 | 
			
		||||
    const users = useUsersList().objects.filter(user => user.payRate?.payrollScheme.key === PaySchemeType.HOURLY);
 | 
			
		||||
 | 
			
		||||
    const getRange = () => {
 | 
			
		||||
        const startDate = selectedBoundaries[0];
 | 
			
		||||
        const endDate = selectedBoundaries[1];
 | 
			
		||||
        if (startDate && endDate) {
 | 
			
		||||
            return getDatesBetween(startDate, endDate);
 | 
			
		||||
        } else {
 | 
			
		||||
            return getDatesInMonth(month);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const range = getRange();
 | 
			
		||||
 | 
			
		||||
    const transformTrackingRecordsToData = (trackingRecords: TimeTrackingRecord[]): EmployeeData[] => {
 | 
			
		||||
        if (!month) return [];
 | 
			
		||||
        const rangeDays = range.map(r => r.date());
 | 
			
		||||
 | 
			
		||||
        trackingRecords = trackingRecords.map(tr => ({
 | 
			
		||||
            ...tr,
 | 
			
		||||
            data: tr.data.filter(d => rangeDays.includes((new Date(d.date)).getDate()))
 | 
			
		||||
        }));
 | 
			
		||||
        const existingUserIds = trackingRecords.map(tr => tr.user.id);
 | 
			
		||||
        const firstResult = trackingRecords.map((record) => ({
 | 
			
		||||
            name: `${record.user.firstName} ${record.user.secondName}`,
 | 
			
		||||
            userId: record.user.id,
 | 
			
		||||
            totalAmount: record.totalAmount,
 | 
			
		||||
            data: record.data,
 | 
			
		||||
            ...Object.fromEntries(getDatesInMonth(month).reduce((acc, day) => {
 | 
			
		||||
                return acc.set(day.date().toString(), 0);
 | 
			
		||||
            }, new Map<string, number>)),
 | 
			
		||||
@@ -45,7 +69,23 @@ const WorkTimeTable = () => {
 | 
			
		||||
            }, new Map<string, number>)),
 | 
			
		||||
        }))
 | 
			
		||||
        const hiddenUserIds = hiddenUsers.map(user => user.id);
 | 
			
		||||
        return (firstResult.concat(restUsersResult) as unknown as EmployeeData[]).filter(r => !hiddenUserIds.includes(r.userId));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | 
			
		||||
        // @ts-expect-error
 | 
			
		||||
        const result = (firstResult.concat(restUsersResult)).filter(r => !hiddenUserIds.includes(r.userId));
 | 
			
		||||
        const firstDate = selectedBoundaries[0];
 | 
			
		||||
        const lastDate = selectedBoundaries[1];
 | 
			
		||||
        if (firstDate && lastDate) {
 | 
			
		||||
            const allDays = getDatesInMonth(month).map(d => d.date().toString());
 | 
			
		||||
            const allowedDays = getDatesBetween(firstDate, lastDate).map(d => d.date().toString());
 | 
			
		||||
            const omitDays = difference(allDays, allowedDays);
 | 
			
		||||
 | 
			
		||||
            return result.map(r => {
 | 
			
		||||
                return omit(r, omitDays);
 | 
			
		||||
            }) as unknown as EmployeeData[];
 | 
			
		||||
        }
 | 
			
		||||
        return result as unknown as EmployeeData[];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const optimisticUpdate = (date: Date, userId: number, value: number) => {
 | 
			
		||||
@@ -56,6 +96,8 @@ const WorkTimeTable = () => {
 | 
			
		||||
            record[date.getDate()] = value;
 | 
			
		||||
            return record;
 | 
			
		||||
        }))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        TimeTrackingService.updateTimeTrackingRecord({
 | 
			
		||||
            requestBody: {
 | 
			
		||||
                date: dateWithoutTimezone(date),
 | 
			
		||||
@@ -71,11 +113,21 @@ const WorkTimeTable = () => {
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    const columns = useWorkTableColumns({
 | 
			
		||||
        month, data, onUpdate: optimisticUpdate
 | 
			
		||||
        month,
 | 
			
		||||
        selectedBoundaries,
 | 
			
		||||
        data,
 | 
			
		||||
        onUpdate: optimisticUpdate,
 | 
			
		||||
        selectedCells: [],
 | 
			
		||||
        setSelectedCells: () => {
 | 
			
		||||
        },
 | 
			
		||||
        range
 | 
			
		||||
    });
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setData(transformTrackingRecordsToData(trackingRecords));
 | 
			
		||||
    }, [trackingRecords, hiddenUsers])
 | 
			
		||||
    }, [trackingRecords, hiddenUsers, selectedBoundaries]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setSelectedBoundaries([null, null]);
 | 
			
		||||
    }, [month])
 | 
			
		||||
    return (
 | 
			
		||||
        <Flex
 | 
			
		||||
            direction={"column"}
 | 
			
		||||
@@ -83,9 +135,11 @@ const WorkTimeTable = () => {
 | 
			
		||||
            gap={rem(10)}
 | 
			
		||||
        >
 | 
			
		||||
            <Flex
 | 
			
		||||
                justify={"flex-end"}
 | 
			
		||||
                align={"center"}
 | 
			
		||||
                justify={"space-between"}
 | 
			
		||||
                gap={rem(10)}
 | 
			
		||||
            >
 | 
			
		||||
 | 
			
		||||
                <MultiSelect
 | 
			
		||||
                    data={users.map(user => ({
 | 
			
		||||
                        label: `${user.firstName} ${user.secondName}`,
 | 
			
		||||
@@ -95,6 +149,23 @@ const WorkTimeTable = () => {
 | 
			
		||||
                    value={hiddenUsers.map(user => user.id.toString())}
 | 
			
		||||
                    placeholder={hiddenUsers.length > 0 ? "" : "Скрытые пользователи"}
 | 
			
		||||
                />
 | 
			
		||||
                <Flex gap={rem(10)}>
 | 
			
		||||
 | 
			
		||||
                    <DatePickerInput
 | 
			
		||||
                        styles={{
 | 
			
		||||
                            input: {
 | 
			
		||||
                                textAlign: "center"
 | 
			
		||||
                            }
 | 
			
		||||
                        }}
 | 
			
		||||
                        miw={rem(80)}
 | 
			
		||||
                        valueFormat={"DD"}
 | 
			
		||||
                        type={"range"}
 | 
			
		||||
                        minDate={dateBoundaries[0]}
 | 
			
		||||
                        maxDate={dateBoundaries[1]}
 | 
			
		||||
                        value={selectedBoundaries}
 | 
			
		||||
                        onChange={setSelectedBoundaries}
 | 
			
		||||
                        placeholder={"Даты"}
 | 
			
		||||
                    />
 | 
			
		||||
                    <MonthPickerInput
 | 
			
		||||
                        allowDeselect={false}
 | 
			
		||||
                        onChange={(event) => event && setMonth(event)}
 | 
			
		||||
@@ -102,6 +173,8 @@ const WorkTimeTable = () => {
 | 
			
		||||
                        placeholder={"Выберите месяц"}
 | 
			
		||||
                    />
 | 
			
		||||
                </Flex>
 | 
			
		||||
 | 
			
		||||
            </Flex>
 | 
			
		||||
            <Flex>
 | 
			
		||||
                <BaseTable
 | 
			
		||||
                    data={data}
 | 
			
		||||
@@ -125,7 +198,7 @@ const WorkTimeTable = () => {
 | 
			
		||||
                                </Tooltip>
 | 
			
		||||
 | 
			
		||||
                            </Flex>
 | 
			
		||||
                        )
 | 
			
		||||
                        ),
 | 
			
		||||
                    } as MRT_TableOptions<EmployeeData>}
 | 
			
		||||
                />
 | 
			
		||||
            </Flex>
 | 
			
		||||
 
 | 
			
		||||
@@ -156,6 +156,32 @@ const ProductAndServiceTab: FC = () => {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    const onCancelBillClick = () => {
 | 
			
		||||
        if (!dealState.deal) return;
 | 
			
		||||
        const dealId = dealState.deal.id;
 | 
			
		||||
        modals.openConfirmModal({
 | 
			
		||||
            withCloseButton: false,
 | 
			
		||||
            children:
 | 
			
		||||
                <Text style={{textAlign: "justify"}}>
 | 
			
		||||
                    Вы уверены что хотите отозвать заявку на оплату?
 | 
			
		||||
                </Text>,
 | 
			
		||||
            onConfirm: () => {
 | 
			
		||||
                BillingService.cancelDealBill({
 | 
			
		||||
                    requestBody: {
 | 
			
		||||
                        dealId
 | 
			
		||||
                    }
 | 
			
		||||
                }).then(async ({ok, message}) => {
 | 
			
		||||
                    notifications.guess(ok, {message});
 | 
			
		||||
                    await dealState.refetch();
 | 
			
		||||
                })
 | 
			
		||||
            },
 | 
			
		||||
            labels: {
 | 
			
		||||
                confirm: "Отозвать",
 | 
			
		||||
                cancel: "Отмена"
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <div
 | 
			
		||||
            className={
 | 
			
		||||
@@ -209,11 +235,19 @@ const ProductAndServiceTab: FC = () => {
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <Divider my={rem(15)}/>
 | 
			
		||||
                        <div className={styles['deal-container-buttons']}>
 | 
			
		||||
                            {isLocked ? <Button
 | 
			
		||||
                                    onClick={onCancelBillClick}
 | 
			
		||||
                                    color={"red"}
 | 
			
		||||
                                >
 | 
			
		||||
                                    Отозвать счет
 | 
			
		||||
                                </Button> :
 | 
			
		||||
                                <Button
 | 
			
		||||
                                    disabled={isLocked}
 | 
			
		||||
                                    onClick={onCreateBillClick}
 | 
			
		||||
                                    variant={"default"}
 | 
			
		||||
                                    fullWidth>Выставить счет</Button>
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                    </Flex>
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,8 @@ const ServicesKitsTable: FC<Props> = ({items, onDelete, onChange}) => {
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    const onDeleteClick = (kit: GetServiceKitSchema) => {
 | 
			
		||||
    const onDeleteClick = () => {
 | 
			
		||||
        if (!onDelete) return;
 | 
			
		||||
        console.log(kit)
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <BaseTable
 | 
			
		||||
@@ -46,7 +45,7 @@ const ServicesKitsTable: FC<Props> = ({items, onDelete, onChange}) => {
 | 
			
		||||
                        </Tooltip>
 | 
			
		||||
                        <Tooltip label="Удалить">
 | 
			
		||||
                            <ActionIcon onClick={() => {
 | 
			
		||||
                                if (onDelete) onDeleteClick(row.original);
 | 
			
		||||
                                if (onDelete) onDeleteClick();
 | 
			
		||||
                            }} variant={"default"}>
 | 
			
		||||
                                <IconTrash/>
 | 
			
		||||
                            </ActionIcon>
 | 
			
		||||
 
 | 
			
		||||
@@ -43,4 +43,17 @@ export const getDayOfWeek = (day: number): string => {
 | 
			
		||||
    }
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getDatesBetween(startDate: Date, endDate: Date): dayjs.Dayjs[] {
 | 
			
		||||
    const dates: dayjs.Dayjs[] = [];
 | 
			
		||||
    const currentDate = new Date(startDate);
 | 
			
		||||
 | 
			
		||||
    while (currentDate <= endDate) {
 | 
			
		||||
        dates.push(dayjs(new Date(currentDate)));
 | 
			
		||||
        currentDate.setDate(currentDate.getDate() + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return dates;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const isWeekend = (day: number) => (day === 6) || (day === 0);
 | 
			
		||||
							
								
								
									
										47
									
								
								src/shared/lib/interpolateCells.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/shared/lib/interpolateCells.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
import {maxBy, minBy} from "lodash";
 | 
			
		||||
 | 
			
		||||
type Cell = { row: number, col: number };
 | 
			
		||||
 | 
			
		||||
function parseCell(cell: string): Cell {
 | 
			
		||||
    const [row, col] = cell.split('_').map(Number);
 | 
			
		||||
    return {row, col};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stringifyCell(cell: Cell): string {
 | 
			
		||||
    return `${cell.row}_${cell.col}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function intropolate(cells: Cell[]) {
 | 
			
		||||
    const interpolatedCells: Cell[] = [];
 | 
			
		||||
    for (let i = 0; i < cells.length - 1; i++) {
 | 
			
		||||
        const current = cells[i];
 | 
			
		||||
        const next = cells[i + 1];
 | 
			
		||||
        interpolatedCells.push(current);
 | 
			
		||||
        if (current.row === next.row) {
 | 
			
		||||
            for (let col = current.col + 1; col < next.col; col++) {
 | 
			
		||||
                interpolatedCells.push({row: current.row, col});
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    interpolatedCells.push(cells[cells.length - 1]);
 | 
			
		||||
    return interpolatedCells;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function processSelectedCells(prevCells: string[], newSelection: string): string[] {
 | 
			
		||||
    const newCell = parseCell(newSelection);
 | 
			
		||||
    let cells = intropolate([...prevCells, newSelection].map(parseCell).sort((a, b) => a.col - b.col));
 | 
			
		||||
    const maxCell = maxBy(cells, cell => cell.col);
 | 
			
		||||
    const minCell = minBy(cells, cell => cell.col);
 | 
			
		||||
    const indexOfNewCell = cells.findIndex(cell => (cell.col === newCell.col && cell.row === newCell.row));
 | 
			
		||||
 | 
			
		||||
    if (minCell && maxCell) {
 | 
			
		||||
        if (newCell.col > minCell.col && newCell.col < maxCell.col) {
 | 
			
		||||
            if (indexOfNewCell >= (cells.length / 2))
 | 
			
		||||
                cells = cells.slice(0, indexOfNewCell);
 | 
			
		||||
            else
 | 
			
		||||
                cells = cells.slice(indexOfNewCell);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return cells.map(stringifyCell);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user