feat: worktime table with dates range
This commit is contained in:
@@ -24,7 +24,7 @@ export type CardSummary = {
|
|||||||
attributes: Array<CardAttributeSchema>;
|
attributes: Array<CardAttributeSchema>;
|
||||||
shipmentWarehouseId: (number | null);
|
shipmentWarehouseId: (number | null);
|
||||||
shipmentWarehouseName: (string | null);
|
shipmentWarehouseName: (string | null);
|
||||||
billRequests?: Array<CardBillRequestSchema>;
|
billRequests: Array<CardBillRequestSchema>;
|
||||||
group?: (CardGroupSchema | null);
|
group?: (CardGroupSchema | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export type GetTimeTrackingRecordsRequest = {
|
export type GetTimeTrackingRecordsRequest = {
|
||||||
date: string;
|
dateFrom: string;
|
||||||
userIds: Array<number>;
|
dateTo: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { processSelectedCells } from "../../../../../shared/lib/interpolateCells
|
|||||||
import { TimeTrackingData } from "../../../../../client";
|
import { TimeTrackingData } from "../../../../../client";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { IMaskInput } from "react-imask";
|
import { IMaskInput } from "react-imask";
|
||||||
import { floatHoursToHoursAndMinutes } from "../../../../../types/utils.ts";
|
import { dateToString, floatHoursToHoursAndMinutes } from "../../../../../types/utils.ts";
|
||||||
|
|
||||||
export type EmployeeData = {
|
export type EmployeeData = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -20,21 +20,17 @@ export type EmployeeData = {
|
|||||||
[key: string]: number | string;
|
[key: string]: number | string;
|
||||||
};
|
};
|
||||||
type Props = {
|
type Props = {
|
||||||
month: Date;
|
|
||||||
data: EmployeeData[];
|
data: EmployeeData[];
|
||||||
onUpdate: (date: Date, userId: number, value: string) => void;
|
onUpdate: (date: Date, userId: number, value: string) => void;
|
||||||
selectedCells: string[];
|
selectedCells: string[];
|
||||||
setSelectedCells: (cells: string[]) => void;
|
setSelectedCells: (cells: string[]) => void;
|
||||||
selectedBoundaries: [Date | null, Date | null];
|
|
||||||
range: dayjs.Dayjs[];
|
range: dayjs.Dayjs[];
|
||||||
};
|
};
|
||||||
const useWorkTableColumns = ({
|
const useWorkTableColumns = ({
|
||||||
month,
|
|
||||||
onUpdate,
|
onUpdate,
|
||||||
data,
|
data,
|
||||||
selectedCells,
|
selectedCells,
|
||||||
setSelectedCells,
|
setSelectedCells,
|
||||||
selectedBoundaries,
|
|
||||||
range,
|
range,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const totalAmount = useMemo(() => {
|
const totalAmount = useMemo(() => {
|
||||||
@@ -48,7 +44,7 @@ const useWorkTableColumns = ({
|
|||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, 0);
|
}, 0);
|
||||||
}, [data, month, selectedCells, selectedBoundaries]);
|
}, [data, selectedCells, range]);
|
||||||
const getBorderStyles = (cellId: string) => {
|
const getBorderStyles = (cellId: string) => {
|
||||||
if (selectedCells.length <= 1) return {};
|
if (selectedCells.length <= 1) return {};
|
||||||
if (selectedCells[0] === cellId)
|
if (selectedCells[0] === cellId)
|
||||||
@@ -86,8 +82,8 @@ const useWorkTableColumns = ({
|
|||||||
|
|
||||||
...range.map(date => ({
|
...range.map(date => ({
|
||||||
size: 100,
|
size: 100,
|
||||||
accessorKey: date.date().toString(),
|
accessorKey: dateToString(date.toDate()) ?? "",
|
||||||
header: date.date().toString(),
|
header: dateToString(date.toDate()) ?? "",
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableColumnActions: false,
|
enableColumnActions: false,
|
||||||
Header: (
|
Header: (
|
||||||
@@ -130,7 +126,7 @@ const useWorkTableColumns = ({
|
|||||||
row.original.userId,
|
row.original.userId,
|
||||||
value,
|
value,
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction={"column"}>
|
<Flex direction={"column"}>
|
||||||
@@ -178,7 +174,7 @@ const useWorkTableColumns = ({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[month, selectedCells, selectedBoundaries, totalAmount],
|
[selectedCells, range, totalAmount],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +1,45 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TimeTrackingRecord, TimeTrackingService } from "../../../../../client";
|
import { TimeTrackingRecord, TimeTrackingService } from "../../../../../client";
|
||||||
import {
|
import { getDefaultEndDate } from "../../WorkShiftsPlanning/utils/utils.tsx";
|
||||||
dateWithoutTimezone,
|
import { dateToString } from "../../../../../types/utils.ts";
|
||||||
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>(
|
const [dateRange, setDateRange] = useState<
|
||||||
new Date(new Date().getFullYear(), new Date().getMonth(), 1)
|
[Date | null, Date | null]
|
||||||
);
|
>([new Date(), getDefaultEndDate()]);
|
||||||
const [trackingRecords, setTrackingRecords] = useState<
|
const [trackingRecords, setTrackingRecords] = useState<
|
||||||
TimeTrackingRecord[]
|
TimeTrackingRecord[]
|
||||||
>([]);
|
>([]);
|
||||||
const [dateBoundaries, setDateBoundaries] = useState(
|
|
||||||
getDateBoundaries(month)
|
|
||||||
);
|
|
||||||
|
|
||||||
const refetch = async () => {
|
const refetch = () => {
|
||||||
return TimeTrackingService.getTimeTrackingRecords({
|
const ending = "T00:00:00";
|
||||||
requestBody: {
|
const dateFrom = dateToString(dateRange[0]);
|
||||||
date: dateWithoutTimezone(month),
|
const dateTo = dateToString(dateRange[1]);
|
||||||
userIds: [],
|
if (!(dateFrom && dateTo)) return;
|
||||||
},
|
|
||||||
}).then(response => setTrackingRecords(response.records));
|
TimeTrackingService
|
||||||
|
.getTimeTrackingRecords({
|
||||||
|
requestBody: {
|
||||||
|
dateFrom: dateFrom + ending,
|
||||||
|
dateTo: dateTo + ending,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
console.log(response.records);
|
||||||
|
setTrackingRecords(response.records);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
refetch();
|
||||||
refetch().then(_ => {
|
}, [dateRange]);
|
||||||
setDateBoundaries(getDateBoundaries(month));
|
|
||||||
});
|
|
||||||
}, [month]);
|
|
||||||
return {
|
return {
|
||||||
month,
|
dateRange,
|
||||||
setMonth,
|
setDateRange,
|
||||||
refetch,
|
refetch,
|
||||||
trackingRecords,
|
trackingRecords,
|
||||||
dateBoundaries,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,44 @@
|
|||||||
import { ActionIcon, Flex, MultiSelect, rem, Tooltip } from "@mantine/core";
|
import { ActionIcon, Flex, MultiSelect, rem, Tooltip } from "@mantine/core";
|
||||||
import { DatePickerInput, MonthPickerInput } from "@mantine/dates";
|
import { DatePickerInput } 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, getDatesBetween, getDatesInMonth } from "../../../../../shared/lib/date.ts";
|
import { getDatesBetween } 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";
|
import { difference, omit } from "lodash";
|
||||||
import { strTimeToFloatHours } from "../../../../../types/utils.ts";
|
import { dateToString, strTimeToFloatHours } from "../../../../../types/utils.ts";
|
||||||
import { useListState } from "@mantine/hooks";
|
import { useListState } from "@mantine/hooks";
|
||||||
|
|
||||||
const WorkTimeTable = () => {
|
const WorkTimeTable = () => {
|
||||||
const [data, setData] = useState<EmployeeData[]>([]);
|
const [data, setData] = useState<EmployeeData[]>([]);
|
||||||
const { dateBoundaries, month, setMonth, trackingRecords, refetch } =
|
const { dateRange, setDateRange, trackingRecords, refetch } = useWorkTableState();
|
||||||
useWorkTableState();
|
|
||||||
const [shownUsers, shownUsersHandlers] = useListState<UserSchema>([]);
|
const [shownUsers, shownUsersHandlers] = useListState<UserSchema>([]);
|
||||||
const [selectedBoundaries, setSelectedBoundaries] = useState<
|
|
||||||
[Date | null, Date | null]
|
|
||||||
>([null, null]);
|
|
||||||
|
|
||||||
const users = useUsersList().objects.filter(
|
const users = useUsersList().objects.filter(
|
||||||
user => user.payRate?.payrollScheme.key === PaySchemeType.HOURLY,
|
user => user.payRate?.payrollScheme.key === PaySchemeType.HOURLY,
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRange = () => {
|
const getRange = () => {
|
||||||
const startDate = selectedBoundaries[0];
|
if (!(dateRange.length === 2 && dateRange[0] && dateRange[1])) return [];
|
||||||
const endDate = selectedBoundaries[1];
|
const startDate = dateRange[0];
|
||||||
if (startDate && endDate) {
|
const endDate = dateRange[1];
|
||||||
return getDatesBetween(startDate, endDate);
|
return getDatesBetween(startDate, endDate);
|
||||||
} else {
|
|
||||||
return getDatesInMonth(month);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const range = getRange();
|
const range = getRange();
|
||||||
|
|
||||||
const transformTrackingRecordsToData = (
|
const transformTrackingRecordsToData = (
|
||||||
trackingRecords: TimeTrackingRecord[],
|
trackingRecords: TimeTrackingRecord[],
|
||||||
): EmployeeData[] => {
|
): EmployeeData[] => {
|
||||||
if (!month) return [];
|
if (!(dateRange.length === 2 && dateRange[0] && dateRange[1])) return [];
|
||||||
const rangeDays = range.map(r => r.date());
|
const dateFrom = dateRange[0];
|
||||||
|
const dateTo = dateRange[1];
|
||||||
|
const dates = getDatesBetween(dateFrom, dateTo);
|
||||||
|
|
||||||
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}`,
|
||||||
@@ -59,14 +47,14 @@ const WorkTimeTable = () => {
|
|||||||
totalAmount: record.totalAmount,
|
totalAmount: record.totalAmount,
|
||||||
data: record.data,
|
data: record.data,
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
getDatesInMonth(month).reduce((acc, day) => {
|
dates.reduce((acc, day) => {
|
||||||
return acc.set(day.date().toString(), 0);
|
return acc.set(dateToString(day.toDate()) ?? "", 0);
|
||||||
}, new Map<string, number>()),
|
}, new Map<string, number>()),
|
||||||
),
|
),
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
record.data.reduce((acc, recordData) => {
|
record.data.reduce((acc, recordData) => {
|
||||||
return acc.set(
|
return acc.set(
|
||||||
new Date(recordData.date).getDate().toString(),
|
recordData.date,
|
||||||
recordData.hours,
|
recordData.hours,
|
||||||
);
|
);
|
||||||
}, new Map<string, number>()),
|
}, new Map<string, number>()),
|
||||||
@@ -80,7 +68,7 @@ const WorkTimeTable = () => {
|
|||||||
comment: user.comment,
|
comment: user.comment,
|
||||||
totalAmount: 0,
|
totalAmount: 0,
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
getDatesInMonth(month).reduce((acc, day) => {
|
dates.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>()),
|
||||||
),
|
),
|
||||||
@@ -93,13 +81,12 @@ const WorkTimeTable = () => {
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.concat(restUsersResult)
|
.concat(restUsersResult)
|
||||||
.filter(r => shownUserIds.includes(r.userId));
|
.filter(r => shownUserIds.includes(r.userId));
|
||||||
const firstDate = selectedBoundaries[0];
|
|
||||||
const lastDate = selectedBoundaries[1];
|
if (dateFrom && dateTo) {
|
||||||
if (firstDate && lastDate) {
|
const allDays = dates.map(d =>
|
||||||
const allDays = getDatesInMonth(month).map(d =>
|
|
||||||
d.date().toString(),
|
d.date().toString(),
|
||||||
);
|
);
|
||||||
const allowedDays = getDatesBetween(firstDate, lastDate).map(d =>
|
const allowedDays = getDatesBetween(dateFrom, dateTo).map(d =>
|
||||||
d.date().toString(),
|
d.date().toString(),
|
||||||
);
|
);
|
||||||
const omitDays = difference(allDays, allowedDays);
|
const omitDays = difference(allDays, allowedDays);
|
||||||
@@ -117,10 +104,7 @@ const WorkTimeTable = () => {
|
|||||||
const user = users.find(user => user.id === userId);
|
const user = users.find(user => user.id === userId);
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
console.log(value);
|
|
||||||
|
|
||||||
const hours = strTimeToFloatHours(value);
|
const hours = strTimeToFloatHours(value);
|
||||||
console.log(hours);
|
|
||||||
if (hours === -1) return;
|
if (hours === -1) return;
|
||||||
|
|
||||||
setData(prevState =>
|
setData(prevState =>
|
||||||
@@ -128,12 +112,13 @@ const WorkTimeTable = () => {
|
|||||||
if (record.userId !== userId) return record;
|
if (record.userId !== userId) return record;
|
||||||
record[date.getDate()] = value;
|
record[date.getDate()] = value;
|
||||||
return record;
|
return record;
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ending = "T00:00:00";
|
||||||
TimeTrackingService.updateTimeTrackingRecord({
|
TimeTrackingService.updateTimeTrackingRecord({
|
||||||
requestBody: {
|
requestBody: {
|
||||||
date: dateWithoutTimezone(date),
|
date: dateToString(date) + ending,
|
||||||
hours,
|
hours,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
},
|
},
|
||||||
@@ -141,13 +126,11 @@ const WorkTimeTable = () => {
|
|||||||
if (!ok) {
|
if (!ok) {
|
||||||
notifications.guess(ok, { message });
|
notifications.guess(ok, { message });
|
||||||
}
|
}
|
||||||
await refetch();
|
refetch();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = useWorkTableColumns({
|
const columns = useWorkTableColumns({
|
||||||
month,
|
|
||||||
selectedBoundaries,
|
|
||||||
data,
|
data,
|
||||||
onUpdate: optimisticUpdate,
|
onUpdate: optimisticUpdate,
|
||||||
selectedCells: [],
|
selectedCells: [],
|
||||||
@@ -158,11 +141,7 @@ const WorkTimeTable = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setData(transformTrackingRecordsToData(trackingRecords));
|
setData(transformTrackingRecordsToData(trackingRecords));
|
||||||
}, [trackingRecords, shownUsers, selectedBoundaries]);
|
}, [trackingRecords, shownUsers, dateRange]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelectedBoundaries([null, null]);
|
|
||||||
}, [month]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -193,25 +172,12 @@ const WorkTimeTable = () => {
|
|||||||
/>
|
/>
|
||||||
<Flex gap={rem(10)}>
|
<Flex gap={rem(10)}>
|
||||||
<DatePickerInput
|
<DatePickerInput
|
||||||
styles={{
|
placeholder={"Выберите временной промежуток"}
|
||||||
input: {
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
miw={rem(80)}
|
|
||||||
valueFormat={"DD"}
|
|
||||||
type={"range"}
|
type={"range"}
|
||||||
minDate={dateBoundaries[0]}
|
value={dateRange}
|
||||||
maxDate={dateBoundaries[1]}
|
onChange={(value) => {
|
||||||
value={selectedBoundaries}
|
setDateRange(value);
|
||||||
onChange={setSelectedBoundaries}
|
}}
|
||||||
placeholder={"Даты"}
|
|
||||||
/>
|
|
||||||
<MonthPickerInput
|
|
||||||
allowDeselect={false}
|
|
||||||
onChange={event => event && setMonth(event)}
|
|
||||||
value={month}
|
|
||||||
placeholder={"Выберите месяц"}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -5,23 +5,6 @@ export const dateWithoutTimezone = (date: Date) => {
|
|||||||
return new Date(date.valueOf() - tzoffset).toISOString().slice(0, -1);
|
return new Date(date.valueOf() - tzoffset).toISOString().slice(0, -1);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDatesInMonth = (inputDate: Date) => {
|
|
||||||
const month = inputDate.getMonth();
|
|
||||||
const year = inputDate.getFullYear();
|
|
||||||
|
|
||||||
// Create a Day.js object for the first day of the specified month and year
|
|
||||||
let date = dayjs(new Date(year, month, 1));
|
|
||||||
const dates = [];
|
|
||||||
|
|
||||||
// Iterate through the days of the month
|
|
||||||
while (date.month() === month) {
|
|
||||||
dates.push(date);
|
|
||||||
date = date.add(1, "day");
|
|
||||||
}
|
|
||||||
|
|
||||||
return dates;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDayOfWeek = (day: number): string => {
|
export const getDayOfWeek = (day: number): string => {
|
||||||
switch (day) {
|
switch (day) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
Reference in New Issue
Block a user