Merge remote-tracking branch 'origin/LabWork04' into LabWork04

# Conflicts:
#	front/premium_store/public/index.html
#	spring_online_calculator/src/main/java/premium_store/model/TankLevel.java
This commit is contained in:
Programmist73 2023-05-26 17:47:51 +04:00
commit d6cdd178cd
44 changed files with 31325 additions and 0 deletions

View File

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `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.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

29676
front/premium_store/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
{
"name": "premuim_store",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.3.5",
"cors": "^2.8.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.10.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"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"
]
}
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -0,0 +1,41 @@
import React from 'react';
import '../styles/App.css';
import { NavLink } from 'react-router-dom';
//компонент с кнопками навигации по сайту
const MainHead = (props) => {
return(
<div>
<div>
<h1 className="Main-label">Мир танков</h1>
</div>
<form className="collapse navbar-collapse">
<nav className="navbar navbar-expand-lg justify-content-around">
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" style={{backgroundColor: '#379dc2'}}>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="Main_head navbar-nav me-auto align-items-center">
{
props.links.map(route =>
<li key={route.path}
className="nav-item">
<div className="Button_Main_Group container p-2" div="div">
<NavLink className="nav-link btn border border-3 border-dark fs-4 lh-15" role="button"
to={route.path}>
{route.label}
</NavLink>
</div>
</li>
)
}
</ul>
</div>
</nav>
</form>
</div>
)
}
export default MainHead;

View File

@ -0,0 +1,133 @@
import React, { useState, useEffect, useRef} from 'react';
import axios from 'axios';
import '../styles/App.css';
import TankList from './items/Tank/TankList';
import './AddTank.css';
const PageForChecking = () => {
const[tankItems, setTankItems] = useState([]);
const [nationItems, setNationItems] = useState([]);
const [levelItems, setLevelItems] = useState([]);
const [chooiceNation, setChooiceNation] = useState();
const [chooiceFirstLevel, setChooiceFirstLevel] = useState();
const [chooiceSecondLevel, setChooiceSecondLevel] = useState();
//загрузка всех имеющихся танков, а также уровней и наций при старте
useEffect(() => {
axios.get('http://localhost:8080/level/')
.then((responce) => {
console.log(responce.data);
setLevelItems(responce.data)
});
axios.get('http://localhost:8080/nation/')
.then((responce) => {
console.log(responce.data);
setNationItems(responce.data)
});
}, [])
const getChoiceNation = (newId) => {
setChooiceNation(nationItems[newId - 1].nation);
}
const getChooiceFirstLevel = (newId) => {
setChooiceFirstLevel(levelItems[newId - 1].level);
}
const getChooiceSecondLevel = (newId) => {
setChooiceSecondLevel(levelItems[newId - 1].level);
}
function findList(){
console.log(chooiceNation);
console.log(chooiceFirstLevel);
console.log(chooiceSecondLevel);
axios.get('http://localhost:8080/tank/filteredList/?nation=' + chooiceNation + '&firstLevel=' + chooiceFirstLevel + '&secondLevel=' + chooiceSecondLevel)
.then((response) => {
setTankItems(response.data)
});
console.log(tankItems)
}
return(
<div>
<div className="Group_create_level">
<h1>Фильтрация танков</h1>
<div>
<p style={{fontWeight: "900"}}>
Выберите нацию:
<select
onChange={(event) => getChoiceNation(event.target.selectedIndex)}
>
<option selected>Выберите нацию</option>
{nationItems.map((nationItem) =>
<option
value={nationItem.nation}
key={nationItem.id}
>
{nationItem.nation}
</option>
)}
</select>
</p>
<p style={{fontWeight: "900"}}>
Выберите начальный диапазон уровней:
<select style={{marginTop: "10px"}}
onChange={(event) => getChooiceFirstLevel(event.target.selectedIndex)}
>
<option selected>Выберите уровень</option>
{levelItems.map((levelItem) =>
<option
value={levelItem.level}
key={levelItem.id}
>
{levelItem.level}
</option>
)}
</select>
</p>
<p style={{fontWeight: "900"}}>
Выберите конечный диапазон уровней:
<select style={{marginTop: "10px"}}
onChange={(event) => getChooiceSecondLevel(event.target.selectedIndex)}
>
<option selected>Выберите уровень</option>
{levelItems.map((levelItem) =>
<option
value={levelItem.level}
key={levelItem.id}
>
{levelItem.level}
</option>
)}
</select>
</p>
</div>
<button className='add-level-button'
onClick={findList}
>
Найти танки
</button>
</div>
<div className="Card_list">
{tankItems.length !== 0
?
<TankList tankItems={tankItems}
/>
:
<h1 style={{textAlign: 'center'}}>Отсутствуют какие-либо танки при таком фильтре!</h1>
}
</div>
</div>
);
}
export default PageForChecking

View File

@ -0,0 +1,106 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import cl from '../GameClient/ModalClient.module.css';
import '../../AddClient.css';
const ModalClient = ({data, visible, setVisible}) => {
//для обновления уровня
const [clientNickName, setClientNickName] = useState(data.nickName);
const [clientEmail, setClientEmail] = useState(data.email);
const [clientBalance, setClientBalance] = useState(data.balance);
const [clientTank, setClientTank] = useState(null);
const [tankItems, setTankItems] = useState([]);
useEffect(() => {
console.log('Обращение к БД');
axios.get('http://localhost:8080/tank/')
.then((responce) => {
console.log(responce.data);
setTankItems(responce.data)
});
}, [])
//для контроля видимости модалки
const rootClasses = [cl.myModal];
if(visible)
{
rootClasses.push(cl.active);
}
//добавление нового уровня
function updateLevel(){
axios.put('http://localhost:8080/client/' + data.id + '?nickName='
+ clientNickName + '&email=' + clientEmail + '&balance=' + clientBalance + '&tankId=' + clientTank)
.then((response) => {
console.log("Обновление клиента с id " + data.id)
});
setVisible(false);
}
const getChoiceNation = (newId) => {
setClientTank(tankItems[newId - 1].id);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
<p style={{marginTop: "10px"}}>
Никнейм:
<input
className="add-client-input"
value={clientNickName}
onChange={e => setClientNickName(e.target.value)}
/>
</p>
<p style={{marginTop: "10px"}}>
Почта:
<input
className="add-client-input"
value={clientEmail}
onChange={e => setClientEmail(e.target.value)}
/>
</p>
<p style={{marginTop: "10px"}}>
Баланс:
<input
className="add-client-input"
value={clientBalance}
onChange={e => setClientBalance(e.target.value)}
/>
</p>
<p style={{fontWeight: "900"}}>
Выберите новый танк:
<select
onChange={(event) => getChoiceNation(event.target.selectedIndex)}
>
<option selected>Выберите танк</option>
{tankItems.map((tankItem) =>
<option
value={tankItem.name}
key={tankItem.id}
>
{tankItem.name}
</option>
)}
</select>
</p>
<button
style={{marginTop: "10px"}}
className={cl.modalButton}
type="button"
onClick={updateLevel}
>
Сохранить
</button>
</div>
</div>
);
}
export default ModalClient

View File

@ -0,0 +1,34 @@
.myModal{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background: rgba(0, 0, 0, 0.8);
}
.myModal.active{
display: flex;
justify-content: center;
align-items: center;
}
.myModalContent{
display: inline-block;
padding: 15px;
background: #FF652F;
border-radius: 16px;
min-width: 300px;
min-height: 100px;
justify-content: space-between;
align-items: center;
}
.modalButton{
padding: 5px;
border-radius: 10px;
background-color: #FFE430;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,37 @@
.level-card{
display: flex;
width: 100%;
padding: 15px;
margin-top: 5px;
border: 5px solid;
border-color: #14A76C;
border-radius: 10px;
justify-content: space-around;
align-items: center;
font-family: Courier, monospace;
font-weight: 900;
}
.level-attribute{
padding: 5px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
align-items: center;
}
.level-button-group{
display: flex;
width: 20%;
justify-content: space-around;
align-items: center;
}
.level-button{
padding: 10px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,46 @@
import React, { useState } from 'react';
import axios from 'axios';
import './LevelItem.css';
import ModalLevel from './ModalLevel';
//отвечает за отдельно взятый уровень (вывод карточки с ним)
const LevelItem = (data) => {
const [level, setLevel] = useState(null);
//состояние для контроля вызова модального окна
const[modal, setModal] = useState(false);
function deleteLevel(){
axios.delete('http://localhost:8080/level/' + data.levelItem.id)
.then((response) => {
console.log("Удаление уровня с id " + data.levelItem.id)
});
}
return (
<div className="level-card">
<p className="level-attribute"> id: {data.levelItem.id} </p>
<p className="level-attribute"> уровень: {data.levelItem.level} </p>
<div className='level-button-group'>
<button className="level-button" type="button"
onClick={() => setModal(true)}
>
Редактировать
</button>
<button className="level-button" type="button"
onClick={deleteLevel}
>
Удалить
</button>
<ModalLevel
data={data.levelItem}
visible={modal}
setVisible={setModal}
/>
</div>
</div>
);
};
export default LevelItem;

View File

@ -0,0 +1,25 @@
import React, { useEffect} from 'react';
import LevelItem from './LevelItem';
//const host = import.meta.env.VITE_API_URL;
//отвечает за список всех уровней. Передаём сюда пропсом массив уровней
const LevelList = (levels) => {
return (
<div>
<div>
<h1 style={{textAlign: 'center', fontFamily: 'courier, monospace', background: '#FF652F', borderRadius: '10px'}}>
Список существующих уровней:
</h1>
</div>
{levels.levelItems.map((levelItem) =>
<LevelItem
levelItem={levelItem}
key={levelItem.id}
/>
)}
</div>
);
};
export default LevelList;

View File

@ -0,0 +1,49 @@
import React, { useState } from 'react';
import axios from 'axios';
import cl from './ModalLevel.module.css';
import '../../AddLevel.css';
const ModalLevel = ({data, visible, setVisible}) => {
//для обновления уровня
const [level, setLevel] = useState(data.level);
//для контроля видимости модалки
const rootClasses = [cl.myModal];
if(visible)
{
rootClasses.push(cl.active);
}
//добавление нового уровня
function updateLevel(){
setLevel()
axios.put('http://localhost:8080/level/' + data.id + '?Level=' + level)
.then((response) => {
console.log("Обновление уровня с id " + data.id)
});
setVisible(false);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
<input
className="add-level-input"
value={level}
onChange={e => setLevel(e.target.value)}
/>
<button
className={cl.modalButton}
type="button"
onClick={updateLevel}
>
Сохранить
</button>
</div>
</div>
);
};
export default ModalLevel;

View File

@ -0,0 +1,34 @@
.myModal{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background: rgba(0, 0, 0, 0.8);
}
.myModal.active{
display: flex;
justify-content: center;
align-items: center;
}
.myModalContent{
display: flex;
padding: 15px;
background: #FF652F;
border-radius: 16px;
min-width: 300px;
min-height: 100px;
justify-content: space-between;
align-items: center;
}
.modalButton{
padding: 5px;
border-radius: 10px;
background-color: #FFE430;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,50 @@
import React, { useState } from 'react';
import axios from 'axios';
import cl from './ModalNation.module.css';
import '../../AddNation.css';
const ModalNation = ({data, visible, setVisible}) => {
//для обновления уровня
const [nation, setNation] = useState(data.nation);
const nullId = 0;
//для контроля видимости модалки
const rootClasses = [cl.myModal];
if(visible)
{
rootClasses.push(cl.active);
}
//добавление новой нации
function updateLevel(){
setNation()
axios.put('http://localhost:8080/nation/' + data.id + '?nation=' + nation + '&tankId=' + nullId)
.then((response) => {
console.log("Обновление нации с id " + data.id)
});
setVisible(false);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
<input
className="add-nation-input"
value={nation}
onChange={e => setNation(e.target.value)}
/>
<button
className={cl.modalButton}
type="button"
onClick={updateLevel}
>
Сохранить
</button>
</div>
</div>
);
}
export default ModalNation

View File

@ -0,0 +1,34 @@
.myModal{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background: rgba(0, 0, 0, 0.8);
}
.myModal.active{
display: flex;
justify-content: center;
align-items: center;
}
.myModalContent{
display: flex;
padding: 15px;
background: #FF652F;
border-radius: 16px;
min-width: 300px;
min-height: 100px;
justify-content: space-between;
align-items: center;
}
.modalButton{
padding: 5px;
border-radius: 10px;
background-color: #FFE430;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,42 @@
import React, { useState, useEffect } from 'react';
import cl from './ModalNation.module.css';
import '../../AddNation.css';
import TankList from '../Tank/TankList';
import { useFetcher } from 'react-router-dom';
const ModalTankNation = ({data, visible, setVisible}) => {
//для обновления уровня
const [nation, setNation] = useState(data.tanks);
//для контроля видимости модалки
const rootClasses = [cl.myModal];
//загрузка всех имеющихся танков, а также уровней и наций при старте
useEffect(() => {
console.log("Загрузка МОДАЛКИ №1")
console.log(nation);
}, [])
if(visible)
{
rootClasses.push(cl.active);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
{nation.length !== 0
?
<TankList
label={"Список танков у нации"}
tankItems={nation}
/>
:
<h1 style={{textAlign: 'center'}}>В БД отсутствуют какие-либо танки данной нации!</h1>
}
</div>
</div>
);
}
export default ModalTankNation

View File

@ -0,0 +1,38 @@
.nation-card{
display: flex;
width: 100%;
padding: 15px;
margin-top: 5px;
border: 5px solid;
border-color: #14A76C;
border-radius: 10px;
justify-content: space-around;
align-items: center;
font-family: Courier, monospace;
font-weight: 900;
}
.nation-attribute{
padding: 5px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
align-items: center;
}
.nation-button-group{
display: flex;
margin: 10px;
width: 20%;
justify-content: space-around;
align-items: center;
}
.nation-button{
padding: 10px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,59 @@
import React, { useState } from 'react';
import axios from 'axios';
import './NationItem.css';
import ModalNation from './ModalNation';
import ModalTankNation from './ModalTankNation';
//отвечает за отдельно взятую нацию (вывод карточки с ним)
const NationItem = (data) => {
const [nation, setNation] = useState(null);
//состояние для контроля вызова модального окна
const[modal, setModal] = useState(false);
//состояние для вызова окна показа списка танков нации
const[modalNation, setModalNation] = useState(false);
function deleteNation(){
axios.delete('http://localhost:8080/nation/' + data.nationItem.id)
.then((response) => {
console.log("Удаление нации с id " + data.nationItem.id)
});
}
return (
<div className="nation-card">
<p className="nation-attribute"> id: {data.nationItem.id} </p>
<p className="nation-attribute"> нация: {data.nationItem.nation} </p>
<div className='nation-button-group'>
<button className="nation-button" type="button"
onClick={() => setModal(true)}
>
Редактировать
</button>
<button className="nation-button" type="button"
onClick={deleteNation}
>
Удалить
</button>
<button className="nation-button" type="button"
onClick={() => setModalNation(true)}
>
Список танков
</button>
<ModalNation
data={data.nationItem}
visible={modal}
setVisible={setModal}
/>
<ModalTankNation
data={data.nationItem}
visible={modalNation}
setVisible={setModalNation}
/>
</div>
</div>
);
}
export default NationItem

View File

@ -0,0 +1,22 @@
import React from 'react'
import NationItem from './NationItem';
const NationList = (nations) => {
return (
<div>
<div>
<h1 style={{textAlign: 'center', fontFamily: 'courier, monospace', background: '#FF652F', borderRadius: '10px'}}>
Список существующих наций:
</h1>
</div>
{nations.nationItems.map((nationItem) =>
<NationItem
nationItem={nationItem}
key={nationItem.id}
/>
)}
</div>
);
}
export default NationList

View File

@ -0,0 +1,125 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import cl from './ModalTank.module.css';
import '../../AddTank.css';
const ModalTank = ({data, visible, setVisible}) => {
//для обновления танка
const [tankName, setTankName] = useState(data.name);
const [tankCost, setTankCost] = useState(data.cost);
const [chooiceNation, setChoiceNation] = useState(data.nation.id);
const [chooiceLevel, setChoiceLevel] = useState(data.nation.id);
const [nationItems, setNationItems] = useState([]);
const [levelItems, setLevelItems] = useState([]);
//загрузка всех имеющихся танков, а также уровней и наций при старте
useEffect(() => {
axios.get('http://localhost:8080/level/')
.then((responce) => {
console.log(responce.data);
setLevelItems(responce.data)
});
axios.get('http://localhost:8080/nation/')
.then((responce) => {
console.log(responce.data);
setNationItems(responce.data)
});
}, [])
//для контроля видимости модалки
const rootClasses = [cl.myModal];
if(visible)
{
rootClasses.push(cl.active);
}
//добавление нового танка
function updateLevel(){
setTankName()
axios.put('http://localhost:8080/tank/' + data.id + '?firstName=' + tankName + '&nationId='
+ chooiceNation + '&levelId=' + chooiceLevel + '&cost=' + tankCost)
.then((response) => {
console.log("Обновление танка с id " + data.id)
});
setVisible(false);
}
const getChoiceNation = (newId) => {
setChoiceNation(nationItems[newId - 1].id);
}
const getChoiceLevel = (newId) => {
setChoiceLevel(levelItems[newId - 1].id);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
<p>
Название:
<input
className="add-tank-input"
value={tankName}
onChange={e => setTankName(e.target.value)}
/>
</p>
<p>
Стоимость:
<input
className="add-tank-input"
value={tankCost}
onChange={e => setTankCost(e.target.value)}
/>
</p>
<p style={{fontWeight: "900"}}>
Выберите нацию:
<select
onChange={(event) => getChoiceNation(event.target.selectedIndex)}
>
<option selected>Выберите нацию</option>
{nationItems.map((nationItem) =>
<option
value={nationItem.nation}
key={nationItem.id}
>
{nationItem.nation}
</option>
)}
</select>
</p>
<p style={{fontWeight: "900"}}>
Выберите уровень:
<select style={{marginTop: "10px"}}
onChange={(event) => getChoiceLevel(event.target.selectedIndex)}
>
<option selected>Выберите уровень</option>
{levelItems.map((levelItem) =>
<option
value={levelItem.level}
key={levelItem.id}
>
{levelItem.level}
</option>
)}
</select>
</p>
<button
className={cl.modalButton}
type="button"
onClick={updateLevel}
>
Сохранить
</button>
</div>
</div>
);
}
export default ModalTank

View File

@ -0,0 +1,33 @@
.myModal{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background: rgba(0, 0, 0, 0.8);
}
.myModal.active{
display: flex;
justify-content: center;
align-items: center;
}
.myModalContent{
display: inline-block;
padding: 15px;
background: #FF652F;
border-radius: 16px;
min-width: 300px;
min-height: 100px;
align-items: center;
}
.modalButton{
padding: 5px;
border-radius: 10px;
background-color: #FFE430;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,37 @@
.tank-card{
display: flex;
width: 100%;
padding: 15px;
margin-top: 5px;
border: 5px solid;
border-color: #14A76C;
border-radius: 10px;
justify-content: space-around;
align-items: center;
font-family: Courier, monospace;
font-weight: 900;
}
.tank-attribute{
padding: 5px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
align-items: center;
}
.tank-button-group{
display: flex;
width: 20%;
justify-content: space-around;
align-items: center;
}
.tank-button{
padding: 10px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,48 @@
import React, { useState } from 'react';
import axios from 'axios';
import './TankItem.css';
import ModalTank from './ModalTank';
const TankItem = (data) => {
const [tank, setTank] = useState(null);
//состояние для контроля вызова модального окна
const[modal, setModal] = useState(false);
function deleteTank(){
axios.delete('http://localhost:8080/tank/' + data.tankItem.id)
.then((response) => {
console.log("Удаление танка с id " + data.tankItem.id)
});
}
return (
<div className="tank-card">
<p className="tank-attribute"> id: {data.tankItem.id} </p>
<p className="tank-attribute"> название: {data.tankItem.name} </p>
<p className="tank-attribute"> уровень: {data.tankItem.level.level} </p>
<p className="tank-attribute"> нация: {data.tankItem.nation.nation} </p>
<p className="tank-attribute"> стоимость: {data.tankItem.cost} </p>
<div className='tank-button-group'>
<button className="tank-button" type="button"
onClick={() => setModal(true)}
>
Редактировать
</button>
<button className="tank-button" type="button"
onClick={deleteTank}
>
Удалить
</button>
<ModalTank
data={data.tankItem}
visible={modal}
setVisible={setModal}
/>
</div>
</div>
);
}
export default TankItem

View File

@ -0,0 +1,22 @@
import React from 'react'
import TankItem from './TankItem';
const TankList = (tanks) => {
return (
<div>
<div>
<h1 style={{textAlign: 'center', fontFamily: 'courier, monospace', background: '#FF652F', borderRadius: '10px'}}>
Список существующих наций:
</h1>
</div>
{tanks.tankItems.map((tankItem) =>
<TankItem
tankItem={tankItem}
key={tankItem.id}
/>
)}
</div>
);
}
export default TankList;

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

View File

@ -0,0 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);

View File

@ -0,0 +1,15 @@
package premium_store;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//отключение Cors фильтра - не позволяет организовавыть взаимодействие с разных доменов
@Configuration
class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
}

View File

@ -0,0 +1,21 @@
package premium_store.controller.DTO;
import premium_store.model.TankLevel;
public class LevelDTO {
private final Long id;
private final int level;
public LevelDTO(TankLevel level){
this.id = level.getId();
this.level = level.getLevel();
}
public Long getId(){
return id;
}
public int getLevel(){
return level;
}
}

View File

@ -0,0 +1,21 @@
package premium_store.controller.DTO;
import premium_store.model.Nation;
public class SimpleNationDTO {
private final Long id;
private final String nation;
public SimpleNationDTO(Nation nation){
this.id = nation.getId();
this.nation = nation.getNation();
}
public Long getId(){
return id;
}
public String getNation(){
return nation;
}
}

View File

@ -0,0 +1,39 @@
package premium_store.controller.DTO;
import premium_store.model.Tank;
public class TankDTO {
private final long id;
private final String name;
private final SimpleNationDTO nation;
private final LevelDTO level;
private final int cost;
public TankDTO(Tank tank){
this.id = tank.getId();
this.nation = new SimpleNationDTO(tank.getNation());
this.level = new LevelDTO(tank.getLevel());
this.name = tank.getName();
this.cost = tank.getCost();
}
public long getId(){
return id;
}
public String getName(){
return name;
}
public SimpleNationDTO getNation(){
return nation;
}
public LevelDTO getLevel(){
return level;
}
public int getCost(){
return cost;
}
}

View File

@ -0,0 +1,56 @@
package premium_store.controller.controller;
import org.springframework.web.bind.annotation.*;
import premium_store.controller.DTO.FullNationDTO;
import premium_store.service.NationService;
import premium_store.service.TankService;
import java.util.List;
//привязываем наш контроллер к придуманному корневому URL благодаря аннотациям
//здесь происходит внедрение зависимости нашего сервиса
//так же здесь прописываем вызовы методов CRUD в привязке к URL
@RestController
@CrossOrigin
@RequestMapping("/nation")
public class NationController {
private final NationService nationService;
private final TankService tankService;
public NationController(NationService nationService, TankService tankService){
this.nationService = nationService;
this.tankService = tankService;
}
//аннотация PathVariable связывает значения id из URL и Long id
@GetMapping("/{id}")
public FullNationDTO getNation(@PathVariable Long id) {
return new FullNationDTO(nationService.findNation(id));
}
//с помощью Java Stream преобразуем набор пришедших данных в объекты StudentDto
@GetMapping("/")
public List<FullNationDTO> getNations() {
return nationService.findAllNations().stream()
.map(FullNationDTO::new)
.toList();
}
@PostMapping("/")
public FullNationDTO createNation(@RequestParam("nation") String nation) {
return new FullNationDTO(nationService.addNation(nation));
}
@PutMapping("/{id}")
public FullNationDTO updateNation(@PathVariable Long id,
@RequestParam("nation") String nation,
@RequestParam(value = "tankId", required = false) Long tankId ) {
return new FullNationDTO(nationService.updateNation(id, nation, (tankId > 0) ? tankService.findTank(tankId) : null));
}
@DeleteMapping("/{id}")
public FullNationDTO deleteNation(@PathVariable Long id) {
return new FullNationDTO(nationService.deleteNation(id));
}
}

View File

@ -0,0 +1,71 @@
package premium_store.controller.controller;
import org.springframework.web.bind.annotation.*;
import premium_store.controller.DTO.TankDTO;
import premium_store.service.NationService;
import premium_store.service.TankLevelService;
import premium_store.service.TankService;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/tank")
public class TankController {
private final TankService tankService;
private final TankLevelService tankLevelService;
private final NationService nationService;
public TankController(TankService tankService, TankLevelService tankLevelService, NationService nationService){
this.tankService = tankService;
this.tankLevelService = tankLevelService;
this.nationService = nationService;
}
//аннотация PathVariable связывает значения id из URL и Long id
@GetMapping("/{id}")
public TankDTO getTank(@PathVariable Long id) {
return new TankDTO(tankService.findTank(id));
}
//с помощью Java Stream преобразуем набор пришедших данных в объекты TankDTO
@GetMapping("/")
public List<TankDTO> getTanks() {
return tankService.findAllTanks().stream()
.map(TankDTO::new)
.toList();
}
@GetMapping("/filteredList/")
public List<TankDTO> getFilteredTanks(@RequestParam("nation") String nation,
@RequestParam("firstLevel") int firstLevel,
@RequestParam("secondLevel") int secondLevel) {
return tankService.findListTank(nation, firstLevel, secondLevel).stream()
.map(TankDTO::new)
.toList();
}
@PostMapping("/")
public TankDTO createTank(@RequestParam("firstName") String name,
@RequestParam("nationId") Long nationId,
@RequestParam("levelId") Long tankLevelId,
@RequestParam("cost") int cost
) {
return new TankDTO(tankService.addTank(name, nationService.findNation(nationId), tankLevelService.findLevel(tankLevelId), cost));
}
@PutMapping("/{id}")
public TankDTO updateTank(@PathVariable Long id,
@RequestParam("firstName") String name,
@RequestParam("nationId") Long nationId,
@RequestParam("levelId") Long tankLevelId,
@RequestParam("cost") int cost
) {
return new TankDTO(tankService.updateTank(id, name, nationService.findNation(nationId), tankLevelService.findLevel(tankLevelId), cost));
}
@DeleteMapping("/{id}")
public TankDTO deleteTank(@PathVariable Long id) {
return new TankDTO(tankService.deleteTank(id));
}
}

View File

@ -0,0 +1,50 @@
package premium_store.controller.controller;
import org.springframework.web.bind.annotation.*;
import premium_store.controller.DTO.LevelDTO;
import premium_store.service.TankLevelService;
import java.util.List;
//привязываем наш контроллер к придуманному корневому URL благодаря аннотациям
//здесь происходит внедрение зависимости нашего сервиса
//так же здесь прописываем вызовы методов CRUD в привязке к URL
@RestController
@RequestMapping("/level")
public class TankLevelController {
private final TankLevelService tankLevelService;
public TankLevelController(TankLevelService tankLevelService){
this.tankLevelService = tankLevelService;
}
//аннотация PathVariable связывает значения id из URL и Long id
@GetMapping("/{id}")
public LevelDTO getLevel(@PathVariable Long id) {
return new LevelDTO(tankLevelService.findLevel(id));
}
//с помощью Java Stream преобразуем набор пришедших данных в объекты StudentDto
@GetMapping("/")
public List<LevelDTO> getLevels() {
return tankLevelService.findAllLevels().stream()
.map(LevelDTO::new)
.toList();
}
@PostMapping("/")
public LevelDTO createLevel(@RequestParam("Level") int level) {
return new LevelDTO(tankLevelService.addLevel(level));
}
@PutMapping("/{id}")
public LevelDTO updateLevel(@PathVariable Long id,
@RequestParam("Level") int level) {
return new LevelDTO(tankLevelService.updateLevel(id, level));
}
@DeleteMapping("/{id}")
public LevelDTO deleteLevel(@PathVariable Long id) {
return new LevelDTO(tankLevelService.deleteLevel(id));
}
}

View File

@ -0,0 +1,62 @@
package premium_store.model;
import javax.persistence.*;
import java.util.Objects;
@Entity
public class Level {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private int level;
public Level() {
}
public Level(int level) {
this.level = level;
}
//возвращает id
public Long getId() {
return id;
}
//возвращает нацию
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
//метод для сравнения
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Level level = (Level) o;
return Objects.equals(id, level.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
//преобразование данных по объекту в строчку
@Override
public String toString() {
return "Level{" +
"id=" + id +
", level='" + level + '}';
}
}

View File

@ -0,0 +1,9 @@
package premium_store.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import premium_store.model.Nation;
//класс для взаимодействия с БД вместо низкоуровневого EntityManager
//передаём тип класса и тип id его элементов
public interface NationRepository extends JpaRepository<Nation, Long> {
}

View File

@ -0,0 +1,9 @@
package premium_store.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import premium_store.model.TankLevel;
//класс для взаимодействия с БД вместо низкоуровневого EntityManager
//передаём тип класса и тип id его элементов
public interface TankLevelRepository extends JpaRepository<TankLevel, Long> {
}

View File

@ -0,0 +1,17 @@
package premium_store.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import premium_store.model.Tank;
import java.util.List;
public interface TankRepository extends JpaRepository<Tank, Long> {
@Query("SELECT t FROM Tank t WHERE t.nation.nation = :nation AND t.tankLevel.level BETWEEN :llevelid AND :l2levelid")
List<Tank> checkNationAndLevel(
@Param("nation") String nation,
@Param("llevelid") int levelOne,
@Param("l2levelid") int levelTwo);
}

View File

@ -0,0 +1,68 @@
package premium_store.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import premium_store.model.TankLevel;
import premium_store.repository.TankLevelRepository;
import premium_store.service.exception.LevelNotFoundException;
import premium_store.util.validation.ValidatorUtil;
import java.util.List;
import java.util.Optional;
//сервис после удаления EntityManager и добавления нашего репозитория. То есть у него уже все методы работы с полями прописаны за нас?
@Service
public class TankLevelService {
private final TankLevelRepository tankLevelRepository;
private final ValidatorUtil validatorUtil;
public TankLevelService(TankLevelRepository tankLevelRepository, ValidatorUtil validatorUtil){
this.tankLevelRepository = tankLevelRepository;
this.validatorUtil = validatorUtil;
}
@Transactional
public TankLevel addLevel(int newLevel) {
final TankLevel tankLevel = new TankLevel(newLevel);
validatorUtil.validate(tankLevel);
return tankLevelRepository.save(tankLevel);
}
//здесь используем Optional - спец. тип данных, позволяющий определять, вернулось ли что-то при вызове метода, или вернулся null
@Transactional(readOnly = true)
public TankLevel findLevel(Long id) {
final Optional<TankLevel> level = tankLevelRepository.findById(id);
//благодаря Optional можем вызвать orElseThrow, который в случае null сделает проброс кастомного исключения
return level.orElseThrow(() -> new LevelNotFoundException(id));
}
@Transactional(readOnly = true)
public List<TankLevel> findAllLevels() {
return tankLevelRepository.findAll();
}
@Transactional
public TankLevel updateLevel(Long id, int newLevel) {
final TankLevel currentTankLevel = findLevel(id);
currentTankLevel.setLevel(newLevel);
validatorUtil.validate(currentTankLevel);
return tankLevelRepository.save(currentTankLevel);
}
@Transactional
public TankLevel deleteLevel(Long id) {
final TankLevel currentTankLevel = findLevel(id);
tankLevelRepository.delete(currentTankLevel);
return currentTankLevel;
}
@Transactional
public void deleteAllLevels() {
tankLevelRepository.deleteAll();
}
}

View File

@ -0,0 +1,10 @@
package premium_store.service.exception;
//класс-обработчик ошибки, когда вытаемся вытащить студента из бд по некорректному id
//наследуем от RuntimeException, т. к. он позволяет его проще вызывать
public class LevelNotFoundException extends RuntimeException {
public LevelNotFoundException(Long id) {
super(String.format("Level with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,7 @@
package premium_store.service.exception;
public class NationNotFoundException extends RuntimeException {
public NationNotFoundException(Long id) {
super(String.format("Nation with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,7 @@
package premium_store.service.exception;
public class TankNotFoundException extends RuntimeException {
public TankNotFoundException(Long id) {
super(String.format("Tank with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,10 @@
package premium_store.util.validation;
import java.util.Set;
//класс для передачи списка ошибок, если они возникают
public class ValidationException extends RuntimeException {
public ValidationException(Set<String> errors) {
super(String.join("\n", errors));
}
}

View File

@ -0,0 +1,32 @@
package premium_store.util.validation;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
import java.util.stream.Collectors;
//компонент для ручной валидации данных (проверка на заполнение или пустоту, выход за допустимые диапазоны значений и т. д.)
@Component
public class ValidatorUtil {
private final Validator validator;
public ValidatorUtil() {
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
this.validator = factory.getValidator();
}
}
public <T> void validate(T object) {
final Set<ConstraintViolation<T>> errors = validator.validate(object);
if (!errors.isEmpty()) {
throw new ValidationException(errors.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toSet()));
}
}
}