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 { BillPaymentStatus } from './models/BillPaymentStatus';
|
||||||
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
|
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
|
||||||
export type { Body_upload_product_image } from './models/Body_upload_product_image';
|
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 { ClientCreateRequest } from './models/ClientCreateRequest';
|
||||||
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
||||||
export type { ClientDeleteRequest } from './models/ClientDeleteRequest';
|
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 */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import type { BillStatusUpdateRequest } from '../models/BillStatusUpdateRequest';
|
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 { CreateDealBillRequest } from '../models/CreateDealBillRequest';
|
||||||
import type { CreateDealBillResponse } from '../models/CreateDealBillResponse';
|
import type { CreateDealBillResponse } from '../models/CreateDealBillResponse';
|
||||||
import type { GetDealBillById } from '../models/GetDealBillById';
|
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
|
* Get Deal Bill By Id
|
||||||
* @returns GetDealBillById Successful Response
|
* @returns GetDealBillById Successful Response
|
||||||
|
|||||||
@@ -1,35 +1,75 @@
|
|||||||
import {useMemo} from "react";
|
import {useMemo} from "react";
|
||||||
import {MRT_ColumnDef} from "mantine-react-table";
|
import {MRT_ColumnDef} from "mantine-react-table";
|
||||||
import {getDatesInMonth, getDayOfWeek} from "../../../../../shared/lib/date.ts";
|
import {getDayOfWeek} from "../../../../../shared/lib/date.ts";
|
||||||
import {Box, Flex, NumberInput} from "@mantine/core";
|
import {Box, Flex, NumberInput, rem} from "@mantine/core";
|
||||||
import {isNumber} from "lodash";
|
import {isNumber, last} from "lodash";
|
||||||
|
import {processSelectedCells} from "../../../../../shared/lib/interpolateCells.ts";
|
||||||
|
import {TimeTrackingData} from "../../../../../client";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export type EmployeeData = {
|
export type EmployeeData = {
|
||||||
name: string;
|
name: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
data?: TimeTrackingData[],
|
||||||
[key: string]: number | string;
|
[key: string]: number | string;
|
||||||
};
|
};
|
||||||
type Props = {
|
type Props = {
|
||||||
month: Date;
|
month: Date;
|
||||||
data: EmployeeData[];
|
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 useWorkTableColumns = ({
|
||||||
const totalAmount = useMemo(() => data.reduce((acc, value) => acc + value.totalAmount, 0), [data]);
|
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>[]>(() => [
|
return useMemo<MRT_ColumnDef<EmployeeData>[]>(() => [
|
||||||
{
|
{
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
header: "ФИО"
|
header: "ФИО"
|
||||||
},
|
},
|
||||||
|
|
||||||
...getDatesInMonth(month).map(date => ({
|
...range.map(date => ({
|
||||||
size: 80,
|
size: 80,
|
||||||
accessorKey: date.date().toString(),
|
accessorKey: date.date().toString(),
|
||||||
header: date.date().toString(),
|
header: date.date().toString(),
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableColumnActions: false,
|
enableColumnActions: false,
|
||||||
|
|
||||||
Header: (
|
Header: (
|
||||||
<Flex
|
<Flex
|
||||||
align={"center"}
|
align={"center"}
|
||||||
@@ -44,6 +84,19 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
|
|||||||
),
|
),
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-expect-error
|
// @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}) => {
|
Cell: ({cell, row}) => {
|
||||||
return (
|
return (
|
||||||
<Flex direction={"column"}>
|
<Flex direction={"column"}>
|
||||||
@@ -71,9 +124,13 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
|
|||||||
{
|
{
|
||||||
accessorKey: "totalAmount",
|
accessorKey: "totalAmount",
|
||||||
header: "Итоговая сумма заработка",
|
header: "Итоговая сумма заработка",
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-expect-error
|
Cell: ({row}) => {
|
||||||
Cell: ({cell}) => cell.renderValue().toLocaleString("ru-RU"),
|
return (row.original.data || []).reduce((acc, value) => {
|
||||||
|
acc += value.amount;
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
Footer: (
|
Footer: (
|
||||||
<Flex>
|
<Flex>
|
||||||
Всего: {totalAmount.toLocaleString("ru-RU")}
|
Всего: {totalAmount.toLocaleString("ru-RU")}
|
||||||
@@ -82,7 +139,7 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
], [month]);
|
], [month, selectedCells, selectedBoundaries, totalAmount]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useWorkTableColumns;
|
export default useWorkTableColumns;
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {TimeTrackingRecord, TimeTrackingService} from "../../../../../client";
|
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 useWorkTableState = () => {
|
||||||
const [month, setMonth] = useState<Date>(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
|
const [month, setMonth] = useState<Date>(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
|
||||||
const [trackingRecords, setTrackingRecords] = useState<TimeTrackingRecord[]>([]);
|
const [trackingRecords, setTrackingRecords] = useState<TimeTrackingRecord[]>([]);
|
||||||
|
const [dateBoundaries, setDateBoundaries] = useState(getDateBoundaries(month));
|
||||||
|
|
||||||
const refetch = async () => {
|
const refetch = async () => {
|
||||||
return TimeTrackingService.getTimeTrackingRecords({
|
return TimeTrackingService.getTimeTrackingRecords({
|
||||||
requestBody: {
|
requestBody: {
|
||||||
@@ -13,16 +19,20 @@ const useWorkTableState = () => {
|
|||||||
}
|
}
|
||||||
}).then((response) => setTrackingRecords(response.records));
|
}).then((response) => setTrackingRecords(response.records));
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
refetch().then(_ => {
|
refetch().then(_ => {
|
||||||
|
setDateBoundaries(getDateBoundaries(month));
|
||||||
});
|
});
|
||||||
|
|
||||||
}, [month])
|
}, [month])
|
||||||
return {
|
return {
|
||||||
month,
|
month,
|
||||||
setMonth,
|
setMonth,
|
||||||
refetch,
|
refetch,
|
||||||
trackingRecords
|
trackingRecords,
|
||||||
|
dateBoundaries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,58 @@
|
|||||||
import {ActionIcon, Flex, MultiSelect, rem, Tooltip} from "@mantine/core";
|
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 useWorkTableState from "../hooks/useWorkTableState.tsx";
|
||||||
import {BaseTable} from "../../../../../components/BaseTable/BaseTable.tsx";
|
import {BaseTable} from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import useWorkTableColumns, {EmployeeData} from "../hooks/useWorkTableColumns.tsx";
|
import useWorkTableColumns, {EmployeeData} from "../hooks/useWorkTableColumns.tsx";
|
||||||
import {TimeTrackingRecord, TimeTrackingService, UserSchema} from "../../../../../client";
|
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 useUsersList from "../../../hooks/useUsersList.tsx";
|
||||||
import {notifications} from "../../../../../shared/lib/notifications.ts";
|
import {notifications} from "../../../../../shared/lib/notifications.ts";
|
||||||
import {PaySchemeType} from "../../../../../shared/enums/PaySchemeType.ts";
|
import {PaySchemeType} from "../../../../../shared/enums/PaySchemeType.ts";
|
||||||
import {IconEyeOff} from "@tabler/icons-react";
|
import {IconEyeOff} from "@tabler/icons-react";
|
||||||
import {MRT_TableOptions} from "mantine-react-table";
|
import {MRT_TableOptions} from "mantine-react-table";
|
||||||
|
import {difference, omit} from "lodash";
|
||||||
|
|
||||||
|
|
||||||
const WorkTimeTable = () => {
|
const WorkTimeTable = () => {
|
||||||
const [data, setData] = useState<EmployeeData[]>([]);
|
const [data, setData] = useState<EmployeeData[]>([]);
|
||||||
const {
|
const {
|
||||||
|
dateBoundaries,
|
||||||
month,
|
month,
|
||||||
setMonth,
|
setMonth,
|
||||||
trackingRecords,
|
trackingRecords,
|
||||||
refetch
|
refetch
|
||||||
} = useWorkTableState();
|
} = useWorkTableState();
|
||||||
const [hiddenUsers, setHiddenUsers] = useState<UserSchema[]>([]);
|
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 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[] => {
|
const transformTrackingRecordsToData = (trackingRecords: TimeTrackingRecord[]): EmployeeData[] => {
|
||||||
if (!month) return [];
|
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 existingUserIds = trackingRecords.map(tr => tr.user.id);
|
||||||
const firstResult = trackingRecords.map((record) => ({
|
const firstResult = trackingRecords.map((record) => ({
|
||||||
name: `${record.user.firstName} ${record.user.secondName}`,
|
name: `${record.user.firstName} ${record.user.secondName}`,
|
||||||
userId: record.user.id,
|
userId: record.user.id,
|
||||||
totalAmount: record.totalAmount,
|
totalAmount: record.totalAmount,
|
||||||
|
data: record.data,
|
||||||
...Object.fromEntries(getDatesInMonth(month).reduce((acc, day) => {
|
...Object.fromEntries(getDatesInMonth(month).reduce((acc, day) => {
|
||||||
return acc.set(day.date().toString(), 0);
|
return acc.set(day.date().toString(), 0);
|
||||||
}, new Map<string, number>)),
|
}, new Map<string, number>)),
|
||||||
@@ -45,7 +69,23 @@ const WorkTimeTable = () => {
|
|||||||
}, new Map<string, number>)),
|
}, new Map<string, number>)),
|
||||||
}))
|
}))
|
||||||
const hiddenUserIds = hiddenUsers.map(user => user.id);
|
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) => {
|
const optimisticUpdate = (date: Date, userId: number, value: number) => {
|
||||||
@@ -56,6 +96,8 @@ const WorkTimeTable = () => {
|
|||||||
record[date.getDate()] = value;
|
record[date.getDate()] = value;
|
||||||
return record;
|
return record;
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
TimeTrackingService.updateTimeTrackingRecord({
|
TimeTrackingService.updateTimeTrackingRecord({
|
||||||
requestBody: {
|
requestBody: {
|
||||||
date: dateWithoutTimezone(date),
|
date: dateWithoutTimezone(date),
|
||||||
@@ -71,11 +113,21 @@ const WorkTimeTable = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const columns = useWorkTableColumns({
|
const columns = useWorkTableColumns({
|
||||||
month, data, onUpdate: optimisticUpdate
|
month,
|
||||||
|
selectedBoundaries,
|
||||||
|
data,
|
||||||
|
onUpdate: optimisticUpdate,
|
||||||
|
selectedCells: [],
|
||||||
|
setSelectedCells: () => {
|
||||||
|
},
|
||||||
|
range
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setData(transformTrackingRecordsToData(trackingRecords));
|
setData(transformTrackingRecordsToData(trackingRecords));
|
||||||
}, [trackingRecords, hiddenUsers])
|
}, [trackingRecords, hiddenUsers, selectedBoundaries]);
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedBoundaries([null, null]);
|
||||||
|
}, [month])
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
direction={"column"}
|
direction={"column"}
|
||||||
@@ -83,9 +135,11 @@ const WorkTimeTable = () => {
|
|||||||
gap={rem(10)}
|
gap={rem(10)}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
justify={"flex-end"}
|
align={"center"}
|
||||||
|
justify={"space-between"}
|
||||||
gap={rem(10)}
|
gap={rem(10)}
|
||||||
>
|
>
|
||||||
|
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
data={users.map(user => ({
|
data={users.map(user => ({
|
||||||
label: `${user.firstName} ${user.secondName}`,
|
label: `${user.firstName} ${user.secondName}`,
|
||||||
@@ -95,12 +149,31 @@ const WorkTimeTable = () => {
|
|||||||
value={hiddenUsers.map(user => user.id.toString())}
|
value={hiddenUsers.map(user => user.id.toString())}
|
||||||
placeholder={hiddenUsers.length > 0 ? "" : "Скрытые пользователи"}
|
placeholder={hiddenUsers.length > 0 ? "" : "Скрытые пользователи"}
|
||||||
/>
|
/>
|
||||||
<MonthPickerInput
|
<Flex gap={rem(10)}>
|
||||||
allowDeselect={false}
|
|
||||||
onChange={(event) => event && setMonth(event)}
|
<DatePickerInput
|
||||||
value={month}
|
styles={{
|
||||||
placeholder={"Выберите месяц"}
|
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)}
|
||||||
|
value={month}
|
||||||
|
placeholder={"Выберите месяц"}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex>
|
<Flex>
|
||||||
<BaseTable
|
<BaseTable
|
||||||
@@ -125,7 +198,7 @@ const WorkTimeTable = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
),
|
||||||
} as MRT_TableOptions<EmployeeData>}
|
} as MRT_TableOptions<EmployeeData>}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
@@ -209,11 +235,19 @@ const ProductAndServiceTab: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Divider my={rem(15)}/>
|
<Divider my={rem(15)}/>
|
||||||
<div className={styles['deal-container-buttons']}>
|
<div className={styles['deal-container-buttons']}>
|
||||||
<Button
|
{isLocked ? <Button
|
||||||
disabled={isLocked}
|
onClick={onCancelBillClick}
|
||||||
onClick={onCreateBillClick}
|
color={"red"}
|
||||||
variant={"default"}
|
>
|
||||||
fullWidth>Выставить счет</Button>
|
Отозвать счет
|
||||||
|
</Button> :
|
||||||
|
<Button
|
||||||
|
disabled={isLocked}
|
||||||
|
onClick={onCreateBillClick}
|
||||||
|
variant={"default"}
|
||||||
|
fullWidth>Выставить счет</Button>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -23,9 +23,8 @@ const ServicesKitsTable: FC<Props> = ({items, onDelete, onChange}) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const onDeleteClick = (kit: GetServiceKitSchema) => {
|
const onDeleteClick = () => {
|
||||||
if (!onDelete) return;
|
if (!onDelete) return;
|
||||||
console.log(kit)
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<BaseTable
|
<BaseTable
|
||||||
@@ -46,7 +45,7 @@ const ServicesKitsTable: FC<Props> = ({items, onDelete, onChange}) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="Удалить">
|
<Tooltip label="Удалить">
|
||||||
<ActionIcon onClick={() => {
|
<ActionIcon onClick={() => {
|
||||||
if (onDelete) onDeleteClick(row.original);
|
if (onDelete) onDeleteClick();
|
||||||
}} variant={"default"}>
|
}} variant={"default"}>
|
||||||
<IconTrash/>
|
<IconTrash/>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|||||||
@@ -43,4 +43,17 @@ export const getDayOfWeek = (day: number): string => {
|
|||||||
}
|
}
|
||||||
return "";
|
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);
|
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