feat: Админ панель, блокировка пользователей
Добавлена возможность блокировки/разблокировки пользователей администратором.
This commit is contained in:
parent
04242d63f1
commit
aadc4bf5bd
@ -69,3 +69,19 @@ async def change_user(
|
||||
):
|
||||
users_service = UsersService(db)
|
||||
return await users_service.update_user(data, user_id, user.id)
|
||||
|
||||
|
||||
@router.post(
|
||||
'/{user_id}/set-is-block/',
|
||||
response_model=Optional[UserEntity],
|
||||
summary='Set is_blocked flag for user',
|
||||
description='Set is_blocked flag for user',
|
||||
)
|
||||
async def set_is_blocked(
|
||||
user_id: int,
|
||||
is_blocked: bool,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user=Depends(require_admin),
|
||||
):
|
||||
users_service = UsersService(db)
|
||||
return await users_service.set_is_blocked(user_id, is_blocked, user.id)
|
||||
|
||||
@ -11,7 +11,7 @@ class UserEntity(BaseModel):
|
||||
last_name: str
|
||||
patronymic: Optional[str] = None
|
||||
login: str
|
||||
is_blocked: bool
|
||||
is_blocked: Optional[bool] = None
|
||||
|
||||
role_id: Optional[int] = None
|
||||
|
||||
|
||||
@ -122,6 +122,33 @@ class UsersService:
|
||||
role_id=created_user.role_id,
|
||||
)
|
||||
|
||||
async def set_is_blocked(self, user_id: int, is_blocked: bool, current_user_id: int) -> Optional[UserEntity]:
|
||||
user_model = await self.users_repository.get_by_id(user_id)
|
||||
if not user_model:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail='Пользователь не найден',
|
||||
)
|
||||
|
||||
current_user = await self.users_repository.get_by_id(current_user_id)
|
||||
if not current_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail='Пользователь не найден',
|
||||
)
|
||||
|
||||
if current_user_id == user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail='Администратор не может заблокировать и разблокировать сам себя',
|
||||
)
|
||||
|
||||
user_model.is_blocked = is_blocked
|
||||
|
||||
user_model = await self.users_repository.update(user_model)
|
||||
|
||||
return self.model_to_entity(user_model)
|
||||
|
||||
async def update_user(self, user: UserEntity, user_id: int, current_user_id: int) -> Optional[UserEntity]:
|
||||
user_model = await self.users_repository.get_by_id(user_id)
|
||||
if not user_model:
|
||||
|
||||
@ -32,6 +32,13 @@ export const usersApi = createApi({
|
||||
}),
|
||||
invalidatesTags: ['User']
|
||||
}),
|
||||
setIsBlocked: builder.mutation({
|
||||
query: ({ userId, isBlocked }) => ({
|
||||
url: `/users/${userId}/set-is-block/?is_blocked=${isBlocked}`,
|
||||
method: "POST",
|
||||
}),
|
||||
invalidatesTags: ['User'],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -40,4 +47,5 @@ export const {
|
||||
useChangePasswordMutation,
|
||||
useUpdateUserMutation,
|
||||
useGetAllUsersQuery,
|
||||
useSetIsBlockedMutation
|
||||
} = usersApi;
|
||||
@ -9,7 +9,7 @@ const AdminRoute = () => {
|
||||
isLoading: isUserLoading,
|
||||
isError: isUserError,
|
||||
} = useGetAuthenticatedUserDataQuery(undefined, {
|
||||
pollingInterval: 60000,
|
||||
pollingInterval: 20000,
|
||||
});
|
||||
|
||||
if (isUserLoading) {
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { Navigate, Outlet } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import {Navigate, Outlet} from "react-router-dom";
|
||||
import {useSelector} from "react-redux";
|
||||
import LoadingIndicator from "../Components/Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
|
||||
const PrivateRoute = () => {
|
||||
const { user, userData, isLoading } = useSelector((state) => state.auth);
|
||||
const {user, userData, isLoading} = useSelector((state) => state.auth);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingIndicator />;
|
||||
return <LoadingIndicator/>;
|
||||
}
|
||||
|
||||
if (!user || !userData) {
|
||||
return <Navigate to="/login" />;
|
||||
if (!user || !userData || userData.is_blocked) {
|
||||
return <Navigate to="/login"/>;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
return <Outlet/>;
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
||||
@ -1,15 +1,12 @@
|
||||
import {Table, Button, Result, Typography, Switch, Row, Input, Col, Tooltip, FloatButton} from "antd";
|
||||
import {Table, Button, Result, Typography, Switch, Input, Tooltip, FloatButton} from "antd";
|
||||
import {ControlOutlined, PlusOutlined} from "@ant-design/icons";
|
||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import useAdminPage from "./useAdminPage.js";
|
||||
import useAdminPageUI from "./useAdminPageUI.js";
|
||||
import CreateUserModalForm from "./Components/CreateUserModalForm/CreateUserModalForm.jsx";
|
||||
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
||||
|
||||
|
||||
const AdminPage = () => {
|
||||
const adminPageData = useAdminPage();
|
||||
const adminPageUI = useAdminPageUI();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@ -43,21 +40,23 @@ const AdminPage = () => {
|
||||
},
|
||||
{
|
||||
title: "Заблокирован",
|
||||
dataIndex: ["is_blocked"],
|
||||
dataIndex: "is_blocked",
|
||||
key: "is_blocked",
|
||||
render: (value) => (
|
||||
render: (value, record) => (
|
||||
<Switch
|
||||
value={value}
|
||||
checkedChildren={"Заблокирован"}
|
||||
unCheckedChildren={"Не заблокирован"}
|
||||
loading={adminPageData.isBlocking}
|
||||
checked={value}
|
||||
checkedChildren="Заблокирован"
|
||||
unCheckedChildren="Не заблокирован"
|
||||
onChange={(isBlocked) => adminPageData.setIsBlockUser(record.id, isBlocked)}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Действия",
|
||||
key: "actions",
|
||||
render: (_, record) => (
|
||||
<Button type="link" onClick={() => adminPageUI.openEditModal(record)}>
|
||||
<Button type="link" onClick={() => adminPageData.openEditModal(record)}>
|
||||
Редактировать
|
||||
</Button>
|
||||
),
|
||||
@ -69,7 +68,7 @@ const AdminPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={adminPageUI.containerStyle}>
|
||||
<div style={adminPageData.containerStyle}>
|
||||
{adminPageData.isLoading ? (
|
||||
<LoadingIndicator/>
|
||||
) : (
|
||||
@ -79,7 +78,6 @@ const AdminPage = () => {
|
||||
</Typography.Title>
|
||||
<Input
|
||||
placeholder="Введите фамилию, имя или отчество"
|
||||
onSearch={adminPageUI.handleSearch}
|
||||
style={{marginBottom: 12}}
|
||||
/>
|
||||
<Table
|
||||
@ -91,7 +89,7 @@ const AdminPage = () => {
|
||||
/>
|
||||
|
||||
<Tooltip title="Добавить пользователя">
|
||||
<FloatButton onClick={adminPageUI.openCreateModal} icon={<PlusOutlined/>} type={"primary"}/>
|
||||
<FloatButton onClick={adminPageData.openCreateModal} icon={<PlusOutlined/>} type={"primary"}/>
|
||||
</Tooltip>
|
||||
|
||||
<CreateUserModalForm/>
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
import {useGetAllUsersQuery} from "../../../Api/usersApi.js";
|
||||
import {Grid, notification} from "antd";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {openModal} from "../../../Redux/Slices/adminSlice.js";
|
||||
import {setIsCurrentUser, setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
|
||||
import {useGetAllUsersQuery, useSetIsBlockedMutation} from "../../../Api/usersApi.js";
|
||||
import {useGetRolesQuery} from "../../../Api/rolesApi.js";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
const useAdminPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const screens = useBreakpoint();
|
||||
|
||||
const {
|
||||
data: users = [], isLoading, isError,
|
||||
} = useGetAllUsersQuery(undefined, {
|
||||
@ -11,15 +20,55 @@ const useAdminPage = () => {
|
||||
const {data: roles = [], isLoading: isLoadingRoles, isError: isErrorRoles} = useGetRolesQuery(undefined, {
|
||||
pollingInterval: 60000,
|
||||
});
|
||||
const [setIsBlocked, {isLoading: isBlocking, isError: isBlockError}] = useSetIsBlockedMutation();
|
||||
|
||||
const containerStyle = {padding: screens.xs ? 16 : 24};
|
||||
|
||||
const openEditModal = (user) => {
|
||||
dispatch(setSelectedUser(user));
|
||||
dispatch(setIsCurrentUser(true));
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
dispatch(openModal());
|
||||
};
|
||||
|
||||
const setIsBlockUser = async (user, isBlock) => {
|
||||
try {
|
||||
await setIsBlocked({userId: user, isBlocked: isBlock}).unwrap();
|
||||
|
||||
notification.success({
|
||||
message: "Успех",
|
||||
description: isBlock
|
||||
? "Пользователь успешно заблокирован"
|
||||
: "Пользователь успешно разблокирован",
|
||||
placement: "topRight",
|
||||
})
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: "Ошибка",
|
||||
description: error?.data?.detail ? error?.data?.detail : isBlock
|
||||
? "Не удалось заблокировать пользователя"
|
||||
: "Не удалось разблокировать пользователя",
|
||||
placement: "topRight",
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
users,
|
||||
roles,
|
||||
isBlocking,
|
||||
isLoading: isLoading || isLoadingRoles,
|
||||
isError: isError || isErrorRoles,
|
||||
updateUser: () => {
|
||||
}, isUpdating: false, isUpdateError: false, createUser: () => {
|
||||
}, isCreating: false, isCreateError: false,
|
||||
isUpdating: false,
|
||||
isUpdateError: false,
|
||||
isCreating: false,
|
||||
isCreateError: false,
|
||||
containerStyle,
|
||||
openEditModal,
|
||||
openCreateModal,
|
||||
setIsBlockUser,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
import {useState} from "react";
|
||||
import {Grid, Form} from "antd";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {openModal} from "../../../Redux/Slices/adminSlice.js";
|
||||
import {setIsCurrentUser, setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
const useAdminPageUI = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const screens = useBreakpoint();
|
||||
|
||||
const containerStyle = {padding: screens.xs ? 16 : 24};
|
||||
|
||||
const openEditModal = (user) => {
|
||||
dispatch(setSelectedUser(user));
|
||||
dispatch(setIsCurrentUser(true));
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
dispatch(openModal());
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
containerStyle,
|
||||
openEditModal,
|
||||
openCreateModal,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAdminPageUI;
|
||||
@ -19,9 +19,8 @@ import AppointmentsListModal from "./Components/AppointmentsListModal/Appointmen
|
||||
|
||||
const AppointmentsPage = () => {
|
||||
const {
|
||||
patients, // Добавляем
|
||||
appointments, // Добавляем
|
||||
scheduledAppointments, // Добавляем
|
||||
appointments,
|
||||
scheduledAppointments,
|
||||
isLoading,
|
||||
isError,
|
||||
collapsed,
|
||||
|
||||
@ -27,6 +27,7 @@ const useProfilePage = () => {
|
||||
};
|
||||
|
||||
return {
|
||||
userData,
|
||||
containerStyle,
|
||||
cardStyle,
|
||||
buttonStyle,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user