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