This commit is contained in:
Inohara 2023-04-18 21:06:26 +04:00
parent b91f23a39e
commit 6c9581c820
47 changed files with 11097 additions and 12509 deletions

23
1/.gitignore vendored Normal file
View 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*

70
1/README.md Normal file
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)

19
1/data.json Normal file
View File

@ -0,0 +1,19 @@
{
"posts": [
{
"id": 1,
"title": "json-server",
"author": "typicode"
}
],
"comments": [
{
"id": 1,
"body": "some comment",
"postId": 1
}
],
"profile": {
"name": "typicode"
}
}

10117
1/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
1/package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "front",
"version": "1.0.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4",
"axios": "^1.1.3",
"bootstrap": "^5.2.2"
},
"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"
]
}
}

18
1/public/index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js" integrity="sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N" crossorigin="anonymous"></script>
<title>Front for spring</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -1,4 +1,4 @@
import axios from 'axios';
import axios from "axios";
function toJSON(data) {
const jsonObj = {};

View File

@ -42,7 +42,7 @@ export default function CatalogProducts(props) {
<div className="mb-3">
<label htmlFor="cost" className="form-label">Цена</label>
<input type="text" id="cost" className="form-control" required
<input type="number" id="cost" className="form-control" required
value={data.cost} onChange={handleFormChange}/>
</div>
</Catalog>

View File

@ -4,10 +4,7 @@ export default function Header(props) {
return (
<nav className="navbar navbar-expand-lg bg-light">
<div className="container-fluid">
<a className="navbar-brand" href="/">
<i className="fa-solid fa-book"></i>
Поставки
</a>
<h1 className="navbar-brand">Поставки</h1>
<button className="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">

View File

@ -1,10 +1,12 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import ProductPage from './components/ProductPage';
import './style.css'
ReactDOM.createRoot(document.getElementById('root')).render(
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
<ProductPage />
</React.StrictMode>
)
);

View File

@ -1,13 +1,22 @@
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@EnableWebMvc
public class WebConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS");
}
};
}
}

View File

@ -5,7 +5,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/product")
public class ProductController {

View File

@ -11,6 +11,7 @@ public class ProductDto {
private List<Orders> orders;
public ProductDto(Product product) {
this.id = product.getId();
this.name = product.getName();
this.cost = product.getCost();
this.orders = product.getOrders();

12681
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,40 @@
{
"name": "front",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"fake-server": "json-server --watch data.json -p 8079",
"start": "npm-run-all --parallel dev fake-server",
"build": "vite build",
"preview": "vite preview"
},
"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",
"bootstrap": "^5.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.4",
"axios": "^1.1.3",
"bootstrap": "^5.2.2",
"@fortawesome/fontawesome-free": "^6.2.1"
"react-router-dom": "^6.10.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"vite": "^3.2.3",
"@vitejs/plugin-react": "^2.2.0",
"npm-run-all": "^4.1.5",
"json-server": "^0.17.1"
"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

@ -1,16 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp" crossorigin="anonymous">
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js" integrity="sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N" crossorigin="anonymous"></script>
<title>Front for spring</title>
<link rel="stylesheet" href="path/to/font-awesome/css/font-awesome.min.css">
<title>React App</title>
</head>
<body>
<div id="root"></div>

22
front/src/App.js Normal file
View File

@ -0,0 +1,22 @@
import { BrowserRouter, Route, Routes} from "react-router-dom";
import CatalogProducts from "./Catalogs/CatalogProducts";
import CatalogSuppliers from "./Catalogs/CatalogSuppliers";
import Header from "./general/Header";
function App() {
return (
<BrowserRouter>
<Header/>
<Routes>
<Route path="/" Component={CatalogProducts}/>
<Route path="/products" Component={CatalogProducts} />
<Route path="/suppliers" Component={CatalogSuppliers} />
<Route path="/orders" Component={CatalogSuppliers} />
</Routes>
</BrowserRouter>
);
}
export default App;

View File

View File

@ -0,0 +1,49 @@
import { useState } from 'react';
import Catalog from '../general/Catalog'
import Product from '../models/Product';
export default function CatalogProducts(props) {
const url = 'product/'
const transformer = (data) => new Product(data)
const catalogProductHeaders = [
{ name: 'name', label: 'Продукт' },
{ name: 'cost', label: 'Цена' }
];
const [data, setData] = useState(new Product());
const add = () => setData(new Product());
const edit = (data) => {
console.log(data)
setData(new Product(data))
}
function handleFormChange(event) {
setData({ ...data, [event.target.id]: event.target.value })
}
return (
<Catalog headers={catalogProductHeaders}
getAllUrl={url}
url={url}
transformer={transformer}
data={data}
add={add}
edit={edit}>
<div className="mb-3">
<label htmlFor="name" className="form-label">Наименование</label>
<input type="text" id="name" className="form-control" required
value={data.name} onChange={handleFormChange}/>
</div>
<div className="mb-3">
<label htmlFor="cost" className="form-label">Цена</label>
<input type="number" id="cost" className="form-control" required
value={data.cost} onChange={handleFormChange}/>
</div>
</Catalog>
);
}

View File

@ -0,0 +1,45 @@
import { useState } from 'react';
import Catalog from '../general/Catalog'
import Supplier from '../models/Supplier';
export default function CatalogSuppliers(props) {
const url = 'supplier/'
const transformer = (data) => new Supplier(data)
const catalogProductHeaders = [
{ name: 'name', label: 'Поставщик' },
{ name: 'license', label: 'Лицензия' }
];
const [data, setData] = useState(new Supplier());
const add = () => setData(new Supplier());
const edit = (data) => setData(new Supplier(data))
const handleFormChange = (event) => {
setData({ ...data, [event.target.id]: event.target.value })
}
return (
<Catalog headers={catalogProductHeaders}
getAllUrl={url}
url={url}
transformer={transformer}
data={data}
add={add}
edit={edit}>
<div className="mb-3">
<label htmlFor="name" className="form-label">Поставщик</label>
<input type="text" id="name" className="form-control" required
value={data.name} onChange={handleFormChange}/>
</div>
<div className="mb-3">
<label htmlFor="license" className="form-label">Лицензия</label>
<input type="number" id="license" className="form-control" required
value={data.license} onChange={handleFormChange}/>
</div>
</Catalog>
);
}

69
front/src/DataService.js Normal file
View File

@ -0,0 +1,69 @@
import axios from 'axios';
function toJSON(data) {
const jsonObj = {};
const fields = Object.getOwnPropertyNames(data);
for (const field of fields) {
if (data[field] === undefined) {
continue;
}
jsonObj[field] = data[field];
}
return jsonObj;
}
export default class DataService {
static dataUrlPrefix = 'http://localhost:8080/';
static async readAll(url, transformer) {
const response = await axios.get(this.dataUrlPrefix + url);
return response.data.map(item => transformer(item));
}
static async read(url, transformer) {
const response = await axios.get(this.dataUrlPrefix + url);
return transformer(response.data);
}
static async create(url, data) {
console.log(toJSON(data))
// const response = await axios.post(this.dataUrlPrefix + url, toJSON(data));
await axios.post('http://localhost:8080/product/', {
name: 'product3',
cost: 123
})
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
return true;
}
static async update(url, data) {
// console.log(toJSON(data))
// const response = await axios.put(url);
await axios.put('http://localhost:8080/product/1', {
name: 'product2',
cost: 342
})
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
return true;
}
static async delete(url) {
const response = await axios.delete(this.dataUrlPrefix + url);
return response.data.id;
}
}

View File

@ -0,0 +1,114 @@
import React, { useState, useEffect } from "react";
import ToolBar from "./ToolBar";
import Modal from "./Modal";
import Table from "./Table";
import DataService from "../DataService";
function Catalog(props) {
const [items, setItems] = useState([]);
const [modalHeader, setModalHeader] = useState('')
const [modalConfirm, setModalConfirm] = useState('')
const [modalVisible, setModalVisible] = useState(false)
const [isEdit, setEdit] = useState(false)
let selectedItems = [];
useEffect(() => {
loadItems()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const loadItems = () => {
DataService.readAll(props.getAllUrl, props.transformer)
.then(data => setItems(data))
}
const saveItem = () => {
if (!isEdit) {
props
DataService.create(props.url, props.data).then(() => loadItems());
} else {
DataService.update(props.url + props.data.id, props.data).then(() => loadItems());
}
}
const add = () => {
setEdit(false);
setModalHeader('Добавление');
setModalConfirm('Добавить');
setModalVisible(true);
props.add();
}
const edit = () => {
if (selectedItems.length === 0) {
return;
}
editItem(selectedItems[0])
}
const editItem = (editedId) => {
DataService.read(props.url + editedId, props.transformer)
.then(data => {
setEdit(true);
setModalHeader('Редактирование элемента');
setModalConfirm('Сохранить');
setModalVisible(true);
props.edit(data);
});
}
const remove = () => {
if (selectedItems.length === 0) {
return;
}
if (window.confirm('Удалить выбранные элементы?')) {
const promises = [];
selectedItems.forEach(item => {
promises.push(DataService.delete(props.url + item));
});
Promise.all(promises).then((results) => {
selectedItems.length = 0;
loadItems();
});
}
}
const handleTableClick = (tableSelectedItems) =>{
console.log(tableSelectedItems)
selectedItems = tableSelectedItems;
}
const handleTableDblClick = (tableSelectedItem) => editItem(tableSelectedItem);
const hideModal = () => setModalVisible(false)
//после принятия в модальном окне идет создание, либо изменение объекта
const modalDone = () => saveItem()
return (
<>
<ToolBar
add={add}
edit={edit}
remove={remove}/>
<Table
headers={props.headers}
items={items}
selectable={true}
onClick={handleTableClick}
onDblClick={handleTableDblClick}/>
<Modal
header={modalHeader}
confirm={modalConfirm}
visible={modalVisible}
onHide={hideModal}
onDone={modalDone}>
{props.children}
</Modal>
</>
);
}
export default Catalog;

View File

@ -0,0 +1,36 @@
import { Link } from 'react-router-dom';
export default function Header() {
return (
<nav className="navbar navbar-expand-lg bg-light">
<div className="container-fluid">
<h1 className="navbar-brand">Поставки</h1>
<button className="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav">
<li className="nav-item">
<Link className="nav-link" to="/products">
Продукты
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/suppliers">
Поставщики
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/orders">
Заказы
</Link>
</li>
</ul>
</div>
</div>
</nav >
);
}

View File

@ -0,0 +1,47 @@
import React from "react";
function Modal(props) {
const formRef = React.createRef();
const hide = () => {
props.onHide();
}
const done = (e) => {
e.preventDefault();
if (formRef.current.checkValidity()) {
props.onDone();
hide();
} else {
formRef.current.reportValidity();
}
}
return (
<div className="modal fade show" tabIndex="-1" aria-hidden="true"
style={{ display: props.visible ? 'block' : 'none' }}>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h1 className="modal-title fs-5" id="exampleModalLabel">{props.header}</h1>
<button className="btn-close" type="button" aria-label="Close"
onClick={hide}></button>
</div>
<div className="modal-body">
<form ref={formRef} onSubmit={done}>
{props.children}
</form>
</div>
<div className="modal-footer">
<button className="btn btn-secondary" type="button" onClick={hide}>Закрыть</button>
<button className="btn btn-primary" type="button" onClick={done}>
{props.confirm}
</button>
</div>
</div>
</div>
</div>
);
}
export default Modal;

View File

@ -0,0 +1,75 @@
import { useState } from 'react';
import styles from './Table.module.css';
function Table(props) {
const [tableUpdate, setTableUpdate] = useState(false);
const [selectedItems, setSelectedItems] = useState([]);
const isSelected = (id) => {
if (!props.selectable) {
return false;
}
return selectedItems.includes(id);
}
const click = (id) => {
if (!props.selectable) {
return;
}
if (isSelected(id)) {
var index = selectedItems.indexOf(id);
if (index !== -1) {
selectedItems.splice(index, 1);
setSelectedItems(selectedItems);
setTableUpdate(!tableUpdate);
}
} else {
selectedItems.push(id);
setSelectedItems(selectedItems);
setTableUpdate(!tableUpdate);
}
props.onClick(selectedItems);
}
const dblClick = (id) => {
if (!props.selectable) {
return;
}
props.onDblClick(id);
}
return (
<table className={`table table-hover ${styles.table} ${props.selectable ? styles.selectable : ''}`}>
<thead>
<tr>
<th scope="col">#</th>
{
props.headers.map(header =>
<th key={header.name} scope="col">
{header.label}
</th>
)
}
</tr>
</thead>
<tbody>
{
props.items.map((item, index) =>
<tr key={item.id}
className={isSelected(item.id) ? styles.selected : ''}
onClick={(e) => click(item.id, e)} onDoubleClick={(e) => dblClick(item.id, e)}>
<th scope="row">{index + 1}</th>
{
props.headers.map(header =>
<td key={item.id + header.name}>{item[header.name]}</td>
)
}
</tr>
)
}
</tbody >
</table >
);
}
export default Table;

View File

@ -0,0 +1,12 @@
.table tbody tr {
user-select: none;
}
.selectable tbody tr:hover {
cursor: pointer;
}
.selected {
background-color: #0d6efd;
opacity: 80%;
}

View File

@ -0,0 +1,34 @@
import React from "react";
import styles from './Toolbar.module.css';
//кнопочки
function ToolBar(props) {
const add = () => {
props.add()
}
const edit = () => {
props.edit()
}
const remove = () => {
props.remove()
}
return (
<div className="btn-group mt-2" role="group">
<button type="button" className={`btn btn-success ${styles.btn}`} onClick={add}>
Добавить
</button>
<button type="button" className={`btn btn-warning ${styles.btn}`} onClick={edit} >
Изменить
</button >
<button type="button" className={`btn btn-danger ${styles.btn}`} onClick={remove}>
Удалить
</button >
</div >
);
}
export default ToolBar;

View File

@ -0,0 +1,3 @@
.btn {
min-width: 140px;
}

10
front/src/index.js Normal file
View File

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

View File

@ -0,0 +1,7 @@
export default class Order {
constructor(data) {
this.id = data?.id;
this.date = data?.date || '';
this.supplierId = data?.supplierId || '';
}
}

View File

@ -0,0 +1,7 @@
export default class Product {
constructor(data) {
this.id = data?.id;
this.name = data?.name || '';
this.cost = data?.cost || '';
}
}

View File

@ -0,0 +1,7 @@
export default class Supplier {
constructor(data) {
this.id = data?.id;
this.name = data?.name || '';
this.license = data?.license || '';
}
}