feat: work shifts by QR codes
This commit is contained in:
@@ -4,7 +4,7 @@ import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
||||
import {
|
||||
IconBriefcase,
|
||||
IconCalendarUser,
|
||||
IconCurrencyDollar,
|
||||
IconCurrencyDollar, IconQrcode,
|
||||
IconUser,
|
||||
} from "@tabler/icons-react";
|
||||
import RolesAndPositionsTab from "./tabs/RolesAndPositions/RolesAndPositionsTab.tsx";
|
||||
@@ -12,6 +12,7 @@ import UsersTab from "./tabs/Users/UsersTab.tsx";
|
||||
import { motion } from "framer-motion";
|
||||
import FinancesTab from "./tabs/Finances/FinancesTab.tsx";
|
||||
import WorkTimeTable from "./tabs/WorkTimeTable/ui/WorkTimeTable.tsx";
|
||||
import { WorkShiftsTab } from "./tabs/WorkShifts/WorkShiftsTab.tsx";
|
||||
|
||||
const AdminPage = () => {
|
||||
return (
|
||||
@@ -42,6 +43,11 @@ const AdminPage = () => {
|
||||
leftSection={<IconCalendarUser />}>
|
||||
Рабочее время
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value={"workShifts"}
|
||||
leftSection={<IconQrcode />}>
|
||||
Смены
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"users"}>
|
||||
<motion.div
|
||||
@@ -75,6 +81,14 @@ const AdminPage = () => {
|
||||
<WorkTimeTable />
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"workShifts"}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<WorkShiftsTab />
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</PageBlock>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { useActiveShiftsTableColumns } from "./columns.tsx";
|
||||
import { ActionIcon, Flex, Stack, Text, Title, Tooltip } from "@mantine/core";
|
||||
import { IconCheck, IconTrash } from "@tabler/icons-react";
|
||||
import { ActiveWorkShiftSchema, WorkShiftsService } from "../../../../client";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { formatDate } from "../../../../types/utils.ts";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
|
||||
|
||||
type Props = {
|
||||
activeShifts: ActiveWorkShiftSchema[];
|
||||
fetchActiveShifts: () => void;
|
||||
}
|
||||
|
||||
export const ActiveShiftsTable = ({ activeShifts, fetchActiveShifts }: Props) => {
|
||||
const columns = useActiveShiftsTableColumns();
|
||||
|
||||
const onDelete = (workShift: ActiveWorkShiftSchema) => {
|
||||
WorkShiftsService.deleteWorkShift({
|
||||
shiftId: workShift.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
fetchActiveShifts();
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteClick = (workShift: ActiveWorkShiftSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление записи о начале смены",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите удалить запись о начале смены работника{" "}
|
||||
{workShift.user.firstName} {workShift.user.secondName} от{" "}
|
||||
{formatDate(workShift.startedAt)}
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => onDelete(workShift),
|
||||
});
|
||||
};
|
||||
|
||||
const onShiftFinish = (workShift: ActiveWorkShiftSchema) => {
|
||||
WorkShiftsService.finishWorkShiftById({
|
||||
shiftId: workShift.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
fetchActiveShifts();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
}
|
||||
|
||||
const onShiftFinishClick = (workShift: ActiveWorkShiftSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Завершение смены",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите завершить смену работника{" "}
|
||||
{workShift.user.firstName} {workShift.user.secondName} от{" "}
|
||||
{formatDate(workShift.startedAt)}
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => onShiftFinish(workShift),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack mx={"xs"}>
|
||||
<Title order={4} mt={"md"}>
|
||||
Активные смены
|
||||
</Title>
|
||||
<BaseTable
|
||||
data={activeShifts}
|
||||
columns={columns}
|
||||
restProps={
|
||||
{
|
||||
enablePagination: true,
|
||||
enableRowActions: true,
|
||||
renderRowActions: ({ row }) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon
|
||||
onClick={() =>
|
||||
onDeleteClick(row.original)
|
||||
}
|
||||
variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Завершить смену">
|
||||
<ActionIcon
|
||||
onClick={() =>
|
||||
onShiftFinishClick(row.original)
|
||||
}
|
||||
variant={"default"}>
|
||||
<IconCheck />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<ActiveWorkShiftSchema>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
29
src/pages/AdminPage/components/ActiveShiftsTable/columns.tsx
Normal file
29
src/pages/AdminPage/components/ActiveShiftsTable/columns.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { ActiveWorkShiftSchema } from "../../../../client";
|
||||
|
||||
export const useActiveShiftsTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<ActiveWorkShiftSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
header: "Начало смены",
|
||||
Cell: ({ row }) =>
|
||||
new Date(row.original.startedAt).toLocaleString("ru-RU"),
|
||||
},
|
||||
{
|
||||
header: "ФИО",
|
||||
Cell: ({ row }) =>
|
||||
`${row.original.user.firstName} ${row.original.user.secondName}`,
|
||||
},
|
||||
{
|
||||
header: "Роль",
|
||||
accessorKey: "user.role.name",
|
||||
},
|
||||
{
|
||||
header: "Должность",
|
||||
accessorKey: "user.position.name",
|
||||
}
|
||||
],
|
||||
[]
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { FC } from "react";
|
||||
import { ActionIcon, Button, Flex, rem, Text, Tooltip } from "@mantine/core";
|
||||
import { useUsersTableColumns } from "./columns.tsx";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { IconEdit, IconQrcode, IconTrash } from "@tabler/icons-react";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
|
||||
@@ -53,6 +53,13 @@ const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
|
||||
size: "md",
|
||||
});
|
||||
};
|
||||
const onGenerateQrClick = (user: UserSchema) => {
|
||||
const pdfWindow = window.open(
|
||||
`${import.meta.env.VITE_API_URL}/work-shifts/generate-qr-code/${user.id}`,
|
||||
);
|
||||
if (!pdfWindow) return;
|
||||
pdfWindow.print();
|
||||
};
|
||||
return (
|
||||
<BaseTable
|
||||
data={items}
|
||||
@@ -92,6 +99,15 @@ const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
onClick={() => {
|
||||
onGenerateQrClick(row.original);
|
||||
}}
|
||||
label="QR-код">
|
||||
<ActionIcon variant={"default"}>
|
||||
<IconQrcode />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<UserSchema>
|
||||
|
||||
92
src/pages/AdminPage/tabs/WorkShifts/WorkShiftsTab.tsx
Normal file
92
src/pages/AdminPage/tabs/WorkShifts/WorkShiftsTab.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Button, Group, Stack } from "@mantine/core";
|
||||
import { ActiveWorkShiftSchema, WorkShiftsService } from "../../../../client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { ActiveShiftsTable } from "../../components/ActiveShiftsTable/ActiveShiftsTable.tsx";
|
||||
|
||||
export const WorkShiftsTab = () => {
|
||||
let inputType: "StartShift" | "FinishShift" = "StartShift";
|
||||
const [activeShifts, setActiveShifts] = useState<ActiveWorkShiftSchema[]>([]);
|
||||
|
||||
const fetchActiveShifts = () => {
|
||||
WorkShiftsService.getActiveShifts()
|
||||
.then(res => {
|
||||
setActiveShifts(res.shifts);
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchActiveShifts();
|
||||
}, []);
|
||||
|
||||
const onInputFinish = (userIdInput: string) => {
|
||||
const userId = parseInt(userIdInput);
|
||||
if (isNaN(userId)) {
|
||||
notifications.error({ message: "Ошибка, некорректные данные в QR-коде" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputType === "StartShift") {
|
||||
WorkShiftsService.startShift({
|
||||
userId: userId!,
|
||||
})
|
||||
.then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
fetchActiveShifts();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
return;
|
||||
}
|
||||
|
||||
WorkShiftsService.finishShift({
|
||||
userId: userId!,
|
||||
})
|
||||
.then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
fetchActiveShifts();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onScanningStart = () => {
|
||||
modals.openContextModal({
|
||||
modal: "scanningModal",
|
||||
innerProps: {
|
||||
label: "Отсканируйте QR-код",
|
||||
onScan: onInputFinish,
|
||||
closeOnScan: true,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const onShiftStart = () => {
|
||||
inputType = "StartShift";
|
||||
onScanningStart();
|
||||
};
|
||||
|
||||
const onShiftFinish = () => {
|
||||
inputType = "FinishShift";
|
||||
onScanningStart();
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Group ml={"xs"} mt={"xs"}>
|
||||
<Button variant={"default"} onClick={onShiftStart}>
|
||||
Начать смену
|
||||
</Button>
|
||||
<Button variant={"default"} onClick={onShiftFinish}>
|
||||
Закончить смену
|
||||
</Button>
|
||||
</Group>
|
||||
<ActiveShiftsTable
|
||||
activeShifts={activeShifts}
|
||||
fetchActiveShifts={fetchActiveShifts}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user