Doc fetch
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import {PageControl} from "../types/common.ts";
|
import {FieldInfo, PageControl} from "../types/common.ts";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {ApiResponse, PageResponse} from "../types/api.ts";
|
import {ApiResponse, PageResponse} from "../types/api.ts";
|
||||||
import {getFetch} from "./common.ts";
|
import {getFetch} from "./common.ts";
|
||||||
import {createQueryString} from "../utils/common.ts";
|
import {createQueryString, hostUrl} from "../utils/common.ts";
|
||||||
|
|
||||||
export function useDataPage<T>(application:string, endpoint: string, params:{[key:string]:string|number}={}, needLoad:boolean=true):PageControl<T> {
|
export function useDataPage<T>(application:string, endpoint: string, params:{[key:string]:string|number}={}, needLoad:boolean=true):PageControl<T> {
|
||||||
const [data, setData] = useState<T[]|null>(null);
|
const [data, setData] = useState<T[]|null>(null);
|
||||||
@@ -137,3 +137,91 @@ export function useCachedData<T>(application: string, endpoint: string): CachedD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Хук для получения документации swagger JSON и извлечения структур.
|
||||||
|
* @param applicationName Название модуля/приложения.
|
||||||
|
* @param endpointName Название эндпоинта.
|
||||||
|
* @returns Массив структур без ID с полями: название, тип, title, format.
|
||||||
|
*/
|
||||||
|
export function useApiDocumentation(
|
||||||
|
applicationName: string,
|
||||||
|
endpointName: string
|
||||||
|
): { schema: FieldInfo[] | null; loading: boolean; error: Error | null } {
|
||||||
|
const [schema, setSchema] = useState<FieldInfo[] | null>(null);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchDocumentation() {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await fetch(hostUrl('/docs/swaggerjson/'));
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch documentation: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Найти путь по applicationName и endpointName
|
||||||
|
const pathKey = `/${applicationName}/${endpointName}/`;
|
||||||
|
const pathItem = data.paths?.[pathKey];
|
||||||
|
if (!pathItem) {
|
||||||
|
throw new Error(`Path ${pathKey} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const getMethod = pathItem.get;
|
||||||
|
if (!getMethod || !getMethod.responses?.['200']) {
|
||||||
|
throw new Error(`GET method or response 200 not found for ${pathKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const schemaRef = getMethod.responses["200"].schema?.properties?.results?.items?.['$ref'];
|
||||||
|
if (!schemaRef) {
|
||||||
|
throw new Error(`$ref not found in response schema for ${pathKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(schemaRef.match(/#\/definitions\/(\w+)/))
|
||||||
|
|
||||||
|
// Получить название определения
|
||||||
|
const defNameMatch = schemaRef.match(/#\/definitions\/(\w+)/);
|
||||||
|
if (!defNameMatch || defNameMatch.length < 2) {
|
||||||
|
throw new Error(`Invalid $ref format: ${schemaRef}`);
|
||||||
|
}
|
||||||
|
const defName = defNameMatch[1];
|
||||||
|
|
||||||
|
const definitions = data.definitions;
|
||||||
|
if (!definitions || !definitions[defName]) {
|
||||||
|
throw new Error(`Definition ${defName} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defProps = definitions[defName].properties;
|
||||||
|
if (!defProps) {
|
||||||
|
throw new Error(`Properties for ${defName} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Собрать поля, исключая id
|
||||||
|
const result: FieldInfo[] = Object.entries(defProps)
|
||||||
|
.filter((prop) => prop[0] !== 'id')
|
||||||
|
.map(([name, prop]) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
type: prop.type || '',
|
||||||
|
title: prop.title || '',
|
||||||
|
format: prop.format || undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setSchema(result);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err as Error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchDocumentation().then();
|
||||||
|
}, [applicationName, endpointName]);
|
||||||
|
|
||||||
|
return { schema, loading, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import { TokenObtainPair, TokenRefresh, TokenVerify, JwtTokenResponse } from '../types/users';
|
import { TokenObtainPair, TokenRefresh, TokenVerify, JwtTokenResponse } from '../types/users';
|
||||||
import {apiUrl} from "../utils/common.ts";
|
import {apiUrl} from "../utils/common.ts";
|
||||||
|
import {postFetch} from "./common.ts";
|
||||||
|
|
||||||
|
|
||||||
export async function login(data: TokenObtainPair): Promise<JwtTokenResponse | null> {
|
export async function login(data: TokenObtainPair): Promise<JwtTokenResponse | null> {
|
||||||
@@ -44,3 +45,10 @@ export async function verifyToken(data: TokenVerify): Promise<boolean> {
|
|||||||
|
|
||||||
return response.ok;
|
return response.ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function changePassword(data: { currentPassword: string; newPassword: string }) {
|
||||||
|
const response = await postFetch("/users/auth/users/set_password/", {new_password: data.newPassword, re_new_password: data.newPassword, current_password: data.currentPassword});
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error('Не удалось изменить пароль');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
10
src/App.tsx
10
src/App.tsx
@@ -1,15 +1,16 @@
|
|||||||
// App.tsx
|
// App.tsx
|
||||||
import {BrowserRouter as Router, Routes, Route, Navigate} from 'react-router-dom';
|
import {BrowserRouter as Router, Routes, Route, Navigate} from 'react-router-dom';
|
||||||
import {MainPage} from "./pages/MainPage.tsx";
|
import {MainPage} from "./pages/MainPage.tsx";
|
||||||
import {SurveyPage} from "./pages/SurveyPage.tsx";
|
import {SurveyPage} from "./pages/surveys/SurveyPage.tsx";
|
||||||
import {DashboardPage} from "./pages/DashboardPage.tsx";
|
import {DashboardPage} from "./pages/dashboards/DashboardPage.tsx";
|
||||||
import { SurveyResultPage } from './pages/SurveyResultPage.tsx';
|
import { SurveyResultPage } from './pages/surveys/SurveyResultPage.tsx';
|
||||||
import { ProfessionPage } from './pages/ProfessionPage.tsx';
|
import { ProfessionPage } from './pages/surveys/ProfessionPage.tsx';
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {LoginPage} from "./pages/LoginPage.tsx";
|
import {LoginPage} from "./pages/LoginPage.tsx";
|
||||||
import {UserProvider} from "./utils/users/UserProvider.tsx";
|
import {UserProvider} from "./utils/users/UserProvider.tsx";
|
||||||
import {DashboardLayout} from "./layouts/DashboardLayout.tsx";
|
import {DashboardLayout} from "./layouts/DashboardLayout.tsx";
|
||||||
import {MainLayout} from "./layouts/MainLayout.tsx";
|
import {MainLayout} from "./layouts/MainLayout.tsx";
|
||||||
|
import {DataPage} from "./pages/dashboards/DataPage.tsx";
|
||||||
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@@ -23,6 +24,7 @@ export default function App() {
|
|||||||
|
|
||||||
<Route path="/dashboard/*" element={<DashboardLayout />}>
|
<Route path="/dashboard/*" element={<DashboardLayout />}>
|
||||||
<Route index element={<DashboardPage />} />
|
<Route index element={<DashboardPage />} />
|
||||||
|
<Route path=":applicationName/:endpointName/" element={<DataPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,11 @@ export function DashboardLayout() {
|
|||||||
<h1 className="logo">Pro-Fi</h1>
|
<h1 className="logo">Pro-Fi</h1>
|
||||||
<p className="role">{user.roleTitle || "Роль не опознана"}</p> {/* Отображаем текущую роль пользователя */}
|
<p className="role">{user.roleTitle || "Роль не опознана"}</p> {/* Отображаем текущую роль пользователя */}
|
||||||
<nav className="navigation">
|
<nav className="navigation">
|
||||||
<Link to="/dashboard/staff" className={isActive('/dashboard/staff')}>Штат</Link>
|
<Link to="/dashboard/employees/employees/" className={isActive('/dashboard/staff')}>Штат</Link>
|
||||||
<Link to="/dashboard/schools" className={isActive('/dashboard/schools')}>Школы</Link>
|
<Link to="/dashboard/outreach/schools/" className={isActive('/dashboard/schools')}>Школы</Link>
|
||||||
<Link to="/dashboard/events" className={isActive('/dashboard/events')}>Мероприятия</Link>
|
<Link to="/dashboard/events/events/" className={isActive('/dashboard/events')}>Мероприятия</Link>
|
||||||
<Link to="/dashboard/students" className={isActive('/dashboard/students')}>Студенты</Link>
|
<Link to="/dashboard/education/students/" className={isActive('/dashboard/students')}>Студенты</Link>
|
||||||
<Link to="/dashboard/reports" className={isActive('/dashboard/reports')}>Отчеты</Link>
|
<Link to="/dashboard/outreach/partners/" className={isActive('/dashboard/partners')}>Партнеры</Link>
|
||||||
<Link to="/dashboard/partners" className={isActive('/dashboard/partners')}>Партнеры</Link>
|
|
||||||
<Link to="/admin" className={isActive('/admin')}>Админ-панель</Link>
|
<Link to="/admin" className={isActive('/admin')}>Админ-панель</Link>
|
||||||
<button onClick={logout} className="logout">Выйти</button>
|
<button onClick={logout} className="logout">Выйти</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export function DashboardPage() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
70
src/pages/dashboards/DashboardPage.tsx
Normal file
70
src/pages/dashboards/DashboardPage.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// pages/DashboardPage.tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import {useUser} from "../../utils/users/UseUser.ts";
|
||||||
|
import {changePassword} from "../../API/users.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export function DashboardPage() {
|
||||||
|
// Получаем данные о пользователе
|
||||||
|
const userContext = useUser();
|
||||||
|
const user = userContext?.user;
|
||||||
|
|
||||||
|
// Состояние для управления модальным окном
|
||||||
|
const [isModalOpen, setModalOpen] = useState(false);
|
||||||
|
const [currentPassword, setCurrentPassword] = useState('');
|
||||||
|
const [newPassword, setNewPassword] = useState('');
|
||||||
|
|
||||||
|
// Обработчик смены пароля
|
||||||
|
const handleChangePassword = async () => {
|
||||||
|
try {
|
||||||
|
await changePassword({ currentPassword, newPassword });
|
||||||
|
alert('Пароль успешно изменен!');
|
||||||
|
setModalOpen(false); // Закрыть модальное окно после успешной смены пароля
|
||||||
|
} catch (error) {
|
||||||
|
alert('Ошибка при смене пароля: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dashboard">
|
||||||
|
<div className="welcome-message">
|
||||||
|
С возвращением, {user?.last_name} {user?.first_name} {user?.middle_name}!
|
||||||
|
</div>
|
||||||
|
<div className="user-role">
|
||||||
|
Вы вошли как: {user?.roleTitle}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
!isModalOpen &&
|
||||||
|
<button className="change-password-button" onClick={() => setModalOpen(true)}>
|
||||||
|
Поменять пароль
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
{isModalOpen && (
|
||||||
|
<div className="change-password">
|
||||||
|
<div className="modal-content">
|
||||||
|
<label>
|
||||||
|
Текущий пароль:
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={currentPassword}
|
||||||
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Новый пароль:
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={newPassword}
|
||||||
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<button onClick={handleChangePassword}>Сменить пароль</button>
|
||||||
|
<button onClick={() => setModalOpen(false)}>Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
src/pages/dashboards/DataPage.tsx
Normal file
47
src/pages/dashboards/DataPage.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import {useApiDocumentation} from "../../API/hooks.ts";
|
||||||
|
|
||||||
|
export function DataPage() {
|
||||||
|
const { applicationName, endpointName } = useParams<{ applicationName: string; endpointName: string }>();
|
||||||
|
|
||||||
|
const {schema} = useApiDocumentation(applicationName || "", endpointName || "");
|
||||||
|
|
||||||
|
console.log(schema)
|
||||||
|
|
||||||
|
const handleExport = () => {
|
||||||
|
// Пока пусто
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
// Пока пусто
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilters = () => {
|
||||||
|
// Пока пусто
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="data-page">
|
||||||
|
<div className="buttons-container">
|
||||||
|
<button className="export-excel-button" onClick={handleExport}>Экспорт в Excel</button>
|
||||||
|
<button className="add-button" onClick={handleAdd}>Добавление</button>
|
||||||
|
<button className="filters-button" onClick={handleFilters}>Фильтры</button>
|
||||||
|
</div>
|
||||||
|
<div className="table-container">
|
||||||
|
<table className="data-table" style={{ overflowX: 'auto', display: 'block' }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="column-header">Header 1</th>
|
||||||
|
<th className="column-header">Header 2</th>
|
||||||
|
<th className="column-header">Header 3</th>
|
||||||
|
<th className="column-header">Header 4</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{/* Тут могут быть строки данных */}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import {Link, useParams} from 'react-router-dom';
|
import {Link, useParams} from 'react-router-dom';
|
||||||
import { Institution, Profession, Specialty } from '../types/survey';
|
import { Institution, Profession, Specialty } from '../../types/survey.ts';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { LoadingData } from '../components/LoadingData';
|
import { LoadingData } from '../../components/LoadingData.tsx';
|
||||||
import { LoadingList } from '../components/LoadingList';
|
import { LoadingList } from '../../components/LoadingList.tsx';
|
||||||
import {useCachedData, useData, useDataPage} from "../API/hooks.ts";
|
import {useCachedData, useData, useDataPage} from "../../API/hooks.ts";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -7,10 +7,10 @@ import {
|
|||||||
AgreementQuestion,
|
AgreementQuestion,
|
||||||
QuestionType,
|
QuestionType,
|
||||||
|
|
||||||
} from "../types/survey";
|
} from "../../types/survey.ts";
|
||||||
import { LoadingData } from "../components/LoadingData";
|
import { LoadingData } from "../../components/LoadingData.tsx";
|
||||||
import { LoadingList } from "../components/LoadingList";
|
import { LoadingList } from "../../components/LoadingList.tsx";
|
||||||
import {useData, useDataPage} from "../API/hooks.ts";
|
import {useData, useDataPage} from "../../API/hooks.ts";
|
||||||
|
|
||||||
export function SurveyPage() {
|
export function SurveyPage() {
|
||||||
const { surveyId } = useParams<{ surveyId: string }>();
|
const { surveyId } = useParams<{ surveyId: string }>();
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { Profession, ScoreVariable, Survey } from '../types/survey';
|
import { Profession, ScoreVariable, Survey } from '../../types/survey.ts';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { LoadingData } from '../components/LoadingData';
|
import { LoadingData } from '../../components/LoadingData.tsx';
|
||||||
import { LoadingList } from '../components/LoadingList';
|
import { LoadingList } from '../../components/LoadingList.tsx';
|
||||||
import {useData, useDataPage} from "../API/hooks.ts";
|
import {useData, useDataPage} from "../../API/hooks.ts";
|
||||||
|
|
||||||
export function SurveyResultPage() {
|
export function SurveyResultPage() {
|
||||||
const { scoreVariableId } = useParams<{ scoreVariableId: string }>();
|
const { scoreVariableId } = useParams<{ scoreVariableId: string }>();
|
||||||
@@ -11,3 +11,9 @@ export interface PageControl<T>{
|
|||||||
previousPage(): void;
|
previousPage(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FieldInfo {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
format?: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user