Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -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: (
|
||||
<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={
|
||||
{
|
||||
enableRowActions: true,
|
||||
enableSorting: true,
|
||||
enableColumnActions: false,
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { ActiveWorkShiftSchema } from "../../../../client";
|
||||
|
||||
export const useActiveShiftsTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<ActiveWorkShiftSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
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,
|
||||
}
|
||||
],
|
||||
[]
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import { FC } from "react";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
import { UserService } from "../../../../client";
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import useImageDropzone from "../../../../hooks/useImageDropzone.tsx";
|
||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
|
||||
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
userId?: number;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const ProductImageDropzone: FC<Props> = ({ imageUrlInputProps, userId }: Props) => {
|
||||
const imageDropzoneProps = useImageDropzone({
|
||||
imageUrlInputProps,
|
||||
});
|
||||
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!userId || !imageUrlInputProps) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const { setIsLoading, setShowDropzone } = imageDropzoneProps;
|
||||
const file = files[0];
|
||||
|
||||
setIsLoading(true);
|
||||
UserService.uploadPassportImage({
|
||||
userId: userId,
|
||||
formData: {
|
||||
upload_file: file,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message, imageUrl }) => {
|
||||
notifications.guess(ok, { message });
|
||||
setIsLoading(false);
|
||||
|
||||
if (!ok || !imageUrl) {
|
||||
setShowDropzone(true);
|
||||
return;
|
||||
}
|
||||
imageUrlInputProps?.onChange(imageUrl);
|
||||
setShowDropzone(false);
|
||||
})
|
||||
.catch(error => {
|
||||
notifications.error({ message: error.toString() });
|
||||
setShowDropzone(true);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageDropzone onDrop={onDrop} imageDropzone={imageDropzoneProps} />
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductImageDropzone;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||
import { FC } from "react";
|
||||
|
||||
export enum ShiftsTableType {
|
||||
ACTIVE,
|
||||
HISTORY,
|
||||
}
|
||||
|
||||
type Props = Omit<SegmentedControlProps, "data">;
|
||||
const data = [
|
||||
{
|
||||
label: "Активные смены",
|
||||
value: ShiftsTableType.ACTIVE.toString(),
|
||||
},
|
||||
{
|
||||
label: "Завершенные смены",
|
||||
value: ShiftsTableType.HISTORY.toString(),
|
||||
},
|
||||
];
|
||||
|
||||
export const ShiftsTableSegmentedControl: FC<Props> = props => {
|
||||
return (
|
||||
<SegmentedControl
|
||||
data={data}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -10,6 +10,8 @@ import { capitalize } from "lodash";
|
||||
import { IMaskInput } from "react-imask";
|
||||
import phone from "phone";
|
||||
import PayRateSelect from "../../../../components/Selects/PayRateSelect/PayRateSelect.tsx";
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import PassportImageDropzone from "../../components/PassportImageDropzone/PassportImageDropzone.tsx";
|
||||
|
||||
type Props = CreateEditFormProps<UserSchema>;
|
||||
const UserFormModal = ({
|
||||
@@ -107,6 +109,10 @@ const UserFormModal = ({
|
||||
{...form.getInputProps("phoneNumber")}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Паспортные данные"}>
|
||||
<Stack>
|
||||
<Input.Wrapper
|
||||
label={"Серия и номер паспорта"}
|
||||
error={form.getInputProps("passportData").error}>
|
||||
@@ -117,6 +123,18 @@ const UserFormModal = ({
|
||||
{...form.getInputProps("passportData")}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
{
|
||||
isEditing && (
|
||||
<PassportImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps(
|
||||
"passportImageUrl",
|
||||
) as BaseFormInputProps<string>
|
||||
}
|
||||
userId={innerProps?.element.id}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Роль и должность"}>
|
||||
|
||||
@@ -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<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();
|
||||
};
|
||||
const {
|
||||
shifts,
|
||||
shiftsTableType,
|
||||
setShiftsTableType,
|
||||
totalPages,
|
||||
page,
|
||||
setPage,
|
||||
fetchShifts,
|
||||
isLoading,
|
||||
} = useWorkShiftsTable();
|
||||
|
||||
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 gap={0}>
|
||||
<WorkShiftInput fetchShifts={fetchShifts} />
|
||||
<Divider />
|
||||
<ShiftsTableSegmentedControl
|
||||
value={shiftsTableType.toString()}
|
||||
onChange={event => {
|
||||
setPage(1);
|
||||
setShiftsTableType(parseInt(event));
|
||||
}}
|
||||
/>
|
||||
<Skeleton visible={isLoading}>
|
||||
<Flex gap={rem(10)} direction={"column"}>
|
||||
<ShiftsTable
|
||||
shiftsTableType={shiftsTableType}
|
||||
shifts={shifts}
|
||||
fetchShifts={fetchShifts}
|
||||
/>
|
||||
{totalPages > 1 && (
|
||||
<Pagination
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
withEdges
|
||||
onChange={event => setPage(event)}
|
||||
value={page}
|
||||
total={totalPages}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Skeleton>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
175
src/pages/AdminPage/tabs/WorkShifts/components/ShiftsTable.tsx
Normal file
175
src/pages/AdminPage/tabs/WorkShifts/components/ShiftsTable.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { useShiftsTableColumns } from "../hooks/columns.tsx";
|
||||
import { ActionIcon, Flex, Text, Tooltip } from "@mantine/core";
|
||||
import { IconCheck, IconPlayerPause, IconPlayerPlay, IconTrash } from "@tabler/icons-react";
|
||||
import { WorkShiftRowSchema, WorkShiftsService } from "../../../../../client";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { formatDate } from "../../../../../types/utils.ts";
|
||||
import { MRT_Row, MRT_TableOptions } from "mantine-react-table";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import { ShiftsTableType } from "../../../components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
|
||||
type Props = {
|
||||
shifts: WorkShiftRowSchema[];
|
||||
fetchShifts: () => void;
|
||||
shiftsTableType: ShiftsTableType;
|
||||
}
|
||||
|
||||
export const ShiftsTable = ({
|
||||
shifts,
|
||||
fetchShifts,
|
||||
shiftsTableType,
|
||||
}: Props) => {
|
||||
const isActiveShiftsTable = shiftsTableType === ShiftsTableType.ACTIVE;
|
||||
const columns = useShiftsTableColumns({ isActiveShiftsTable });
|
||||
|
||||
const onDelete = (workShiftRow: WorkShiftRowSchema) => {
|
||||
WorkShiftsService.deleteWorkShift({
|
||||
shiftId: workShiftRow.workShift.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
fetchShifts();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onDeleteClick = (workShiftRow: WorkShiftRowSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление смены",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите удалить смену работника{" "}
|
||||
{workShiftRow.workShift.user.firstName} {workShiftRow.workShift.user.secondName} от{" "}
|
||||
{formatDate(workShiftRow.workShift.startedAt)}
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => onDelete(workShiftRow),
|
||||
});
|
||||
};
|
||||
|
||||
const onShiftFinish = (workShiftRow: WorkShiftRowSchema) => {
|
||||
WorkShiftsService.finishWorkShiftById({
|
||||
shiftId: workShiftRow.workShift.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
fetchShifts();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onShiftFinishClick = (workShiftRow: WorkShiftRowSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Завершение смены",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите завершить смену работника{" "}
|
||||
{workShiftRow.workShift.user.firstName} {workShiftRow.workShift.user.secondName} от{" "}
|
||||
{formatDate(workShiftRow.workShift.startedAt)}
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => onShiftFinish(workShiftRow),
|
||||
});
|
||||
};
|
||||
|
||||
const onShiftPauseClick = (workShiftRow: WorkShiftRowSchema) => {
|
||||
WorkShiftsService.startPauseByShiftId({
|
||||
shiftId: workShiftRow.workShift.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
fetchShifts();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onShiftResumeClick = (workShiftRow: WorkShiftRowSchema) => {
|
||||
WorkShiftsService.finishPauseByShiftId({
|
||||
shiftId: workShiftRow.workShift.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
fetchShifts();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const getAction = (
|
||||
label: string,
|
||||
func: () => void,
|
||||
icon: ReactNode,
|
||||
) => {
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<ActionIcon
|
||||
onClick={func}
|
||||
variant={"default"}>
|
||||
{icon}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const getRowActions = (row: MRT_Row<WorkShiftRowSchema>) => {
|
||||
const actions = [
|
||||
getAction("Удалить", () => onDeleteClick(row.original), <IconTrash />),
|
||||
];
|
||||
|
||||
if (isActiveShiftsTable) {
|
||||
actions.push(
|
||||
getAction(
|
||||
"Завершить смену",
|
||||
() => onShiftFinishClick(row.original),
|
||||
<IconCheck />,
|
||||
),
|
||||
);
|
||||
if (row.original.workShift.isPaused) {
|
||||
actions.push(
|
||||
getAction(
|
||||
"Продолжить смену",
|
||||
() => onShiftResumeClick(row.original),
|
||||
<IconPlayerPlay />,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
actions.push(
|
||||
getAction(
|
||||
"Поставить смену на паузу",
|
||||
() => onShiftPauseClick(row.original),
|
||||
<IconPlayerPause />,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
data={shifts}
|
||||
columns={columns}
|
||||
restProps={
|
||||
{
|
||||
enableRowActions: true,
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
renderRowActions: ({ row }) => {
|
||||
return (
|
||||
<Flex gap="md">
|
||||
{...getRowActions(row)}
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
} as MRT_TableOptions<WorkShiftRowSchema>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Button, Group } from "@mantine/core";
|
||||
import useWorkShiftInput from "../hooks/useWorkShiftInput.tsx";
|
||||
|
||||
type Props = {
|
||||
fetchShifts: () => void;
|
||||
}
|
||||
|
||||
const WorkShiftInput = ({ fetchShifts }: Props) => {
|
||||
const {
|
||||
onShiftStart,
|
||||
onShiftFinish,
|
||||
onShiftResume,
|
||||
onShiftPause,
|
||||
} = useWorkShiftInput({ fetchShifts });
|
||||
|
||||
return (
|
||||
<Group ml={"xs"} my={"xs"}>
|
||||
<Button variant={"default"} onClick={onShiftStart}>
|
||||
Начать смену
|
||||
</Button>
|
||||
<Button variant={"default"} onClick={onShiftFinish}>
|
||||
Закончить смену
|
||||
</Button>
|
||||
<Button variant={"default"} onClick={onShiftPause}>
|
||||
Начать перерыв
|
||||
</Button>
|
||||
<Button variant={"default"} onClick={onShiftResume}>
|
||||
Закончить перерыв
|
||||
</Button>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkShiftInput;
|
||||
73
src/pages/AdminPage/tabs/WorkShifts/hooks/columns.tsx
Normal file
73
src/pages/AdminPage/tabs/WorkShifts/hooks/columns.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef, MRT_Row } from "mantine-react-table";
|
||||
import { WorkShiftRowSchema } from "../../../../../client";
|
||||
|
||||
|
||||
type Props = {
|
||||
isActiveShiftsTable: boolean;
|
||||
}
|
||||
|
||||
export const useShiftsTableColumns = ({ isActiveShiftsTable }: Props) => {
|
||||
const getWorkedHoursString = (seconds: number) => {
|
||||
const hours = Math.floor(seconds / 3_600);
|
||||
const minutes = Math.floor(seconds % 3_600 / 60);
|
||||
if (hours === 0) {
|
||||
return `${minutes} мин.`;
|
||||
}
|
||||
return `${hours} ч. ${minutes} мин.`;
|
||||
};
|
||||
|
||||
const getColumnsForHistory = () => {
|
||||
return isActiveShiftsTable ? [] : [
|
||||
{
|
||||
header: "Конец смены",
|
||||
accessorKey: "workShift.finishedAt",
|
||||
Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
|
||||
row.original.workShift.finishedAt && new Date(row.original.workShift.finishedAt).toLocaleString("ru-RU"),
|
||||
},
|
||||
{
|
||||
header: "Длительность смены",
|
||||
accessorKey: "totalHours",
|
||||
Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
|
||||
getWorkedHoursString(row.original.totalHours ?? 0),
|
||||
},
|
||||
{
|
||||
header: "Перерывы",
|
||||
accessorKey: "pauseHours",
|
||||
Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
|
||||
getWorkedHoursString(row.original.pauseHours ?? 0),
|
||||
},
|
||||
{
|
||||
header: "Отработано",
|
||||
Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
|
||||
getWorkedHoursString((row.original.totalHours ?? 0) - (row.original.pauseHours ?? 0)),
|
||||
},
|
||||
] as MRT_ColumnDef<WorkShiftRowSchema>[];
|
||||
};
|
||||
|
||||
return useMemo<MRT_ColumnDef<WorkShiftRowSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
header: "ФИО",
|
||||
Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
|
||||
`${row.original.workShift.user.firstName} ${row.original.workShift.user.secondName}`,
|
||||
},
|
||||
{
|
||||
header: "Роль",
|
||||
accessorKey: "workShift.user.role.name",
|
||||
},
|
||||
{
|
||||
header: "Должность",
|
||||
accessorKey: "workShift.user.position.name",
|
||||
},
|
||||
{
|
||||
header: "Начало смены",
|
||||
accessorKey: "workShift.startedAt",
|
||||
Cell: ({ row }) =>
|
||||
new Date(row.original.workShift.startedAt).toLocaleString("ru-RU"),
|
||||
},
|
||||
...getColumnsForHistory(),
|
||||
],
|
||||
[isActiveShiftsTable],
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import { WorkShiftsService } from "../../../../../client";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import { modals } from "@mantine/modals";
|
||||
|
||||
type Props = {
|
||||
fetchShifts: () => void;
|
||||
}
|
||||
|
||||
enum InputType {
|
||||
START_SHIFT,
|
||||
FINISH_SHIFT,
|
||||
RESUME_SHIFT,
|
||||
PAUSE_SHIFT,
|
||||
}
|
||||
|
||||
const useWorkShiftInput = ({ fetchShifts }: Props) => {
|
||||
let inputType: InputType = InputType.START_SHIFT;
|
||||
|
||||
const workShiftMethods = {
|
||||
[InputType.START_SHIFT]: WorkShiftsService.startShift,
|
||||
[InputType.FINISH_SHIFT]: WorkShiftsService.finishShift,
|
||||
[InputType.RESUME_SHIFT]: WorkShiftsService.finishPauseByUserId,
|
||||
[InputType.PAUSE_SHIFT]: WorkShiftsService.startPauseByUserId,
|
||||
};
|
||||
|
||||
const onInputFinish = (userIdInput: string) => {
|
||||
const userId = parseInt(userIdInput);
|
||||
if (isNaN(userId)) {
|
||||
notifications.error({ message: "Ошибка, некорректные данные в QR-коде" });
|
||||
return;
|
||||
}
|
||||
|
||||
workShiftMethods[inputType]({ 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 = InputType.START_SHIFT;
|
||||
onScanningStart();
|
||||
};
|
||||
|
||||
const onShiftFinish = () => {
|
||||
inputType = InputType.FINISH_SHIFT;
|
||||
onScanningStart();
|
||||
};
|
||||
|
||||
const onShiftResume = () => {
|
||||
inputType = InputType.RESUME_SHIFT;
|
||||
onScanningStart();
|
||||
};
|
||||
|
||||
const onShiftPause = () => {
|
||||
inputType = InputType.PAUSE_SHIFT;
|
||||
onScanningStart();
|
||||
};
|
||||
|
||||
return {
|
||||
onShiftStart,
|
||||
onShiftFinish,
|
||||
onShiftResume,
|
||||
onShiftPause,
|
||||
};
|
||||
};
|
||||
|
||||
export default useWorkShiftInput;
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { WorkShiftRowSchema, 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<WorkShiftRowSchema[]>([]);
|
||||
const [shiftsTableType, setShiftsTableType] = useState<ShiftsTableType>(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;
|
||||
@@ -0,0 +1,60 @@
|
||||
import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import { FC } from "react";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
import { ProductService } from "../../../../client";
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import useImageDropzone from "../../../../hooks/useImageDropzone.tsx";
|
||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
|
||||
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
productId?: number;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const ProductImageDropzone: FC<Props> = ({ imageUrlInputProps, productId }: Props) => {
|
||||
const imageDropzoneProps = useImageDropzone({
|
||||
imageUrlInputProps,
|
||||
});
|
||||
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!productId || !imageUrlInputProps) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const { setIsLoading, setShowDropzone } = imageDropzoneProps;
|
||||
const file = files[0];
|
||||
|
||||
setIsLoading(true);
|
||||
ProductService.uploadProductImage({
|
||||
productId,
|
||||
formData: {
|
||||
upload_file: file,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message, imageUrl }) => {
|
||||
notifications.guess(ok, { message });
|
||||
setIsLoading(false);
|
||||
|
||||
if (!ok || !imageUrl) {
|
||||
setShowDropzone(true);
|
||||
return;
|
||||
}
|
||||
imageUrlInputProps?.onChange(imageUrl);
|
||||
setShowDropzone(false);
|
||||
})
|
||||
.catch(error => {
|
||||
notifications.error({ message: error.toString() });
|
||||
setShowDropzone(true);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageDropzone onDrop={onDrop} imageDropzone={imageDropzoneProps} />
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductImageDropzone;
|
||||
@@ -4,9 +4,9 @@ import { useForm } from "@mantine/form";
|
||||
import { BaseProduct, CreateProductRequest } from "../../types.ts";
|
||||
import { ProductSchema } from "../../../../client";
|
||||
import BarcodeTemplateSelect from "../../../../components/Selects/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx";
|
||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import BarcodeImageDropzone from "../../../../components/BarcodeImageDropzone/BarcodeImageDropzone.tsx";
|
||||
import ProductImageDropzone from "../../components/ProductImageDropzone/ProductImageDropzone.tsx";
|
||||
|
||||
type CreateProps = {
|
||||
clientId: number;
|
||||
@@ -117,7 +117,7 @@ const CreateProductModal = ({
|
||||
isEditProps && (
|
||||
// <Fieldset legend={"Изображение"}>
|
||||
<>
|
||||
<ImageDropzone
|
||||
<ProductImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps(
|
||||
"imageUrl",
|
||||
|
||||
Reference in New Issue
Block a user