feat: time tracking in minutes

This commit is contained in:
2024-11-22 21:43:07 +04:00
parent 20616d9e81
commit de55f9fa1c
4 changed files with 116 additions and 49 deletions

View File

@@ -3,36 +3,67 @@ import { MRT_ColumnDef } from "mantine-react-table";
import { PaymentRecordGetSchema } from "../../../../client";
import { PaySchemeType } from "../../../../shared/enums/PaySchemeType.ts";
import { getPluralForm } from "../../../../shared/lib/utils.ts";
import { formatDate } from "../../../../types/utils.ts";
import { floatHoursToHoursAndMinutes, formatDate } from "../../../../types/utils.ts";
import { isEqual } from "lodash";
export const usePaymentRecordsTableColumns = () => {
const getPluralMonths = (months: number) => {
return getPluralForm(
months,
"месяц",
"месяца",
"месяцев",
);
};
const getPluralDays = (days: number) => {
return getPluralForm(
days,
"день",
"дня",
"дней",
);
};
const getPluralHours = (hours: number) => {
return getPluralForm(
hours,
"час",
"часа",
"часов",
);
};
const getPluralMinutes = (minutes: number) => {
return getPluralForm(
minutes,
"минута",
"минуты",
"минут",
);
};
const getWorkUnitsText = (paymentRecord: PaymentRecordGetSchema) => {
const payrollScheme = paymentRecord.payrollScheme;
if (payrollScheme.key === PaySchemeType.HOURLY) {
return getPluralForm(
paymentRecord.workUnits,
"час",
"часа",
"часов"
);
} else if (payrollScheme.key === PaySchemeType.DAILY) {
return getPluralForm(
paymentRecord.workUnits,
"день",
"дня",
"дней"
);
if (payrollScheme.key === PaySchemeType.DAILY) {
return getPluralDays(paymentRecord.workUnits);
} else if (payrollScheme.key === PaySchemeType.MONTHLY) {
return getPluralForm(
paymentRecord.workUnits,
"месяц",
"месяца",
"месяцев"
);
return getPluralMonths(paymentRecord.workUnits);
}
return "";
};
const getWorkUnits = (paymentRecord: PaymentRecordGetSchema) => {
const payrollScheme = paymentRecord.payrollScheme;
if (payrollScheme.key === PaySchemeType.HOURLY) {
const [hours, minutes] = floatHoursToHoursAndMinutes(paymentRecord.workUnits);
const hoursPlural = getPluralHours(hours);
if (minutes === 0) {
return `${hours} ${hoursPlural}`;
}
const minutesPlural = getPluralMinutes(minutes);
return `${hours} ${hoursPlural} ${minutes} ${minutesPlural}`;
}
return `${paymentRecord.workUnits} ${getWorkUnitsText(paymentRecord)}`;
};
const getDateRangesText = (paymentRecord: PaymentRecordGetSchema) => {
if (
paymentRecord.endDate &&
@@ -62,18 +93,17 @@ export const usePaymentRecordsTableColumns = () => {
},
{
header: "Количество",
Cell: ({ row }) =>
`${row.original.workUnits} ${getWorkUnitsText(row.original)}`,
Cell: ({ row }) => getWorkUnits(row.original),
},
{
header: "Сумма начисления",
Cell: ({ row }) => row.original.amount.toLocaleString("ru-RU"),
Cell: ({ row }) => Math.round(row.original.amount).toLocaleString("ru-RU"),
},
{
header: "Временной промежуток",
Cell: ({ row }) => getDateRangesText(row.original),
},
],
[]
[],
);
};

View File

@@ -1,11 +1,13 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { getDayOfWeek } from "../../../../../shared/lib/date.ts";
import { Box, Flex, NumberInput, rem, Text } from "@mantine/core";
import { Box, Flex, Input, rem, Text } from "@mantine/core";
import { isNumber, last } from "lodash";
import { processSelectedCells } from "../../../../../shared/lib/interpolateCells.ts";
import { TimeTrackingData } from "../../../../../client";
import dayjs from "dayjs";
import { IMaskInput } from "react-imask";
import { floatHoursToHoursAndMinutes } from "../../../../../types/utils.ts";
export type EmployeeData = {
name: string;
@@ -20,7 +22,7 @@ export type EmployeeData = {
type Props = {
month: Date;
data: EmployeeData[];
onUpdate: (date: Date, userId: number, value: number) => void;
onUpdate: (date: Date, userId: number, value: string) => void;
selectedCells: string[];
setSelectedCells: (cells: string[]) => void;
selectedBoundaries: [Date | null, Date | null];
@@ -62,6 +64,13 @@ const useWorkTableColumns = ({
return {};
};
const formatValue = (value: number) => {
const [hours, minutes] = floatHoursToHoursAndMinutes(value);
const hoursStr = String(hours).padStart(2, "0");
const minutesStr = String(minutes).padStart(2, "0");
return `${hoursStr}:${minutesStr}`;
};
return useMemo<MRT_ColumnDef<EmployeeData>[]>(
() => [
{
@@ -76,7 +85,7 @@ const useWorkTableColumns = ({
},
...range.map(date => ({
size: 80,
size: 100,
accessorKey: date.date().toString(),
header: date.date().toString(),
enableSorting: false,
@@ -112,35 +121,38 @@ const useWorkTableColumns = ({
Cell: ({ cell, row }) => {
return (
<Flex direction={"column"}>
<NumberInput
<Input
component={IMaskInput}
mask="00:00"
// key={row.original.name + date.date().toString()}
onChange={event =>
isNumber(event) &&
/^\d\d:\d\d$/.test(event.currentTarget.value) &&
onUpdate(
date.toDate(),
row.original.userId,
event
event.currentTarget.value,
)
}
styles={{ input: { textAlign: "center" } }}
hideControls
value={cell.renderValue()}
value={formatValue(cell.renderValue() as number)}
/>
</Flex>
);
},
})),
{
header: "Всего часов",
header: "Всего времени",
Cell: ({ row }) => {
return Object.entries(row.original).reduce(
(acc, [key, value]) => {
if (isNaN(parseInt(key)) || !isNumber(value))
return formatValue(
Object.entries(row.original).reduce(
(acc, [key, value]) => {
if (isNaN(parseInt(key)) || !isNumber(value))
return acc;
acc += value;
return acc;
acc += value;
return acc;
},
0
},
0,
),
);
},
},
@@ -149,17 +161,19 @@ const useWorkTableColumns = ({
header: "Итоговая сумма заработка",
Cell: ({ row }) => {
return (row.original.data || []).reduce((acc, value) => {
acc += value.amount;
return acc;
}, 0);
return Math.floor(
(row.original.data || []).reduce((acc, value) => {
acc += value.amount;
return acc;
}, 0),
);
},
Footer: (
<Flex>Всего: {totalAmount.toLocaleString("ru-RU")}</Flex>
<Flex>Всего: {Math.floor(totalAmount).toLocaleString("ru-RU")}</Flex>
),
},
],
[month, selectedCells, selectedBoundaries, totalAmount]
[month, selectedCells, selectedBoundaries, totalAmount],
);
};

View File

@@ -22,6 +22,7 @@ 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";
const WorkTimeTable = () => {
const [data, setData] = useState<EmployeeData[]>([]);
@@ -121,9 +122,16 @@ const WorkTimeTable = () => {
);
};
const optimisticUpdate = (date: Date, userId: number, value: number) => {
const optimisticUpdate = (date: Date, userId: number, value: string) => {
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 =>
prevState.map(record => {
if (record.userId !== userId) return record;
@@ -135,7 +143,7 @@ const WorkTimeTable = () => {
TimeTrackingService.updateTimeTrackingRecord({
requestBody: {
date: dateWithoutTimezone(date),
hours: value,
hours,
userId: user.id,
},
}).then(async ({ ok, message }) => {

View File

@@ -42,3 +42,18 @@ export function ObjectStateToTableProps<T extends MRT_RowData>(
onCreate: state.onCreate,
};
}
export const floatHoursToHoursAndMinutes = (hours: number): number[] => {
const resHours = Math.floor(hours);
const minutes = Math.round((hours - resHours) * 60);
return [resHours, minutes];
};
export const strTimeToFloatHours = (time: string): number => {
const values = time.split(":");
if (values.length !== 2) return -1;
const hours = parseInt(values[0]);
const minutes = parseInt(values[1]);
return hours + minutes / 60;
}