init
This commit is contained in:
commit
a458210f0a
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Getting Started with Create React App
|
||||||
|
|
||||||
|
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||||
|
|
||||||
|
### `npm start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.\
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||||
|
|
||||||
|
The page will reload when you make changes.\
|
||||||
|
You may also see any lint errors in the console.
|
||||||
|
|
||||||
|
### Preview
|
||||||
|
|
||||||
|
You can visit app at [github.io](https://FutureXpo.github.io/gold-point).
|
12051
package-lock.json
generated
Normal file
12051
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
package.json
Normal file
49
package.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "gold-point",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"homepage": "https://FutureXpo.github.io/gold-point",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.8.2",
|
||||||
|
"@emotion/styled": "^11.8.1",
|
||||||
|
"@mui/material": "^5.5.1",
|
||||||
|
"@testing-library/jest-dom": "^5.16.2",
|
||||||
|
"@testing-library/react": "^12.1.4",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-scripts": "5.0.0",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier --write --use-tabs \"src/**/*.js\"",
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"predeploy": "npm run build",
|
||||||
|
"deploy": "gh-pages -d build"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"gh-pages": "^3.2.3",
|
||||||
|
"prettier": "^2.6.0"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
44
public/index.html
Normal file
44
public/index.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
public/logo192.png
Normal file
BIN
public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
public/logo512.png
Normal file
BIN
public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
25
public/manifest.json
Normal file
25
public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
3
public/robots.txt
Normal file
3
public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
27
src/App/App.css
Normal file
27
src/App/App.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.App {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #eeeeff;
|
||||||
|
position:relative;
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items: center;
|
||||||
|
font-size: calc(1rem + 1.2vmin);
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
padding-top:7.5rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
min-height: 30rem;
|
||||||
|
width: 40rem;
|
||||||
|
position:relative;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: #282c34;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.body {
|
||||||
|
width: 95%;
|
||||||
|
padding-top:5.5rem;
|
||||||
|
}
|
||||||
|
}
|
121
src/App/App.js
Normal file
121
src/App/App.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
|
||||||
|
import Header from "../Components/Header/Header";
|
||||||
|
import MoneyList from "../Components/MoneyList/MoneyList";
|
||||||
|
import Loading from "../Components/Loading/Loading";
|
||||||
|
import Footer from "../Components/Footer/Footer";
|
||||||
|
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
|
||||||
|
const [data, setData] = useState({money:{}, isLoading:true, error:false});
|
||||||
|
|
||||||
|
async function getDateInfo(date_url,next_day){
|
||||||
|
try {
|
||||||
|
var response = await fetch(date_url||"https://www.cbr-xml-daily.ru/daily_json.js").then(response => response.json());
|
||||||
|
if (!response.Valute) {
|
||||||
|
throw new Error("Something went wrong!");
|
||||||
|
}
|
||||||
|
const dayData = response.Valute;
|
||||||
|
|
||||||
|
const dailyData = {};
|
||||||
|
for (const key in dayData) {
|
||||||
|
let money_info = {
|
||||||
|
charCode: dayData[key].CharCode,
|
||||||
|
name: names[dayData[key].ID]||dayData[key].Name, //Поскольку сервер возвращает имена не в именительном падеже, необходимо брать имена из массива
|
||||||
|
value: (dayData[key].Value/dayData[key].Nominal).toFixed(4),
|
||||||
|
};
|
||||||
|
dailyData[dayData[key].ID] = money_info;
|
||||||
|
|
||||||
|
if(next_day&&next_day.values[dayData[key].ID]){ //если есть информация про следующий день, вычисляем процентную разницу
|
||||||
|
next_day.values[dayData[key].ID].diff = (((next_day.values[dayData[key].ID].value - money_info.value) / money_info.value) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
date:response.Date,
|
||||||
|
next_day:next_day,
|
||||||
|
info:{
|
||||||
|
values: dailyData,
|
||||||
|
previousDate:response.PreviousDate,
|
||||||
|
previousURL:response.PreviousURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {error:error}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getData = useCallback(async () => {
|
||||||
|
var money = {};
|
||||||
|
let date_info = await getDateInfo();
|
||||||
|
if(date_info.error) return setData({money:{}, error:date_info.error});
|
||||||
|
|
||||||
|
let previous_date_info = await getDateInfo(date_info.info.previousURL,date_info.info); //поскольку previousValue предоставляется без previousNominal, то необходимо получать все данные за прошлый день и вычислять diff используя их
|
||||||
|
if(previous_date_info.error) return setData({money:{}, error:date_info.error});
|
||||||
|
|
||||||
|
money[date_info.date] = previous_date_info.next_day;
|
||||||
|
money[previous_date_info.date] = previous_date_info.info;
|
||||||
|
|
||||||
|
setData({money:money, date:date_info.date});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [getData]);
|
||||||
|
|
||||||
|
const updateData = (money) => setData({...data, money:money});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<Header />
|
||||||
|
<div className="body">
|
||||||
|
{data.isLoading?
|
||||||
|
<Loading />:
|
||||||
|
<MoneyList money={data.money} date={data.date} updateData={updateData} getDateInfo={getDateInfo}/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Footer isLoading={data.isLoading}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
||||||
|
const names = {
|
||||||
|
"R01010":"Австралийский доллар",
|
||||||
|
"R01020A":"Азербайджанский манат",
|
||||||
|
"R01035":"Фунт стерлингов Соединенного королевства",
|
||||||
|
"R01060":"Армянский драм",
|
||||||
|
"R01090B":"Белорусский рубль",
|
||||||
|
"R01100":"Болгарский лев",
|
||||||
|
"R01115":"Бразильский реал",
|
||||||
|
"R01135":"Венгерский форинт",
|
||||||
|
"R01200":"Гонконгский доллар",
|
||||||
|
"R01215":"Датская крона",
|
||||||
|
"R01235":"Доллар США",
|
||||||
|
"R01239":"Евро",
|
||||||
|
"R01270":"Индийская рупия",
|
||||||
|
"R01335":"Казахстанский тенге",
|
||||||
|
"R01350":"Канадский доллар",
|
||||||
|
"R01370":"Киргизский сом",
|
||||||
|
"R01375":"Китайский юань",
|
||||||
|
"R01500":"Молдавский лей",
|
||||||
|
"R01535":"Норвежская крона",
|
||||||
|
"R01565":"Польский злотый",
|
||||||
|
"R01585F":"Румынский лей",
|
||||||
|
"R01589":"СДР (специальные права заимствования)",
|
||||||
|
"R01625":"Сингапурский доллар",
|
||||||
|
"R01670":"Таджикский сомони",
|
||||||
|
"R01700J":"Турецкая лира",
|
||||||
|
"R01710A":"Новый туркменский манат",
|
||||||
|
"R01717":"Узбекский сум",
|
||||||
|
"R01720":"Украинская гривна",
|
||||||
|
"R01760":"Чешская крона",
|
||||||
|
"R01770":"Шведская крона",
|
||||||
|
"R01775":"Швейцарский франк",
|
||||||
|
"R01810":"Южноафриканский рэнд",
|
||||||
|
"R01815":"Южнокорейская вона",
|
||||||
|
"R01820":"Японская иена"
|
||||||
|
}
|
21
src/Components/Footer/Footer.css
Normal file
21
src/Components/Footer/Footer.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.footer {
|
||||||
|
font-size:1.1rem;
|
||||||
|
width:100%;
|
||||||
|
/*background-color: #dddddd;*/
|
||||||
|
padding-top:1rem;
|
||||||
|
padding-bottom:1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.fixed{
|
||||||
|
position:absolute;
|
||||||
|
bottom:0;
|
||||||
|
}
|
||||||
|
.footer .link {
|
||||||
|
color:darkblue;
|
||||||
|
margin-left:0.5rem;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.footer {
|
||||||
|
font-size:0.9rem;
|
||||||
|
}
|
||||||
|
}
|
10
src/Components/Footer/Footer.js
Normal file
10
src/Components/Footer/Footer.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import './Footer.css';
|
||||||
|
|
||||||
|
export default function Footer(props) {
|
||||||
|
return (
|
||||||
|
<footer className={props.isLoading?"footer fixed":"footer"}>
|
||||||
|
Создано при помощи
|
||||||
|
<a href="https://www.cbr-xml-daily.ru/" className="link">API для курсов ЦБ РФ</a>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
41
src/Components/Header/Header.css
Normal file
41
src/Components/Header/Header.css
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.header{
|
||||||
|
width:100%;
|
||||||
|
display:flex;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
|
.header_box {
|
||||||
|
margin-top:1rem;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
z-index:10;
|
||||||
|
width: 41rem;
|
||||||
|
}
|
||||||
|
.header .under_titles {
|
||||||
|
top: 0;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height:4rem;
|
||||||
|
z-index:9;
|
||||||
|
background-color: #eeeeff;
|
||||||
|
}
|
||||||
|
.header_box .titles {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: #8899dd;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.header_box {
|
||||||
|
margin-top:0rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.header_box .titles {
|
||||||
|
border-radius: 0rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
16
src/Components/Header/Header.js
Normal file
16
src/Components/Header/Header.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import './Header.css';
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<header className="header">
|
||||||
|
<div className="header_box">
|
||||||
|
<div className="titles">
|
||||||
|
<h4>Код валюты</h4>
|
||||||
|
<h4>Курс (р.)</h4>
|
||||||
|
<h4>Разница</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="under_titles"></div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
53
src/Components/Loading/Loading.css
Normal file
53
src/Components/Loading/Loading.css
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
.loading{
|
||||||
|
width:100%;
|
||||||
|
height:25rem;
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
|
.loading .loading_anim{
|
||||||
|
width:100%;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
|
.loading .loading_anim .loading_animation{
|
||||||
|
transform:scale(.3);
|
||||||
|
background-color:rgb(255,255,255);
|
||||||
|
width:2rem;
|
||||||
|
height:2rem;
|
||||||
|
margin:0.1rem;
|
||||||
|
border-radius:100%;
|
||||||
|
animation: animation1 infinite 1000ms alternate ease-in-out forwards;
|
||||||
|
}
|
||||||
|
.loading .loading_anim .loading_animation.a2{
|
||||||
|
animation-delay:100ms;
|
||||||
|
}
|
||||||
|
.loading .loading_anim .loading_animation.a3{
|
||||||
|
animation-delay:200ms;
|
||||||
|
}
|
||||||
|
.loading .loading_anim .loading_animation.a4{
|
||||||
|
animation-delay:300ms;
|
||||||
|
}
|
||||||
|
.loading .loading_anim .loading_animation.a5{
|
||||||
|
animation-delay:400ms;
|
||||||
|
}
|
||||||
|
.loading .loading_anim .loading_animation.a6{
|
||||||
|
animation-delay:500ms;
|
||||||
|
}
|
||||||
|
.loading .loading_anim .loading_animation.a7{
|
||||||
|
animation-delay:600ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1{
|
||||||
|
0%{
|
||||||
|
transform:scale(.3);
|
||||||
|
background-color:rgb(255,255,255);
|
||||||
|
}
|
||||||
|
|
||||||
|
100%{
|
||||||
|
transform:scale(1);
|
||||||
|
background-color:rgb(0,0,0);
|
||||||
|
}
|
||||||
|
}
|
17
src/Components/Loading/Loading.js
Normal file
17
src/Components/Loading/Loading.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import './Loading.css';
|
||||||
|
|
||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<div className="loading">
|
||||||
|
<div className="loading_anim">
|
||||||
|
<div className="loading_animation a1"></div>
|
||||||
|
<div className="loading_animation a2"></div>
|
||||||
|
<div className="loading_animation a3"></div>
|
||||||
|
<div className="loading_animation a4"></div>
|
||||||
|
<div className="loading_animation a5"></div>
|
||||||
|
<div className="loading_animation a6"></div>
|
||||||
|
<div className="loading_animation a7"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
32
src/Components/MoneyList/Item/Item.css
Normal file
32
src/Components/MoneyList/Item/Item.css
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.item {
|
||||||
|
display:inline-block;
|
||||||
|
width:100%;
|
||||||
|
max-width:100%;
|
||||||
|
background: #aaaacc;
|
||||||
|
border-radius:1rem;
|
||||||
|
margin-top:0.3rem;
|
||||||
|
padding-top:2rem;
|
||||||
|
padding-bottom:2rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position:relative;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item_info{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.item_info .value{
|
||||||
|
}
|
||||||
|
.item_info .diff.plus{
|
||||||
|
color:green;
|
||||||
|
}
|
||||||
|
.item_info .diff.minus{
|
||||||
|
color:darkred;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover{
|
||||||
|
transform: scale(1.02);
|
||||||
|
background: #cacafa;
|
||||||
|
}
|
23
src/Components/MoneyList/Item/Item.js
Normal file
23
src/Components/MoneyList/Item/Item.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import './Item.css';
|
||||||
|
|
||||||
|
export default function Item(props) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={props.item.name} followCursor arrow placement="bottom-end">
|
||||||
|
<div className="item" onClick={()=>{props.openModal(props.item.id)}}>
|
||||||
|
<Grid container rowSpacing={0} columnSpacing={{ xs: 1, sm: 2, md: 0 }} className="item_info">
|
||||||
|
<Grid item xs={4} className="charCode">
|
||||||
|
{props.item.charCode}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={4} className="value">
|
||||||
|
{props.item.value}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={4} className={props.item.diff>=0?"diff plus":"diff minus"}>
|
||||||
|
{props.item.diff}%{props.item.diff>=0?"▲":"▼"}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
52
src/Components/MoneyList/Modal/Item/Item.css
Normal file
52
src/Components/MoneyList/Modal/Item/Item.css
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
.modal_item {
|
||||||
|
width:100%;
|
||||||
|
max-width:100%;
|
||||||
|
background: #fff;
|
||||||
|
margin-top:0.3rem;
|
||||||
|
padding-top:1rem;
|
||||||
|
padding-bottom:1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position:relative;
|
||||||
|
border-bottom:1px solid;
|
||||||
|
}
|
||||||
|
.modal_item.first {
|
||||||
|
border-top:1px solid;
|
||||||
|
}
|
||||||
|
.modal_item .item_info{
|
||||||
|
padding-left:2rem;
|
||||||
|
}
|
||||||
|
.modal_item .item_info .date{
|
||||||
|
font-size:1.5rem;
|
||||||
|
text-align:left;
|
||||||
|
color:#009;
|
||||||
|
}
|
||||||
|
.modal_item .item_info .value{
|
||||||
|
font-size:1.2rem;
|
||||||
|
font-weight:bold;
|
||||||
|
text-align:right;
|
||||||
|
}
|
||||||
|
.modal_item .item_info .diff{
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
.modal_item .item_info .diff.plus{
|
||||||
|
color:green;
|
||||||
|
}
|
||||||
|
.modal_item .item_info .diff.minus{
|
||||||
|
color:darkred;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.modal_item {
|
||||||
|
margin-top:0.1rem;
|
||||||
|
padding-top:0.7rem;
|
||||||
|
padding-bottom:0.7rem;
|
||||||
|
}
|
||||||
|
.modal_item .item_info .date{
|
||||||
|
font-size:1.3rem;
|
||||||
|
}
|
||||||
|
.modal_item .item_info .value{
|
||||||
|
font-size:1.1rem;
|
||||||
|
}
|
||||||
|
.modal_item .item_info .diff{
|
||||||
|
font-size:0.9rem;
|
||||||
|
}
|
||||||
|
}
|
26
src/Components/MoneyList/Modal/Item/Item.js
Normal file
26
src/Components/MoneyList/Modal/Item/Item.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import './Item.css';
|
||||||
|
|
||||||
|
export default function Item(props) {
|
||||||
|
|
||||||
|
function getDate(date){
|
||||||
|
let d = new Date(date);
|
||||||
|
return [("0" + d.getDate()).slice(-2),("0"+(d.getMonth()+1)).slice(-2),d.getFullYear()].join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={props.index===0?"modal_item first":"modal_item"} >
|
||||||
|
<Grid container rowSpacing={0} columnSpacing={{ xs: 1, sm: 2, md: 2 }} className="item_info">
|
||||||
|
<Grid item xs={5} className="date">
|
||||||
|
{getDate(props.item.date)}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={4} className="value">
|
||||||
|
{props.item.value}₽
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3} className={props.item.diff>=0?"diff plus":"diff minus"}>
|
||||||
|
{props.item.diff}%{props.item.diff>=0?"▲":"▼"}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
54
src/Components/MoneyList/Modal/Modal.css
Normal file
54
src/Components/MoneyList/Modal/Modal.css
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
.modal{
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background:#fff;
|
||||||
|
padding:2rem;
|
||||||
|
border-radius:1rem;
|
||||||
|
width:40rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-back{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
.modal .modal_header{
|
||||||
|
font-size: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom:2rem;
|
||||||
|
}
|
||||||
|
.modal .modal_body {
|
||||||
|
overflow-y:scroll;
|
||||||
|
max-height:60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.modal {
|
||||||
|
overflow-y:scroll;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding:0;
|
||||||
|
border-radius:0;
|
||||||
|
}
|
||||||
|
.modal .modal-back{
|
||||||
|
padding-top:1.5rem;
|
||||||
|
padding-bottom:1.5rem;
|
||||||
|
padding-left:1rem;
|
||||||
|
display: block;
|
||||||
|
color:#555;
|
||||||
|
}
|
||||||
|
.modal .modal_header{
|
||||||
|
padding-left:1rem;
|
||||||
|
padding-right:1rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
.modal .modal_body {
|
||||||
|
overflow-y:hidden;
|
||||||
|
max-height:none;
|
||||||
|
padding-bottom:2rem;
|
||||||
|
padding-left:2.5%;
|
||||||
|
padding-right:2.5%;
|
||||||
|
max-width:95%;
|
||||||
|
}
|
||||||
|
}
|
99
src/Components/MoneyList/Modal/Modal.js
Normal file
99
src/Components/MoneyList/Modal/Modal.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import Loading from "../../Loading/Loading";
|
||||||
|
import Backdrop from '@mui/material/Backdrop';
|
||||||
|
import Modal from '@mui/material/Modal';
|
||||||
|
import Fade from '@mui/material/Fade';
|
||||||
|
|
||||||
|
import Item from "./Item/Item";
|
||||||
|
|
||||||
|
import './Modal.css';
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms || 250));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ModalMoney(props) {
|
||||||
|
var {open, money, id, date} = props;
|
||||||
|
const [data,setData]=useState({isLoading:true});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [props.open]);
|
||||||
|
|
||||||
|
if(!id) return null;
|
||||||
|
|
||||||
|
async function getData(){
|
||||||
|
if(data.isLoading&&id){
|
||||||
|
let day_date = date;
|
||||||
|
let day_url = money[date].previousURL;
|
||||||
|
let next_day_date;
|
||||||
|
|
||||||
|
let money_={...money}
|
||||||
|
let data_ = [];
|
||||||
|
|
||||||
|
while (data_.length<11){
|
||||||
|
if(!money_[day_date]){ //проверка что существует день
|
||||||
|
let response = await props.getDateInfo(day_url,money_[next_day_date]||null); //получаем информацию за день и обновляем разцницу за следующий
|
||||||
|
money_[day_date] = response.info;
|
||||||
|
if(response.next_day){
|
||||||
|
money_[next_day_date] = response.next_day;
|
||||||
|
data_[data_.length-1].diff = response.next_day.values[id].diff
|
||||||
|
}
|
||||||
|
await sleep();
|
||||||
|
}
|
||||||
|
let date_info = {
|
||||||
|
date:day_date,
|
||||||
|
value:money_[day_date].values[id].value,
|
||||||
|
diff:money_[day_date].values[id].diff
|
||||||
|
}
|
||||||
|
data_.push(date_info);
|
||||||
|
|
||||||
|
next_day_date = day_date;
|
||||||
|
day_url = money_[day_date].previousURL;
|
||||||
|
day_date = money_[day_date].previousDate;
|
||||||
|
|
||||||
|
}
|
||||||
|
props.updateData(money_);
|
||||||
|
setData({values:data_});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
props.closeModal();
|
||||||
|
setTimeout(()=>{setData({isLoading:true})},300);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={closeModal}
|
||||||
|
closeAfterTransition
|
||||||
|
BackdropComponent={Backdrop}
|
||||||
|
BackdropProps={{
|
||||||
|
timeout: 300,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Fade in={open} timeout={300}>
|
||||||
|
<div className="modal">
|
||||||
|
<div className="modal-back" onClick={()=>closeModal()}>
|
||||||
|
← Вернуться к списку валют
|
||||||
|
</div >
|
||||||
|
<div className="modal_header">
|
||||||
|
{money[date].values[id].name} ({money[date].values[id].charCode})
|
||||||
|
</div>
|
||||||
|
<div className="modal_body">
|
||||||
|
{data.isLoading?<Loading/>:
|
||||||
|
data.values.map((item, index) => {
|
||||||
|
if(index===data.values.length-1) return null;
|
||||||
|
return <Item item={item} index={index}/>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fade>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
5
src/Components/MoneyList/MoneyList.css
Normal file
5
src/Components/MoneyList/MoneyList.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.money-list {
|
||||||
|
width:100%;
|
||||||
|
position:relative;
|
||||||
|
display:block;
|
||||||
|
}
|
26
src/Components/MoneyList/MoneyList.js
Normal file
26
src/Components/MoneyList/MoneyList.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import './MoneyList.css';
|
||||||
|
|
||||||
|
import Item from "./Item/Item";
|
||||||
|
import Modal from "./Modal/Modal";
|
||||||
|
|
||||||
|
export default function MoneyList(props) {
|
||||||
|
const [modal, setModal] = useState({open:false});
|
||||||
|
|
||||||
|
const openModal = (id) => setModal({id:id,open:true});
|
||||||
|
const closeModal = () => setModal({...modal,open:false});
|
||||||
|
|
||||||
|
let values = [];
|
||||||
|
|
||||||
|
for (let [id, info] of Object.entries(props.money[props.date].values)) values.push({...info, id:id}); //получаем массив для вывода
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="money_list">
|
||||||
|
{values.map((item, index) => { {
|
||||||
|
return <Item item={item} openModal={openModal}/>;
|
||||||
|
}})}
|
||||||
|
<Modal id={modal.id} open={modal.open} money={props.money} date={props.date} closeModal={closeModal} updateData={props.updateData} getDateInfo={props.getDateInfo}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
13
src/index.css
Normal file
13
src/index.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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;
|
||||||
|
}
|
11
src/index.js
Normal file
11
src/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App/App';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
Reference in New Issue
Block a user