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 { GetDepartmentsResponse } from './models/GetDepartmentsResponse';
|
||||||
export type { GetManagersResponse } from './models/GetManagersResponse';
|
export type { GetManagersResponse } from './models/GetManagersResponse';
|
||||||
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
||||||
|
export type { GetPlannedWorkShiftsResponse } from './models/GetPlannedWorkShiftsResponse';
|
||||||
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
||||||
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
|
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
|
||||||
export type { GetProductBarcodeRequest } from './models/GetProductBarcodeRequest';
|
export type { GetProductBarcodeRequest } from './models/GetProductBarcodeRequest';
|
||||||
@@ -234,6 +235,7 @@ export type { GetServiceKitSchema } from './models/GetServiceKitSchema';
|
|||||||
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
|
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
|
||||||
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
|
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
|
||||||
export type { GetTransactionTagsResponse } from './models/GetTransactionTagsResponse';
|
export type { GetTransactionTagsResponse } from './models/GetTransactionTagsResponse';
|
||||||
|
export type { GetWorkShiftsPlanningDataRequest } from './models/GetWorkShiftsPlanningDataRequest';
|
||||||
export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse';
|
export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse';
|
||||||
export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema';
|
export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema';
|
||||||
export type { HTTPValidationError } from './models/HTTPValidationError';
|
export type { HTTPValidationError } from './models/HTTPValidationError';
|
||||||
@@ -257,6 +259,8 @@ export type { PayRateSchema } from './models/PayRateSchema';
|
|||||||
export type { PayRateSchemaBase } from './models/PayRateSchemaBase';
|
export type { PayRateSchemaBase } from './models/PayRateSchemaBase';
|
||||||
export type { PayrollSchemeSchema } from './models/PayrollSchemeSchema';
|
export type { PayrollSchemeSchema } from './models/PayrollSchemeSchema';
|
||||||
export type { PermissionSchema } from './models/PermissionSchema';
|
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 { PositionSchema } from './models/PositionSchema';
|
||||||
export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest';
|
export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest';
|
||||||
export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse';
|
export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse';
|
||||||
@@ -332,6 +336,8 @@ export type { UpdateMarketplaceRequest } from './models/UpdateMarketplaceRequest
|
|||||||
export type { UpdateMarketplaceResponse } from './models/UpdateMarketplaceResponse';
|
export type { UpdateMarketplaceResponse } from './models/UpdateMarketplaceResponse';
|
||||||
export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest';
|
export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest';
|
||||||
export type { UpdatePayRateResponse } from './models/UpdatePayRateResponse';
|
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 { UpdatePriceCategoryRequest } from './models/UpdatePriceCategoryRequest';
|
||||||
export type { UpdatePriceCategoryResponse } from './models/UpdatePriceCategoryResponse';
|
export type { UpdatePriceCategoryResponse } from './models/UpdatePriceCategoryResponse';
|
||||||
export type { UpdateResidualProductRequest } from './models/UpdateResidualProductRequest';
|
export type { UpdateResidualProductRequest } from './models/UpdateResidualProductRequest';
|
||||||
@@ -387,3 +393,4 @@ export { TimeTrackingService } from './services/TimeTrackingService';
|
|||||||
export { TransactionService } from './services/TransactionService';
|
export { TransactionService } from './services/TransactionService';
|
||||||
export { UserService } from './services/UserService';
|
export { UserService } from './services/UserService';
|
||||||
export { WorkShiftsService } from './services/WorkShiftsService';
|
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 { Tabs } from "@mantine/core";
|
||||||
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
||||||
import {
|
import {
|
||||||
IconTopologyStar3,
|
IconCalendarEvent,
|
||||||
IconCalendarUser,
|
IconCalendarUser,
|
||||||
IconCoins,
|
IconCoins,
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconQrcode,
|
IconQrcode,
|
||||||
|
IconTopologyStar3,
|
||||||
IconUser,
|
IconUser,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import UsersTab from "./tabs/Users/UsersTab.tsx";
|
import UsersTab from "./tabs/Users/UsersTab.tsx";
|
||||||
@@ -18,11 +19,26 @@ import { TransactionsTab } from "./tabs/Transactions/TransactionsTab.tsx";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../redux/store.ts";
|
import { RootState } from "../../redux/store.ts";
|
||||||
import OrganizationalStructureTab from "./tabs/OrganizationalStructureTab/OrganizationalStructureTab.tsx";
|
import OrganizationalStructureTab from "./tabs/OrganizationalStructureTab/OrganizationalStructureTab.tsx";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import WorkShiftsPlanning from "./tabs/WorkShiftsPlanning/WorkShiftsPlanning.tsx";
|
||||||
|
|
||||||
const AdminPage = () => {
|
const AdminPage = () => {
|
||||||
const userRole = useSelector((state: RootState) => state.auth.role);
|
const userRole = useSelector((state: RootState) => state.auth.role);
|
||||||
const isAdmin = userRole === "admin";
|
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 (
|
return (
|
||||||
<div className={styles["container"]}>
|
<div className={styles["container"]}>
|
||||||
<PageBlock fullHeight>
|
<PageBlock fullHeight>
|
||||||
@@ -55,6 +71,11 @@ const AdminPage = () => {
|
|||||||
Рабочее время
|
Рабочее время
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
)}
|
)}
|
||||||
|
<Tabs.Tab
|
||||||
|
value={"workShiftsPlanning"}
|
||||||
|
leftSection={<IconCalendarEvent />}>
|
||||||
|
Планирование смен
|
||||||
|
</Tabs.Tab>
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
value={"workShifts"}
|
value={"workShifts"}
|
||||||
leftSection={<IconQrcode />}>
|
leftSection={<IconQrcode />}>
|
||||||
@@ -68,54 +89,13 @@ const AdminPage = () => {
|
|||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
)}
|
)}
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Panel value={"users"}>
|
{getTabPanel("users", <UsersTab />)}
|
||||||
<motion.div
|
{getTabPanel("rolesAndPositions", <OrganizationalStructureTab />)}
|
||||||
initial={{ opacity: 0 }}
|
{getTabPanel("finances", <FinancesTab />)}
|
||||||
animate={{ opacity: 1 }}
|
{getTabPanel("workTimeTable", <WorkTimeTable />)}
|
||||||
transition={{ duration: 0.2 }}>
|
{getTabPanel("workShiftsPlanning", <WorkShiftsPlanning />)}
|
||||||
<UsersTab />
|
{getTabPanel("workShifts", <WorkShiftsTab />)}
|
||||||
</motion.div>
|
{getTabPanel("transactions", <TransactionsTab />)}
|
||||||
</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>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PageBlock>
|
</PageBlock>
|
||||||
</div>
|
</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