diff --git a/src/client/index.ts b/src/client/index.ts index d5bdf04..c313dca 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -7,8 +7,6 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise'; export { OpenAPI } 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 { AuthLoginResponse } from './models/AuthLoginResponse'; export type { BarcodeAttributeSchema } from './models/BarcodeAttributeSchema'; @@ -187,6 +185,7 @@ export type { GetProfitTableDataResponse } from './models/GetProfitTableDataResp export type { GetServiceKitSchema } from './models/GetServiceKitSchema'; export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest'; export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse'; +export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse'; export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema'; export type { HTTPValidationError } from './models/HTTPValidationError'; export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema'; @@ -275,6 +274,7 @@ export type { UserCreate } from './models/UserCreate'; export type { UserSchema } from './models/UserSchema'; export type { UserUpdate } from './models/UserUpdate'; export type { ValidationError } from './models/ValidationError'; +export type { WorkShiftSchema } from './models/WorkShiftSchema'; export { AuthService } from './services/AuthService'; export { BarcodeService } from './services/BarcodeService'; diff --git a/src/client/models/ActiveWorkShiftsResponse.ts b/src/client/models/ActiveWorkShiftsResponse.ts deleted file mode 100644 index 37dfb85..0000000 --- a/src/client/models/ActiveWorkShiftsResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* 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; -}; - diff --git a/src/client/models/GetWorkShiftsResponse.ts b/src/client/models/GetWorkShiftsResponse.ts new file mode 100644 index 0000000..d557b26 --- /dev/null +++ b/src/client/models/GetWorkShiftsResponse.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { PaginationInfoSchema } from './PaginationInfoSchema'; +import type { WorkShiftSchema } from './WorkShiftSchema'; +export type GetWorkShiftsResponse = { + shifts: Array; + paginationInfo: PaginationInfoSchema; +}; + diff --git a/src/client/models/ActiveWorkShiftSchema.ts b/src/client/models/WorkShiftSchema.ts similarity index 72% rename from src/client/models/ActiveWorkShiftSchema.ts rename to src/client/models/WorkShiftSchema.ts index 59d3047..40ef15e 100644 --- a/src/client/models/ActiveWorkShiftSchema.ts +++ b/src/client/models/WorkShiftSchema.ts @@ -3,9 +3,11 @@ /* tslint:disable */ /* eslint-disable */ import type { UserSchema } from './UserSchema'; -export type ActiveWorkShiftSchema = { +export type WorkShiftSchema = { id: number; startedAt: string; + finishedAt?: (string | null); + hours?: (number | null); user: UserSchema; }; diff --git a/src/client/services/WorkShiftsService.ts b/src/client/services/WorkShiftsService.ts index 5be255a..6b12bd7 100644 --- a/src/client/services/WorkShiftsService.ts +++ b/src/client/services/WorkShiftsService.ts @@ -2,10 +2,10 @@ /* 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 { GetWorkShiftsResponse } from '../models/GetWorkShiftsResponse'; import type { StartShiftResponse } from '../models/StartShiftResponse'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; @@ -96,14 +96,32 @@ export class WorkShiftsService { }); } /** - * Get Active Shifts - * @returns ActiveWorkShiftsResponse Successful Response + * Get Shifts + * @returns GetWorkShiftsResponse Successful Response * @throws ApiError */ - public static getActiveShifts(): CancelablePromise { + public static getShifts({ + isActive, + page, + itemsPerPage, + }: { + isActive: boolean, + page?: (number | null), + itemsPerPage?: (number | null), + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', - url: '/work-shifts/get-active-shifts', + url: '/work-shifts/get-shifts/{is_active}', + path: { + 'is_active': isActive, + }, + query: { + 'page': page, + 'items_per_page': itemsPerPage, + }, + errors: { + 422: `Validation Error`, + }, }); } /** diff --git a/src/pages/AdminPage/components/ActiveShiftsTable/ActiveShiftsTable.tsx b/src/pages/AdminPage/components/ActiveShiftsTable/ActiveShiftsTable.tsx deleted file mode 100644 index f921bc2..0000000 --- a/src/pages/AdminPage/components/ActiveShiftsTable/ActiveShiftsTable.tsx +++ /dev/null @@ -1,116 +0,0 @@ -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: ( - - Вы уверены что хотите удалить запись о начале смены работника{" "} - {workShift.user.firstName} {workShift.user.secondName} от{" "} - {formatDate(workShift.startedAt)} - - ), - 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: ( - - Вы уверены что хотите завершить смену работника{" "} - {workShift.user.firstName} {workShift.user.secondName} от{" "} - {formatDate(workShift.startedAt)} - - ), - labels: { confirm: "Да", cancel: "Нет" }, - confirmProps: { color: "red" }, - onConfirm: () => onShiftFinish(workShift), - }); - } - - return ( - - - Активные смены - - ( - - - - onDeleteClick(row.original) - } - variant={"default"}> - - - - - - onShiftFinishClick(row.original) - } - variant={"default"}> - - - - - ), - } as MRT_TableOptions - } - /> - - ); -}; \ No newline at end of file diff --git a/src/pages/AdminPage/components/ActiveShiftsTable/columns.tsx b/src/pages/AdminPage/components/ActiveShiftsTable/columns.tsx deleted file mode 100644 index e3b9253..0000000 --- a/src/pages/AdminPage/components/ActiveShiftsTable/columns.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useMemo } from "react"; -import { MRT_ColumnDef } from "mantine-react-table"; -import { ActiveWorkShiftSchema } from "../../../../client"; - -export const useActiveShiftsTableColumns = () => { - return useMemo[]>( - () => [ - { - header: "Начало смены", - accessorKey: "startedAt", - 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", - enableSorting: false, - }, - { - header: "Должность", - accessorKey: "user.position.name", - enableSorting: false, - } - ], - [] - ); -}; diff --git a/src/pages/AdminPage/components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx b/src/pages/AdminPage/components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx new file mode 100644 index 0000000..73fd8a0 --- /dev/null +++ b/src/pages/AdminPage/components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx @@ -0,0 +1,28 @@ +import { SegmentedControl, SegmentedControlProps } from "@mantine/core"; +import { FC } from "react"; + +export enum ShiftsTableType { + ACTIVE, + HISTORY, +} + +type Props = Omit; +const data = [ + { + label: "Активные смены", + value: ShiftsTableType.ACTIVE.toString(), + }, + { + label: "Завершенные смены", + value: ShiftsTableType.HISTORY.toString(), + }, +]; + +export const ShiftsTableSegmentedControl: FC = props => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/pages/AdminPage/tabs/WorkShifts/WorkShiftsTab.tsx b/src/pages/AdminPage/tabs/WorkShifts/WorkShiftsTab.tsx index 75ab8a6..0d9a46f 100644 --- a/src/pages/AdminPage/tabs/WorkShifts/WorkShiftsTab.tsx +++ b/src/pages/AdminPage/tabs/WorkShifts/WorkShiftsTab.tsx @@ -1,91 +1,52 @@ -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"; +import { Divider, Flex, Pagination, rem, Skeleton, Stack } from "@mantine/core"; +import { ShiftsTable } from "./components/ShiftsTable.tsx"; +import { + ShiftsTableSegmentedControl, +} from "../../components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx"; +import useWorkShiftsTable from "./hooks/useWorkShiftsTable.tsx"; +import WorkShiftInput from "./components/WorkShiftInput.tsx"; export const WorkShiftsTab = () => { - let inputType: "StartShift" | "FinishShift" = "StartShift"; - const [activeShifts, setActiveShifts] = useState([]); - - 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(); - }; + const { + shifts, + shiftsTableType, + setShiftsTableType, + totalPages, + page, + setPage, + fetchShifts, + isLoading, + } = useWorkShiftsTable(); return ( - - - - - - + + + { + setPage(1); + setShiftsTableType(parseInt(event)); + }} /> + + + + {totalPages > 1 && ( + setPage(event)} + value={page} + total={totalPages} + /> + )} + + ); }; diff --git a/src/pages/AdminPage/tabs/WorkShifts/components/ShiftsTable.tsx b/src/pages/AdminPage/tabs/WorkShifts/components/ShiftsTable.tsx new file mode 100644 index 0000000..5cb48a1 --- /dev/null +++ b/src/pages/AdminPage/tabs/WorkShifts/components/ShiftsTable.tsx @@ -0,0 +1,120 @@ +import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx"; +import { useShiftsTableColumns } from "../hooks/columns.tsx"; +import { ActionIcon, Flex, Text, Tooltip } from "@mantine/core"; +import { IconCheck, IconTrash } from "@tabler/icons-react"; +import { WorkShiftSchema, 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"; +import { ShiftsTableType } from "../../../components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx"; + + +type Props = { + shifts: WorkShiftSchema[]; + fetchShifts: () => void; + shiftsTableType: ShiftsTableType; +} + +export const ShiftsTable = ({ + shifts, + fetchShifts, + shiftsTableType, + }: Props) => { + const isActiveShiftsTable = shiftsTableType === ShiftsTableType.ACTIVE; + const columns = useShiftsTableColumns({ isActiveShiftsTable }); + + const onDelete = (workShift: WorkShiftSchema) => { + WorkShiftsService.deleteWorkShift({ + shiftId: workShift.id, + }) + .then(({ ok, message }) => { + notifications.guess(ok, { message }); + fetchShifts(); + }) + .catch(err => console.log(err)); + }; + + const onDeleteClick = (workShift: WorkShiftSchema) => { + modals.openConfirmModal({ + title: "Удаление смены", + children: ( + + Вы уверены что хотите удалить смену работника{" "} + {workShift.user.firstName} {workShift.user.secondName} от{" "} + {formatDate(workShift.startedAt)} + + ), + labels: { confirm: "Да", cancel: "Нет" }, + confirmProps: { color: "red" }, + onConfirm: () => onDelete(workShift), + }); + }; + + const onShiftFinish = (workShift: WorkShiftSchema) => { + WorkShiftsService.finishWorkShiftById({ + shiftId: workShift.id, + }) + .then(({ ok, message }) => { + notifications.guess(ok, { message }); + fetchShifts(); + }) + .catch(err => console.log(err)); + }; + + const onShiftFinishClick = (workShift: WorkShiftSchema) => { + modals.openConfirmModal({ + title: "Завершение смены", + children: ( + + Вы уверены что хотите завершить смену работника{" "} + {workShift.user.firstName} {workShift.user.secondName} от{" "} + {formatDate(workShift.startedAt)} + + ), + labels: { confirm: "Да", cancel: "Нет" }, + confirmProps: { color: "red" }, + onConfirm: () => onShiftFinish(workShift), + }); + }; + + return ( + { + return ( + + + + onDeleteClick(row.original) + } + variant={"default"}> + + + + {isActiveShiftsTable && ( + + + onShiftFinishClick(row.original) + } + variant={"default"}> + + + + )} + + ); + }, + } as MRT_TableOptions + } + /> + ); +}; \ No newline at end of file diff --git a/src/pages/AdminPage/tabs/WorkShifts/components/WorkShiftInput.tsx b/src/pages/AdminPage/tabs/WorkShifts/components/WorkShiftInput.tsx new file mode 100644 index 0000000..4e3ba1a --- /dev/null +++ b/src/pages/AdminPage/tabs/WorkShifts/components/WorkShiftInput.tsx @@ -0,0 +1,26 @@ +import { Button, Group } from "@mantine/core"; +import useWorkShiftInput from "../hooks/useWorkShiftInput.tsx"; + +type Props = { + fetchShifts: () => void; +} + +const WorkShiftInput = ({ fetchShifts }: Props) => { + const { + onShiftStart, + onShiftFinish, + } = useWorkShiftInput({ fetchShifts }); + + return ( + + + + + ); +}; + +export default WorkShiftInput; diff --git a/src/pages/AdminPage/tabs/WorkShifts/hooks/columns.tsx b/src/pages/AdminPage/tabs/WorkShifts/hooks/columns.tsx new file mode 100644 index 0000000..39dce21 --- /dev/null +++ b/src/pages/AdminPage/tabs/WorkShifts/hooks/columns.tsx @@ -0,0 +1,64 @@ +import { useMemo } from "react"; +import { MRT_ColumnDef, MRT_Row } from "mantine-react-table"; +import { WorkShiftSchema } from "../../../../../client"; + + +type Props = { + isActiveShiftsTable: boolean; +} + +export const useShiftsTableColumns = ({ isActiveShiftsTable }: Props) => { + const getWorkedHoursString = (startedAtStr: string, finishedAtStr: string) => { + const finishedAt = new Date(finishedAtStr); + const startedAt = new Date(startedAtStr); + const diff: number = finishedAt.getTime() - startedAt.getTime(); + const hours = Math.floor(diff / 3_600_000); + const minutes = Math.round(diff % 3_600_000 / 60_000); + if (hours === 0) { + return `${minutes} мин.`; + } + return `${hours} ч. ${minutes} мин.`; + }; + + const getColumnsForHistory = () => { + return isActiveShiftsTable ? [] : [ + { + header: "Конец смены", + accessorKey: "finishedAt", + Cell: ({ row }: { row: MRT_Row }) => + row.original.finishedAt && new Date(row.original.finishedAt).toLocaleString("ru-RU"), + }, + { + header: "Отработано", + Cell: ({ row }: { row: MRT_Row }) => + getWorkedHoursString(row.original.startedAt, row.original.finishedAt ?? ""), + }, + ]; + }; + + return useMemo[]>( + () => [ + { + header: "ФИО", + Cell: ({ row }: { row: MRT_Row }) => + `${row.original.user.firstName} ${row.original.user.secondName}`, + }, + { + header: "Роль", + accessorKey: "user.role.name", + }, + { + header: "Должность", + accessorKey: "user.position.name", + }, + { + header: "Начало смены", + accessorKey: "startedAt", + Cell: ({ row }) => + new Date(row.original.startedAt).toLocaleString("ru-RU"), + }, + ...getColumnsForHistory(), + ], + [isActiveShiftsTable], + ); +}; diff --git a/src/pages/AdminPage/tabs/WorkShifts/hooks/useWorkShiftInput.tsx b/src/pages/AdminPage/tabs/WorkShifts/hooks/useWorkShiftInput.tsx new file mode 100644 index 0000000..bf0c8fe --- /dev/null +++ b/src/pages/AdminPage/tabs/WorkShifts/hooks/useWorkShiftInput.tsx @@ -0,0 +1,69 @@ +import { WorkShiftsService } from "../../../../../client"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; +import { modals } from "@mantine/modals"; + +type Props = { + fetchShifts: () => void; +} + +const useWorkShiftInput = ({ fetchShifts }: Props) => { + let inputType: "StartShift" | "FinishShift" = "StartShift"; + + 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 }); + fetchShifts(); + }) + .catch(err => console.log(err)); + return; + } + + WorkShiftsService.finishShift({ + userId: userId!, + }) + .then(async ({ ok, message }) => { + notifications.guess(ok, { message }); + fetchShifts(); + }) + .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 { + onShiftStart, + onShiftFinish, + }; +}; + +export default useWorkShiftInput; diff --git a/src/pages/AdminPage/tabs/WorkShifts/hooks/useWorkShiftsTable.tsx b/src/pages/AdminPage/tabs/WorkShifts/hooks/useWorkShiftsTable.tsx new file mode 100644 index 0000000..8b71f75 --- /dev/null +++ b/src/pages/AdminPage/tabs/WorkShifts/hooks/useWorkShiftsTable.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState } from "react"; +import { WorkShiftSchema, WorkShiftsService } from "../../../../../client"; +import { ShiftsTableType } from "../../../components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx"; + + +const useWorkShiftsTable = () => { + const [totalPages, setTotalPages] = useState(1); + const [page, setPage] = useState(1); + const [shifts, setShifts] = useState([]); + const [shiftsTableType, setShiftsTableType] = useState(ShiftsTableType.ACTIVE); + const [isLoading, setIsLoading] = useState(false); + + const fetchShifts = () => { + setIsLoading(true); + WorkShiftsService.getShifts({ + isActive: shiftsTableType === ShiftsTableType.ACTIVE, + page, + itemsPerPage: 10, + }) + .then(res => { + setShifts(res.shifts); + setTotalPages(res.paginationInfo.totalPages); + }) + .catch(err => console.log(err)) + .finally(() => setIsLoading(false)); + }; + + useEffect(() => { + fetchShifts(); + }, [shiftsTableType, page]); + + return { + shifts, + shiftsTableType, + setShiftsTableType, + totalPages, + page, + setPage, + fetchShifts, + isLoading, + }; +}; + +export default useWorkShiftsTable;