feat: Админ панель, блокировка пользователей
Добавлена возможность блокировки/разблокировки пользователей администратором.
This commit is contained in:
parent
04242d63f1
commit
aadc4bf5bd
@ -69,3 +69,19 @@ async def change_user(
|
|||||||
):
|
):
|
||||||
users_service = UsersService(db)
|
users_service = UsersService(db)
|
||||||
return await users_service.update_user(data, user_id, user.id)
|
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
|
last_name: str
|
||||||
patronymic: Optional[str] = None
|
patronymic: Optional[str] = None
|
||||||
login: str
|
login: str
|
||||||
is_blocked: bool
|
is_blocked: Optional[bool] = None
|
||||||
|
|
||||||
role_id: Optional[int] = None
|
role_id: Optional[int] = None
|
||||||
|
|
||||||
|
|||||||
@ -122,6 +122,33 @@ class UsersService:
|
|||||||
role_id=created_user.role_id,
|
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]:
|
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)
|
user_model = await self.users_repository.get_by_id(user_id)
|
||||||
if not user_model:
|
if not user_model:
|
||||||
|
|||||||
@ -32,6 +32,13 @@ export const usersApi = createApi({
|
|||||||
}),
|
}),
|
||||||
invalidatesTags: ['User']
|
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,
|
useChangePasswordMutation,
|
||||||
useUpdateUserMutation,
|
useUpdateUserMutation,
|
||||||
useGetAllUsersQuery,
|
useGetAllUsersQuery,
|
||||||
|
useSetIsBlockedMutation
|
||||||
} = usersApi;
|
} = usersApi;
|
||||||
@ -9,7 +9,7 @@ const AdminRoute = () => {
|
|||||||
isLoading: isUserLoading,
|
isLoading: isUserLoading,
|
||||||
isError: isUserError,
|
isError: isUserError,
|
||||||
} = useGetAuthenticatedUserDataQuery(undefined, {
|
} = useGetAuthenticatedUserDataQuery(undefined, {
|
||||||
pollingInterval: 60000,
|
pollingInterval: 20000,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isUserLoading) {
|
if (isUserLoading) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const PrivateRoute = () => {
|
|||||||
return <LoadingIndicator/>;
|
return <LoadingIndicator/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user || !userData) {
|
if (!user || !userData || userData.is_blocked) {
|
||||||
return <Navigate to="/login"/>;
|
return <Navigate to="/login"/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {ControlOutlined, PlusOutlined} from "@ant-design/icons";
|
||||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import useAdminPage from "./useAdminPage.js";
|
import useAdminPage from "./useAdminPage.js";
|
||||||
import useAdminPageUI from "./useAdminPageUI.js";
|
|
||||||
import CreateUserModalForm from "./Components/CreateUserModalForm/CreateUserModalForm.jsx";
|
import CreateUserModalForm from "./Components/CreateUserModalForm/CreateUserModalForm.jsx";
|
||||||
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
||||||
|
|
||||||
|
|
||||||
const AdminPage = () => {
|
const AdminPage = () => {
|
||||||
const adminPageData = useAdminPage();
|
const adminPageData = useAdminPage();
|
||||||
const adminPageUI = useAdminPageUI();
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -43,21 +40,23 @@ const AdminPage = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Заблокирован",
|
title: "Заблокирован",
|
||||||
dataIndex: ["is_blocked"],
|
dataIndex: "is_blocked",
|
||||||
key: "is_blocked",
|
key: "is_blocked",
|
||||||
render: (value) => (
|
render: (value, record) => (
|
||||||
<Switch
|
<Switch
|
||||||
value={value}
|
loading={adminPageData.isBlocking}
|
||||||
checkedChildren={"Заблокирован"}
|
checked={value}
|
||||||
unCheckedChildren={"Не заблокирован"}
|
checkedChildren="Заблокирован"
|
||||||
|
unCheckedChildren="Не заблокирован"
|
||||||
|
onChange={(isBlocked) => adminPageData.setIsBlockUser(record.id, isBlocked)}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Действия",
|
title: "Действия",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Button type="link" onClick={() => adminPageUI.openEditModal(record)}>
|
<Button type="link" onClick={() => adminPageData.openEditModal(record)}>
|
||||||
Редактировать
|
Редактировать
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
@ -69,7 +68,7 @@ const AdminPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={adminPageUI.containerStyle}>
|
<div style={adminPageData.containerStyle}>
|
||||||
{adminPageData.isLoading ? (
|
{adminPageData.isLoading ? (
|
||||||
<LoadingIndicator/>
|
<LoadingIndicator/>
|
||||||
) : (
|
) : (
|
||||||
@ -79,7 +78,6 @@ const AdminPage = () => {
|
|||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Введите фамилию, имя или отчество"
|
placeholder="Введите фамилию, имя или отчество"
|
||||||
onSearch={adminPageUI.handleSearch}
|
|
||||||
style={{marginBottom: 12}}
|
style={{marginBottom: 12}}
|
||||||
/>
|
/>
|
||||||
<Table
|
<Table
|
||||||
@ -91,7 +89,7 @@ const AdminPage = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Tooltip title="Добавить пользователя">
|
<Tooltip title="Добавить пользователя">
|
||||||
<FloatButton onClick={adminPageUI.openCreateModal} icon={<PlusOutlined/>} type={"primary"}/>
|
<FloatButton onClick={adminPageData.openCreateModal} icon={<PlusOutlined/>} type={"primary"}/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<CreateUserModalForm/>
|
<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";
|
import {useGetRolesQuery} from "../../../Api/rolesApi.js";
|
||||||
|
|
||||||
|
const {useBreakpoint} = Grid;
|
||||||
|
|
||||||
const useAdminPage = () => {
|
const useAdminPage = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: users = [], isLoading, isError,
|
data: users = [], isLoading, isError,
|
||||||
} = useGetAllUsersQuery(undefined, {
|
} = useGetAllUsersQuery(undefined, {
|
||||||
@ -11,15 +20,55 @@ const useAdminPage = () => {
|
|||||||
const {data: roles = [], isLoading: isLoadingRoles, isError: isErrorRoles} = useGetRolesQuery(undefined, {
|
const {data: roles = [], isLoading: isLoadingRoles, isError: isErrorRoles} = useGetRolesQuery(undefined, {
|
||||||
pollingInterval: 60000,
|
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 {
|
return {
|
||||||
users,
|
users,
|
||||||
roles,
|
roles,
|
||||||
|
isBlocking,
|
||||||
isLoading: isLoading || isLoadingRoles,
|
isLoading: isLoading || isLoadingRoles,
|
||||||
isError: isError || isErrorRoles,
|
isError: isError || isErrorRoles,
|
||||||
updateUser: () => {
|
isUpdating: false,
|
||||||
}, isUpdating: false, isUpdateError: false, createUser: () => {
|
isUpdateError: false,
|
||||||
}, isCreating: false, isCreateError: 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 AppointmentsPage = () => {
|
||||||
const {
|
const {
|
||||||
patients, // Добавляем
|
appointments,
|
||||||
appointments, // Добавляем
|
scheduledAppointments,
|
||||||
scheduledAppointments, // Добавляем
|
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
collapsed,
|
collapsed,
|
||||||
|
|||||||
@ -27,6 +27,7 @@ const useProfilePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
userData,
|
||||||
containerStyle,
|
containerStyle,
|
||||||
cardStyle,
|
cardStyle,
|
||||||
buttonStyle,
|
buttonStyle,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user