mirror of
https://github.com/FutureOfMedTech-FITM-hack/frontend.git
synced 2024-11-21 16:46:34 +03:00
add
This commit is contained in:
parent
45f5c4e5b1
commit
ad9b072c40
1882
package-lock.json
generated
1882
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,9 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
@ -10,9 +13,15 @@
|
|||
"@types/node": "^16.18.6",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"antd": "^5.0.4",
|
||||
"antd-mask-input": "^2.0.7",
|
||||
"axios": "^1.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.4.5",
|
||||
"react-scripts": "5.0.1",
|
||||
"redux": "^4.2.0",
|
||||
"typescript": "^4.9.3",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
|
|
|
@ -29,6 +29,14 @@
|
|||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<style>
|
||||
body{
|
||||
background-color: white;
|
||||
}
|
||||
*::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
|
38
src/App.css
38
src/App.css
|
@ -1,38 +0,0 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
39
src/App.tsx
39
src/App.tsx
|
@ -1,25 +1,28 @@
|
|||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import {RouterProvider} from 'react-router-dom';
|
||||
import { Header } from './components/Header';
|
||||
import router from './router';
|
||||
import {ConfigProvider} from 'antd';
|
||||
import store from './store'
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<>
|
||||
<Provider store={store}>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: '#00ABB5'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
<RouterProvider router={router}></RouterProvider>
|
||||
</ConfigProvider>
|
||||
</Provider>
|
||||
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
|
33
src/components/ArticlePreview/index.tsx
Normal file
33
src/components/ArticlePreview/index.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { ShareAltOutlined } from '@ant-design/icons'
|
||||
import { Avatar, Tag, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import './style.css'
|
||||
|
||||
interface IArticelPreview{
|
||||
doctorName: string;
|
||||
tags: string[]
|
||||
title: string
|
||||
time: string
|
||||
}
|
||||
|
||||
|
||||
export const ArticlePreview: react.FC<IArticelPreview> = (props) => {
|
||||
return <div className="article-preview__container">
|
||||
<div className="article-preview__headings">
|
||||
<div className="article-preview__author">
|
||||
<Avatar>Врач</Avatar>
|
||||
<Typography.Text>{props.doctorName}</Typography.Text>
|
||||
</div>
|
||||
<ShareAltOutlined />
|
||||
</div>
|
||||
<div className="tags">
|
||||
{
|
||||
props.tags.map(e => <Tag>{e}</Tag>)
|
||||
}
|
||||
</div>
|
||||
<Typography.Text style={{fontWeight: 'bold', marginTop: 30, paddingRight: 20}}>
|
||||
{props.title}
|
||||
</Typography.Text>
|
||||
<Typography.Text type='secondary'>{props.time}</Typography.Text>
|
||||
</div>
|
||||
}
|
29
src/components/ArticlePreview/style.css
Normal file
29
src/components/ArticlePreview/style.css
Normal file
|
@ -0,0 +1,29 @@
|
|||
.article-preview__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
gap: 10px;
|
||||
box-shadow: 0px 0px 25px rgba(0, 0, 0, 0.06);
|
||||
border-radius: 6px;
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
}
|
||||
.article-preview__author{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.article-preview__headings{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.articles__list{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
54
src/components/AssignQuestions/ColorsAssign/index.tsx
Normal file
54
src/components/AssignQuestions/ColorsAssign/index.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import react from 'react'
|
||||
import './style.css'
|
||||
|
||||
interface IColorBlock {
|
||||
text: string;
|
||||
color: string;
|
||||
onSelected: () => void;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
const ColorBlock: react.FC<IColorBlock> = (props) => {
|
||||
return <div className='color-block__container' onClick={() => {
|
||||
props.onSelected();
|
||||
}}>
|
||||
<div className="circle" style={{background: props.color}}></div>
|
||||
<div className="text" style={{
|
||||
fontWeight: props.selected ? '600' : '500'
|
||||
}}> - {props.text}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
interface IColorAssign {
|
||||
names: string[]
|
||||
}
|
||||
|
||||
export const ColorsAssign: react.FC<IColorAssign> = (props) => {
|
||||
const colors = [
|
||||
'#56D96B',
|
||||
'rgba(86, 217, 107, 0.8)',
|
||||
'linear-gradient(0deg, rgba(245, 199, 33, 0.1), rgba(245, 199, 33, 0.1)), rgba(86, 217, 107, 0.6)',
|
||||
'rgba(245, 199, 33, 0.6)',
|
||||
'rgba(245, 199, 33, 0.8)',
|
||||
'#F5C721',
|
||||
'linear-gradient(0deg, rgba(249, 87, 33, 0.3), rgba(249, 87, 33, 0.3)), rgba(245, 199, 33, 0.8)',
|
||||
'rgba(249, 87, 33, 0.8)',
|
||||
'#F95721'
|
||||
]
|
||||
const [selected, setSelected] = react.useState(new Array(10).fill(false));
|
||||
|
||||
return <>
|
||||
{props.names.map((e, index) => {
|
||||
return <ColorBlock
|
||||
color={colors[index]}
|
||||
text={e}
|
||||
selected={selected[index]}
|
||||
onSelected={() => {
|
||||
setSelected(
|
||||
selected.map((sel, selIndex) => selIndex == index ? true : false)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
})}
|
||||
</>
|
||||
}
|
11
src/components/AssignQuestions/ColorsAssign/style.css
Normal file
11
src/components/AssignQuestions/ColorsAssign/style.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.circle{
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.color-block__container{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
}
|
8
src/components/AssignQuestions/NumberAssign/index.tsx
Normal file
8
src/components/AssignQuestions/NumberAssign/index.tsx
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { Input } from 'antd'
|
||||
import react from 'react'
|
||||
|
||||
export const NumberAssign: react.FC = () => {
|
||||
return <>
|
||||
<Input />
|
||||
</>
|
||||
}
|
32
src/components/AssignQuestions/Question/index.tsx
Normal file
32
src/components/AssignQuestions/Question/index.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { ColorsAssign } from '../ColorsAssign'
|
||||
import { NumberAssign } from '../NumberAssign'
|
||||
import './style.css'
|
||||
|
||||
interface IAssignQuestion{
|
||||
type: "Colors" | "Input";
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const AssignQuestion: react.FC<IAssignQuestion> = (props) => {
|
||||
|
||||
return <div className="assign-question-el__container">
|
||||
<Typography.Text style={{fontWeight: 'bold', marginBottom: 20}}>{props.title}</Typography.Text>
|
||||
{
|
||||
props.type == 'Colors' ? <ColorsAssign
|
||||
names={[
|
||||
"Тошноты нет",
|
||||
"Что-то чувствую",
|
||||
"Небольшая тошнота",
|
||||
"Подташнивает",
|
||||
"Тошнит",
|
||||
"Очень тошнит",
|
||||
"Очень сильно тошнит",
|
||||
"Сейчас вырвет",
|
||||
"Рвота"
|
||||
]}
|
||||
/> : <NumberAssign />
|
||||
}
|
||||
</div>
|
||||
}
|
8
src/components/AssignQuestions/Question/style.css
Normal file
8
src/components/AssignQuestions/Question/style.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
.assign-question-el__container{
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
box-shadow: 0px 0px 25px rgba(0, 0, 0, 0.06);
|
||||
border-radius: 6px;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
12
src/components/DoctorAdd/index.tsx
Normal file
12
src/components/DoctorAdd/index.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import './style.css'
|
||||
|
||||
|
||||
export const DoctorAdd: react.FC = () => {
|
||||
return <div className='doctor-add__container'>
|
||||
<Typography.Text style={{fontWeight: 'bold'}}>
|
||||
Прикрепиться к врачу
|
||||
</Typography.Text>
|
||||
</div>
|
||||
}
|
10
src/components/DoctorAdd/style.css
Normal file
10
src/components/DoctorAdd/style.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
.doctor-add__container{
|
||||
width: 400px;
|
||||
height: 116px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #00ABB5;
|
||||
cursor: pointer;
|
||||
}
|
15
src/components/DoctorPreview/index.tsx
Normal file
15
src/components/DoctorPreview/index.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Avatar, Button, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import './style.css'
|
||||
|
||||
|
||||
export const DoctorPreview: react.FC = () => {
|
||||
return <div className='doctor-preview__container'>
|
||||
<Avatar>Врач</Avatar>
|
||||
<div className="doctor-preview__content">
|
||||
<Typography.Text>Севастьянов А.А</Typography.Text>
|
||||
<Typography.Text type='secondary'>Врач гастроэнтеролог</Typography.Text>
|
||||
<Button>Написать врачу</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
14
src/components/DoctorPreview/style.css
Normal file
14
src/components/DoctorPreview/style.css
Normal file
|
@ -0,0 +1,14 @@
|
|||
.doctor-preview__container{
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
box-shadow: 0px 0px 25px rgba(0, 0, 0, 0.06);
|
||||
border-radius: 6px;
|
||||
width: 400px;
|
||||
padding: 15px;
|
||||
}
|
||||
.doctor-preview__content{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
13
src/components/DottedTemplate/index.tsx
Normal file
13
src/components/DottedTemplate/index.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import react from 'react';
|
||||
import { Popover, Button } from 'antd';
|
||||
import {EllipsisOutlined} from '@ant-design/icons'
|
||||
|
||||
interface DottedTemplate {
|
||||
content: react.ReactNode
|
||||
}
|
||||
|
||||
export const DottedTemplate: react.FC<DottedTemplate> = (props) => {
|
||||
return <Popover content={props.content} trigger={'click'}>
|
||||
<Button type='text' icon={<EllipsisOutlined style={{transform: 'rotate(90deg)'}}/>}/>
|
||||
</Popover>
|
||||
}
|
23
src/components/Header/AssignHeader.tsx
Normal file
23
src/components/Header/AssignHeader.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { PlusOutlined } from '@ant-design/icons'
|
||||
import { Button, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
export const AssignHeader: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
return <>
|
||||
<Button className="cross" icon={<PlusOutlined />} type='text' onClick={() => {
|
||||
navigate(-1);
|
||||
}}/>
|
||||
<div className='header__container'>
|
||||
<div className="positioned">
|
||||
<div className="logo__container">
|
||||
<Typography.Title level={3}>Название анкеты</Typography.Title>
|
||||
<Button type='primary' onClick={() => {
|
||||
navigate(-1)
|
||||
}}>Отправить анкету</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
25
src/components/Header/CreateHeader.tsx
Normal file
25
src/components/Header/CreateHeader.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { PlusOutlined } from '@ant-design/icons'
|
||||
import { Button, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import './style.css'
|
||||
|
||||
|
||||
export const CreateQuestionHeader: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
return <>
|
||||
<Button className="cross" icon={<PlusOutlined />} type='text' onClick={() => {
|
||||
navigate(-1);
|
||||
}}/>
|
||||
<div className='header__container'>
|
||||
<div className="positioned">
|
||||
<div className="logo__container">
|
||||
<Typography.Title level={3}>Создание анкеты</Typography.Title>
|
||||
<Button type='primary' onClick={() => {
|
||||
navigate(-1)
|
||||
}}>Создать анкету</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
80
src/components/Header/index.tsx
Normal file
80
src/components/Header/index.tsx
Normal file
|
@ -0,0 +1,80 @@
|
|||
import react from 'react'
|
||||
import { Logo } from './logo';
|
||||
import {Avatar, Typography} from 'antd';
|
||||
import {useLocation} from 'react-router-dom';
|
||||
import { green, grey } from '@ant-design/colors';
|
||||
|
||||
import './style.css';
|
||||
|
||||
|
||||
export const Header: react.FC = () => {
|
||||
const [selected, setSelected] = react.useState(-1)
|
||||
const location = useLocation()
|
||||
console.log(location.pathname);
|
||||
|
||||
var links = [
|
||||
"/patients",
|
||||
"/questions",
|
||||
"/articles",
|
||||
"https://t.me/+W2DTe7Qw5e9lYmVi"
|
||||
]
|
||||
var linkText = [
|
||||
'Пациенты',
|
||||
'Анкеты',
|
||||
'Статьи',
|
||||
'Чаты'
|
||||
]
|
||||
var avatarName = 'Врач'
|
||||
var fullName = 'Севастьянов А.А.'
|
||||
|
||||
if (location.pathname.startsWith('/index')) {
|
||||
links = [
|
||||
'/index/diary',
|
||||
'/index/articles',
|
||||
'https://t.me/s4nspie'
|
||||
]
|
||||
linkText = [
|
||||
'Дневник пациента',
|
||||
'Рекомендации',
|
||||
'Чаты'
|
||||
]
|
||||
avatarName = 'Пациент'
|
||||
fullName = 'Иванов И.И.'
|
||||
}
|
||||
|
||||
for (var i = 0; i < links.length; ++i) {
|
||||
if (i != selected) {
|
||||
if (location.pathname == links[i]) {
|
||||
setSelected(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="header__container">
|
||||
<div className="positioned">
|
||||
<div className="header__content">
|
||||
<div className="logo__container">
|
||||
<Logo />
|
||||
<div className="header__menu">
|
||||
{
|
||||
linkText.map(
|
||||
e => <Typography.Link
|
||||
href={links[linkText.indexOf(e)]}
|
||||
style ={{color:
|
||||
linkText.indexOf(e) == selected ?
|
||||
'#00ABB5' : 'black'}}
|
||||
>
|
||||
{e}
|
||||
</Typography.Link>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="doctor__menu">
|
||||
<Avatar>{avatarName}</Avatar>
|
||||
<div className="fi">{fullName}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
25
src/components/Header/logo.tsx
Normal file
25
src/components/Header/logo.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
export const Logo = () => {
|
||||
return <svg width="155" height="18" viewBox="0 0 155 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_37_89)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.80301 0.947266C7.6842 0.947266 9.58836 1.4 10.9485 2.16992V5.34042C9.61132 4.38887 7.82021 3.91377 6.05251 3.93658C4.32984 3.95895 3.46829 4.4345 3.46829 5.40842C3.46829 8.14855 12.1501 7.129 12.1501 12.7681C12.1501 16.3233 9.29382 17.4332 5.96154 17.4332C3.76283 17.4332 1.72266 16.9121 0.0905271 16.0066V12.7681C1.90416 13.9684 3.89885 14.4435 5.71248 14.4435C7.54819 14.4435 8.68177 14.0587 8.68177 12.9264C8.68177 9.95995 0 11.0699 0 5.45361C0 2.39629 2.67519 0.947266 5.80301 0.947266Z" fill="#E30611"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.0096 1.26465V17.1163H27.6093V7.35557L23.4159 13.7194H21.4438L17.2504 7.3332V17.1163H13.8501V1.26465H17.0009L22.4413 9.50786L27.8588 1.26465H31.0096Z" fill="#E30611"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.9604 11.3416H43.0829L40.8616 5.65781C40.7481 5.34062 40.6351 5.02344 40.5216 4.41233C40.4086 5.02344 40.2951 5.34062 40.1591 5.65781L37.9604 11.3416ZM32.2024 17.1158L38.6859 1.26465H42.3578L48.8179 17.1158H45.3045L44.1489 14.1493H36.8948L35.7157 17.1158H32.2024Z" fill="#E30611"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M53.4113 8.73749H56.6981C58.1262 8.73749 59.3728 8.10312 59.3728 6.49528C59.3728 4.86507 58.1262 4.25396 56.6981 4.25396H53.4113V8.73749ZM50.011 17.1163V1.26465H56.5391C59.6219 1.26465 62.8411 2.41886 62.8411 6.42773C62.8411 9.14504 61.2999 10.5261 59.3728 11.1381L64.1102 17.1163H60.1889L55.9045 11.568H53.4113V17.1163H50.011Z" fill="#E30611"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M76.4488 1.26465V4.32152H71.763V17.1163H68.3852V4.32152H63.6995V1.26465H76.4488Z" fill="#E30611"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M147.732 9.18974C147.732 11.9366 147.359 14.594 146.664 17.1158H148.609C149.261 14.5846 149.609 11.9285 149.609 9.18974C149.609 6.4514 149.261 3.79537 148.609 1.26416H146.664C147.359 3.78598 147.732 6.44334 147.732 9.18974Z" fill="#00ABB5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M153.756 9.18974C153.756 11.9147 153.452 14.5676 152.878 17.1158H153.839C154.398 14.564 154.694 11.912 154.694 9.18974C154.694 6.4675 154.398 3.81595 153.839 1.26416H152.878C153.452 3.81237 153.756 6.46527 153.756 9.18974Z" fill="#00ABB5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M135.683 9.18974C135.683 12.0493 134.991 14.7434 133.772 17.1158H137.91C138.895 14.67 139.439 11.9947 139.439 9.18974C139.439 6.38519 138.895 3.70992 137.91 1.26416H133.772C134.991 3.63655 135.683 6.33061 135.683 9.18974Z" fill="#00ABB5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M132.255 1.26465H126.369C128.399 3.2787 129.659 6.0846 129.659 9.19023C129.659 12.2959 128.399 15.1022 126.369 17.1163H132.255C133.587 14.7841 134.354 12.0789 134.354 9.19023C134.354 6.30202 133.587 3.59678 132.255 1.26465Z" fill="#00ABB5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M141.708 9.18974C141.708 11.9733 141.225 14.6418 140.342 17.1158H143.316C144.1 14.6154 144.524 11.9531 144.524 9.18974C144.524 6.42679 144.1 3.7645 143.316 1.26416H140.342C141.225 3.73811 141.708 6.40666 141.708 9.18974Z" fill="#00ABB5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M95.4967 1.26465V17.1163H92.0964V7.35557L87.903 13.7194H85.9308L81.7374 7.3332V17.1163H78.3372V1.26465H81.4875L86.9284 9.50786L92.3454 1.26465H95.4967Z" fill="#00ABB5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.801 1.26465V4.25396H102.026V7.62757H109.121V10.5945H102.026V14.1274H109.937V17.1163H98.6255V1.26465H109.801Z" fill="#00ABB5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M118.524 4.25348H115.985V14.1269H118.524C121.675 14.1269 123.375 12.4506 123.375 9.19019C123.375 5.92932 121.675 4.25348 118.524 4.25348ZM118.524 1.26416C124.168 1.26416 126.843 4.59303 126.843 9.19019C126.843 13.7873 124.168 17.1158 118.524 17.1158H112.585V1.26416H118.524Z" fill="#00ABB5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_37_89">
|
||||
<rect width="155" height="17" fill="white" transform="translate(0 0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
}
|
44
src/components/Header/style.css
Normal file
44
src/components/Header/style.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
.header__container{
|
||||
background-color: white;
|
||||
box-shadow: 0px 2px 10px rgba(32, 45, 61, 0.24);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
.logo__container{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header__menu{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
.positioned{
|
||||
width: 1100px;
|
||||
}
|
||||
|
||||
.doctor__menu{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.patients__content{
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.patients__head{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.cross{
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 20px;
|
||||
transform: rotate(45deg);
|
||||
}
|
66
src/components/Heatmap/index.tsx
Normal file
66
src/components/Heatmap/index.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { Divider, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import './style.css'
|
||||
|
||||
interface IColoredRect {
|
||||
level: number;
|
||||
}
|
||||
|
||||
const ColoredRect: react.FC<IColoredRect> = (props) => {
|
||||
const colors = [
|
||||
'#56D96B',
|
||||
'rgba(86, 217, 107, 0.8)',
|
||||
'linear-gradient(0deg, rgba(245, 199, 33, 0.1), rgba(245, 199, 33, 0.1)), rgba(86, 217, 107, 0.6)',
|
||||
'rgba(245, 199, 33, 0.6)',
|
||||
'rgba(245, 199, 33, 0.8)',
|
||||
'#F5C721',
|
||||
'linear-gradient(0deg, rgba(249, 87, 33, 0.3), rgba(249, 87, 33, 0.3)), rgba(245, 199, 33, 0.8)',
|
||||
'rgba(249, 87, 33, 0.8)',
|
||||
'#F95721'
|
||||
]
|
||||
|
||||
return <div className='rect' style={{background: colors[props.level]}}></div>
|
||||
}
|
||||
|
||||
interface IHeatmapParam {
|
||||
title: string;
|
||||
levels: number[];
|
||||
}
|
||||
|
||||
const HeatmapParam: react.FC<IHeatmapParam> = (props) => {
|
||||
return <>
|
||||
<div className="heatmap__param">
|
||||
<div className="heatmap-param__heading">
|
||||
<Typography.Text style={{fontSize: 16}}>{props.title}</Typography.Text>
|
||||
</div>
|
||||
{props.levels.map(e => <ColoredRect level={e} />)}
|
||||
</div>
|
||||
<Divider style={{margin: 0}}/>
|
||||
</>
|
||||
}
|
||||
|
||||
const HeatmapHeadings: react.FC = () => {
|
||||
return <>
|
||||
<Divider style={{margin: 0}}/>
|
||||
<div className='heatmap__headings'>
|
||||
<div className="heatmap-param__heading">
|
||||
<Typography.Text style={{fontWeight: 'bold', fontSize: 16}}>Показатели</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{margin: 0}}/>
|
||||
</>
|
||||
}
|
||||
|
||||
export const Heatmap: react.FC = () => {
|
||||
return <div className="heatmap__container">
|
||||
<HeatmapHeadings />
|
||||
<HeatmapParam
|
||||
title='Тошнота'
|
||||
levels={[0, 2, 1, 5, 2, 8, 2]}
|
||||
/>
|
||||
<HeatmapParam
|
||||
title='Уровень гемоглобина'
|
||||
levels={[4, 0, 1, 7, 3, 8, 6]}
|
||||
/>
|
||||
</div>
|
||||
}
|
17
src/components/Heatmap/style.css
Normal file
17
src/components/Heatmap/style.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.rect{
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.heatmap__param{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.heatmap-param__heading{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import react from 'react';
|
||||
import {Input, Modal} from 'antd'
|
||||
import './style.css';
|
||||
import { TitledInput } from '../../TitledInput';
|
||||
import {Select} from 'antd';
|
||||
import { RegularitySelect } from '../../RegularitySelect';
|
||||
|
||||
|
||||
interface ICreateUpdateNotificationModal {
|
||||
open: boolean;
|
||||
setOpened: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const CreateUpdateNotificationModal: react.FC<ICreateUpdateNotificationModal> = (props) => {
|
||||
const optionTime = <Select mode='multiple'>
|
||||
{
|
||||
[...(Array(24).keys() as any)].map((e) => {
|
||||
var text = e.toString()
|
||||
if (e < 10) {
|
||||
text = '0' + e.toString()
|
||||
}
|
||||
return <Select.Option key={text}>{text}:00</Select.Option>
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
return <Modal
|
||||
open={props.open}
|
||||
onCancel={() => {props.setOpened(false)}}
|
||||
title={'Добавить напоминание'}
|
||||
okText="Добавить напоминание"
|
||||
cancelText="Отмена"
|
||||
>
|
||||
<div className='create-not__container'>
|
||||
<TitledInput
|
||||
title='Напоминание'
|
||||
placeholder='Введите напоминание'
|
||||
inputComponent={<Input placeholder='Напоминание'></Input>}
|
||||
></TitledInput>
|
||||
<TitledInput
|
||||
title='Регулярность'
|
||||
placeholder='Введите напоминание'
|
||||
inputComponent={<RegularitySelect />}
|
||||
></TitledInput>
|
||||
<TitledInput
|
||||
title='Выберете время'
|
||||
placeholder='Введите напоминание'
|
||||
inputComponent={optionTime}
|
||||
></TitledInput>
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
.create-not__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
104
src/components/Modals/CreateUpdatePatient/index.tsx
Normal file
104
src/components/Modals/CreateUpdatePatient/index.tsx
Normal file
|
@ -0,0 +1,104 @@
|
|||
import { Alert, DatePicker, Input, Modal } from 'antd';
|
||||
import { MaskedInput } from 'antd-mask-input';
|
||||
import react from 'react'
|
||||
import { useAppDispatch } from '../../../store';
|
||||
import { createUser } from '../../../store/reducers/patients/asyncThunks';
|
||||
import { TitledInput } from '../../TitledInput';
|
||||
import './style.css';
|
||||
|
||||
|
||||
interface ICreateUpdatePatient {
|
||||
open: boolean;
|
||||
setOpened: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const CreateUpdatePatient: react.FC<ICreateUpdatePatient> = (props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [fam, setFam] = react.useState(' ');
|
||||
const [name, setNam] = react.useState(' ');
|
||||
const [midn, setMidn] = react.useState(' ');
|
||||
const [dob, setDob] = react.useState(' ');
|
||||
const [email, setEmail] = react.useState(' ');
|
||||
|
||||
return <Modal
|
||||
open={props.open}
|
||||
title="Добавить пациента"
|
||||
okText="Добавить пациента"
|
||||
cancelText="Отмена"
|
||||
onCancel={() => {
|
||||
props.setOpened(false)
|
||||
}}
|
||||
onOk={() => {
|
||||
dispatch(
|
||||
createUser({
|
||||
fullname: fam + ' ' + name[0] + '.' + midn[0] + '.',
|
||||
born: dob,
|
||||
email: email
|
||||
})
|
||||
)
|
||||
props.setOpened(false)
|
||||
}}
|
||||
destroyOnClose
|
||||
>
|
||||
<div className="patient__content-modal">
|
||||
<TitledInput
|
||||
title='Фамилия'
|
||||
inputComponent={<Input value={fam} onChange={(e) => {
|
||||
setFam(e.target.value);
|
||||
}}></Input>}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Имя'
|
||||
inputComponent={<Input
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
setNam(e.target.value)
|
||||
}}
|
||||
></Input>}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Отчество'
|
||||
inputComponent={<Input
|
||||
value={midn}
|
||||
onChange={(e) => {
|
||||
setMidn(e.target.value);
|
||||
}}
|
||||
></Input>}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Дата рождения'
|
||||
inputComponent={<DatePicker placeholder='Выберите дату'
|
||||
onChange={(e) => {
|
||||
setDob(e?.toDate().toISOString()!)
|
||||
}}
|
||||
></DatePicker>}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Email'
|
||||
inputComponent={<Input
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value)
|
||||
}}
|
||||
></Input>}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Телефон'
|
||||
inputComponent={<MaskedInput
|
||||
mask={"+7(000)000-00-00"}
|
||||
></MaskedInput>}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Телефон доверенного лица'
|
||||
inputComponent={<MaskedInput
|
||||
mask={"+7(000)000-00-00"}
|
||||
></MaskedInput>}
|
||||
/>
|
||||
{/* <Alert
|
||||
type='info'
|
||||
message='Если поле “Телефон доверенного лица” остается пустым, то при экстренной ситуации будет вызвана машина скорой помощи'
|
||||
showIcon
|
||||
|
||||
></Alert> */}
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
5
src/components/Modals/CreateUpdatePatient/style.css
Normal file
5
src/components/Modals/CreateUpdatePatient/style.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.patient__content-modal{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
38
src/components/Modals/SosModal/index.tsx
Normal file
38
src/components/Modals/SosModal/index.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { Input, Modal } from 'antd'
|
||||
import react from 'react'
|
||||
import { TitledInput } from '../../TitledInput'
|
||||
import './style.css'
|
||||
|
||||
interface ISosModal{
|
||||
open: boolean;
|
||||
onOpenChagen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const SosModal: react.FC<ISosModal> = (props) => {
|
||||
return <Modal
|
||||
title="SOS"
|
||||
open={props.open}
|
||||
onOk={() => {
|
||||
props.onOpenChagen(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
props.onOpenChagen(false);
|
||||
}}
|
||||
destroyOnClose
|
||||
okText={"Вызвать скорую"}
|
||||
cancelText="Закрыть"
|
||||
>
|
||||
<div className="sos__container">
|
||||
<TitledInput
|
||||
title='Телефон пациента'
|
||||
inputComponent={<Input value={'+7(999)678-35-52'}/>
|
||||
}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Телефон доверительного лица'
|
||||
inputComponent={<Input value={'+7(999)987-78-89'}/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
5
src/components/Modals/SosModal/style.css
Normal file
5
src/components/Modals/SosModal/style.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.sos__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
12
src/components/MyNotification/index.tsx
Normal file
12
src/components/MyNotification/index.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Checkbox, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import './style.css'
|
||||
|
||||
export const MyNotification: react.FC = () => {
|
||||
return <div className='notification__container'>
|
||||
<Typography.Text>Заполнить анкету “Название анкеты”</Typography.Text>
|
||||
<Typography.Text>08.12.2022, 10:00</Typography.Text>
|
||||
<Typography.Text>Севастьянов А.А.</Typography.Text>
|
||||
<Checkbox>Выполнено</Checkbox>
|
||||
</div>
|
||||
}
|
9
src/components/MyNotification/style.css
Normal file
9
src/components/MyNotification/style.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.notification__container{
|
||||
box-shadow: 0px 0px 25px rgba(0, 0, 0, 0.06);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 80px;
|
||||
padding-right: 30px;
|
||||
}
|
30
src/components/QuerstionsPreview/index.tsx
Normal file
30
src/components/QuerstionsPreview/index.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Button, Tag, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import './style.css'
|
||||
|
||||
interface IQuestionPreivew {
|
||||
onQuestionClick: () => void;
|
||||
onHeatMapClick: () => void;
|
||||
}
|
||||
|
||||
export const QuestionPreivew: react.FC<IQuestionPreivew> = (props) => {
|
||||
return <div className='question-preview__container'>
|
||||
<Typography.Text style={{fontWeight: 'bold'}}>Название анкеты</Typography.Text>
|
||||
<span>
|
||||
<Typography.Text type='secondary'>Регулярность: </Typography.Text>
|
||||
<Typography.Text>2 раза в день </Typography.Text>
|
||||
</span>
|
||||
<span>
|
||||
<Typography.Text type='secondary'>Последнее прохождение: </Typography.Text>
|
||||
<Typography.Text>07.12.2022</Typography.Text>
|
||||
</span>
|
||||
<span>
|
||||
<Typography.Text type='secondary'>Последний результат: </Typography.Text>
|
||||
<Tag color='green'>В норме</Tag>
|
||||
</span>
|
||||
<div className="buttons">
|
||||
<Button onClick={() => {props.onHeatMapClick()}}>Тепловая карта</Button>
|
||||
<Button type='primary' onClick={() => {props.onQuestionClick()}}>Заполнить анкету</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
16
src/components/QuerstionsPreview/style.css
Normal file
16
src/components/QuerstionsPreview/style.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
.question-preview__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
width: 350px;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 0px 25px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.buttons{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
33
src/components/Questions/InputNumberQuestion/index.tsx
Normal file
33
src/components/Questions/InputNumberQuestion/index.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { Divider, Input } from 'antd'
|
||||
import react from 'react'
|
||||
import { TitledInput } from '../../TitledInput'
|
||||
import './style.css'
|
||||
|
||||
interface IInputNumber {
|
||||
refMin: number,
|
||||
refMax: number,
|
||||
onChange: (refMin: number, refMax: number) => void
|
||||
}
|
||||
|
||||
export const InputNumberQuestion: react.FC<IInputNumber> = (props) => {
|
||||
const [data, setData] = react.useState(props)
|
||||
|
||||
return <TitledInput
|
||||
title='Укажите референсные значения'
|
||||
inputComponent={
|
||||
<div className='ref-input__container'>
|
||||
<Input type='number' onChange={(e) => {
|
||||
setData({...data, refMin: parseInt(e.target.value)})
|
||||
props.onChange(parseInt(e.target.value), data.refMax);
|
||||
}} value={props.refMin}/> - <Input
|
||||
type='number'
|
||||
onChange={(e) => {
|
||||
setData({...data, refMax: parseInt(e.target.value)})
|
||||
props.onChange(data.refMin, parseInt(e.target.value));
|
||||
}}
|
||||
value={props.refMax}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
}
|
5
src/components/Questions/InputNumberQuestion/style.css
Normal file
5
src/components/Questions/InputNumberQuestion/style.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.ref-input__container{
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
56
src/components/Questions/Question/index.tsx
Normal file
56
src/components/Questions/Question/index.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { Divider, Input, Select } from 'antd'
|
||||
import react from 'react'
|
||||
import { TitledInput } from '../../TitledInput'
|
||||
import { InputNumberQuestion } from '../InputNumberQuestion'
|
||||
import { TenChoicesQuestion } from '../TenChoicesQuestion'
|
||||
import questions, { getActiveQuestions, IQuestion, setQuestion } from '../../../store/reducers/questions'
|
||||
import { useSelector } from 'react-redux/es/hooks/useSelector'
|
||||
import { useDispatch } from 'react-redux/es/exports'
|
||||
import { useAppDispatch } from '../../../store'
|
||||
|
||||
export const Question: react.FC<{question: IQuestion, index: number}> = (props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
return <>
|
||||
<TitledInput
|
||||
title={'Вопрос ' + props.index}
|
||||
inputComponent={
|
||||
<Input onChange={() => {}}/>
|
||||
}
|
||||
></TitledInput>
|
||||
<TitledInput
|
||||
title='Тип ответа'
|
||||
inputComponent={
|
||||
<Select value={props.question.type} onChange={(e) => {
|
||||
dispatch(setQuestion(
|
||||
{
|
||||
index: props.index,
|
||||
question: {...props.question, type: e}
|
||||
}
|
||||
))
|
||||
}}>
|
||||
<Select.Option key={"ten_choices"}>Шкала от 1 до 10</Select.Option>
|
||||
<Select.Option key={"input"}>Ввод числа</Select.Option>
|
||||
</Select>
|
||||
}
|
||||
></TitledInput>
|
||||
{
|
||||
props.question.type == 'input' ? <InputNumberQuestion
|
||||
refMax={props.question.refMax!}
|
||||
refMin={props.question.refMin!}
|
||||
onChange={(refMin, refMax) => {
|
||||
dispatch(setQuestion({index: props.index, question: {...props.question, refMin: refMin, refMax: refMax}}))
|
||||
}}
|
||||
></InputNumberQuestion> : <TenChoicesQuestion
|
||||
names={props.question.names!}
|
||||
onChange={(names) => {
|
||||
dispatch(setQuestion({
|
||||
index: props.index, question: {...props.question, names: names}
|
||||
}))
|
||||
}}
|
||||
/>
|
||||
}
|
||||
<Divider></Divider>
|
||||
</>
|
||||
}
|
53
src/components/Questions/QuestionTitle/QuestionTitle.tsx
Normal file
53
src/components/Questions/QuestionTitle/QuestionTitle.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { Input, Select, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux/es/exports'
|
||||
import { getActiveIndex, getActiveQuestion, questions, setDescriptionActiveQuestions, setIllnessesActiveQuestions, setNameActiveQuestions, setRegularityActiveQuestions } from '../../../store/reducers/questions'
|
||||
import { RegularitySelect } from '../../RegularitySelect'
|
||||
import { TitledInput } from '../../TitledInput'
|
||||
import './style.css'
|
||||
|
||||
export const QuestionTitle: react.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
const activeQuestion = useSelector(getActiveQuestion);
|
||||
return <>
|
||||
<Typography.Text>Введите общую информацию</Typography.Text>
|
||||
<div className='title__container title__spaced'>
|
||||
<TitledInput
|
||||
title='Название'
|
||||
inputComponent={
|
||||
<Input placeholder='Введите название' onChange={(e) => {
|
||||
dispatch(setNameActiveQuestions(e.target.value))
|
||||
}} value={activeQuestion.title}/>
|
||||
}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Описание'
|
||||
inputComponent={
|
||||
<Input.TextArea rows={4} placeholder='Введите описание' onChange={(e) => {
|
||||
dispatch(setDescriptionActiveQuestions(e.target.value))
|
||||
}}
|
||||
value={activeQuestion.description}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Регулярность'
|
||||
inputComponent={
|
||||
<RegularitySelect onChange={
|
||||
(e) => dispatch(setRegularityActiveQuestions(e))
|
||||
}
|
||||
value={activeQuestion.regularity}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Нозология'
|
||||
inputComponent={
|
||||
<Select mode='tags' value={activeQuestion.illnesses} onChange={(e) => {
|
||||
dispatch(setIllnessesActiveQuestions(e));
|
||||
}}/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
11
src/components/Questions/QuestionTitle/style.css
Normal file
11
src/components/Questions/QuestionTitle/style.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.title__container{
|
||||
box-shadow: 0px 0px 25px rgba(0, 0, 0, 0.06);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.title__spaced{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
36
src/components/Questions/TenChoicesQuestion/index.tsx
Normal file
36
src/components/Questions/TenChoicesQuestion/index.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Checkbox, Input, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import './style.css'
|
||||
|
||||
interface ITenChoices {
|
||||
names: string[],
|
||||
onChange: (names: string[]) => void;
|
||||
}
|
||||
|
||||
export const TenChoicesQuestion: react.FC<ITenChoices> = (props) => {
|
||||
const [checked, setChecked] = react.useState(false);
|
||||
const [data, setData] = react.useState(props.names)
|
||||
|
||||
return <div className="ten-choices__container">
|
||||
<Checkbox onChange={(e) => {
|
||||
setChecked(e.target.checked);
|
||||
console.log(e.target.checked)
|
||||
}}>Подписать значения шкалы</Checkbox>
|
||||
{
|
||||
checked ? ([...(new Array(10)).keys() as any]).map((e: number) => {
|
||||
return <div className='choices__question-names'>
|
||||
<Typography.Text style={{width: 20}}>{e+1}</Typography.Text>
|
||||
<Input
|
||||
placeholder='Введите значение (необязательно)'
|
||||
value={props.names[e]}
|
||||
onChange={(ee) => {
|
||||
var names = data.map((eee, index) => index == e ? ee.target.value : eee );
|
||||
setData(names);
|
||||
props.onChange(names)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}) : <></>
|
||||
}
|
||||
</div>
|
||||
}
|
10
src/components/Questions/TenChoicesQuestion/style.css
Normal file
10
src/components/Questions/TenChoicesQuestion/style.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
.choices__question-names{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.ten-choices__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
20
src/components/RegularitySelect/index.tsx
Normal file
20
src/components/RegularitySelect/index.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Select } from 'antd'
|
||||
import react from 'react'
|
||||
|
||||
interface IRegularitySelect {
|
||||
onChange?: (key: string) => void;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
|
||||
export const RegularitySelect: react.FC<IRegularitySelect> = (props) => {
|
||||
return <Select onChange={(e) => {
|
||||
return props.onChange ? props.onChange(e) : ''
|
||||
}} value={props.value ? props.value : null}>
|
||||
<Select.Option key={'1'}>1 раз в день</Select.Option>
|
||||
<Select.Option key={'2'}>2 раза в день</Select.Option>
|
||||
<Select.Option key={'3'}>3 раза в день</Select.Option>
|
||||
<Select.Option key={'5'}>5 раз в день</Select.Option>
|
||||
<Select.Option key={'10'}>10 раз в день</Select.Option>
|
||||
</Select>
|
||||
}
|
24
src/components/SosTableComponent/index.tsx
Normal file
24
src/components/SosTableComponent/index.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import react from 'react'
|
||||
import {Tag, Button} from 'antd';
|
||||
|
||||
interface ISosComponent {
|
||||
text: string;
|
||||
id: string;
|
||||
onSosClick?: () => void
|
||||
}
|
||||
|
||||
export const SosTableComponent: react.FC<ISosComponent> = (props) => {
|
||||
var color = 'green';
|
||||
if (props.text == 'На границе') color = 'yellow';
|
||||
if (props.text == 'Вне нормы') color = 'red';
|
||||
return <div className='patients__tag'>
|
||||
<Tag color={color} style={{height: 30, display: 'flex', alignItems: 'center'}}>{props.text}</Tag>
|
||||
{
|
||||
color == 'red' ? <Button danger type='primary' shape='round'
|
||||
onClick={() => {
|
||||
return props.onSosClick ? props.onSosClick() : null
|
||||
}}
|
||||
>SOS</Button> : <></>
|
||||
}
|
||||
</div>
|
||||
}
|
62
src/components/Tables/TheDiaryTable/index.tsx
Normal file
62
src/components/Tables/TheDiaryTable/index.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import react from 'react';
|
||||
import { Table, Typography } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { SosTableComponent } from '../../SosTableComponent';
|
||||
|
||||
|
||||
interface DiaryCell {
|
||||
name: string;
|
||||
regularity: string;
|
||||
lastVisit: string;
|
||||
result: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
|
||||
export const TheDiaryTable: react.FC = () => {
|
||||
|
||||
const columns: ColumnsType<DiaryCell> = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
title: 'Название анкеты',
|
||||
},
|
||||
{
|
||||
dataIndex: 'regularity',
|
||||
key: 'regularity',
|
||||
title: 'Регулярность'
|
||||
},
|
||||
{
|
||||
dataIndex: 'lastVisit',
|
||||
key: 'lastVisit',
|
||||
title: 'Регулярность'
|
||||
},
|
||||
{
|
||||
dataIndex: 'result',
|
||||
key: 'result',
|
||||
title: 'Результаты последней анкеты',
|
||||
render: (e: string, record: any, id: any) => {
|
||||
return <div className='diary__results'>
|
||||
<SosTableComponent text={e} id={record.key}></SosTableComponent>
|
||||
<Typography.Link
|
||||
style={{color: '#00ABB5', textDecoration: 'underline'}}
|
||||
href='1/heatmap'
|
||||
>Подробнее</Typography.Link>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const data: DiaryCell[] = [
|
||||
{
|
||||
name: 'Биохимический анализ крови',
|
||||
regularity: '1 раз в месяц',
|
||||
lastVisit: '07.12.2022, 17:30',
|
||||
result: 'В норме',
|
||||
key: '1'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
return <Table style={{marginTop: 30}} columns={columns} dataSource={data}></Table>
|
||||
}
|
62
src/components/Tables/TheNotificationsTable/index.tsx
Normal file
62
src/components/Tables/TheNotificationsTable/index.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { Button, Divider, Table } from 'antd'
|
||||
import react from 'react'
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { DottedTemplate } from '../../DottedTemplate';
|
||||
import { CreateUpdateNotificationModal } from '../../Modals/CreateUpdateNotificationModal';
|
||||
|
||||
|
||||
interface INotifiaction {
|
||||
name: string;
|
||||
regularity: string;
|
||||
time: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export const TheNotificationsTable: react.FC = (props) => {
|
||||
const [opened, setOpened] = react.useState(false);
|
||||
const columns: ColumnsType<INotifiaction> = [
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Напоминание',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
key: 'regularity',
|
||||
title: 'Регулярность',
|
||||
dataIndex: 'regularity'
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
title: 'Время',
|
||||
dataIndex: 'time',
|
||||
render: (el: string, data: any, id: any) => {
|
||||
const content = <div className='notif__actions'>
|
||||
<Button type='text' onClick={() => {setOpened(true)}}>Редактировать</Button>
|
||||
<Button type='text' danger>Удалить</Button>
|
||||
</div>
|
||||
|
||||
return <div className='notif__time'>
|
||||
<span>{el}</span>
|
||||
<DottedTemplate content={content}></DottedTemplate>
|
||||
</div>
|
||||
}
|
||||
},
|
||||
];
|
||||
const data: INotifiaction[] = [
|
||||
{
|
||||
key: '1',
|
||||
regularity: '1 раз в день',
|
||||
time: '10:00',
|
||||
name: 'Измерить давление'
|
||||
}
|
||||
]
|
||||
|
||||
return <>
|
||||
<Table
|
||||
style={{marginTop: 30}}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
></Table>
|
||||
<CreateUpdateNotificationModal open={opened} setOpened={setOpened}></CreateUpdateNotificationModal>
|
||||
</>
|
||||
}
|
118
src/components/Tables/ThePatientsTable/index.tsx
Normal file
118
src/components/Tables/ThePatientsTable/index.tsx
Normal file
|
@ -0,0 +1,118 @@
|
|||
import react from 'react'
|
||||
import { Button, Divider, Popover, Table, Tag } from 'antd'
|
||||
import {EllipsisOutlined} from '@ant-design/icons'
|
||||
import './style.css';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { SosTableComponent } from '../../SosTableComponent';
|
||||
import { DottedTemplate } from '../../DottedTemplate';
|
||||
import { useAppDispatch, useAppSelector } from '../../../store';
|
||||
import { usersGet } from '../../../store/reducers/patients';
|
||||
import { getUsers } from '../../../store/reducers/patients/asyncThunks';
|
||||
import { SosModal } from '../../Modals/SosModal';
|
||||
|
||||
|
||||
|
||||
|
||||
export const ThePatientsTable: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const [sosOpened, setSosOpened] = react.useState(false);
|
||||
const users = useAppSelector(usersGet);
|
||||
|
||||
if (!users.length) {
|
||||
dispatch(
|
||||
getUsers()
|
||||
)
|
||||
}
|
||||
console.log(users);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ФИО',
|
||||
dataIndex: 'fio',
|
||||
key: 'fio'
|
||||
},
|
||||
{
|
||||
title: 'Пол',
|
||||
dataIndex: 'male',
|
||||
key: 'male'
|
||||
},
|
||||
{
|
||||
title: 'Возраст',
|
||||
dataIndex: 'age',
|
||||
key: 'age'
|
||||
},
|
||||
{
|
||||
title: 'Результаты последней анкеты',
|
||||
dataIndex: 'results',
|
||||
key: 'results',
|
||||
render: (e: string, record: any, index: any) => {
|
||||
console.log(e, record, index)
|
||||
var color = 'green';
|
||||
if (e == 'На границе') color = 'yellow';
|
||||
if (e == 'Вне нормы') color = 'red';
|
||||
|
||||
const patientContent = <div className='patients__click'>
|
||||
<Button type='text'>Перейти в чат</Button>
|
||||
<Button type='text' onClick={() => {
|
||||
navigate(`/patients/${record.key}/diary`)
|
||||
}}>Дневник пациента</Button>
|
||||
<Button type='text'>Отправить анекту</Button>
|
||||
<Button type='text'>Редактировать</Button>
|
||||
<Button type='text'>Удалить</Button>
|
||||
<Divider style={{margin: 0}}></Divider>
|
||||
<Button type='text' style={{color: '#E30611'}} onClick={() => {
|
||||
setSosOpened(true);
|
||||
}}>SOS</Button>
|
||||
</div>
|
||||
|
||||
return <div className='patients__cont'>
|
||||
<SosTableComponent
|
||||
text={e}
|
||||
id={record.key}
|
||||
onSosClick={() => {
|
||||
setSosOpened(true)
|
||||
}}
|
||||
></SosTableComponent>
|
||||
<DottedTemplate content={patientContent}></DottedTemplate>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const data = [
|
||||
{
|
||||
fio: 'Васильев Василий Васильевич',
|
||||
male: 'Мужской',
|
||||
age: '20',
|
||||
results: 'На границе',
|
||||
key: '1'
|
||||
},
|
||||
{
|
||||
fio: 'Васильев Василий Васильевич',
|
||||
male: 'Мужской',
|
||||
age: '30',
|
||||
results: 'Вне нормы',
|
||||
key: '2'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
return <>
|
||||
<Table columns={columns} dataSource={users.map(e => {
|
||||
{
|
||||
return {
|
||||
fio: e.fio,
|
||||
male: e.male,
|
||||
age: e.dob,
|
||||
results: e.results == 'ok' ? 'В норме' : 'Вне нормы',
|
||||
key: e.key
|
||||
}
|
||||
}
|
||||
})} ></Table>
|
||||
<SosModal
|
||||
open={sosOpened}
|
||||
onOpenChagen={setSosOpened}
|
||||
/>
|
||||
</>
|
||||
}
|
17
src/components/Tables/ThePatientsTable/style.css
Normal file
17
src/components/Tables/ThePatientsTable/style.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.patients__cont{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.patients__click{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
align-items: start;
|
||||
}
|
||||
.patients__tag{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
58
src/components/Tables/TheQuestionsTable/index.tsx
Normal file
58
src/components/Tables/TheQuestionsTable/index.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { Button, Divider, Table, Tag } from 'antd'
|
||||
import react from 'react'
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { DottedTemplate } from '../../DottedTemplate';
|
||||
import './style.css';
|
||||
|
||||
|
||||
interface QuestionCell {
|
||||
name: string;
|
||||
regularity: string;
|
||||
illnesses: string;
|
||||
}
|
||||
|
||||
export const TheQuestionsTable: react.FC = () => {
|
||||
|
||||
const columns: ColumnsType<QuestionCell> = [
|
||||
{
|
||||
title: 'Название анкеты',
|
||||
key: 'name',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: 'Регулярность',
|
||||
key: 'regularity',
|
||||
dataIndex: 'regularity'
|
||||
},
|
||||
{
|
||||
title: 'Нозология',
|
||||
key: 'illnesses',
|
||||
dataIndex: 'illnesses',
|
||||
render: (value: string, record: QuestionCell, index: number) => {
|
||||
const content = <div className='illness__modal'>
|
||||
<Button type='text'>Отправить пациенту</Button>
|
||||
<Button type='text'>Редактировать</Button>
|
||||
<Button type='text'>Сделать копию на основе</Button>
|
||||
<Button type='text' danger>Удалить</Button>
|
||||
</div>
|
||||
|
||||
return <div className='illness__container'>
|
||||
<div className="illness__tags">
|
||||
{value.split(' ').map((e) => <Tag>{e}</Tag>)}
|
||||
</div>
|
||||
<DottedTemplate content={content}></DottedTemplate>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const data: QuestionCell[] = [
|
||||
{
|
||||
name: "Состояние при мигрени",
|
||||
regularity: '1 раз в день',
|
||||
illnesses: "Болезнь1 Болезнь2"
|
||||
}
|
||||
]
|
||||
|
||||
return <Table columns={columns} dataSource={data}></Table>
|
||||
}
|
13
src/components/Tables/TheQuestionsTable/style.css
Normal file
13
src/components/Tables/TheQuestionsTable/style.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
.illness__container{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.illness__modal{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
justify-content: start;
|
||||
align-items: start;
|
||||
}
|
16
src/components/TitledInput/index.tsx
Normal file
16
src/components/TitledInput/index.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { Input, Typography } from 'antd';
|
||||
import react from 'react';
|
||||
import './style.css'
|
||||
|
||||
interface ITitledInput {
|
||||
title: string;
|
||||
placeholder?: string;
|
||||
inputComponent: react.ReactNode
|
||||
}
|
||||
|
||||
export const TitledInput: react.FC<ITitledInput> = (props) => {
|
||||
return <div className='titled-input__container'>
|
||||
<Typography.Text type='secondary'>{props.title}</Typography.Text>
|
||||
{props.inputComponent}
|
||||
</div>
|
||||
}
|
5
src/components/TitledInput/style.css
Normal file
5
src/components/TitledInput/style.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.titled-input__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0px;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
|
@ -1,19 +1,18 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import router from './router'
|
||||
import {RouterProvider} from 'react-router-dom';
|
||||
|
||||
import 'antd/dist/reset.css';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App/>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
Before Width: | Height: | Size: 2.6 KiB |
3
src/network/index.ts
Normal file
3
src/network/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const origin = 'https://dev2.akarpov.ru/'
|
||||
export const doctorToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyQGV4YW1wbGUuY29tIiwiZXhwIjoxNjcxNDUwNDUwfQ.-z1zP0SxBA-BYdV-YrfjO27C_n6tVdkbiF35IYoWLAE'
|
||||
export const patientToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyNkBleGFtcGxlLmNvbSIsImV4cCI6MTY3MTQzNDU1N30.-ikjYmc2KCMr-ECzn7qO5tYWf77Uw6EmF9ZNWbmEG10'
|
38
src/pages/Articles/index.tsx
Normal file
38
src/pages/Articles/index.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { Button, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ArticlePreview } from '../../components/ArticlePreview'
|
||||
import { Header } from '../../components/Header'
|
||||
import './style.css'
|
||||
|
||||
export const Articles: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return <>
|
||||
<Header></Header>
|
||||
<div className="centered">
|
||||
<div className="sized_">
|
||||
<div className="article__heading">
|
||||
<Typography.Title level={3}>Рекомендации</Typography.Title>
|
||||
<Button type='primary' onClick={() => {
|
||||
navigate('/articles/create')
|
||||
}}>Написать рекомендацию</Button>
|
||||
</div>
|
||||
<div className="articles__list">
|
||||
<ArticlePreview
|
||||
title="Частые головные боли. Что делать, если боль становится невыносимой?"
|
||||
doctorName='Севастьянов А. А.'
|
||||
tags={['Мигрень', 'Опухоль']}
|
||||
time='10.11.2022, 07:15'
|
||||
/>
|
||||
<ArticlePreview
|
||||
title="Частые головные боли. Что делать, если боль становится невыносимой?"
|
||||
doctorName='Севастьянов А. А.'
|
||||
tags={['Гастрит']}
|
||||
time='10.11.2022, 07:15'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
5
src/pages/Articles/style.css
Normal file
5
src/pages/Articles/style.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.article__heading{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
36
src/pages/CreateArticle/index.tsx
Normal file
36
src/pages/CreateArticle/index.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Button, Input, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Header } from '../../components/Header'
|
||||
import { TitledInput } from '../../components/TitledInput'
|
||||
import './style.css'
|
||||
|
||||
export const CreateArticle: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
return <>
|
||||
<Header></Header>
|
||||
<div className="centered">
|
||||
<div className="sized_ ">
|
||||
<div className="article-create__container">
|
||||
<Typography.Title level={3}>Опубликовать рекомендацию</Typography.Title>
|
||||
<TitledInput
|
||||
title='Введите заголовок'
|
||||
inputComponent={<Input placeholder='Место для Вашего заголовка'></Input>}
|
||||
/>
|
||||
<TitledInput
|
||||
title='Введите описание'
|
||||
inputComponent={<Input.TextArea placeholder='Место для Вашего описания' rows={15}></Input.TextArea>}
|
||||
/>
|
||||
<Button
|
||||
type='primary'
|
||||
style={{width: 200}}
|
||||
onClick={() => {
|
||||
navigate(-1)
|
||||
}}
|
||||
>Опубликовать запись</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
}
|
6
src/pages/CreateArticle/style.css
Normal file
6
src/pages/CreateArticle/style.css
Normal file
|
@ -0,0 +1,6 @@
|
|||
.article-create__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
width: 500px;
|
||||
}
|
32
src/pages/CreateUpdateQuestion/index.tsx
Normal file
32
src/pages/CreateUpdateQuestion/index.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Button, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { CreateQuestionHeader } from '../../components/Header/CreateHeader'
|
||||
import { Question } from '../../components/Questions/Question'
|
||||
import { QuestionTitle } from '../../components/Questions/QuestionTitle/QuestionTitle'
|
||||
import {useSelector} from 'react-redux';
|
||||
import './style.css'
|
||||
import { addNewQuestion, getActiveQuestions } from '../../store/reducers/questions'
|
||||
import { useDispatch } from 'react-redux/es/exports'
|
||||
|
||||
export const CreateUpdateQuestion: react.FC = () => {
|
||||
const questions = useSelector(getActiveQuestions);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return <>
|
||||
<CreateQuestionHeader></CreateQuestionHeader>
|
||||
<div className="centered">
|
||||
<div className="sized__ question__container">
|
||||
<QuestionTitle />
|
||||
<Typography.Text>Сформируйте вопросы для анкеты</Typography.Text>
|
||||
<div className='title__container title__spaced quest__container'>
|
||||
{questions.map((e, index) => {
|
||||
return <Question question={e} index={index}></Question>
|
||||
})}
|
||||
<Button onClick={() => {
|
||||
dispatch(addNewQuestion())
|
||||
}}>Добавить вопрос</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
13
src/pages/CreateUpdateQuestion/style.css
Normal file
13
src/pages/CreateUpdateQuestion/style.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
.sized__{
|
||||
width: 680px;
|
||||
}
|
||||
|
||||
.question__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 40px;
|
||||
overflow-y: scroll;
|
||||
max-height: 80vh;
|
||||
padding: 20px;
|
||||
}
|
52
src/pages/MyDiary/index.tsx
Normal file
52
src/pages/MyDiary/index.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { DoctorAdd } from '../../components/DoctorAdd'
|
||||
import { DoctorPreview } from '../../components/DoctorPreview'
|
||||
import { Header } from '../../components/Header'
|
||||
import { MyNotification } from '../../components/MyNotification'
|
||||
import { QuestionPreivew } from '../../components/QuerstionsPreview'
|
||||
import './style.css'
|
||||
|
||||
export const MyDiary: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
return <>
|
||||
<Header></Header>
|
||||
<div className="centered">
|
||||
<div className="sized_ my-diary__container">
|
||||
<div className="my-diary__header">
|
||||
<Typography.Title level={3}>
|
||||
Дневник пациента
|
||||
</Typography.Title>
|
||||
</div>
|
||||
<div className="doctors">
|
||||
<Typography.Text style={{fontWeight: 'bold'}}>Мой лечащий врач</Typography.Text>
|
||||
<div className="doctor__blocks">
|
||||
<DoctorPreview />
|
||||
<DoctorPreview />
|
||||
<DoctorAdd />
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-questions">
|
||||
<Typography.Text style={{fontWeight: 'bold'}}>Мои анкеты</Typography.Text>
|
||||
<div className="doctor__blocks">
|
||||
<QuestionPreivew
|
||||
onHeatMapClick={() => {
|
||||
navigate('/index/questions/1/heatmap')
|
||||
}}
|
||||
onQuestionClick={() => {
|
||||
navigate('/index/questions/1/assign')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-notifications">
|
||||
<Typography.Text style={{fontWeight: 'bold'}}>Мои Напоминания</Typography.Text>
|
||||
<div className="not__blocks">
|
||||
<MyNotification />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
16
src/pages/MyDiary/style.css
Normal file
16
src/pages/MyDiary/style.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
.doctor__blocks{
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.my-diary__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.not__blocks{
|
||||
margin-top: 30px;
|
||||
}
|
38
src/pages/Notifications/index.tsx
Normal file
38
src/pages/Notifications/index.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import react from 'react';
|
||||
import { Header } from '../../components/Header';
|
||||
import { Breadcrumb, Typography, Button } from 'antd';
|
||||
import './style.css';
|
||||
import { TheNotificationsTable } from '../../components/Tables/TheNotificationsTable';
|
||||
import { CreateUpdateNotificationModal } from '../../components/Modals/CreateUpdateNotificationModal';
|
||||
|
||||
|
||||
export const Notifications: react.FC = () => {
|
||||
const [open, setOpened] = react.useState(false);
|
||||
|
||||
return <>
|
||||
<Header></Header>
|
||||
<div className="centered">
|
||||
<div className="sized_">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>
|
||||
Пациенты
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>
|
||||
Дневник пациента Иван Иванович
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>
|
||||
Напоминания
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="notification__heading">
|
||||
<Typography.Title level={3}>Напоминания</Typography.Title>
|
||||
<Button type='primary' onClick={() => {
|
||||
setOpened(true);
|
||||
}}>Добавить напоминание</Button>
|
||||
</div>
|
||||
<TheNotificationsTable></TheNotificationsTable>
|
||||
<CreateUpdateNotificationModal open={open} setOpened={setOpened}></CreateUpdateNotificationModal>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
17
src/pages/Notifications/style.css
Normal file
17
src/pages/Notifications/style.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.notification__heading{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.notif__time{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.notif__actions{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
align-items: start;
|
||||
}
|
31
src/pages/PatientDiary/index.tsx
Normal file
31
src/pages/PatientDiary/index.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import react from 'react'
|
||||
import { Header } from '../../components/Header'
|
||||
import {Breadcrumb, Typography, Button} from 'antd';
|
||||
import './style.css';
|
||||
import { TheDiaryTable } from '../../components/Tables/TheDiaryTable';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
|
||||
export const PatientDiary: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const {id} = useParams<{id: string}>();
|
||||
|
||||
return <>
|
||||
<Header></Header>
|
||||
<div className='centered'>
|
||||
<div className="sized_">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Пациенты</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>Дневник пациента Василий Васильевич</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="diary__headings">
|
||||
<Typography.Title level={3}>Дневник пациента Василий Васильевич</Typography.Title>
|
||||
<Button type='primary' onClick={() => {
|
||||
navigate(`/patients/${id}/notifications`);
|
||||
}}>Напоминания</Button>
|
||||
</div>
|
||||
<TheDiaryTable></TheDiaryTable>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
19
src/pages/PatientDiary/style.css
Normal file
19
src/pages/PatientDiary/style.css
Normal file
|
@ -0,0 +1,19 @@
|
|||
.centered{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.sized_{
|
||||
width: 1100px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.diary__headings{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.diary__results{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
31
src/pages/Patients/index.tsx
Normal file
31
src/pages/Patients/index.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import react from 'react';
|
||||
import { Header } from '../../components/Header';
|
||||
import {Typography, Button} from 'antd';
|
||||
import { ThePatientsTable } from '../../components/Tables/ThePatientsTable';
|
||||
import { CreateUpdatePatient } from '../../components/Modals/CreateUpdatePatient';
|
||||
import './styles.css'
|
||||
|
||||
|
||||
export const Patients: react.FC = () => {
|
||||
const [opened, setOpened] = react.useState(false);
|
||||
return <>
|
||||
<CreateUpdatePatient
|
||||
open={opened}
|
||||
setOpened={setOpened}
|
||||
></CreateUpdatePatient>
|
||||
<Header></Header>
|
||||
<div className="sized">
|
||||
<div className="positioned">
|
||||
<div className="patients__content">
|
||||
<div className="patients__head">
|
||||
<Typography.Title level={3}>Пациенты</Typography.Title>
|
||||
<Button type="primary" onClick={() => {
|
||||
setOpened(true)
|
||||
}}>Добавить пациента</Button>
|
||||
</div>
|
||||
<ThePatientsTable></ThePatientsTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
8
src/pages/Patients/styles.css
Normal file
8
src/pages/Patients/styles.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
.sized{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.positioned{
|
||||
width: 1100px;
|
||||
}
|
32
src/pages/Questions/index.tsx
Normal file
32
src/pages/Questions/index.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Button, Table, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { useDispatch } from 'react-redux/es/exports'
|
||||
import { useSelector } from 'react-redux/es/hooks/useSelector'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Header } from '../../components/Header'
|
||||
import { TheQuestionsTable } from '../../components/Tables/TheQuestionsTable'
|
||||
import { createNewQuestions, questions } from '../../store/reducers/questions'
|
||||
import './style.css'
|
||||
|
||||
export const Questions: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const getQuestions = useSelector(questions);
|
||||
|
||||
return <>
|
||||
<Header></Header>
|
||||
<div className="centered">
|
||||
<div className="sized_">
|
||||
<div className="questions__header">
|
||||
<Typography.Title level={3}>Анкеты</Typography.Title>
|
||||
<Button type='primary' onClick={() => {
|
||||
navigate('/questions/create');
|
||||
dispatch(createNewQuestions())
|
||||
console.log(getQuestions)
|
||||
}}>Создать анкету</Button>
|
||||
</div>
|
||||
<TheQuestionsTable></TheQuestionsTable>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
4
src/pages/Questions/style.css
Normal file
4
src/pages/Questions/style.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.questions__header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
28
src/pages/QuestionsAssign/index.tsx
Normal file
28
src/pages/QuestionsAssign/index.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Button } from 'antd'
|
||||
import react from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { AssignQuestion } from '../../components/AssignQuestions/Question'
|
||||
import { AssignHeader } from '../../components/Header/AssignHeader'
|
||||
import './style.css'
|
||||
|
||||
|
||||
export const QuestionsAssign: react.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
return <>
|
||||
<AssignHeader></AssignHeader>
|
||||
<div className="centered">
|
||||
<div className="sized__ assign-questions__container">
|
||||
<AssignQuestion type='Colors' title='Тошнота'/>
|
||||
<AssignQuestion type='Input' title='Уровень гемоглобина'/>
|
||||
<Button
|
||||
style={{width: 200}}
|
||||
type='primary'
|
||||
onClick={() => {
|
||||
navigate('/index/diary')
|
||||
}}
|
||||
>Отправить</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
}
|
7
src/pages/QuestionsAssign/style.css
Normal file
7
src/pages/QuestionsAssign/style.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
.assign-questions__container{
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
justify-content: flex-end;
|
||||
}
|
26
src/pages/QuestionsHeatmap/index.tsx
Normal file
26
src/pages/QuestionsHeatmap/index.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { Breadcrumb, Typography } from 'antd'
|
||||
import react from 'react'
|
||||
import { Header } from '../../components/Header'
|
||||
import { Heatmap } from '../../components/Heatmap'
|
||||
import './style.css'
|
||||
|
||||
|
||||
export const QuestionHeatmap: react.FC = () => {
|
||||
return <>
|
||||
<Header />
|
||||
<div className="centered">
|
||||
<div className="sized_ q_heatmap__container">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>
|
||||
Дневник пациента
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>
|
||||
Название анкеты
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<Typography.Title level={3}>Название анкеты</Typography.Title>
|
||||
<Heatmap />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
5
src/pages/QuestionsHeatmap/style.css
Normal file
5
src/pages/QuestionsHeatmap/style.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.q_heatmap__container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
1
src/react-app-env.d.ts
vendored
1
src/react-app-env.d.ts
vendored
|
@ -1 +0,0 @@
|
|||
/// <reference types="react-scripts" />
|
|
@ -1,15 +0,0 @@
|
|||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
60
src/router.tsx
Normal file
60
src/router.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import {createBrowserRouter} from 'react-router-dom';
|
||||
import { Articles } from './pages/Articles';
|
||||
import { CreateArticle } from './pages/CreateArticle';
|
||||
import { CreateUpdateQuestion } from './pages/CreateUpdateQuestion';
|
||||
import { MyDiary } from './pages/MyDiary';
|
||||
import { Notifications } from './pages/Notifications';
|
||||
import { PatientDiary } from './pages/PatientDiary';
|
||||
import { Patients } from './pages/Patients';
|
||||
import { Questions } from './pages/Questions';
|
||||
import { QuestionsAssign } from './pages/QuestionsAssign';
|
||||
import { QuestionHeatmap } from './pages/QuestionsHeatmap';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/patients',
|
||||
element: <Patients></Patients>
|
||||
},
|
||||
{
|
||||
path: '/patients/:id/diary',
|
||||
element: <PatientDiary></PatientDiary>
|
||||
},
|
||||
{
|
||||
path: '/patients/:id/notifications',
|
||||
element: <Notifications></Notifications>
|
||||
},
|
||||
{
|
||||
path: '/questions',
|
||||
element: <Questions></Questions>
|
||||
},
|
||||
{
|
||||
path: '/questions/create',
|
||||
element: <CreateUpdateQuestion></CreateUpdateQuestion>
|
||||
},
|
||||
{
|
||||
path: '/articles',
|
||||
element: <Articles></Articles>
|
||||
},
|
||||
{
|
||||
path: '/articles/create',
|
||||
element: <CreateArticle></CreateArticle>
|
||||
},
|
||||
{
|
||||
path: '/index/diary',
|
||||
element: <MyDiary></MyDiary>
|
||||
},
|
||||
{
|
||||
path: '/index/questions/:id/assign',
|
||||
element: <QuestionsAssign></QuestionsAssign>
|
||||
},
|
||||
{
|
||||
path: '/index/questions/:id/heatmap',
|
||||
element: <QuestionHeatmap />
|
||||
},
|
||||
{
|
||||
path: '/patients/:id/:question_id/heatmap',
|
||||
element: <QuestionHeatmap />
|
||||
}
|
||||
])
|
||||
|
||||
export default router;
|
|
@ -1,5 +0,0 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
19
src/store/index.ts
Normal file
19
src/store/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
||||
import { configureStore, combineReducers } from "@reduxjs/toolkit";
|
||||
import questionReducer from './reducers/questions';
|
||||
import patientsReducer from './reducers/patients';
|
||||
|
||||
const baseReducer = combineReducers({questionReducer, patientsReducer})
|
||||
|
||||
|
||||
const store = configureStore({reducer: baseReducer})
|
||||
|
||||
export default store;
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||
|
65
src/store/reducers/patients/asyncThunks.ts
Normal file
65
src/store/reducers/patients/asyncThunks.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import {createAsyncThunk} from '@reduxjs/toolkit';
|
||||
import axios from 'axios';
|
||||
import { IPatient } from '.';
|
||||
import { doctorToken, origin } from '../../../network';
|
||||
|
||||
|
||||
export const getUsers = createAsyncThunk(
|
||||
'patient/getPatients',
|
||||
async (thunkApi) => {
|
||||
const response = await axios.get(
|
||||
origin + 'api/users/list',
|
||||
{
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + doctorToken
|
||||
}
|
||||
}
|
||||
)
|
||||
console.log(response)
|
||||
const data = response.data;
|
||||
|
||||
return data.map((e: any) => {
|
||||
return {
|
||||
fio: e.fio,
|
||||
male: e.gender,
|
||||
results: e.latest_form_result,
|
||||
dob: e.age,
|
||||
key: e.key,
|
||||
} as IPatient;
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
interface IPatientCreate {
|
||||
fullname: string;
|
||||
born: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export const createUser = createAsyncThunk(
|
||||
'patient/createPatient',
|
||||
async (patientData: IPatientCreate, thunkApi) => {
|
||||
console.log({
|
||||
...patientData,
|
||||
password: "1234",
|
||||
gender: "Мужской"
|
||||
})
|
||||
const response = await axios.post(origin + 'api/auth/signup', {
|
||||
...patientData,
|
||||
password: "1234",
|
||||
gender: "Мужской"
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + doctorToken,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = response.data;
|
||||
return {
|
||||
fio: data.fullname,
|
||||
male: "Мужской",
|
||||
dob: patientData.born,
|
||||
results: "ok"
|
||||
} as IPatient;
|
||||
}
|
||||
)
|
38
src/store/reducers/patients/index.tsx
Normal file
38
src/store/reducers/patients/index.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import {createSlice, createSelector} from '@reduxjs/toolkit';
|
||||
import { RootState } from '../..';
|
||||
import { createUser, getUsers } from './asyncThunks';
|
||||
|
||||
export interface IPatient {
|
||||
fio: string;
|
||||
male: string;
|
||||
dob: string;
|
||||
results: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
interface IPatients {
|
||||
patients: IPatient[]
|
||||
}
|
||||
|
||||
const initialState: IPatients = {
|
||||
patients: []
|
||||
}
|
||||
|
||||
const patientSlice = createSlice({
|
||||
initialState: initialState,
|
||||
name: 'patientSlice',
|
||||
reducers: {
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(getUsers.fulfilled, (state, action) => {
|
||||
state.patients = action.payload;
|
||||
});
|
||||
builder.addCase(createUser.fulfilled, (state, action) => {
|
||||
state.patients = state.patients.concat([action.payload])
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default patientSlice.reducer
|
||||
|
||||
export const usersGet = createSelector((store: RootState) => store.patientsReducer.patients, (a) => a)
|
112
src/store/reducers/questions/index.ts
Normal file
112
src/store/reducers/questions/index.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import {createSlice, createSelector, PayloadAction} from '@reduxjs/toolkit'
|
||||
import { RootState } from '../..';
|
||||
|
||||
export interface IQuestion {
|
||||
type: "ten_choices" | "input",
|
||||
title: string;
|
||||
names?: string[],
|
||||
refMin?: number,
|
||||
refMax?: number,
|
||||
}
|
||||
|
||||
interface Questions {
|
||||
questions: IQuestion[],
|
||||
title: string,
|
||||
description: string,
|
||||
regularity: string,
|
||||
illnesses: string[]
|
||||
}
|
||||
|
||||
interface QuestionTable {
|
||||
questions: Questions[],
|
||||
active_questions_index: number
|
||||
}
|
||||
|
||||
const initialState: QuestionTable = {
|
||||
questions: [],
|
||||
active_questions_index: -1
|
||||
}
|
||||
|
||||
const questionSlice = createSlice({
|
||||
initialState: initialState,
|
||||
name: "questions",
|
||||
reducers: {
|
||||
createNewQuestions(state) {
|
||||
state.questions = state.questions.concat([{
|
||||
questions: [],
|
||||
title: "",
|
||||
description: "",
|
||||
regularity: "",
|
||||
illnesses: [""]
|
||||
}]);
|
||||
state.active_questions_index = state.questions.length - 1
|
||||
},
|
||||
setNameActiveQuestions(state, payload: PayloadAction<string>) {
|
||||
state.questions = state.questions.map((e, index) => {
|
||||
return index == state.active_questions_index ?
|
||||
{
|
||||
...e,
|
||||
title: payload.payload,
|
||||
} : e
|
||||
})
|
||||
},
|
||||
setDescriptionActiveQuestions(state, payload: PayloadAction<string>) {
|
||||
state.questions = state.questions.map((e, index) => {
|
||||
return index == state.active_questions_index ?
|
||||
{
|
||||
...e,
|
||||
description: payload.payload,
|
||||
} : e
|
||||
})
|
||||
},
|
||||
setRegularityActiveQuestions(state, payload: PayloadAction<string>) {
|
||||
state.questions = state.questions.map((e, index) => {
|
||||
return index == state.active_questions_index ?
|
||||
{
|
||||
...e,
|
||||
regularity: payload.payload,
|
||||
} : e
|
||||
})
|
||||
},
|
||||
setIllnessesActiveQuestions(state, payload: PayloadAction<string[]>) {
|
||||
state.questions = state.questions.map((e, index) => {
|
||||
return index == state.active_questions_index ?
|
||||
{
|
||||
...e,
|
||||
illnesses: payload.payload,
|
||||
} : e
|
||||
})
|
||||
},
|
||||
addNewQuestion(state) {
|
||||
state.questions[state.active_questions_index].questions = state.questions[state.active_questions_index].questions.concat([{
|
||||
title: "",
|
||||
type: "input",
|
||||
refMax: -100,
|
||||
refMin: 100,
|
||||
names: new Array(10).fill(' ')
|
||||
}])
|
||||
},
|
||||
setQuestion(state, payload: PayloadAction<{index: number, question: IQuestion}>) {
|
||||
state.questions[state.active_questions_index].questions = state.questions[state.active_questions_index].questions.map((e, index) => {
|
||||
return index == payload.payload.index ? payload.payload.question : e
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default questionSlice.reducer
|
||||
|
||||
export const {
|
||||
createNewQuestions,
|
||||
setDescriptionActiveQuestions,
|
||||
setNameActiveQuestions,
|
||||
setRegularityActiveQuestions,
|
||||
setIllnessesActiveQuestions,
|
||||
setQuestion,
|
||||
addNewQuestion
|
||||
} = questionSlice.actions;
|
||||
|
||||
export const getActiveQuestion = createSelector((store: RootState) => store.questionReducer.questions[store.questionReducer.active_questions_index], (e) => e)
|
||||
export const questions = createSelector((store: RootState) => store.questionReducer.questions, (a) => a)
|
||||
export const getActiveIndex = createSelector((store: RootState) => store.questionReducer.active_questions_index, (a) => a)
|
||||
export const getActiveQuestions = createSelector((store: RootState) => store.questionReducer.questions[store.questionReducer.active_questions_index].questions, (a) => a)
|
Loading…
Reference in New Issue
Block a user