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
|
||||
patronymic: Optional[str] = None
|
||||
login: str
|
||||
is_blocked: bool
|
||||
|
||||
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 werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
@ -13,6 +13,7 @@ class User(BaseModel):
|
||||
patronymic = Column(VARCHAR(200))
|
||||
login = Column(String, nullable=False, unique=True)
|
||||
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)
|
||||
|
||||
|
||||
@ -180,6 +180,7 @@ class UsersService:
|
||||
last_name=user.last_name,
|
||||
patronymic=user.patronymic,
|
||||
login=user.login,
|
||||
is_blocked=user.is_blocked,
|
||||
)
|
||||
|
||||
if user.id is not None:
|
||||
@ -196,4 +197,5 @@ class UsersService:
|
||||
patronymic=user.patronymic,
|
||||
login=user.login,
|
||||
role_id=user.role_id,
|
||||
is_blocked=user.is_blocked,
|
||||
)
|
||||
|
||||
@ -11,8 +11,8 @@ service:
|
||||
|
||||
resources:
|
||||
limits:
|
||||
memory: 512Mi
|
||||
cpu: 500m
|
||||
memory: 128Mi
|
||||
cpu: 200m
|
||||
|
||||
persistence:
|
||||
path: /mnt/k8s_storage/visus-api
|
||||
|
||||
@ -11,8 +11,8 @@ service:
|
||||
|
||||
resources:
|
||||
limits:
|
||||
memory: 512Mi
|
||||
cpu: 500m
|
||||
memory: 128Mi
|
||||
cpu: 200m
|
||||
|
||||
ingress:
|
||||
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 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 LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import useAdminPage from "./useAdminPage.js";
|
||||
@ -41,6 +41,18 @@ const AdminPage = () => {
|
||||
filters: adminPageData.roles.map(role => ({text: role.title, value: role.title})),
|
||||
onFilter: (value, record) => record.role.title === value,
|
||||
},
|
||||
{
|
||||
title: "Заблокирован",
|
||||
dataIndex: ["is_blocked"],
|
||||
key: "is_blocked",
|
||||
render: (value, record) => (
|
||||
<Switch
|
||||
value={value}
|
||||
checkedChildren={"Заблокирован"}
|
||||
unCheckedChildren={"Разблокирован"}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Действия",
|
||||
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 useLoginPageUI from "./useLoginPageUI.js";
|
||||
|
||||
const {Title} = Typography;
|
||||
const { Title } = Typography;
|
||||
|
||||
const LoginPage = () => {
|
||||
const {onFinish, isLoading} = useLoginPage();
|
||||
const {
|
||||
containerStyle,
|
||||
formContainerStyle,
|
||||
@ -13,8 +11,10 @@ const LoginPage = () => {
|
||||
logoBlockStyle,
|
||||
logoStyle,
|
||||
appNameStyle,
|
||||
labels
|
||||
} = useLoginPageUI();
|
||||
labels,
|
||||
isLoading,
|
||||
onFinish,
|
||||
} = useLoginPage();
|
||||
|
||||
return (
|
||||
<Row justify="center" align="middle" style={containerStyle}>
|
||||
@ -35,19 +35,19 @@ const LoginPage = () => {
|
||||
{labels.title}
|
||||
</Title>
|
||||
|
||||
<Form name="login" initialValues={{remember: true}} onFinish={onFinish}>
|
||||
<Form name="login" initialValues={{ remember: true }} onFinish={onFinish}>
|
||||
<Form.Item
|
||||
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
|
||||
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>
|
||||
|
||||
@ -1,12 +1,73 @@
|
||||
import { useDispatch } from "react-redux";
|
||||
import { notification } from "antd";
|
||||
import { useEffect, useRef } from "react";
|
||||
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 { useLoginMutation } from "../../../Api/authApi.js";
|
||||
import { checkAuth } from "../../../Redux/Slices/authSlice.js";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
const useLoginPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
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) => {
|
||||
try {
|
||||
@ -32,6 +93,13 @@ const useLoginPage = () => {
|
||||
};
|
||||
|
||||
return {
|
||||
containerStyle,
|
||||
formContainerStyle,
|
||||
titleStyle,
|
||||
logoStyle,
|
||||
appNameStyle,
|
||||
labels,
|
||||
logoBlockStyle,
|
||||
onFinish,
|
||||
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 LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import useProfilePage from "./useProfilePage.js";
|
||||
import useProfilePageUI from "./useProfilePageUI.js";
|
||||
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
||||
|
||||
const ProfilePage = () => {
|
||||
const {
|
||||
userData,
|
||||
isLoading,
|
||||
isError,
|
||||
} = useProfilePage();
|
||||
|
||||
const {
|
||||
containerStyle,
|
||||
cardStyle,
|
||||
buttonStyle,
|
||||
isLoading,
|
||||
isError,
|
||||
userData,
|
||||
handleEditUser,
|
||||
} = useProfilePageUI(userData);
|
||||
} = useProfilePage();
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import {
|
||||
useGetAuthenticatedUserDataQuery,
|
||||
} from "../../../Api/usersApi.js";
|
||||
import {Grid} from "antd";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
|
||||
import {useGetAuthenticatedUserDataQuery} from "../../../Api/usersApi.js";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
const useProfilePage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const screens = useBreakpoint();
|
||||
|
||||
const {
|
||||
data: userData = {},
|
||||
isLoading: isLoadingUserData,
|
||||
@ -11,10 +17,23 @@ const useProfilePage = () => {
|
||||
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 {
|
||||
userData,
|
||||
containerStyle,
|
||||
cardStyle,
|
||||
buttonStyle,
|
||||
isMobile: screens.xs,
|
||||
isLoading: isLoadingUserData,
|
||||
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 useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js";
|
||||
import useScheduledAppointmentsViewModalUI from "./useScheduledAppointmentsViewModalUI.js";
|
||||
|
||||
const ScheduledAppointmentsViewModal = () => {
|
||||
const scheduledAppointmentsViewModalData = useScheduledAppointmentsViewModal();
|
||||
const {
|
||||
selectedScheduledAppointment,
|
||||
modalWidth,
|
||||
@ -19,7 +17,7 @@ const ScheduledAppointmentsViewModal = () => {
|
||||
onCancel,
|
||||
cancelScheduledAppointment,
|
||||
handleConvertToAppointment,
|
||||
} = useScheduledAppointmentsViewModalUI(scheduledAppointmentsViewModalData.cancelAppointment);
|
||||
} = useScheduledAppointmentsViewModal();
|
||||
|
||||
if (!selectedScheduledAppointment) {
|
||||
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";
|
||||
|
||||
|
||||
const useScheduledAppointmentsViewModal = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { selectedScheduledAppointment } = useSelector((state) => state.appointmentsUI);
|
||||
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;
|
||||
@ -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 PropTypes from "prop-types";
|
||||
import { ViewModPropType } from "../../../Types/viewModPropType.js";
|
||||
import useSelectViewModeUI from "./useSelectViewModeUI.js";
|
||||
import useSelectViewMode from "./useSelectViewMode.js";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const SelectViewMode = ({ viewMode, setViewMode, localStorageKey, toolTipText, viewModes }) => {
|
||||
const { selectStyle, handleChange } = useSelectViewModeUI({ setViewMode, localStorageKey });
|
||||
const { selectStyle, handleChange } = useSelectViewMode({ setViewMode, localStorageKey });
|
||||
|
||||
return (
|
||||
<Tooltip title={toolTipText}>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { cacheInfo } from "../../../Utils/cachedInfoUtils.js";
|
||||
|
||||
const useSelectViewModeUI = ({ setViewMode, localStorageKey }) => {
|
||||
const useSelectViewMode = ({ setViewMode, localStorageKey }) => {
|
||||
const selectStyle = {
|
||||
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