feat: work shifts planning
This commit is contained in:
@@ -220,6 +220,7 @@ export type { GetDepartmentSectionsResponse } from './models/GetDepartmentSectio
|
||||
export type { GetDepartmentsResponse } from './models/GetDepartmentsResponse';
|
||||
export type { GetManagersResponse } from './models/GetManagersResponse';
|
||||
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
||||
export type { GetPlannedWorkShiftsResponse } from './models/GetPlannedWorkShiftsResponse';
|
||||
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
||||
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
|
||||
export type { GetProductBarcodeRequest } from './models/GetProductBarcodeRequest';
|
||||
@@ -234,6 +235,7 @@ export type { GetServiceKitSchema } from './models/GetServiceKitSchema';
|
||||
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
|
||||
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
|
||||
export type { GetTransactionTagsResponse } from './models/GetTransactionTagsResponse';
|
||||
export type { GetWorkShiftsPlanningDataRequest } from './models/GetWorkShiftsPlanningDataRequest';
|
||||
export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse';
|
||||
export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema';
|
||||
export type { HTTPValidationError } from './models/HTTPValidationError';
|
||||
@@ -257,6 +259,8 @@ export type { PayRateSchema } from './models/PayRateSchema';
|
||||
export type { PayRateSchemaBase } from './models/PayRateSchemaBase';
|
||||
export type { PayrollSchemeSchema } from './models/PayrollSchemeSchema';
|
||||
export type { PermissionSchema } from './models/PermissionSchema';
|
||||
export type { PlannedWorkShiftSchema } from './models/PlannedWorkShiftSchema';
|
||||
export type { PlanningTableRow } from './models/PlanningTableRow';
|
||||
export type { PositionSchema } from './models/PositionSchema';
|
||||
export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest';
|
||||
export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse';
|
||||
@@ -332,6 +336,8 @@ export type { UpdateMarketplaceRequest } from './models/UpdateMarketplaceRequest
|
||||
export type { UpdateMarketplaceResponse } from './models/UpdateMarketplaceResponse';
|
||||
export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest';
|
||||
export type { UpdatePayRateResponse } from './models/UpdatePayRateResponse';
|
||||
export type { UpdatePlanningWorkShiftRequest } from './models/UpdatePlanningWorkShiftRequest';
|
||||
export type { UpdatePlanningWorkShiftResponse } from './models/UpdatePlanningWorkShiftResponse';
|
||||
export type { UpdatePriceCategoryRequest } from './models/UpdatePriceCategoryRequest';
|
||||
export type { UpdatePriceCategoryResponse } from './models/UpdatePriceCategoryResponse';
|
||||
export type { UpdateResidualProductRequest } from './models/UpdateResidualProductRequest';
|
||||
@@ -387,3 +393,4 @@ export { TimeTrackingService } from './services/TimeTrackingService';
|
||||
export { TransactionService } from './services/TransactionService';
|
||||
export { UserService } from './services/UserService';
|
||||
export { WorkShiftsService } from './services/WorkShiftsService';
|
||||
export { WorkShiftsPlanningService } from './services/WorkShiftsPlanningService';
|
||||
|
||||
9
src/client/models/GetPlannedWorkShiftsResponse.ts
Normal file
9
src/client/models/GetPlannedWorkShiftsResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PlanningTableRow } from './PlanningTableRow';
|
||||
export type GetPlannedWorkShiftsResponse = {
|
||||
shifts: Array<PlanningTableRow>;
|
||||
};
|
||||
|
||||
9
src/client/models/GetWorkShiftsPlanningDataRequest.ts
Normal file
9
src/client/models/GetWorkShiftsPlanningDataRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type GetWorkShiftsPlanningDataRequest = {
|
||||
dateFrom: string;
|
||||
dateTo: string;
|
||||
};
|
||||
|
||||
11
src/client/models/PlannedWorkShiftSchema.ts
Normal file
11
src/client/models/PlannedWorkShiftSchema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PositionSchema } from './PositionSchema';
|
||||
export type PlannedWorkShiftSchema = {
|
||||
id: number;
|
||||
shiftDate: string;
|
||||
positions: Array<PositionSchema>;
|
||||
};
|
||||
|
||||
11
src/client/models/PlanningTableRow.ts
Normal file
11
src/client/models/PlanningTableRow.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PlannedWorkShiftSchema } from './PlannedWorkShiftSchema';
|
||||
import type { UserSchema } from './UserSchema';
|
||||
export type PlanningTableRow = {
|
||||
user: UserSchema;
|
||||
shifts: Array<PlannedWorkShiftSchema>;
|
||||
};
|
||||
|
||||
10
src/client/models/UpdatePlanningWorkShiftRequest.ts
Normal file
10
src/client/models/UpdatePlanningWorkShiftRequest.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type UpdatePlanningWorkShiftRequest = {
|
||||
shiftDate: string;
|
||||
positionKeys: Array<string>;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
9
src/client/models/UpdatePlanningWorkShiftResponse.ts
Normal file
9
src/client/models/UpdatePlanningWorkShiftResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type UpdatePlanningWorkShiftResponse = {
|
||||
ok: boolean;
|
||||
message: string;
|
||||
};
|
||||
|
||||
53
src/client/services/WorkShiftsPlanningService.ts
Normal file
53
src/client/services/WorkShiftsPlanningService.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { GetPlannedWorkShiftsResponse } from '../models/GetPlannedWorkShiftsResponse';
|
||||
import type { GetWorkShiftsPlanningDataRequest } from '../models/GetWorkShiftsPlanningDataRequest';
|
||||
import type { UpdatePlanningWorkShiftRequest } from '../models/UpdatePlanningWorkShiftRequest';
|
||||
import type { UpdatePlanningWorkShiftResponse } from '../models/UpdatePlanningWorkShiftResponse';
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import { OpenAPI } from '../core/OpenAPI';
|
||||
import { request as __request } from '../core/request';
|
||||
export class WorkShiftsPlanningService {
|
||||
/**
|
||||
* Get Work Shifts
|
||||
* @returns GetPlannedWorkShiftsResponse Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getWorkShifts({
|
||||
requestBody,
|
||||
}: {
|
||||
requestBody: GetWorkShiftsPlanningDataRequest,
|
||||
}): CancelablePromise<GetPlannedWorkShiftsResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/work-shifts-planning/',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Update Work Shift
|
||||
* @returns UpdatePlanningWorkShiftResponse Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static updateWorkShift({
|
||||
requestBody,
|
||||
}: {
|
||||
requestBody: UpdatePlanningWorkShiftRequest,
|
||||
}): CancelablePromise<UpdatePlanningWorkShiftResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/work-shifts-planning/update',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@ import styles from "./AdminPage.module.css";
|
||||
import { Tabs } from "@mantine/core";
|
||||
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
||||
import {
|
||||
IconTopologyStar3,
|
||||
IconCalendarEvent,
|
||||
IconCalendarUser,
|
||||
IconCoins,
|
||||
IconCurrencyDollar,
|
||||
IconQrcode,
|
||||
IconTopologyStar3,
|
||||
IconUser,
|
||||
} from "@tabler/icons-react";
|
||||
import UsersTab from "./tabs/Users/UsersTab.tsx";
|
||||
@@ -18,11 +19,26 @@ import { TransactionsTab } from "./tabs/Transactions/TransactionsTab.tsx";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../redux/store.ts";
|
||||
import OrganizationalStructureTab from "./tabs/OrganizationalStructureTab/OrganizationalStructureTab.tsx";
|
||||
import { ReactNode } from "react";
|
||||
import WorkShiftsPlanning from "./tabs/WorkShiftsPlanning/WorkShiftsPlanning.tsx";
|
||||
|
||||
const AdminPage = () => {
|
||||
const userRole = useSelector((state: RootState) => state.auth.role);
|
||||
const isAdmin = userRole === "admin";
|
||||
|
||||
const getTabPanel = (label: string, content: ReactNode) => {
|
||||
return (
|
||||
<Tabs.Panel value={label}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
{content}
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles["container"]}>
|
||||
<PageBlock fullHeight>
|
||||
@@ -55,6 +71,11 @@ const AdminPage = () => {
|
||||
Рабочее время
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
<Tabs.Tab
|
||||
value={"workShiftsPlanning"}
|
||||
leftSection={<IconCalendarEvent />}>
|
||||
Планирование смен
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value={"workShifts"}
|
||||
leftSection={<IconQrcode />}>
|
||||
@@ -68,54 +89,13 @@ const AdminPage = () => {
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"users"}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<UsersTab />
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"rolesAndPositions"}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<OrganizationalStructureTab />
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"finances"}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<FinancesTab />
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"workTimeTable"}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<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.Panel value={"transactions"}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<TransactionsTab />
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
{getTabPanel("users", <UsersTab />)}
|
||||
{getTabPanel("rolesAndPositions", <OrganizationalStructureTab />)}
|
||||
{getTabPanel("finances", <FinancesTab />)}
|
||||
{getTabPanel("workTimeTable", <WorkTimeTable />)}
|
||||
{getTabPanel("workShiftsPlanning", <WorkShiftsPlanning />)}
|
||||
{getTabPanel("workShifts", <WorkShiftsTab />)}
|
||||
{getTabPanel("transactions", <TransactionsTab />)}
|
||||
</Tabs>
|
||||
</PageBlock>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { MultiSelect } from "@mantine/core";
|
||||
import { UserSchema } from "../../../../client";
|
||||
|
||||
|
||||
type Props = {
|
||||
shownUsers: UserSchema[];
|
||||
setShownUsers: (shownUsersProp: UserSchema[]) => void;
|
||||
users: UserSchema[];
|
||||
}
|
||||
|
||||
const ShownUsersMultiSelect = ({
|
||||
shownUsers,
|
||||
setShownUsers,
|
||||
users,
|
||||
}: Props) => {
|
||||
return (
|
||||
<MultiSelect
|
||||
data={users.map(user => ({
|
||||
label: `${user.firstName} ${user.secondName}`,
|
||||
value: user.id.toString(),
|
||||
}))}
|
||||
onChange={event =>
|
||||
setShownUsers(
|
||||
users.filter(user =>
|
||||
event.includes(user.id.toString()),
|
||||
),
|
||||
)
|
||||
}
|
||||
value={shownUsers.map(user => user.id.toString())}
|
||||
placeholder={
|
||||
shownUsers.length > 0 ? "" : "Показанные пользователи"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShownUsersMultiSelect;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { WorkShiftsPlanningContextProvider } from "./contexts/WorkShiftsPlanningContext.tsx";
|
||||
import WorkShiftsPlanningTable from "./components/WorkShiftsPlanningTable/WorkShiftsPlanningTable.tsx";
|
||||
import { rem, Stack } from "@mantine/core";
|
||||
import WorkShiftsPlanningHeader from "./components/WorkShiftsPlanningHeader/WorkShiftsPlanningHeader.tsx";
|
||||
|
||||
const WorkShiftsPlanning = () => {
|
||||
return (
|
||||
<WorkShiftsPlanningContextProvider>
|
||||
<Stack p={rem(10)} gap={rem(10)}>
|
||||
<WorkShiftsPlanningHeader />
|
||||
<WorkShiftsPlanningTable />
|
||||
</Stack>
|
||||
</WorkShiftsPlanningContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkShiftsPlanning;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { MultiSelect, rem } from "@mantine/core";
|
||||
import { PositionSchema, UserSchema } from "../../../../../../client";
|
||||
import { useWorkShiftsPlanningContext } from "../../contexts/WorkShiftsPlanningContext.tsx";
|
||||
|
||||
|
||||
type Props = {
|
||||
date: Date;
|
||||
positions?: PositionSchema[];
|
||||
user: UserSchema;
|
||||
}
|
||||
|
||||
const PositionsMultiSelect = ({ date, positions, user }: Props) => {
|
||||
const {
|
||||
onUpdate,
|
||||
userPositions,
|
||||
} = useWorkShiftsPlanningContext();
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
data={userPositions.get(user.id)?.map(
|
||||
position => ({
|
||||
value: position.key,
|
||||
label: position.name,
|
||||
}),
|
||||
)}
|
||||
|
||||
onChange={values => {
|
||||
onUpdate(user.id, date, values);
|
||||
}}
|
||||
|
||||
value={positions?.map(p => p.key) ?? []}
|
||||
|
||||
miw={rem(140)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PositionsMultiSelect;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Flex, rem } from "@mantine/core";
|
||||
import ShownUsersMultiSelect from "../../../../components/ShownUsersMultiselect/ShownUsersMultiSelect.tsx";
|
||||
import { DatePickerInput } from "@mantine/dates";
|
||||
import { useWorkShiftsPlanningContext } from "../../contexts/WorkShiftsPlanningContext.tsx";
|
||||
|
||||
const WorkShiftsPlanningHeader = () => {
|
||||
const {
|
||||
dateRange,
|
||||
setDateRange,
|
||||
users,
|
||||
shownUsers,
|
||||
setShownUsers,
|
||||
} = useWorkShiftsPlanningContext();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align={"center"}
|
||||
justify={"space-between"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
<ShownUsersMultiSelect
|
||||
shownUsers={shownUsers}
|
||||
setShownUsers={setShownUsers}
|
||||
users={users}
|
||||
/>
|
||||
<DatePickerInput
|
||||
placeholder={"Выберите временной промежуток"}
|
||||
type={"range"}
|
||||
value={dateRange}
|
||||
onChange={(value) => {
|
||||
setDateRange(value);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkShiftsPlanningHeader;
|
||||
@@ -0,0 +1,50 @@
|
||||
import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { useWorkShiftsPlanningContext } from "../../contexts/WorkShiftsPlanningContext.tsx";
|
||||
import useWorkShiftsPlanningColumns from "../../hooks/useWorkShiftsPlanningColumns.tsx";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { WorkShiftPlanningRow } from "../../types/WorkShiftPlanningRow.tsx";
|
||||
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||
import { IconEyeOff } from "@tabler/icons-react";
|
||||
|
||||
const WorkShiftsPlanningTable = () => {
|
||||
const {
|
||||
tableRows,
|
||||
shownUsers,
|
||||
setShownUsers,
|
||||
} = useWorkShiftsPlanningContext();
|
||||
|
||||
const columns = useWorkShiftsPlanningColumns();
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
data={tableRows}
|
||||
columns={columns}
|
||||
restProps={
|
||||
{
|
||||
enableColumnActions: false,
|
||||
enableSorting: false,
|
||||
enableRowActions: true,
|
||||
renderRowActions: ({ row }) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Скрыть">
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
setShownUsers(
|
||||
shownUsers.filter(
|
||||
user => user.id !== row.original.user.id,
|
||||
),
|
||||
);
|
||||
}}
|
||||
variant={"default"}>
|
||||
<IconEyeOff />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<WorkShiftPlanningRow>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkShiftsPlanningTable;
|
||||
@@ -0,0 +1,176 @@
|
||||
import React, { createContext, FC, useContext, useEffect, useState } from "react";
|
||||
import { PlanningTableRow, PositionSchema, UserSchema, WorkShiftsPlanningService } from "../../../../../client";
|
||||
import { useListState } from "@mantine/hooks";
|
||||
import { dateToString } from "../../../../../types/utils.ts";
|
||||
import { getDefaultEndDate } from "../utils/utils.tsx";
|
||||
import { WorkShiftPlanningRow } from "../types/WorkShiftPlanningRow.tsx";
|
||||
import useUsersList from "../../../hooks/useUsersList.tsx";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import { getDatesBetween } from "../../../../../shared/lib/date.ts";
|
||||
|
||||
type WorkShiftsPlanningContextState = {
|
||||
dateRange: [Date | null, Date | null];
|
||||
setDateRange: React.Dispatch<React.SetStateAction<[Date | null, Date | null]>>;
|
||||
tableRows: WorkShiftPlanningRow[];
|
||||
onUpdate: (userId: number, date: Date, positions: string[]) => void;
|
||||
users: UserSchema[];
|
||||
shownUsers: UserSchema[];
|
||||
setShownUsers: (showUsersProp: UserSchema[]) => void;
|
||||
userPositions: Map<number, PositionSchema[]>;
|
||||
range: Date[];
|
||||
};
|
||||
|
||||
const WorkShiftsPlanningContext = createContext<WorkShiftsPlanningContextState | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const useWorkShiftsPlanningContextState = () => {
|
||||
const [tableRows, tableRowsHandlers] = useListState<WorkShiftPlanningRow>([]);
|
||||
const [filteredTableRows, filteredTableRowsHandlers] = useListState<WorkShiftPlanningRow>([]);
|
||||
const [dateRange, setDateRange] = useState<
|
||||
[Date | null, Date | null]
|
||||
>([new Date(), getDefaultEndDate()]);
|
||||
|
||||
const { objects: users } = useUsersList();
|
||||
const [shownUsers, shownUsersHandlers] = useListState<UserSchema>([]);
|
||||
|
||||
const userPositions = new Map<number, PositionSchema[]>(
|
||||
users.map(user => [user.id, user.position ? [user.position] : []]),
|
||||
);
|
||||
|
||||
const filterRows = (showUsersProp: UserSchema[], rows?: WorkShiftPlanningRow[]) => {
|
||||
shownUsersHandlers.setState(showUsersProp);
|
||||
if (!rows) {
|
||||
rows = tableRows;
|
||||
}
|
||||
filteredTableRowsHandlers.setState(
|
||||
rows.filter(row => {
|
||||
return showUsersProp.findIndex(user => user.id === row.user.id) !== -1;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setShownUsers = (showUsersProp: UserSchema[]) => {
|
||||
filterRows(showUsersProp);
|
||||
};
|
||||
|
||||
const processShifts = (shifts: PlanningTableRow[]) => {
|
||||
const processedRows: WorkShiftPlanningRow[] = [];
|
||||
shifts.forEach((row: PlanningTableRow) => {
|
||||
|
||||
const processedRow: WorkShiftPlanningRow = {
|
||||
user: row.user,
|
||||
...Object.fromEntries(
|
||||
row.shifts.reduce((acc, workShift) => {
|
||||
return acc.set(
|
||||
dateToString(new Date(workShift.shiftDate)) ?? "",
|
||||
workShift.positions,
|
||||
);
|
||||
}, new Map<string, PositionSchema[]>()),
|
||||
),
|
||||
};
|
||||
processedRows.push(processedRow);
|
||||
});
|
||||
tableRowsHandlers.setState(processedRows);
|
||||
|
||||
let shownUsersProp = shownUsers;
|
||||
if (shownUsersProp.length === 0) {
|
||||
shownUsersProp = processedRows.map(row => row.user);
|
||||
}
|
||||
|
||||
users.forEach((user: UserSchema) => {
|
||||
if (processedRows.findIndex(row => row.user.id === user.id) === -1) {
|
||||
processedRows.push({ user });
|
||||
}
|
||||
});
|
||||
|
||||
processedRows.sort((a, b) => a.user.id - b.user.id);
|
||||
filterRows(shownUsersProp, processedRows);
|
||||
};
|
||||
|
||||
const refetch = () => {
|
||||
const dateFrom = dateToString(dateRange[0]);
|
||||
const dateTo = dateToString(dateRange[1]);
|
||||
if (!(dateFrom && dateTo)) return;
|
||||
|
||||
WorkShiftsPlanningService.getWorkShifts({
|
||||
requestBody: {
|
||||
dateFrom,
|
||||
dateTo,
|
||||
},
|
||||
})
|
||||
.then(res => {
|
||||
processShifts(res.shifts);
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onUpdate = (userId: number, date: Date, positionKeys: string[]) => {
|
||||
const shiftDate = dateToString(date);
|
||||
if (!shiftDate) return;
|
||||
|
||||
WorkShiftsPlanningService.updateWorkShift({
|
||||
requestBody: {
|
||||
userId,
|
||||
shiftDate,
|
||||
positionKeys,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
}
|
||||
refetch();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refetch();
|
||||
}, [dateRange]);
|
||||
|
||||
const getRange = () => {
|
||||
const startDate = dateRange[0];
|
||||
if (!startDate) return [];
|
||||
const endDate = dateRange[1] ?? getDefaultEndDate(new Date(startDate));
|
||||
const dayjsDates = getDatesBetween(startDate, endDate);
|
||||
return dayjsDates.map(date => date.toDate());
|
||||
};
|
||||
|
||||
const range = getRange();
|
||||
|
||||
return {
|
||||
dateRange,
|
||||
setDateRange,
|
||||
tableRows: filteredTableRows,
|
||||
onUpdate,
|
||||
users,
|
||||
shownUsers,
|
||||
setShownUsers,
|
||||
userPositions,
|
||||
range,
|
||||
};
|
||||
};
|
||||
|
||||
type WorkShiftsPlanningContextProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const WorkShiftsPlanningContextProvider: FC<WorkShiftsPlanningContextProviderProps> = ({ children }) => {
|
||||
const state = useWorkShiftsPlanningContextState();
|
||||
return (
|
||||
<WorkShiftsPlanningContext.Provider value={state}>
|
||||
{children}
|
||||
</WorkShiftsPlanningContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useWorkShiftsPlanningContext = () => {
|
||||
const context = useContext(WorkShiftsPlanningContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useWorkShiftsPlanningContext must be used within a WorkShiftsPlanningContextProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_Cell, MRT_ColumnDef, MRT_Row } from "mantine-react-table";
|
||||
import { getDayOfWeek } from "../../../../../shared/lib/date.ts";
|
||||
import { Box, Flex, Text } from "@mantine/core";
|
||||
import { WorkShiftPlanningRow } from "../types/WorkShiftPlanningRow.tsx";
|
||||
import { useWorkShiftsPlanningContext } from "../contexts/WorkShiftsPlanningContext.tsx";
|
||||
import { PositionSchema } from "../../../../../client";
|
||||
import PositionsMultiSelect from "../components/PositionsMultiSelect/PositionsMultiSelect.tsx";
|
||||
import { dateToString } from "../../../../../types/utils.ts";
|
||||
|
||||
|
||||
const useWorkShiftsPlanningColumns = () => {
|
||||
const { range } = useWorkShiftsPlanningContext();
|
||||
|
||||
return useMemo<MRT_ColumnDef<WorkShiftPlanningRow>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "ФИО",
|
||||
Cell: ({ row }: { row: MRT_Row<WorkShiftPlanningRow> }) => (
|
||||
<Flex direction={"column"}>
|
||||
<Text size={"sm"}>
|
||||
{row.original.user.firstName} {row.original.user.secondName}
|
||||
</Text>
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
|
||||
...range.map(date => ({
|
||||
size: 100,
|
||||
accessorKey: dateToString(date) ?? "",
|
||||
header: dateToString(date) ?? "",
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
Header: (
|
||||
<Flex direction={"column"}>
|
||||
<Box>{date.toLocaleString("ru").substring(0, 10)}</Box>
|
||||
<Box>{getDayOfWeek(date.getDay())}</Box>
|
||||
</Flex>
|
||||
),
|
||||
Cell: ({ cell }: { cell: MRT_Cell<WorkShiftPlanningRow> }) => {
|
||||
return (
|
||||
<Flex direction={"column"}>
|
||||
<PositionsMultiSelect
|
||||
date={date}
|
||||
positions={cell.getValue() as PositionSchema[]}
|
||||
user={cell.row.original.user}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
})),
|
||||
],
|
||||
[range],
|
||||
);
|
||||
};
|
||||
|
||||
export default useWorkShiftsPlanningColumns;
|
||||
@@ -0,0 +1,6 @@
|
||||
import { PlannedWorkShiftSchema, UserSchema } from "../../../../../client";
|
||||
|
||||
export type WorkShiftPlanningRow = {
|
||||
user: UserSchema;
|
||||
[key: string]: UserSchema | PlannedWorkShiftSchema;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
const DEFAULT_PLANNING_DURATION = 14;
|
||||
|
||||
export const getDefaultEndDate = (startDate?: Date) => {
|
||||
const date = startDate ?? new Date();
|
||||
date.setDate(date.getDate() + DEFAULT_PLANNING_DURATION);
|
||||
return date;
|
||||
};
|
||||
Reference in New Issue
Block a user