feat: work shifts by QR codes
This commit is contained in:
@@ -7,6 +7,8 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise';
|
|||||||
export { OpenAPI } from './core/OpenAPI';
|
export { OpenAPI } from './core/OpenAPI';
|
||||||
export type { OpenAPIConfig } from './core/OpenAPI';
|
export type { OpenAPIConfig } from './core/OpenAPI';
|
||||||
|
|
||||||
|
export type { ActiveWorkShiftSchema } from './models/ActiveWorkShiftSchema';
|
||||||
|
export type { ActiveWorkShiftsResponse } from './models/ActiveWorkShiftsResponse';
|
||||||
export type { AuthLoginRequest } from './models/AuthLoginRequest';
|
export type { AuthLoginRequest } from './models/AuthLoginRequest';
|
||||||
export type { AuthLoginResponse } from './models/AuthLoginResponse';
|
export type { AuthLoginResponse } from './models/AuthLoginResponse';
|
||||||
export type { BarcodeAttributeSchema } from './models/BarcodeAttributeSchema';
|
export type { BarcodeAttributeSchema } from './models/BarcodeAttributeSchema';
|
||||||
@@ -140,8 +142,11 @@ export type { DeletePositionRequest } from './models/DeletePositionRequest';
|
|||||||
export type { DeletePositionResponse } from './models/DeletePositionResponse';
|
export type { DeletePositionResponse } from './models/DeletePositionResponse';
|
||||||
export type { DeletePriceCategoryRequest } from './models/DeletePriceCategoryRequest';
|
export type { DeletePriceCategoryRequest } from './models/DeletePriceCategoryRequest';
|
||||||
export type { DeletePriceCategoryResponse } from './models/DeletePriceCategoryResponse';
|
export type { DeletePriceCategoryResponse } from './models/DeletePriceCategoryResponse';
|
||||||
|
export type { DeleteShiftResponse } from './models/DeleteShiftResponse';
|
||||||
export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest';
|
export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest';
|
||||||
export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse';
|
export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse';
|
||||||
|
export type { FinishShiftByIdResponse } from './models/FinishShiftByIdResponse';
|
||||||
|
export type { FinishShiftResponse } from './models/FinishShiftResponse';
|
||||||
export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse';
|
export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse';
|
||||||
export type { GetAllBarcodeTemplateSizesResponse } from './models/GetAllBarcodeTemplateSizesResponse';
|
export type { GetAllBarcodeTemplateSizesResponse } from './models/GetAllBarcodeTemplateSizesResponse';
|
||||||
export type { GetAllBarcodeTemplatesResponse } from './models/GetAllBarcodeTemplatesResponse';
|
export type { GetAllBarcodeTemplatesResponse } from './models/GetAllBarcodeTemplatesResponse';
|
||||||
@@ -225,6 +230,7 @@ export type { ServiceUpdateCategoryResponse } from './models/ServiceUpdateCatego
|
|||||||
export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest';
|
export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest';
|
||||||
export type { ServiceUpdateResponse } from './models/ServiceUpdateResponse';
|
export type { ServiceUpdateResponse } from './models/ServiceUpdateResponse';
|
||||||
export type { ShippingWarehouseSchema } from './models/ShippingWarehouseSchema';
|
export type { ShippingWarehouseSchema } from './models/ShippingWarehouseSchema';
|
||||||
|
export type { StartShiftResponse } from './models/StartShiftResponse';
|
||||||
export type { SynchronizeMarketplaceRequest } from './models/SynchronizeMarketplaceRequest';
|
export type { SynchronizeMarketplaceRequest } from './models/SynchronizeMarketplaceRequest';
|
||||||
export type { TaskInfoResponse } from './models/TaskInfoResponse';
|
export type { TaskInfoResponse } from './models/TaskInfoResponse';
|
||||||
export type { TimeTrackingData } from './models/TimeTrackingData';
|
export type { TimeTrackingData } from './models/TimeTrackingData';
|
||||||
@@ -264,3 +270,4 @@ export { ShippingWarehouseService } from './services/ShippingWarehouseService';
|
|||||||
export { TaskService } from './services/TaskService';
|
export { TaskService } from './services/TaskService';
|
||||||
export { TimeTrackingService } from './services/TimeTrackingService';
|
export { TimeTrackingService } from './services/TimeTrackingService';
|
||||||
export { UserService } from './services/UserService';
|
export { UserService } from './services/UserService';
|
||||||
|
export { WorkShiftsService } from './services/WorkShiftsService';
|
||||||
|
|||||||
11
src/client/models/ActiveWorkShiftSchema.ts
Normal file
11
src/client/models/ActiveWorkShiftSchema.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { UserSchema } from './UserSchema';
|
||||||
|
export type ActiveWorkShiftSchema = {
|
||||||
|
id: number;
|
||||||
|
startedAt: string;
|
||||||
|
user: UserSchema;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/ActiveWorkShiftsResponse.ts
Normal file
9
src/client/models/ActiveWorkShiftsResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ActiveWorkShiftSchema } from './ActiveWorkShiftSchema';
|
||||||
|
export type ActiveWorkShiftsResponse = {
|
||||||
|
shifts: Array<ActiveWorkShiftSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DeleteShiftResponse.ts
Normal file
9
src/client/models/DeleteShiftResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DeleteShiftResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/FinishShiftByIdResponse.ts
Normal file
9
src/client/models/FinishShiftByIdResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type FinishShiftByIdResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/FinishShiftResponse.ts
Normal file
9
src/client/models/FinishShiftResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type FinishShiftResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/StartShiftResponse.ts
Normal file
9
src/client/models/StartShiftResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type StartShiftResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
130
src/client/services/WorkShiftsService.ts
Normal file
130
src/client/services/WorkShiftsService.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ActiveWorkShiftsResponse } from '../models/ActiveWorkShiftsResponse';
|
||||||
|
import type { DeleteShiftResponse } from '../models/DeleteShiftResponse';
|
||||||
|
import type { FinishShiftByIdResponse } from '../models/FinishShiftByIdResponse';
|
||||||
|
import type { FinishShiftResponse } from '../models/FinishShiftResponse';
|
||||||
|
import type { StartShiftResponse } from '../models/StartShiftResponse';
|
||||||
|
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||||
|
import { OpenAPI } from '../core/OpenAPI';
|
||||||
|
import { request as __request } from '../core/request';
|
||||||
|
export class WorkShiftsService {
|
||||||
|
/**
|
||||||
|
* Generate Qr Code
|
||||||
|
* @returns any Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static generateQrCode({
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
userId: number,
|
||||||
|
}): CancelablePromise<any> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/work-shifts/generate-qr-code/{user_id}',
|
||||||
|
path: {
|
||||||
|
'user_id': userId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Start Shift
|
||||||
|
* @returns StartShiftResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static startShift({
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
userId: number,
|
||||||
|
}): CancelablePromise<StartShiftResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/work-shifts/start-shift/{user_id}',
|
||||||
|
path: {
|
||||||
|
'user_id': userId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Finish Shift
|
||||||
|
* @returns FinishShiftResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static finishShift({
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
userId: number,
|
||||||
|
}): CancelablePromise<FinishShiftResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/work-shifts/finish-shift/{user_id}',
|
||||||
|
path: {
|
||||||
|
'user_id': userId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Finish Work Shift By Id
|
||||||
|
* @returns FinishShiftByIdResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static finishWorkShiftById({
|
||||||
|
shiftId,
|
||||||
|
}: {
|
||||||
|
shiftId: number,
|
||||||
|
}): CancelablePromise<FinishShiftByIdResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/work-shifts/finish-shift-by-id/{shift_id}',
|
||||||
|
path: {
|
||||||
|
'shift_id': shiftId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Active Shifts
|
||||||
|
* @returns ActiveWorkShiftsResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getActiveShifts(): CancelablePromise<ActiveWorkShiftsResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/work-shifts/get-active-shifts',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Delete Work Shift
|
||||||
|
* @returns DeleteShiftResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deleteWorkShift({
|
||||||
|
shiftId,
|
||||||
|
}: {
|
||||||
|
shiftId: number,
|
||||||
|
}): CancelablePromise<DeleteShiftResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/work-shifts/delete-shift/{shift_id}',
|
||||||
|
path: {
|
||||||
|
'shift_id': shiftId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/modals/ScanningModal/ScanningModal.tsx
Normal file
38
src/modals/ScanningModal/ScanningModal.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import { Text } from "@mantine/core";
|
||||||
|
import { useWindowEvent } from "@mantine/hooks";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string;
|
||||||
|
onScan: (value: string) => void;
|
||||||
|
closeOnScan?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ScanningModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const [inputValues, setInputValues] = useState<string>("");
|
||||||
|
|
||||||
|
const { label, onScan, closeOnScan } = innerProps;
|
||||||
|
|
||||||
|
useWindowEvent("keydown", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log(inputValues);
|
||||||
|
setInputValues(prevState => prevState + event.key);
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
onScan(inputValues);
|
||||||
|
if (closeOnScan) {
|
||||||
|
context.closeContextModal(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text>{label}</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScanningModal;
|
||||||
@@ -21,6 +21,7 @@ import SelectDealProductsModal from "../pages/LeadsPage/modals/SelectDealProduct
|
|||||||
import ShippingWarehouseForm from "../pages/ShippingWarehousesPage/modals/ShippingWarehouseForm.tsx";
|
import ShippingWarehouseForm from "../pages/ShippingWarehousesPage/modals/ShippingWarehouseForm.tsx";
|
||||||
import MarketplaceFormModal from "../pages/MarketplacesPage/modals/MarketplaceFormModal/MarketplaceFormModal.tsx";
|
import MarketplaceFormModal from "../pages/MarketplacesPage/modals/MarketplaceFormModal/MarketplaceFormModal.tsx";
|
||||||
import ServicePriceCategoryForm from "../pages/ServicesPage/modals/ServicePriceCategoryForm.tsx";
|
import ServicePriceCategoryForm from "../pages/ServicesPage/modals/ServicePriceCategoryForm.tsx";
|
||||||
|
import ScanningModal from "./ScanningModal/ScanningModal.tsx";
|
||||||
|
|
||||||
export const modals = {
|
export const modals = {
|
||||||
enterDeadline: EnterDeadlineModal,
|
enterDeadline: EnterDeadlineModal,
|
||||||
@@ -46,4 +47,5 @@ export const modals = {
|
|||||||
shippingWarehouseForm: ShippingWarehouseForm,
|
shippingWarehouseForm: ShippingWarehouseForm,
|
||||||
marketplaceFormModal: MarketplaceFormModal,
|
marketplaceFormModal: MarketplaceFormModal,
|
||||||
servicePriceCategoryForm: ServicePriceCategoryForm,
|
servicePriceCategoryForm: ServicePriceCategoryForm,
|
||||||
|
scanningModal: ScanningModal,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
|||||||
import {
|
import {
|
||||||
IconBriefcase,
|
IconBriefcase,
|
||||||
IconCalendarUser,
|
IconCalendarUser,
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar, IconQrcode,
|
||||||
IconUser,
|
IconUser,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import RolesAndPositionsTab from "./tabs/RolesAndPositions/RolesAndPositionsTab.tsx";
|
import RolesAndPositionsTab from "./tabs/RolesAndPositions/RolesAndPositionsTab.tsx";
|
||||||
@@ -12,6 +12,7 @@ import UsersTab from "./tabs/Users/UsersTab.tsx";
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import FinancesTab from "./tabs/Finances/FinancesTab.tsx";
|
import FinancesTab from "./tabs/Finances/FinancesTab.tsx";
|
||||||
import WorkTimeTable from "./tabs/WorkTimeTable/ui/WorkTimeTable.tsx";
|
import WorkTimeTable from "./tabs/WorkTimeTable/ui/WorkTimeTable.tsx";
|
||||||
|
import { WorkShiftsTab } from "./tabs/WorkShifts/WorkShiftsTab.tsx";
|
||||||
|
|
||||||
const AdminPage = () => {
|
const AdminPage = () => {
|
||||||
return (
|
return (
|
||||||
@@ -42,6 +43,11 @@ const AdminPage = () => {
|
|||||||
leftSection={<IconCalendarUser />}>
|
leftSection={<IconCalendarUser />}>
|
||||||
Рабочее время
|
Рабочее время
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value={"workShifts"}
|
||||||
|
leftSection={<IconQrcode />}>
|
||||||
|
Смены
|
||||||
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Panel value={"users"}>
|
<Tabs.Panel value={"users"}>
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -75,6 +81,14 @@ const AdminPage = () => {
|
|||||||
<WorkTimeTable />
|
<WorkTimeTable />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
<Tabs.Panel value={"workShifts"}>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.2 }}>
|
||||||
|
<WorkShiftsTab />
|
||||||
|
</motion.div>
|
||||||
|
</Tabs.Panel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PageBlock>
|
</PageBlock>
|
||||||
</div>
|
</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 { FC } from "react";
|
||||||
import { ActionIcon, Button, Flex, rem, Text, Tooltip } from "@mantine/core";
|
import { ActionIcon, Button, Flex, rem, Text, Tooltip } from "@mantine/core";
|
||||||
import { useUsersTableColumns } from "./columns.tsx";
|
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 { modals } from "@mantine/modals";
|
||||||
import { MRT_TableOptions } from "mantine-react-table";
|
import { MRT_TableOptions } from "mantine-react-table";
|
||||||
|
|
||||||
@@ -53,6 +53,13 @@ const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
|
|||||||
size: "md",
|
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 (
|
return (
|
||||||
<BaseTable
|
<BaseTable
|
||||||
data={items}
|
data={items}
|
||||||
@@ -92,6 +99,15 @@ const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
|
|||||||
<IconTrash />
|
<IconTrash />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
onClick={() => {
|
||||||
|
onGenerateQrClick(row.original);
|
||||||
|
}}
|
||||||
|
label="QR-код">
|
||||||
|
<ActionIcon variant={"default"}>
|
||||||
|
<IconQrcode />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
),
|
),
|
||||||
} as MRT_TableOptions<UserSchema>
|
} 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