From b91f23a39e2d0f0de988cd62dcb7025d6bac1f96 Mon Sep 17 00:00:00 2001 From: Ino Date: Wed, 12 Apr 2023 16:13:25 +0400 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BA=D1=82=20+=5F+=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BC=D0=BE=D0=B3=D0=B8=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../supply/Supplier/SupplierController.java | 3 + front/package.json | 51 ++++---- front/src/App.jsx | 40 +++++++ front/src/Navigation.js | 24 ---- front/src/components/DataService.js | 42 +++++++ front/src/{ => components}/ProductPage.js | 4 - front/src/components/catalogs/Catalog.jsx | 113 ++++++++++++++++++ .../src/components/catalogs/CatalogOrders.jsx | 41 +++++++ .../components/catalogs/CatalogProducts.jsx | 50 ++++++++ .../components/catalogs/CatalogSuppliers.jsx | 48 ++++++++ front/src/components/catalogs/Catalogs.jsx | 13 ++ front/src/components/common/Header.jsx | 33 +++++ front/src/components/common/LinksList.jsx | 16 +++ front/src/components/common/Modal.jsx | 46 +++++++ front/src/components/common/Table.jsx | 73 +++++++++++ front/src/components/common/Table.module.css | 12 ++ front/src/components/common/Toolbar.jsx | 29 +++++ .../src/components/common/Toolbar.module.css | 3 + front/src/components/models/Order.js | 7 ++ front/src/components/models/Product.js | 7 ++ front/src/components/models/Supplier.js | 7 ++ front/src/index.css | 13 -- front/src/index.js | 11 -- front/src/main.jsx | 10 ++ 24 files changed, 614 insertions(+), 82 deletions(-) create mode 100644 front/src/App.jsx delete mode 100644 front/src/Navigation.js create mode 100644 front/src/components/DataService.js rename front/src/{ => components}/ProductPage.js (99%) create mode 100644 front/src/components/catalogs/Catalog.jsx create mode 100644 front/src/components/catalogs/CatalogOrders.jsx create mode 100644 front/src/components/catalogs/CatalogProducts.jsx create mode 100644 front/src/components/catalogs/CatalogSuppliers.jsx create mode 100644 front/src/components/catalogs/Catalogs.jsx create mode 100644 front/src/components/common/Header.jsx create mode 100644 front/src/components/common/LinksList.jsx create mode 100644 front/src/components/common/Modal.jsx create mode 100644 front/src/components/common/Table.jsx create mode 100644 front/src/components/common/Table.module.css create mode 100644 front/src/components/common/Toolbar.jsx create mode 100644 front/src/components/common/Toolbar.module.css create mode 100644 front/src/components/models/Order.js create mode 100644 front/src/components/models/Product.js create mode 100644 front/src/components/models/Supplier.js delete mode 100644 front/src/index.css delete mode 100644 front/src/index.js create mode 100644 front/src/main.jsx diff --git a/demo/src/main/java/com/example/demo/supply/Supplier/SupplierController.java b/demo/src/main/java/com/example/demo/supply/Supplier/SupplierController.java index 2f0e0cf..7e2cd9b 100644 --- a/demo/src/main/java/com/example/demo/supply/Supplier/SupplierController.java +++ b/demo/src/main/java/com/example/demo/supply/Supplier/SupplierController.java @@ -4,6 +4,9 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +@RestController +@CrossOrigin +@RequestMapping("/supplier") public class SupplierController { private final SupplierService supplierService; diff --git a/front/package.json b/front/package.json index 099b979..28584da 100644 --- a/front/package.json +++ b/front/package.json @@ -1,39 +1,30 @@ + { "name": "front", - "version": "0.1.0", "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", - "bootstrap": "^5.3.0-alpha2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" + "react-router-dom": "^6.4.4", + "axios": "^1.1.3", + "bootstrap": "^5.2.2", + "@fortawesome/fontawesome-free": "^6.2.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" - ] + "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" } } diff --git a/front/src/App.jsx b/front/src/App.jsx new file mode 100644 index 0000000..3308100 --- /dev/null +++ b/front/src/App.jsx @@ -0,0 +1,40 @@ +import { useRoutes, Outlet, BrowserRouter } from 'react-router-dom'; +import Header from './components/common/Header'; +import Catalogs from './components/catalogs/Catalogs'; +import CatalogProducts from './components/catalogs/CatalogProducts'; +import CatalogSuppliers from './components/catalogs/CatalogSuppliers'; + + +function Router(props) { + return useRoutes(props.rootRoute); +} + +export default function App() { + const routes = [ + { index: true, element: }, + { path: 'catalogs', element: , label: 'Справочники' }, + { path: 'catalogs/products', element: }, + { path: 'catalogs/suppliers', element: } + ]; + const links = routes.filter(route => route.hasOwnProperty('label')); + const rootRoute = [ + { path: '/', element: render(links), children: routes } + ]; + + function render(links) { + return ( + <> +
+
+ +
+ + ); + } + + return ( + + + + ); +} \ No newline at end of file diff --git a/front/src/Navigation.js b/front/src/Navigation.js deleted file mode 100644 index 0fcff31..0000000 --- a/front/src/Navigation.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -function Navigation(){ - return( -
- -
- ); -} - -export default nav; \ No newline at end of file diff --git a/front/src/components/DataService.js b/front/src/components/DataService.js new file mode 100644 index 0000000..f490167 --- /dev/null +++ b/front/src/components/DataService.js @@ -0,0 +1,42 @@ +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) { + const response = await axios.post(this.dataUrlPrefix + url, toJSON(data)); + return true; + } + + static async update(url, data) { + const response = await axios.put(this.dataUrlPrefix + url, toJSON(data)); + return true; + } + + static async delete(url) { + const response = await axios.delete(this.dataUrlPrefix + url); + return response.data.id; + } +} \ No newline at end of file diff --git a/front/src/ProductPage.js b/front/src/components/ProductPage.js similarity index 99% rename from front/src/ProductPage.js rename to front/src/components/ProductPage.js index 51975e7..fbba300 100644 --- a/front/src/ProductPage.js +++ b/front/src/components/ProductPage.js @@ -29,10 +29,6 @@ export default function ProductPage(){ }) } - - - - return(
diff --git a/front/src/components/catalogs/Catalog.jsx b/front/src/components/catalogs/Catalog.jsx new file mode 100644 index 0000000..4ed06ce --- /dev/null +++ b/front/src/components/catalogs/Catalog.jsx @@ -0,0 +1,113 @@ +import { useState, useEffect } from "react"; +import Toolbar from "../common/Toolbar"; +import Table from "../common/Table"; +import Modal from "../common/Modal"; +import DataService from '../DataService'; + +export default 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(); + }, []); + + function loadItems() { + DataService.readAll(props.getAllUrl, props.transformer) + .then(data => setItems(data)); + } + + function saveItem() { + if (!isEdit) { + DataService.create(props.url, props.data).then(() => loadItems()); + } else { + DataService.update(props.url + props.data.id, props.data).then(() => loadItems()); + } + } + + function handleAdd() { + setEdit(false); + setModalHeader('Добавление элемента'); + setModalConfirm('Добавить'); + setModalVisible(true); + props.onAdd(); + } + + function handleEdit() { + if (selectedItems.length === 0) { + return; + } + edit(selectedItems[0]); + } + + function edit(editedId) { + DataService.read(props.url + editedId, props.transformer) + .then(data => { + setEdit(true); + setModalHeader('Редактирование элемента'); + setModalConfirm('Сохранить'); + setModalVisible(true); + props.onEdit(data); + }); + } + + function handleRemove() { + if (selectedItems.length === 0) { + return; + } + if (confirm('Удалить выбранные элементы?')) { + const promises = []; + selectedItems.forEach(item => { + promises.push(DataService.delete(props.url + item)); + }); + Promise.all(promises).then((results) => { + selectedItems.length = 0; + loadItems(); + }); + } + } + + function handleTableClick(tableSelectedItems) { + selectedItems = tableSelectedItems; + } + + function handleTableDblClick(tableSelectedItem) { + edit(tableSelectedItem); + } + + function handleModalHide() { + setModalVisible(false); + } + + function handleModalDone() { + saveItem(); + } + + return ( + <> + + + + {props.children} + + + ); +} \ No newline at end of file diff --git a/front/src/components/catalogs/CatalogOrders.jsx b/front/src/components/catalogs/CatalogOrders.jsx new file mode 100644 index 0000000..1b7a86b --- /dev/null +++ b/front/src/components/catalogs/CatalogOrders.jsx @@ -0,0 +1,41 @@ +import { useState } from 'react'; +import Catalog from './Catalog'; +import Discipline from '../../models/Discipline'; + +export default function CatalogOrders(props) { + const url = 'product/'; + const transformer = (data) => new Discipline(data); + const catalogProductHeaders = [ + { name: 'name', label: 'Продукт' } + ]; + + const [data, setData] = useState(new Discipline()); + + function handleOnAdd() { + setData(new Discipline()); + } + + function handleOnEdit(data) { + setData(new Discipline(data)); + } + + function handleFormChange(event) { + setData({ ...data, [event.target.id]: event.target.value }) + } + + return ( + +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/front/src/components/catalogs/CatalogProducts.jsx b/front/src/components/catalogs/CatalogProducts.jsx new file mode 100644 index 0000000..0f9d963 --- /dev/null +++ b/front/src/components/catalogs/CatalogProducts.jsx @@ -0,0 +1,50 @@ +import { useState } from 'react'; +import Catalog from './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()); + + function handleOnAdd() { + setData(new Product()); + } + + function handleOnEdit(data) { + setData(new Product(data)); + } + + function handleFormChange(event) { + setData({ ...data, [event.target.id]: event.target.value }) + } + + return ( + +
+ + +
+ +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/front/src/components/catalogs/CatalogSuppliers.jsx b/front/src/components/catalogs/CatalogSuppliers.jsx new file mode 100644 index 0000000..8ba1a66 --- /dev/null +++ b/front/src/components/catalogs/CatalogSuppliers.jsx @@ -0,0 +1,48 @@ +import { useState } from 'react'; +import Catalog from './Catalog'; +import Supplier from '../models/Supplier'; + +export default function CatalogSuppliers(props) { + const url = 'supplier/'; + const transformer = (data) => new Supplier(data); + const catalogSupplierHeaders = [ + { name: 'name', label: 'Название организации' }, + { name: 'license', label: 'Номер лицензии' } + ]; + + const [data, setData] = useState(new Supplier()); + + function handleOnAdd() { + setData(new Supplier()); + } + + function handleOnEdit(data) { + setData(new Supplier(data)); + } + + function handleFormChange(event) { + setData({ ...data, [event.target.id]: event.target.value }) + } + + return ( + +
+ + +
+ +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/front/src/components/catalogs/Catalogs.jsx b/front/src/components/catalogs/Catalogs.jsx new file mode 100644 index 0000000..939a156 --- /dev/null +++ b/front/src/components/catalogs/Catalogs.jsx @@ -0,0 +1,13 @@ +import LinksList from '../common/LinksList'; + +export default function Catalogs(props) { + const catalogs = [ + { name: 'products', label: 'Продукты' }, + { name: 'suppliers', label: 'Поставщики' }, + // { name: 'orders', label: 'Заказы' } + ]; + + return ( + + ); +} \ No newline at end of file diff --git a/front/src/components/common/Header.jsx b/front/src/components/common/Header.jsx new file mode 100644 index 0000000..5a97719 --- /dev/null +++ b/front/src/components/common/Header.jsx @@ -0,0 +1,33 @@ +import { NavLink } from 'react-router-dom'; + +export default function Header(props) { + return ( + + ); +} \ No newline at end of file diff --git a/front/src/components/common/LinksList.jsx b/front/src/components/common/LinksList.jsx new file mode 100644 index 0000000..53b6b27 --- /dev/null +++ b/front/src/components/common/LinksList.jsx @@ -0,0 +1,16 @@ +import { Link } from 'react-router-dom'; + +export default function LinksList(props) { + return ( +
+ { + props.items.map(catalog => + + {catalog.label} + + ) + } +
+ ); +} \ No newline at end of file diff --git a/front/src/components/common/Modal.jsx b/front/src/components/common/Modal.jsx new file mode 100644 index 0000000..71ff72a --- /dev/null +++ b/front/src/components/common/Modal.jsx @@ -0,0 +1,46 @@ +import React from "react"; + +export default function Modal(props) { + const formRef = React.createRef(); + + function hide() { + props.onHide(); + } + + function done(e) { + e.preventDefault(); + if (formRef.current.checkValidity()) { + props.onDone(); + hide(); + } else { + formRef.current.reportValidity(); + } + + } + + return ( + + ); +} \ No newline at end of file diff --git a/front/src/components/common/Table.jsx b/front/src/components/common/Table.jsx new file mode 100644 index 0000000..3febb28 --- /dev/null +++ b/front/src/components/common/Table.jsx @@ -0,0 +1,73 @@ +import { useState } from 'react'; +import styles from './Table.module.css'; + +export default function Table(props) { + const [tableUpdate, setTableUpdate] = useState(false); + const [selectedItems, setSelectedItems] = useState([]); + + function isSelected(id) { + if (!props.selectable) { + return false; + } + return selectedItems.includes(id); + } + + function 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); + } + + function dblClick(id) { + if (!props.selectable) { + return; + } + props.onDblClick(id); + } + + return ( +
+ + + + { + props.headers.map(header => + + ) + } + + + + { + props.items.map((item, index) => + click(item.id, e)} onDoubleClick={(e) => dblClick(item.id, e)}> + + { + props.headers.map(header => + + ) + } + + ) + } + +
# + {header.label} +
{index + 1}{item[header.name]}
+ ); +} \ No newline at end of file diff --git a/front/src/components/common/Table.module.css b/front/src/components/common/Table.module.css new file mode 100644 index 0000000..f300e07 --- /dev/null +++ b/front/src/components/common/Table.module.css @@ -0,0 +1,12 @@ +.table tbody tr { + user-select: none; +} + +.selectable tbody tr:hover { + cursor: pointer; +} + +.selected { + background-color: #0d6efd; + opacity: 80%; +} \ No newline at end of file diff --git a/front/src/components/common/Toolbar.jsx b/front/src/components/common/Toolbar.jsx new file mode 100644 index 0000000..255847b --- /dev/null +++ b/front/src/components/common/Toolbar.jsx @@ -0,0 +1,29 @@ +import styles from './Toolbar.module.css'; + +export default function Toolbar(props) { + function add() { + props.onAdd(); + } + + function edit() { + props.onEdit(); + } + + function remove() { + props.onRemove(); + } + + return ( +
+ + + +
+ ); +} \ No newline at end of file diff --git a/front/src/components/common/Toolbar.module.css b/front/src/components/common/Toolbar.module.css new file mode 100644 index 0000000..b169b69 --- /dev/null +++ b/front/src/components/common/Toolbar.module.css @@ -0,0 +1,3 @@ +.btn { + min-width: 140px; +} \ No newline at end of file diff --git a/front/src/components/models/Order.js b/front/src/components/models/Order.js new file mode 100644 index 0000000..bd33edc --- /dev/null +++ b/front/src/components/models/Order.js @@ -0,0 +1,7 @@ +export default class Order { + constructor(data) { + this.id = data?.id; + this.date = data?.date || ''; + this.supplierId = data?.supplierId || ''; + } +} \ No newline at end of file diff --git a/front/src/components/models/Product.js b/front/src/components/models/Product.js new file mode 100644 index 0000000..cb404b4 --- /dev/null +++ b/front/src/components/models/Product.js @@ -0,0 +1,7 @@ +export default class Product { + constructor(data) { + this.id = data?.id; + this.name = data?.name || ''; + this.cost = data?.cost || ''; + } +} \ No newline at end of file diff --git a/front/src/components/models/Supplier.js b/front/src/components/models/Supplier.js new file mode 100644 index 0000000..85f0a4f --- /dev/null +++ b/front/src/components/models/Supplier.js @@ -0,0 +1,7 @@ +export default class Supplier { + constructor(data) { + this.id = data?.id; + this.name = data?.name || ''; + this.license = data?.license || ''; + } +} \ No newline at end of file diff --git a/front/src/index.css b/front/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/front/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/front/src/index.js b/front/src/index.js deleted file mode 100644 index 08d9345..0000000 --- a/front/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import ProductPage from './ProductPage'; - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - -); \ No newline at end of file diff --git a/front/src/main.jsx b/front/src/main.jsx new file mode 100644 index 0000000..27d26de --- /dev/null +++ b/front/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './style.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +)