feat: Добавлена блокировка пользователя.
This commit is contained in:
parent
7574b08b25
commit
ceee769100
@ -0,0 +1,30 @@
|
|||||||
|
"""0005_добавил блокировку пользователя
|
||||||
|
|
||||||
|
Revision ID: 9e7ab1a46b64
|
||||||
|
Revises: 69fee5fc14c8
|
||||||
|
Create Date: 2025-06-15 13:41:20.591874
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '9e7ab1a46b64'
|
||||||
|
down_revision: Union[str, None] = '69fee5fc14c8'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('is_blocked', sa.Boolean(), server_default='false', nullable=False))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'is_blocked')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -11,6 +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
|
||||||
|
|
||||||
role_id: Optional[int] = None
|
role_id: Optional[int] = None
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, String
|
from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, String, Boolean
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ class User(BaseModel):
|
|||||||
patronymic = Column(VARCHAR(200))
|
patronymic = Column(VARCHAR(200))
|
||||||
login = Column(String, nullable=False, unique=True)
|
login = Column(String, nullable=False, unique=True)
|
||||||
password = Column(String, nullable=False)
|
password = Column(String, nullable=False)
|
||||||
|
is_blocked = Column(Boolean, nullable=False, default=False, server_default='false')
|
||||||
|
|
||||||
role_id = Column(Integer, ForeignKey('roles.id'), nullable=False)
|
role_id = Column(Integer, ForeignKey('roles.id'), nullable=False)
|
||||||
|
|
||||||
|
|||||||
@ -180,6 +180,7 @@ class UsersService:
|
|||||||
last_name=user.last_name,
|
last_name=user.last_name,
|
||||||
patronymic=user.patronymic,
|
patronymic=user.patronymic,
|
||||||
login=user.login,
|
login=user.login,
|
||||||
|
is_blocked=user.is_blocked,
|
||||||
)
|
)
|
||||||
|
|
||||||
if user.id is not None:
|
if user.id is not None:
|
||||||
@ -196,4 +197,5 @@ class UsersService:
|
|||||||
patronymic=user.patronymic,
|
patronymic=user.patronymic,
|
||||||
login=user.login,
|
login=user.login,
|
||||||
role_id=user.role_id,
|
role_id=user.role_id,
|
||||||
|
is_blocked=user.is_blocked,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -11,8 +11,8 @@ service:
|
|||||||
|
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 512Mi
|
memory: 128Mi
|
||||||
cpu: 500m
|
cpu: 200m
|
||||||
|
|
||||||
persistence:
|
persistence:
|
||||||
path: /mnt/k8s_storage/visus-api
|
path: /mnt/k8s_storage/visus-api
|
||||||
|
|||||||
@ -11,8 +11,8 @@ service:
|
|||||||
|
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 512Mi
|
memory: 128Mi
|
||||||
cpu: 500m
|
cpu: 200m
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
secretTLSName: visus-web-tls-secret
|
secretTLSName: visus-web-tls-secret
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {Button, Form, Input, Modal, Select, Space, Typography} from "antd";
|
import {Button, Form, Input, Modal, Select, Space, Switch, Typography} from "antd";
|
||||||
import useUpdateUserModalFormUI from "./useUpdateUserModalFormUI.js";
|
import useUpdateUserModalFormUI from "./useUpdateUserModalFormUI.js";
|
||||||
import useUpdateUserModalForm from "./useUpdateUserModalForm.js";
|
import useUpdateUserModalForm from "./useUpdateUserModalForm.js";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {Table, Button, Result, Typography} from "antd";
|
import {Table, Button, Result, Typography, Switch} from "antd";
|
||||||
import {ControlOutlined} from "@ant-design/icons";
|
import {ControlOutlined} 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";
|
||||||
@ -41,6 +41,18 @@ const AdminPage = () => {
|
|||||||
filters: adminPageData.roles.map(role => ({text: role.title, value: role.title})),
|
filters: adminPageData.roles.map(role => ({text: role.title, value: role.title})),
|
||||||
onFilter: (value, record) => record.role.title === value,
|
onFilter: (value, record) => record.role.title === value,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Заблокирован",
|
||||||
|
dataIndex: ["is_blocked"],
|
||||||
|
key: "is_blocked",
|
||||||
|
render: (value, record) => (
|
||||||
|
<Switch
|
||||||
|
value={value}
|
||||||
|
checkedChildren={"Заблокирован"}
|
||||||
|
unCheckedChildren={"Разблокирован"}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Действия",
|
title: "Действия",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import {Form, Input, Button, Row, Col, Typography, Image, Space} from "antd";
|
import { Form, Input, Button, Row, Col, Typography, Image, Space } from "antd";
|
||||||
import useLoginPage from "./useLoginPage.js";
|
import useLoginPage from "./useLoginPage.js";
|
||||||
import useLoginPageUI from "./useLoginPageUI.js";
|
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const {onFinish, isLoading} = useLoginPage();
|
|
||||||
const {
|
const {
|
||||||
containerStyle,
|
containerStyle,
|
||||||
formContainerStyle,
|
formContainerStyle,
|
||||||
@ -13,8 +11,10 @@ const LoginPage = () => {
|
|||||||
logoBlockStyle,
|
logoBlockStyle,
|
||||||
logoStyle,
|
logoStyle,
|
||||||
appNameStyle,
|
appNameStyle,
|
||||||
labels
|
labels,
|
||||||
} = useLoginPageUI();
|
isLoading,
|
||||||
|
onFinish,
|
||||||
|
} = useLoginPage();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row justify="center" align="middle" style={containerStyle}>
|
<Row justify="center" align="middle" style={containerStyle}>
|
||||||
@ -35,19 +35,19 @@ const LoginPage = () => {
|
|||||||
{labels.title}
|
{labels.title}
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Form name="login" initialValues={{remember: true}} onFinish={onFinish}>
|
<Form name="login" initialValues={{ remember: true }} onFinish={onFinish}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="login"
|
name="login"
|
||||||
rules={[{required: true, message: labels.loginRequired}]}
|
rules={[{ required: true, message: labels.loginRequired }]}
|
||||||
>
|
>
|
||||||
<Input placeholder={labels.loginPlaceholder}/>
|
<Input placeholder={labels.loginPlaceholder} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
rules={[{required: true, message: labels.passwordRequired}]}
|
rules={[{ required: true, message: labels.passwordRequired }]}
|
||||||
>
|
>
|
||||||
<Input.Password placeholder={labels.passwordPlaceholder}/>
|
<Input.Password placeholder={labels.passwordPlaceholder} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
|
|||||||
@ -1,12 +1,73 @@
|
|||||||
import { useDispatch } from "react-redux";
|
import { useEffect, useRef } from "react";
|
||||||
import { notification } from "antd";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { Grid, notification } from "antd";
|
||||||
import { setError, setUser } from "../../../Redux/Slices/authSlice.js";
|
import { setError, setUser } from "../../../Redux/Slices/authSlice.js";
|
||||||
import { useLoginMutation } from "../../../Api/authApi.js";
|
import { useLoginMutation } from "../../../Api/authApi.js";
|
||||||
import { checkAuth } from "../../../Redux/Slices/authSlice.js";
|
import { checkAuth } from "../../../Redux/Slices/authSlice.js";
|
||||||
|
|
||||||
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
const useLoginPage = () => {
|
const useLoginPage = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [loginUser, { isLoading }] = useLoginMutation();
|
const [loginUser, { isLoading }] = useLoginMutation();
|
||||||
|
const { user, userData } = useSelector((state) => state.auth);
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
const hasRedirected = useRef(false);
|
||||||
|
|
||||||
|
const containerStyle = {
|
||||||
|
minHeight: "100vh",
|
||||||
|
};
|
||||||
|
|
||||||
|
const formContainerStyle = {
|
||||||
|
padding: screens.xs ? 10 : 20,
|
||||||
|
border: "1px solid #ddd",
|
||||||
|
borderRadius: 8,
|
||||||
|
textAlign: "center",
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleStyle = {
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
const logoStyle = {
|
||||||
|
width: 80,
|
||||||
|
marginBottom: 10,
|
||||||
|
borderRadius: 20,
|
||||||
|
border: "1px solid #ddd",
|
||||||
|
};
|
||||||
|
|
||||||
|
const appNameStyle = {
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#1890ff",
|
||||||
|
marginBottom: 40,
|
||||||
|
};
|
||||||
|
|
||||||
|
const logoBlockStyle = {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
};
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
title: "Авторизация",
|
||||||
|
loginPlaceholder: "Логин",
|
||||||
|
passwordPlaceholder: "Пароль",
|
||||||
|
submitButton: "Войти",
|
||||||
|
loginRequired: "Пожалуйста, введите логин",
|
||||||
|
passwordRequired: "Пожалуйста, введите пароль",
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && userData && !isLoading && !hasRedirected.current) {
|
||||||
|
hasRedirected.current = true;
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
document.title = labels.title;
|
||||||
|
}, [user, userData, isLoading, navigate]);
|
||||||
|
|
||||||
const onFinish = async (loginData) => {
|
const onFinish = async (loginData) => {
|
||||||
try {
|
try {
|
||||||
@ -32,6 +93,13 @@ const useLoginPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
containerStyle,
|
||||||
|
formContainerStyle,
|
||||||
|
titleStyle,
|
||||||
|
logoStyle,
|
||||||
|
appNameStyle,
|
||||||
|
labels,
|
||||||
|
logoBlockStyle,
|
||||||
onFinish,
|
onFinish,
|
||||||
isLoading,
|
isLoading,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,78 +0,0 @@
|
|||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { Grid } from "antd";
|
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
|
||||||
|
|
||||||
const useLoginPageUI = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { user, userData, isLoading } = useSelector((state) => state.auth);
|
|
||||||
const screens = useBreakpoint();
|
|
||||||
const hasRedirected = useRef(false);
|
|
||||||
|
|
||||||
const containerStyle = {
|
|
||||||
minHeight: "100vh",
|
|
||||||
};
|
|
||||||
|
|
||||||
const formContainerStyle = {
|
|
||||||
padding: screens.xs ? 10 : 20,
|
|
||||||
border: "1px solid #ddd",
|
|
||||||
borderRadius: 8,
|
|
||||||
textAlign: "center",
|
|
||||||
};
|
|
||||||
|
|
||||||
const titleStyle = {
|
|
||||||
textAlign: "center",
|
|
||||||
marginBottom: 20,
|
|
||||||
};
|
|
||||||
|
|
||||||
const logoStyle = {
|
|
||||||
width: 80,
|
|
||||||
marginBottom: 10,
|
|
||||||
borderRadius: 20,
|
|
||||||
border: "1px solid #ddd",
|
|
||||||
};
|
|
||||||
|
|
||||||
const appNameStyle = {
|
|
||||||
textAlign: "center",
|
|
||||||
color: "#1890ff",
|
|
||||||
marginBottom: 40,
|
|
||||||
};
|
|
||||||
|
|
||||||
const logoBlockStyle = {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
};
|
|
||||||
|
|
||||||
const labels = {
|
|
||||||
title: "Авторизация",
|
|
||||||
loginPlaceholder: "Логин",
|
|
||||||
passwordPlaceholder: "Пароль",
|
|
||||||
submitButton: "Войти",
|
|
||||||
loginRequired: "Пожалуйста, введите логин",
|
|
||||||
passwordRequired: "Пожалуйста, введите пароль",
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (user && userData && !isLoading && !hasRedirected.current) {
|
|
||||||
hasRedirected.current = true;
|
|
||||||
navigate("/");
|
|
||||||
}
|
|
||||||
document.title = labels.title;
|
|
||||||
}, [user, userData, isLoading, navigate]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
containerStyle,
|
|
||||||
formContainerStyle,
|
|
||||||
titleStyle,
|
|
||||||
logoStyle,
|
|
||||||
appNameStyle,
|
|
||||||
labels,
|
|
||||||
logoBlockStyle,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useLoginPageUI;
|
|
||||||
@ -2,22 +2,19 @@ import {Button, Card, Col, Row, Typography, Result} from "antd";
|
|||||||
import {EditOutlined, UserOutlined} from "@ant-design/icons";
|
import {EditOutlined, UserOutlined} from "@ant-design/icons";
|
||||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import useProfilePage from "./useProfilePage.js";
|
import useProfilePage from "./useProfilePage.js";
|
||||||
import useProfilePageUI from "./useProfilePageUI.js";
|
|
||||||
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const {
|
|
||||||
userData,
|
|
||||||
isLoading,
|
|
||||||
isError,
|
|
||||||
} = useProfilePage();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
containerStyle,
|
containerStyle,
|
||||||
cardStyle,
|
cardStyle,
|
||||||
buttonStyle,
|
buttonStyle,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
userData,
|
||||||
handleEditUser,
|
handleEditUser,
|
||||||
} = useProfilePageUI(userData);
|
} = useProfilePage();
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
import {
|
import {Grid} from "antd";
|
||||||
useGetAuthenticatedUserDataQuery,
|
import {useDispatch} from "react-redux";
|
||||||
} from "../../../Api/usersApi.js";
|
import {setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
|
||||||
|
import {useGetAuthenticatedUserDataQuery} from "../../../Api/usersApi.js";
|
||||||
|
|
||||||
|
const {useBreakpoint} = Grid;
|
||||||
|
|
||||||
const useProfilePage = () => {
|
const useProfilePage = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: userData = {},
|
data: userData = {},
|
||||||
isLoading: isLoadingUserData,
|
isLoading: isLoadingUserData,
|
||||||
@ -11,10 +17,23 @@ const useProfilePage = () => {
|
|||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const containerStyle = {padding: screens.xs ? 16 : 24};
|
||||||
|
const cardStyle = {marginBottom: 24};
|
||||||
|
const buttonStyle = {width: screens.xs ? "100%" : "auto"};
|
||||||
|
|
||||||
|
|
||||||
|
const handleEditUser = () => {
|
||||||
|
dispatch(setSelectedUser(userData))
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userData,
|
containerStyle,
|
||||||
|
cardStyle,
|
||||||
|
buttonStyle,
|
||||||
|
isMobile: screens.xs,
|
||||||
isLoading: isLoadingUserData,
|
isLoading: isLoadingUserData,
|
||||||
isError: isErrorUserData,
|
isError: isErrorUserData,
|
||||||
|
handleEditUser,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import {Grid} from "antd";
|
|
||||||
import {useDispatch} from "react-redux";
|
|
||||||
import {setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
|
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
|
||||||
|
|
||||||
const useProfilePageUI = (userData) => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const screens = useBreakpoint();
|
|
||||||
|
|
||||||
const containerStyle = { padding: screens.xs ? 16 : 24 };
|
|
||||||
const cardStyle = { marginBottom: 24 };
|
|
||||||
const buttonStyle = { width: screens.xs ? "100%" : "auto" };
|
|
||||||
|
|
||||||
|
|
||||||
const handleEditUser = () => {
|
|
||||||
dispatch(setSelectedUser(userData))
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
containerStyle,
|
|
||||||
cardStyle,
|
|
||||||
buttonStyle,
|
|
||||||
isMobile: screens.xs,
|
|
||||||
handleEditUser,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useProfilePageUI;
|
|
||||||
@ -1,9 +1,7 @@
|
|||||||
import {Button, Modal, Popconfirm, Row, Typography} from "antd";
|
import {Button, Modal, Popconfirm, Row, Typography} from "antd";
|
||||||
import useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js";
|
import useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js";
|
||||||
import useScheduledAppointmentsViewModalUI from "./useScheduledAppointmentsViewModalUI.js";
|
|
||||||
|
|
||||||
const ScheduledAppointmentsViewModal = () => {
|
const ScheduledAppointmentsViewModal = () => {
|
||||||
const scheduledAppointmentsViewModalData = useScheduledAppointmentsViewModal();
|
|
||||||
const {
|
const {
|
||||||
selectedScheduledAppointment,
|
selectedScheduledAppointment,
|
||||||
modalWidth,
|
modalWidth,
|
||||||
@ -19,7 +17,7 @@ const ScheduledAppointmentsViewModal = () => {
|
|||||||
onCancel,
|
onCancel,
|
||||||
cancelScheduledAppointment,
|
cancelScheduledAppointment,
|
||||||
handleConvertToAppointment,
|
handleConvertToAppointment,
|
||||||
} = useScheduledAppointmentsViewModalUI(scheduledAppointmentsViewModalData.cancelAppointment);
|
} = useScheduledAppointmentsViewModal();
|
||||||
|
|
||||||
if (!selectedScheduledAppointment) {
|
if (!selectedScheduledAppointment) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,10 +1,110 @@
|
|||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { setSelectedScheduledAppointment, openModalWithScheduledData } from "../../../Redux/Slices/appointmentsSlice.js";
|
||||||
|
import { notification } from "antd";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import {useCancelScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
|
import {useCancelScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
|
||||||
|
|
||||||
|
|
||||||
const useScheduledAppointmentsViewModal = () => {
|
const useScheduledAppointmentsViewModal = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { selectedScheduledAppointment } = useSelector((state) => state.appointmentsUI);
|
||||||
const [cancelAppointment] = useCancelScheduledAppointmentMutation();
|
const [cancelAppointment] = useCancelScheduledAppointmentMutation();
|
||||||
|
|
||||||
return {cancelAppointment};
|
|
||||||
|
const modalWidth = 700;
|
||||||
|
const blockStyle = { marginBottom: 16 };
|
||||||
|
const footerRowStyle = { marginTop: 16, gap: 8 };
|
||||||
|
const footerButtonStyle = { marginRight: 8 };
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
title: "Просмотр запланированного приема",
|
||||||
|
patient: "Пациент:",
|
||||||
|
birthday: "Дата рождения:",
|
||||||
|
email: "Email:",
|
||||||
|
phone: "Телефон:",
|
||||||
|
type: "Тип приема:",
|
||||||
|
appointmentTime: "Время приема:",
|
||||||
|
closeButton: "Закрыть",
|
||||||
|
convertButton: "Конвертировать в прием",
|
||||||
|
cancelButton: "Отмена приема",
|
||||||
|
popconfirmTitle: "Вы уверены, что хотите отменить прием?",
|
||||||
|
popconfirmOk: "Да, отменить",
|
||||||
|
popconfirmCancel: "Отмена",
|
||||||
|
notSpecified: "Не указан",
|
||||||
|
};
|
||||||
|
|
||||||
|
const visible = !!selectedScheduledAppointment;
|
||||||
|
|
||||||
|
const getDateString = (date) => {
|
||||||
|
return date ? dayjs(date).format("DD.MM.YYYY") : labels.notSpecified;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAppointmentTime = (datetime) => {
|
||||||
|
return datetime
|
||||||
|
? dayjs(datetime).format("DD.MM.YYYY HH:mm")
|
||||||
|
: labels.notSpecified;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPatientName = (patient) => {
|
||||||
|
return patient
|
||||||
|
? `${patient.last_name} ${patient.first_name}`
|
||||||
|
: labels.notSpecified;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPatientField = (field) => {
|
||||||
|
return field || labels.notSpecified;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
dispatch(setSelectedScheduledAppointment(null));
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelScheduledAppointment = async () => {
|
||||||
|
try {
|
||||||
|
await cancelAppointment(selectedScheduledAppointment.id);
|
||||||
|
notification.success({
|
||||||
|
message: "Прием отменен",
|
||||||
|
placement: "topRight",
|
||||||
|
description: "Прием успешно отменен.",
|
||||||
|
});
|
||||||
|
onCancel();
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка",
|
||||||
|
description: error?.data?.detail || "Не удалось отменить прием.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConvertToAppointment = () => {
|
||||||
|
if (selectedScheduledAppointment) {
|
||||||
|
dispatch(
|
||||||
|
openModalWithScheduledData({
|
||||||
|
id: selectedScheduledAppointment.id,
|
||||||
|
patient_id: selectedScheduledAppointment.patient?.id,
|
||||||
|
type_id: selectedScheduledAppointment.type?.id,
|
||||||
|
appointment_datetime: selectedScheduledAppointment.scheduled_datetime,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedScheduledAppointment,
|
||||||
|
modalWidth,
|
||||||
|
blockStyle,
|
||||||
|
footerRowStyle,
|
||||||
|
footerButtonStyle,
|
||||||
|
labels,
|
||||||
|
visible,
|
||||||
|
getDateString,
|
||||||
|
getAppointmentTime,
|
||||||
|
getPatientName,
|
||||||
|
getPatientField,
|
||||||
|
onCancel,
|
||||||
|
cancelScheduledAppointment,
|
||||||
|
handleConvertToAppointment,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useScheduledAppointmentsViewModal;
|
export default useScheduledAppointmentsViewModal;
|
||||||
@ -1,107 +0,0 @@
|
|||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { setSelectedScheduledAppointment, openModalWithScheduledData } from "../../../Redux/Slices/appointmentsSlice.js";
|
|
||||||
import { notification } from "antd";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
const useScheduledAppointmentsViewModalUI = (cancelAppointment) => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { selectedScheduledAppointment } = useSelector((state) => state.appointmentsUI);
|
|
||||||
|
|
||||||
const modalWidth = 700;
|
|
||||||
const blockStyle = { marginBottom: 16 };
|
|
||||||
const footerRowStyle = { marginTop: 16, gap: 8 };
|
|
||||||
const footerButtonStyle = { marginRight: 8 };
|
|
||||||
|
|
||||||
const labels = {
|
|
||||||
title: "Просмотр запланированного приема",
|
|
||||||
patient: "Пациент:",
|
|
||||||
birthday: "Дата рождения:",
|
|
||||||
email: "Email:",
|
|
||||||
phone: "Телефон:",
|
|
||||||
type: "Тип приема:",
|
|
||||||
appointmentTime: "Время приема:",
|
|
||||||
closeButton: "Закрыть",
|
|
||||||
convertButton: "Конвертировать в прием",
|
|
||||||
cancelButton: "Отмена приема",
|
|
||||||
popconfirmTitle: "Вы уверены, что хотите отменить прием?",
|
|
||||||
popconfirmOk: "Да, отменить",
|
|
||||||
popconfirmCancel: "Отмена",
|
|
||||||
notSpecified: "Не указан",
|
|
||||||
};
|
|
||||||
|
|
||||||
const visible = !!selectedScheduledAppointment;
|
|
||||||
|
|
||||||
const getDateString = (date) => {
|
|
||||||
return date ? dayjs(date).format("DD.MM.YYYY") : labels.notSpecified;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAppointmentTime = (datetime) => {
|
|
||||||
return datetime
|
|
||||||
? dayjs(datetime).format("DD.MM.YYYY HH:mm")
|
|
||||||
: labels.notSpecified;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPatientName = (patient) => {
|
|
||||||
return patient
|
|
||||||
? `${patient.last_name} ${patient.first_name}`
|
|
||||||
: labels.notSpecified;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPatientField = (field) => {
|
|
||||||
return field || labels.notSpecified;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCancel = () => {
|
|
||||||
dispatch(setSelectedScheduledAppointment(null));
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelScheduledAppointment = async () => {
|
|
||||||
try {
|
|
||||||
await cancelAppointment(selectedScheduledAppointment.id);
|
|
||||||
notification.success({
|
|
||||||
message: "Прием отменен",
|
|
||||||
placement: "topRight",
|
|
||||||
description: "Прием успешно отменен.",
|
|
||||||
});
|
|
||||||
onCancel();
|
|
||||||
} catch (error) {
|
|
||||||
notification.error({
|
|
||||||
message: "Ошибка",
|
|
||||||
description: error?.data?.detail || "Не удалось отменить прием.",
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConvertToAppointment = () => {
|
|
||||||
if (selectedScheduledAppointment) {
|
|
||||||
dispatch(
|
|
||||||
openModalWithScheduledData({
|
|
||||||
id: selectedScheduledAppointment.id,
|
|
||||||
patient_id: selectedScheduledAppointment.patient?.id,
|
|
||||||
type_id: selectedScheduledAppointment.type?.id,
|
|
||||||
appointment_datetime: selectedScheduledAppointment.scheduled_datetime,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectedScheduledAppointment,
|
|
||||||
modalWidth,
|
|
||||||
blockStyle,
|
|
||||||
footerRowStyle,
|
|
||||||
footerButtonStyle,
|
|
||||||
labels,
|
|
||||||
visible,
|
|
||||||
getDateString,
|
|
||||||
getAppointmentTime,
|
|
||||||
getPatientName,
|
|
||||||
getPatientField,
|
|
||||||
onCancel,
|
|
||||||
cancelScheduledAppointment,
|
|
||||||
handleConvertToAppointment,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useScheduledAppointmentsViewModalUI;
|
|
||||||
@ -1,12 +1,12 @@
|
|||||||
import { Select, Tooltip } from "antd";
|
import { Select, Tooltip } from "antd";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { ViewModPropType } from "../../../Types/viewModPropType.js";
|
import { ViewModPropType } from "../../../Types/viewModPropType.js";
|
||||||
import useSelectViewModeUI from "./useSelectViewModeUI.js";
|
import useSelectViewMode from "./useSelectViewMode.js";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const SelectViewMode = ({ viewMode, setViewMode, localStorageKey, toolTipText, viewModes }) => {
|
const SelectViewMode = ({ viewMode, setViewMode, localStorageKey, toolTipText, viewModes }) => {
|
||||||
const { selectStyle, handleChange } = useSelectViewModeUI({ setViewMode, localStorageKey });
|
const { selectStyle, handleChange } = useSelectViewMode({ setViewMode, localStorageKey });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={toolTipText}>
|
<Tooltip title={toolTipText}>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { cacheInfo } from "../../../Utils/cachedInfoUtils.js";
|
import { cacheInfo } from "../../../Utils/cachedInfoUtils.js";
|
||||||
|
|
||||||
const useSelectViewModeUI = ({ setViewMode, localStorageKey }) => {
|
const useSelectViewMode = ({ setViewMode, localStorageKey }) => {
|
||||||
const selectStyle = {
|
const selectStyle = {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
};
|
};
|
||||||
@ -16,4 +16,4 @@ const useSelectViewModeUI = ({ setViewMode, localStorageKey }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useSelectViewModeUI;
|
export default useSelectViewMode;
|
||||||
Loading…
x
Reference in New Issue
Block a user