Commit 24fa6480 authored by Merekeyev Dias's avatar Merekeyev Dias

refactoring

parent 41fd096a
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { RootState } from '../main';
import { notification } from 'antd';
const baseQuery = fetchBaseQuery({
baseUrl: 'http://77.243.80.217:8080/esutd/api',
prepareHeaders: (headers, { getState }) => {
const accessToken = (getState() as RootState).auth.token;
if (accessToken) {
headers.set("Authorization", `Bearer ${accessToken}`);
}
headers.set("Accept", "*/*");
return headers;
},
});
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: async (arg, api, extraOptions) => {
try {
const result: any = await baseQuery(arg, api, extraOptions);
if (result && result.error && result.error.data.errorMessage) {
notification.error({ message: result.error.data.errorMessage });
}
return result;
} catch (error) {
console.error("Error occurred during API call:", error);
throw error;
}
},
tagTypes: ["organizations", "requests", "manuals"],
endpoints: () => ({}),
});
\ No newline at end of file
import { apiSlice } from "./apiSlice";
import { apiSlice } from "./mainApi";
export const manualApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
......
import { apiSlice } from "./apiSlice";
import { apiSlice } from "./mainApi";
interface Organization {
id: number;
......@@ -18,33 +18,26 @@ export const organizationApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getOrganizations: builder.query<Organization[], void>({
query: () => 'organizations/list',
providesTags: ["organizations"],
providesTags: [{ type: "organizations", id: "LIST" }],
}),
getOrganization: builder.query<Organization, string>({
query: (orgId) => `organizations/info?id=${orgId}`,
}),
addNewOrganization: builder.mutation<Organization, Partial<Organization>>({
query: (initialOrg) => ({
url: 'organizations/save',
method: 'POST',
body: initialOrg,
}),
invalidatesTags: ["organizations"],
providesTags: (_, __, arg) => [{ type: "organizations", id: arg }]
}),
editOrganization: builder.mutation<Organization, Partial<Organization>>({
query: ({ ...rest }) => ({
url: `organizations/save?id=${rest.id}`,
query: ( initialOrg ) => ({
url: `organizations/save?id=${initialOrg.id}`,
method: 'POST',
body: rest,
body: initialOrg,
}),
invalidatesTags: ["organizations"],
invalidatesTags: (res) => [{ type: "organizations", id: res?.id }, { type: "organizations", id: "LIST" }],
}),
deleteOrganization: builder.mutation<{ success: boolean; code: string }, string>({
query: (orgCode) => ({
url: `organizations/delete?code=${orgCode}`,
method: 'POST'
}),
invalidatesTags: ["organizations"],
invalidatesTags: [{ type: "organizations", id: "LIST" }],
}),
})
})
......@@ -52,7 +45,6 @@ export const organizationApiSlice = apiSlice.injectEndpoints({
export const {
useGetOrganizationsQuery,
useGetOrganizationQuery,
useAddNewOrganizationMutation,
useEditOrganizationMutation,
useDeleteOrganizationMutation,
} = organizationApiSlice;
\ No newline at end of file
import { apiSlice } from "./apiSlice";
import { apiSlice } from "./mainApi";
interface Request {
id: number;
......
......@@ -4,7 +4,6 @@
display: flex;
padding: 1rem .5rem;
justify-content: space-between;
/* min-width: 1440px; */
}
.header_left {
......
import React, { useEffect, useState } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import { Menu, Button, Drawer } from 'antd';
import { useDispatch } from 'react-redux';
import { clearToken } from '../../store/store';
import { Menu, Button, Drawer } from 'antd';
import type { MenuProps } from 'antd';
import { clearToken } from '@/store/store';
import './Header.css';
type MenuItem = Required<MenuProps>['items'][number];
......
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { RootState } from '../../main';
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: "http://77.243.80.217:8080/esutd/api",
prepareHeaders: (headers, { getState }) => {
const state = getState() as RootState;
const accessToken = state.auth.token;
if (accessToken) {
headers.set("Authorization", `Bearer ${accessToken}`);
}
headers.set("Content-Type", "application/json");
headers.set("Accept", "application/json");
return headers;
},
}),
tagTypes: ["organizations", "requests", "manuals"],
endpoints: () => ({}),
});
\ No newline at end of file
......@@ -4,9 +4,10 @@
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
padding: 24px;
padding: 1.5rem;
background-color: rgba(209, 209, 209, 0.866);
border-radius: 8px;
border-radius: .5rem;
}
.form_btn {
......
......@@ -2,9 +2,9 @@ import React, { useState } from 'react';
import { Input, Typography, Button, notification } from 'antd';
import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { setToken } from '../../store/store';
import { setToken } from '@/store/store';
import styles from './AuthPage.module.css';
// import { useGetOrganizationsQuery } from '../../features/api/apiSlice';
const AuthPage: React.FC = () => {
const [token, setTokenInput] = useState<string>('');
......@@ -13,7 +13,7 @@ const AuthPage: React.FC = () => {
const handleLogin = async () => {
try {
const response = await fetch('http://77.243.80.217:8080/esutd/api/organizations/list', { // Get запрос
const response = await fetch('http://77.243.80.217:8080/esutd/api/organizations/list', {
headers: {
'Authorization': `Bearer ${token}`,
},
......@@ -42,7 +42,6 @@ const AuthPage: React.FC = () => {
};
return (
<div className={styles.wrapper}>
<div className={styles.form}>
<Typography.Title level={5}>Токен</Typography.Title>
<Input
......@@ -53,7 +52,6 @@ const AuthPage: React.FC = () => {
/>
<Button className={styles.form_btn} type="primary" onClick={handleLogin}>Войти</Button>
</div>
</div>
);
};
......
......@@ -4,9 +4,9 @@ import { useDispatch, useSelector } from 'react-redux';
import { Button, Space, Tag, Modal, Checkbox, message } from 'antd';
import type { TableProps } from 'antd';
import { useDeleteOrganizationMutation } from '../../features/api/organizationApiSlice';
import { addCheckedCode, removeCheckedCode } from '../../features/sync_btn/syncBtn';
import { setCurrentPage } from '../../features/pagination/mainPagination';
import { useDeleteOrganizationMutation } from '@/api/organizationApi';
import { addCheckedCode, removeCheckedCode } from '@/store/slices/syncBtn';
import { setCurrentPage } from '@/store/slices/mainPagination';
import { getStatusText, getStatusColor } from './utils';
export interface DataType {
......
import React, { createRef, useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
import { useSelector, useDispatch } from 'react-redux';
import { Button, message, Space, Spin, Table } from 'antd';
import { Button, message, Space, Table } from 'antd';
import { TableRef } from 'antd/es/table';
import { useGetOrganizationsQuery } from '../../features/api/organizationApiSlice';
import { useUpdateDictionaryMutation } from '../../features/api/manualApiSlice';
import { setCurrentPage, setTotalOrgNumber } from '../../features/pagination/mainPagination';
import { setDisabled, clearCheckedCodes } from '../../features/sync_btn/syncBtn';
import { useGetOrganizationsQuery } from '@/api/organizationApi';
import { useUpdateDictionaryMutation } from '@/api/manualApi';
import { setCurrentPage, setTotalOrgNumber } from '@/store/slices/mainPagination';
import { setDisabled, clearCheckedCodes } from '@/store/slices/syncBtn';
import { DataType, columns } from './Columns';
import './Homepage.css';
......@@ -15,38 +15,33 @@ const HomePage: React.FC = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const tableRef = createRef<TableRef>();
const [updateDictionary] = useUpdateDictionaryMutation();
const totalOrgNumber = useSelector((state: any) => state.mainPagination.totalOrgNumber);
const currentPage = useSelector((state: any) => state.mainPagination.currentPage);
const ORG_NUMBER_PER_PAGE = useSelector((state: any) => state.mainPagination.ORG_NUMBER_PER_PAGE);
const syncBtnState = useSelector((state: any) => state.syncBtn.disabled);
const checkedCodes = useSelector((state: any) => state.syncBtn.checkedCodes);
const [updateDictionary] = useUpdateDictionaryMutation();
const [isSyncLoading, setIsSyncLoading] = useState(false);
const [isTableLoading, setIsTableLoading] = useState(false);
const handleAddClick = () => {
navigate("/add");
};
const handleSyncClick = async () => {
setIsSyncLoading(true);
try {
console.log(checkedCodes);
await updateDictionary(checkedCodes).unwrap();
message.success('Организации успешны синхронизированы!')
} catch (err) {
console.log(err);
message.error('Произошла ошибка при синхронизации организации!')
} finally {
dispatch(clearCheckedCodes());
setIsSyncLoading(false);
}
};
};
const handlePaginationChange = (page: number) => {
/*
scrollIntoView resets to the top of the table after changing the page with Pagination. But the bug was found - the div above the table scrolls to top of the page too, under the Header component. It happens because of overflow
*/
// tableRef.current?.nativeElement.scrollIntoView({ behavior: 'smooth' });
dispatch(setCurrentPage(page));
};
......@@ -66,15 +61,19 @@ const HomePage: React.FC = () => {
let table;
if (isLoading) {
table = <Spin />
} else if (isSuccess) {
if (isSuccess) {
table = <Table
dataSource={paginatedData(orgs)}
columns={columns}
rowKey="id"
className='home_table'
ref={tableRef}
loading={isTableLoading}
scroll={{
scrollToFirstRowOnChange: true,
x: 'max-content',
y: 470
}}
pagination={{
position: ['bottomCenter'],
defaultCurrent: 1,
......@@ -91,7 +90,10 @@ const HomePage: React.FC = () => {
}
useEffect(() => {
if (isSuccess) {
if (isLoading) {
setIsTableLoading(true);
} else if (isSuccess) {
setIsTableLoading(false);
dispatch(setTotalOrgNumber(orgs.length));
}
}, [isSuccess, orgs]);
......@@ -128,17 +130,6 @@ const HomePage: React.FC = () => {
<div className="data">
{table}
</div>
{/* <div className='pagination-container'>
<Pagination
current={currentPage}
defaultCurrent={1}
pageSize={ORG_NUMBER_PER_PAGE}
total={totalOrgNumber}
onChange={handlePaginationChange}
hideOnSinglePage={true}
showSizeChanger={false}
/>
</div> */}
</section>
</div>
);
......
.section {
margin-top: 10px 20px;
display: flex;
flex-direction: column;
height: calc(100vh - 60px);
}
.add_title {
font-size: 12px;
line-height: 18px;
......@@ -13,28 +6,7 @@
margin-left: -10px;
}
.data {
flex: 1;
overflow-y: auto;
padding-bottom: 60px;
margin-top: 20px;
margin-left: -10px;
}
.pagination-container {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: white;
padding: 10px 20px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: center;
}
/* Custom checkbox */
/* Custom antd checkbox */
.checkbox_org--custom .ant-checkbox-inner {
border-color: #33333386;
}
\ No newline at end of file
.container {
display: flex;
flex-direction: column;
height: calc(100vh - 80px);
padding: 10px 20px;
overflow: hidden;
}
.form {
margin: 10px 20px 50px;
padding-right: 20px;
......
......@@ -3,8 +3,8 @@ import { useNavigate, useParams } from 'react-router';
import { useSelector, useDispatch } from 'react-redux';
import { Form, Input, Button, Select, message } from 'antd';
import { useAddNewOrganizationMutation, useEditOrganizationMutation, useGetOrganizationQuery, useGetOrganizationsQuery } from '../../features/api/organizationApiSlice';
import { setTotalOrgNumber, setCurrentPage } from '../../features/pagination/mainPagination';
import { useEditOrganizationMutation, useGetOrganizationQuery, useGetOrganizationsQuery } from '@/api/organizationApi';
import { setTotalOrgNumber, setCurrentPage } from '@/store/slices/mainPagination';
import './ManageOrganization.css';
interface OrganizationValues {
......@@ -28,7 +28,6 @@ const ManageOrganization: React.FC = () => {
const isEdit = Boolean(id);
const { data, isLoading, isError } = useGetOrganizationQuery(id!, { skip: !isEdit });
const { data: organizations } = useGetOrganizationsQuery();
const [addNewOrganization] = useAddNewOrganizationMutation();
const [editOrganization] = useEditOrganizationMutation();
const [form] = Form.useForm();
const totalOrgNumber = useSelector((state: any) => state.mainPagination.totalOrgNumber);
......@@ -65,7 +64,6 @@ const ManageOrganization: React.FC = () => {
await editOrganization({ ...normalizedValues }).unwrap();
message.success('Организация успешно обновлена!');
} else {
await addNewOrganization({ ...normalizedValues, created: new Date().toISOString() }).unwrap();
message.success('Организация успешно добавлена!');
dispatch(setTotalOrgNumber(totalOrgNumber + 1));
const newCurrentPage = Math.ceil((totalOrgNumber + 1) / ORG_NUMBER_PER_PAGE);
......
import React, { useState } from 'react';
import { Button, Tag, Modal, message } from 'antd';
import type { TableProps } from 'antd';
import { getStatusText, getStatusColor, openXMLInNewPage, downloadXML } from './utils';
import dayjs from 'dayjs';
import { getStatusText, getStatusColor, openXMLInNewPage, downloadXML } from './utils';
export interface DataType {
id: number;
xml: string;
......@@ -22,16 +23,21 @@ export const columns: TableProps<DataType>['columns'] = [
title: "ID",
dataIndex: "id",
key: "id",
width: '1%'
},
{
title: "XML",
dataIndex: "xml",
key: "xml",
width: '1%',
render: (_, record) => (
<div style={{display: 'flex', alignItems: 'center', gap: 5}}>
<Button type='link' onClick={() => downloadXML(record.xml, record.id)} style={{padding: 0}}>
<img src="img/download_icon.png" alt="" width={18}/>
</Button>
<Button
type='link'
onClick={() => downloadXML(record.xml, record.id)}
style={{padding: 0}}
icon={<span className='material-icons'>download</span>}
/>
<Button type='link' onClick={() => openXMLInNewPage(record.xml)} style={{padding: 0}}>Открыть XML</Button>
</div>
),
......@@ -40,25 +46,28 @@ export const columns: TableProps<DataType>['columns'] = [
title: "Дата отправки",
dataIndex: "sendDate",
key: "sendDate",
width: '11%',
width: '6%',
render: (_, record) => (
<span>{dayjs(record.sendDate).format('DD-MM-YYYY HH:mm:ss')}</span>
)
},
{
title: "Код организации",
title: "Код",
dataIndex: "orgCode",
key: "esutdOrganizationCode",
width: '5%'
},
{
title: "Метод",
dataIndex: "requestType",
key: "requestType"
key: "requestType",
width: '1%'
},
{
title: "Статус",
dataIndex: "status",
key: "status",
width: '1%',
render: (_, record) => (
<Tag color={getStatusColor(record.status)} style={{ fontSize: '12px' }}>
{getStatusText(record.status)}
......@@ -69,6 +78,7 @@ export const columns: TableProps<DataType>['columns'] = [
title: "Сообщение об ошибке",
dataIndex: "errorMessage",
key: "errorMessage",
width: '1%',
render: (_, record) => (
<Button type='link' onClick={() => openXMLInNewPage(record.xml)} style={{padding: 0}}>Открыть сообщение</Button>
)
......@@ -76,16 +86,17 @@ export const columns: TableProps<DataType>['columns'] = [
{
title: "Дата переотправки",
dataIndex: "resendDate",
key: "resendDate"
key: "resendDate",
width: '6%'
},
{
title: "Количество попыток",
dataIndex: "resentCount",
key: "resentCount"
key: "resentCount",
width: '5%'
},
{
title: "Действия",
width: 60,
width: '1%',
render: (_, record) => {
return (
<ResendColumn
......
......@@ -9,15 +9,3 @@
display: flex;
gap: 1rem;
}
\ No newline at end of file
.pagination_container {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding: .5rem 1rem;
display: flex;
justify-content: center;
background: white;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
}
\ No newline at end of file
import React, { useEffect, useState, createRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import dayjs, { Dayjs } from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween';
import { DatePicker, Select, Button, Spin, Table } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import { columns, DataType } from './Columns';
import { useGetRequestsQuery } from '../../features/api/requestApiSlice';
import { setCurrentPage, setTotalReqNumber } from '../../features/pagination/reqPagination';
dayjs.extend(isBetween);
import { DatePicker, Select, Button, Table } from 'antd';
import { TableRef } from 'antd/es/table';
import { useGetRequestsQuery } from '@/api/requestApi';
import { setCurrentPage, setTotalReqNumber } from '@/store/slices/reqPagination'
import { columns, DataType } from './Columns';
import './RequestPage.css';
dayjs.extend(isBetween);
const DATE_FORMAT = 'YYYY-MM-DD';
const DEFAULT_DATES = {
......@@ -32,8 +33,6 @@ const RequestPage: React.FC = () => {
const [isTableLoading, setIsTableLoading] = useState(false);
const handlePaginationChange = (page: number) => {
//scrollIntoView resets to the top of the table after changing the page with Pagination. But the bug was found - the div above the table scrolls to top of the page too, under the Header component. It happens because of overflow
//tableRef.current?.nativeElement.scrollIntoView({ behavior: 'smooth' });
dispatch(setCurrentPage(page));
};
......@@ -103,6 +102,13 @@ const RequestPage: React.FC = () => {
dataSource={paginateData(data)}
columns={columns}
loading={isTableLoading}
rowKey="id"
ref={tableRef}
scroll={{
scrollToFirstRowOnChange: true,
x: 'max-content',
y: 400
}}
pagination={{
position: ['bottomCenter'],
current: currentPage,
......@@ -112,8 +118,6 @@ const RequestPage: React.FC = () => {
onChange: handlePaginationChange,
hideOnSinglePage: true
}}
rowKey="id"
ref={tableRef}
/>;
} else if (isError) {
table = <div>{error.toString()}</div>;
......@@ -167,7 +171,6 @@ const RequestPage: React.FC = () => {
/>
</div>
</div>
<Button
type='primary'
onClick={applyFilters}
......
......@@ -9,7 +9,7 @@ interface InitialState {
const initialState: InitialState = {
currentPage: 1,
totalOrgNumber: 0,
ORG_NUMBER_PER_PAGE: 11
ORG_NUMBER_PER_PAGE: 15
}
const mainPaginationSlice = createSlice({
......
import { configureStore, Store, createSlice, PayloadAction } from '@reduxjs/toolkit';
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import { apiSlice } from '../features/api/apiSlice';
import mainPaginationReducer from '../features/pagination/mainPagination';
import reqPaginationReducer from '../features/pagination/reqPagination';
import syncBtnReducer from '../features/sync_btn/syncBtn';
import { apiSlice } from '../api/mainApi';
import mainPaginationReducer from './slices/mainPagination';
import reqPaginationReducer from './slices/reqPagination';
import syncBtnReducer from './slices/syncBtn';
interface AuthState {
token: string | null;
......
{
"compilerOptions": {
"baseUrl": ".", // This must be specified if "paths" is.
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
......
......@@ -3,6 +3,11 @@ import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': '/src',
},
},
server: {
proxy: {
'/api': {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment