feat: Обновление UI и логики форм и страниц

Обновлены компоненты форм, страницы авторизации и другие. Добавлены индикаторы загрузки и улучшены обработка ошибок.
This commit is contained in:
Андрей Дувакин 2025-06-06 19:24:47 +05:00
parent 333a471f9d
commit 6cfa2dcca2
12 changed files with 85 additions and 42 deletions

View File

@ -331,7 +331,7 @@ const AppointmentFormModal = () => {
<Button
style={appointmentFormModalUI.footerButtonStyle}
onClick={appointmentFormModalUI.handleClickBackButton}
disabled={appointmentFormModalUI.disableBackButton || appointmentFormModalUI.isUploadingFile || appointmentFormModalData.isCreating}
disabled={appointmentFormModalUI.disableBackButton || appointmentFormModalUI.isUploadingFile || appointmentFormModalData.isProcessed}
>
Назад
</Button>
@ -339,7 +339,7 @@ const AppointmentFormModal = () => {
type="primary"
onClick={appointmentFormModalUI.handleClickNextButton}
disabled={appointmentFormModalUI.disableNextButton}
loading={appointmentFormModalData.isCreating || appointmentFormModalUI.isUploadingFile}
loading={appointmentFormModalData.isProcessed || appointmentFormModalUI.isUploadingFile}
>
{appointmentFormModalUI.nextButtonText}
</Button>

View File

@ -1,10 +1,10 @@
import { useGetPatientsQuery } from "../../../Api/patientsApi.js";
import { useGetAppointmentTypesQuery } from "../../../Api/appointmentTypesApi.js";
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js";
import {
useCreateAppointmentMutation,
useGetByPatientIdQuery,
} from "../../../Api/appointmentsApi.js";
import { useCancelScheduledAppointmentMutation } from "../../../Api/scheduledAppointmentsApi.js";
import {useCancelScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
const useAppointmentFormModal = () => {
const {
@ -18,8 +18,11 @@ const useAppointmentFormModal = () => {
isError: isErrorAppointmentTypes,
} = useGetAppointmentTypesQuery(undefined);
const [createAppointment, { isLoading: isCreating, isError: isErrorCreating }] = useCreateAppointmentMutation();
const [cancelAppointment] = useCancelScheduledAppointmentMutation();
const [createAppointment, {isLoading: isCreating, isError: isErrorCreating}] = useCreateAppointmentMutation();
const [cancelAppointment, {
isLoading: isCanceling,
isError: isErrorCanceling
}] = useCancelScheduledAppointmentMutation();
return {
patients,
@ -27,9 +30,9 @@ const useAppointmentFormModal = () => {
createAppointment,
cancelAppointment,
useGetByPatientIdQuery,
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
isCreating,
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating || isCanceling,
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating || isErrorCanceling,
isProcessed: isCreating || isCanceling,
};
};

View File

@ -17,6 +17,7 @@ const PatientFormModal = () => {
open={patientFormModalUI.isModalVisible}
onCancel={patientFormModalUI.handleCancel}
onOk={patientFormModalUI.handleOk}
confirmLoading={patientFormModalData.isOperationLoading}
okText={"Сохранить"}
cancelText={"Отмена"}
maskClosable={false}

View File

@ -7,8 +7,8 @@ import {useAddPatientMutation, useUpdatePatientMutation} from "../../../Api/pati
const usePatientFormModal = () => {
const dispatch = useDispatch();
const [addPatient] = useAddPatientMutation();
const [updatePatient] = useUpdatePatientMutation();
const [addPatient, { isLoading: isAdding }] = useAddPatientMutation();
const [updatePatient, { isLoading: isUpdating }] = useUpdatePatientMutation();
const {
selectedPatient,
@ -44,6 +44,7 @@ const usePatientFormModal = () => {
return {
handleModalSubmit,
isOperationLoading: isAdding || isUpdating
};
};

View File

@ -96,6 +96,7 @@ const ScheduledAppointmentFormModal = () => {
disabled={scheduledAppointmentFormModalUI.selectedPatient === null || scheduledAppointmentFormModalUI.selectedDateTime === null || scheduledAppointmentFormModalUI.selectedAppointmentType === null}
onClick={scheduledAppointmentFormModalUI.handleCreateScheduledAppointment}
style={scheduledAppointmentFormModalUI.buttonStyle}
loading={scheduledAppointmentFormModalData.isProcessing}
>
Запланировать
</Button>

View File

@ -28,6 +28,7 @@ const useScheduledAppointmentFormModal = () => {
createScheduledAppointment,
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
isProcessing: isCreating
};
};

View File

@ -5,7 +5,7 @@ import useLensForm from "./useLensForm.js";
import useLensFormUI from "./useLensFormUI.js";
const LensFormModal = ({visible, onCancel, onSubmit, lens}) => {
const LensFormModal = ({visible, onCancel, onSubmit, lens, isProcessing}) => {
const lensFormData = useLensForm();
const lensFormUI = useLensFormUI(lensFormData.lensTypes, visible, onCancel, onSubmit, lens);
@ -15,6 +15,7 @@ const LensFormModal = ({visible, onCancel, onSubmit, lens}) => {
open={visible}
onCancel={lensFormUI.handleCancel}
onOk={lensFormUI.handleOk}
confirmLoading={isProcessing}
okText={"Сохранить"}
cancelText={"Отмена"}
maskClosable={false}
@ -156,6 +157,7 @@ LensFormModal.propTypes = {
onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
lens: LensPropType,
isProcessing: PropTypes.bool,
}
export default LensFormModal;

View File

@ -124,7 +124,12 @@ const LensesTab = () => {
okText="Да, удалить"
cancelText="Отмена"
>
<Button danger>Удалить</Button>
<Button
loading={lensesData.isProcessing}
danger
>
Удалить
</Button>
</Popconfirm>
</div>
),
@ -327,6 +332,7 @@ const LensesTab = () => {
onCancel={lensesUI.handleCloseModal}
onSubmit={lensesData.handleModalSubmit}
lens={lensesUI.selectedLens}
isProcessing={lensesData.isProcessing}
/>
</div>
);

View File

@ -20,9 +20,9 @@ const useLenses = () => {
pollingInterval: 20000,
});
const [addLens] = useAddLensMutation();
const [updateLens] = useUpdateLensMutation();
const [deleteLens] = useDeleteLensMutation();
const [addLens, {isLoading: isAdding}] = useAddLensMutation();
const [updateLens, {isLoading: isUpdating}] = useUpdateLensMutation();
const [deleteLens, {isLoading: isDeleting}] = useDeleteLensMutation();
const handleDeleteLens = async (lensId) => {
try {
@ -73,6 +73,7 @@ const useLenses = () => {
lenses,
isLoading,
isError,
isProcessing: isAdding || isUpdating || isDeleting,
handleDeleteLens,
handleModalSubmit,
}

View File

@ -1,31 +1,40 @@
import {Form, Input, Button, Row, Col, Typography} from "antd";
import {useSelector} from "react-redux";
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 LoginPage = () => {
const {error} = useSelector((state) => state.auth);
const {onFinish, isLoading} = useLoginPage();
const {containerStyle, formContainerStyle, titleStyle, errorStyle, labels} = useLoginPageUI();
const {
containerStyle,
formContainerStyle,
titleStyle,
logoBlockStyle,
logoStyle,
appNameStyle,
labels
} = useLoginPageUI();
return (
<Row justify="center" align="middle" style={containerStyle}>
<Col xs={24} sm={18} md={12} lg={8} xl={6}>
<Space direction="horizontal" style={logoBlockStyle} size="large">
<Image
src="/logo_rounded.png"
style={logoStyle}
preview={false}
alt="Visus API Logo"
/>
<Title level={1} style={appNameStyle}>
Visus+
</Title>
</Space>
<div style={formContainerStyle}>
<Title level={2} style={titleStyle}>
{labels.title}
</Title>
{error && (
<div style={errorStyle}>
{labels.errorPrefix}
{error}
</div>
)}
<Form name="login" initialValues={{remember: true}} onFinish={onFinish}>
<Form.Item
name="login"

View File

@ -13,14 +13,14 @@ const useLoginPage = () => {
const response = await loginUser(loginData).unwrap();
const token = response.access_token || response.token;
if (!token) {
throw new Error("Токен не получен от сервера");
throw new Error("Сервер не вернул токен авторизации");
}
localStorage.setItem("access_token", token);
dispatch(setUser({ token }));
await dispatch(checkAuth()).unwrap();
} catch (error) {
const errorMessage = error?.data?.detail || "Не удалось войти";
const errorMessage = error?.data?.detail || "Не удалось войти. Проверьте логин и пароль.";
console.error(error);
dispatch(setError(errorMessage));
notification.error({

View File

@ -1,13 +1,13 @@
import {useEffect, useRef} from "react";
import {useNavigate} from "react-router-dom";
import {useSelector} from "react-redux";
import {Grid} from "antd";
import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { Grid } from "antd";
const {useBreakpoint} = Grid;
const { useBreakpoint } = Grid;
const useLoginPageUI = () => {
const navigate = useNavigate();
const {user, userData, isLoading} = useSelector((state) => state.auth);
const { user, userData, isLoading } = useSelector((state) => state.auth);
const screens = useBreakpoint();
const hasRedirected = useRef(false);
@ -19,15 +19,32 @@ const useLoginPageUI = () => {
padding: screens.xs ? 10 : 20,
border: "1px solid #ddd",
borderRadius: 8,
textAlign: "center",
};
const titleStyle = {
textAlign: "center",
marginBottom: 20,
};
const errorStyle = {
color: "red",
marginBottom: 15,
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 = {
@ -37,7 +54,6 @@ const useLoginPageUI = () => {
submitButton: "Войти",
loginRequired: "Пожалуйста, введите логин",
passwordRequired: "Пожалуйста, введите пароль",
errorPrefix: "Ошибка при входе: ",
};
useEffect(() => {
@ -52,8 +68,10 @@ const useLoginPageUI = () => {
containerStyle,
formContainerStyle,
titleStyle,
errorStyle,
logoStyle,
appNameStyle,
labels,
logoBlockStyle,
};
};