diff --git a/README.md b/README.md
new file mode 100644
index 0000000..16401ed
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+запуск:
+
+cd C:\Users\student\Desktop\ekz\back
+node index.js
+
+C:\Users\student\Desktop\ekz\front
+npm run dev
\ No newline at end of file
diff --git a/back/index.js b/back/index.js
index 1c8ac76..af79ab1 100644
--- a/back/index.js
+++ b/back/index.js
@@ -1,5 +1,80 @@
-const app = require('express');
+const express = require('express');
+
+const app = express();
const cors = require('cors');
const bcrypt = require('bcryptjs');
const mysql = require('mysql2');
-
\ No newline at end of file
+
+const connection = mysql.createConnection({
+ host: '192.168.201.207',
+ port: '3306',
+ user: 'pozorisp',
+ password: 'pozorisp',
+ database: 'pozorisp_Moskvin'
+});
+
+app.use(express.json());
+app.use(express.static('public'));
+app.use(cors());
+
+app.get('/material', (req, res) => {
+ connection.query(
+ "SELECT m.*, t.type as type, me.name as measure_name, me.code as measure_code FROM `material` as m LEFT JOIN `material_type` as t ON m.id_material_type = t.id LEFT JOIN `measure` as me ON m.id_measure = me.id",
+ (err, result) => {
+ if (err) {
+ return res.status(500).json({message: 'smth went wrong', data: null, error: err.message});
+ }
+
+ return res.status(200).json({message: 'ok', data: result});
+ }
+ );
+});
+
+app.get('/types', (req, res) => {
+ connection.query(
+ "SELECT id, type from `material_type`",
+ (err, result) => {
+ if (err) {
+ return res.status(500).json({message: 'smth went wrong', data: null, error: err.message});
+ }
+
+ return res.status(200).json({message: 'ok', data: result});
+ }
+ )
+});
+
+app.get('/measures', (req, res) => {
+ connection.query(
+ "SELECT id, name from `measure`",
+ (err, result) => {
+ if (err) {
+ return res.status(500).json({message: 'smth went wrong', data: null, error: err.message});
+ }
+
+ return res.status(200).json({message: 'ok', data: result});
+ }
+ )
+});
+
+app.post('/create', (req, res) => {
+ const {name, type, one_price, left, count_in_set, measure} = req.body;
+
+ if (!name || !type || !one_price || !left || !count_in_set || !measure) {
+ return res.status(422).json({message: "Заполните все поля", data: null});
+ }
+
+ connection.query(
+ "INSERT INTO `material` (name, id_material_type, one_price, left, count_in_set, id_measure VALUES(?, ?, ?, ?)",
+ (err, result) => {
+ if (err) {
+ return res.status(500).json({message: 'smth went wrong', data: null, error: err.message});
+ }
+
+ return res.status(200).json({message: 'ok', data: result});
+ }
+ )
+});
+
+app.listen(3000, () => {
+ console.log('Running or port: ', 3000);
+});
diff --git a/er.pdf b/er.pdf
new file mode 100644
index 0000000..94b3516
Binary files /dev/null and b/er.pdf differ
diff --git a/front/index.html b/front/index.html
index b2f34e8..e47256a 100644
--- a/front/index.html
+++ b/front/index.html
@@ -2,7 +2,7 @@
-
+
front
diff --git a/front/package-lock.json b/front/package-lock.json
index 47c1a47..47dc115 100644
--- a/front/package-lock.json
+++ b/front/package-lock.json
@@ -9,7 +9,8 @@
"version": "0.0.0",
"dependencies": {
"react": "^19.2.6",
- "react-dom": "^19.2.6"
+ "react-dom": "^19.2.6",
+ "react-router": "^7.15.1"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
@@ -264,31 +265,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@emnapi/core": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
- "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.1",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
- "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
@@ -1056,6 +1032,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2110,6 +2099,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -2117,6 +2107,28 @@
"react": "^19.2.6"
}
},
+ "node_modules/react-router": {
+ "version": "7.15.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
+ "integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/rolldown": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
@@ -2167,6 +2179,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/front/package.json b/front/package.json
index 2991cb5..3c548a7 100644
--- a/front/package.json
+++ b/front/package.json
@@ -11,7 +11,8 @@
},
"dependencies": {
"react": "^19.2.6",
- "react-dom": "^19.2.6"
+ "react-dom": "^19.2.6",
+ "react-router": "^7.15.1"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
diff --git a/front/public/favicon.ico b/front/public/favicon.ico
new file mode 100644
index 0000000..31513ba
Binary files /dev/null and b/front/public/favicon.ico differ
diff --git a/front/public/favicon.svg b/front/public/favicon.svg
deleted file mode 100644
index 6893eb1..0000000
--- a/front/public/favicon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/front/public/icons.svg b/front/public/icons.svg
deleted file mode 100644
index e952219..0000000
--- a/front/public/icons.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
diff --git a/front/src/App.css b/front/src/App.css
index f90339d..5601b8f 100644
--- a/front/src/App.css
+++ b/front/src/App.css
@@ -1,184 +1,71 @@
-.counter {
- font-size: 16px;
- padding: 5px 10px;
- border-radius: 5px;
- color: var(--accent);
- background: var(--accent-bg);
- border: 2px solid transparent;
- transition: border-color 0.3s;
- margin-bottom: 24px;
-
- &:hover {
- border-color: var(--accent-border);
- }
- &:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
- }
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ font-family: "Comic Sans MS";
}
-.hero {
- position: relative;
-
- .base,
- .framework,
- .vite {
- inset-inline: 0;
- margin: 0 auto;
- }
-
- .base {
- width: 170px;
- position: relative;
- z-index: 0;
- }
-
- .framework,
- .vite {
- position: absolute;
- }
-
- .framework {
- z-index: 1;
- top: 34px;
- height: 28px;
- transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
- scale(1.4);
- }
-
- .vite {
- z-index: 0;
- top: 107px;
- height: 26px;
- width: auto;
- transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
- scale(0.8);
- }
+body {
+ background-color: #FFFFFF;
}
-#center {
+header {
+ background-color: #ABCFCE;
+}
+
+.container {
+ padding: 20px;
+}
+
+header .container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+
+.materials-list {
+ margin-top: 30px;
display: flex;
flex-direction: column;
- gap: 25px;
- place-content: center;
- place-items: center;
- flex-grow: 1;
-
- @media (max-width: 1024px) {
- padding: 32px 20px 24px;
- gap: 18px;
- }
+ gap: 20px;
}
-#next-steps {
+.material-card {
+ border: 1px solid #000;
+ padding: 5px 10px;
display: flex;
- border-top: 1px solid var(--border);
- text-align: left;
-
- & > div {
- flex: 1 1 0;
- padding: 32px;
- @media (max-width: 1024px) {
- padding: 24px 20px;
- }
- }
-
- .icon {
- margin-bottom: 16px;
- width: 22px;
- height: 22px;
- }
-
- @media (max-width: 1024px) {
- flex-direction: column;
- text-align: center;
- }
+ align-items: center;
+ justify-content: space-between;
}
-#docs {
- border-right: 1px solid var(--border);
-
- @media (max-width: 1024px) {
- border-right: none;
- border-bottom: 1px solid var(--border);
- }
-}
-
-#next-steps ul {
- list-style: none;
- padding: 0;
+.field {
+ margin-top: 15px;
display: flex;
- gap: 8px;
- margin: 32px 0 0;
-
- .logo {
- height: 18px;
- }
-
- a {
- color: var(--text-h);
- font-size: 16px;
- border-radius: 6px;
- background: var(--social-bg);
- display: flex;
- padding: 6px 12px;
- align-items: center;
- gap: 8px;
- text-decoration: none;
- transition: box-shadow 0.3s;
-
- &:hover {
- box-shadow: var(--shadow);
- }
- .button-icon {
- height: 18px;
- width: 18px;
- }
- }
-
- @media (max-width: 1024px) {
- margin-top: 20px;
- flex-wrap: wrap;
- justify-content: center;
-
- li {
- flex: 1 1 calc(50% - 8px);
- }
-
- a {
- width: 100%;
- justify-content: center;
- box-sizing: border-box;
- }
- }
+ flex-direction: column;
+ width: fit-content;
}
-#spacer {
- height: 88px;
- border-top: 1px solid var(--border);
- @media (max-width: 1024px) {
- height: 48px;
- }
+.field input,
+.field button,
+.field a {
+ margin-top: 5px;
+ padding: 5px 10px;
}
-.ticks {
- position: relative;
- width: 100%;
-
- &::before,
- &::after {
- content: '';
- position: absolute;
- top: -4.5px;
- border: 5px solid transparent;
- }
-
- &::before {
- left: 0;
- border-left-color: var(--border);
- }
- &::after {
- right: 0;
- border-right-color: var(--border);
- }
+.field a {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #ABCFCE;
+ border-radius: 5px;
}
+
+.field a:hover {
+ background: #83aaa9;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
\ No newline at end of file
diff --git a/front/src/App.jsx b/front/src/App.jsx
index 4f03aa1..3c029f8 100644
--- a/front/src/App.jsx
+++ b/front/src/App.jsx
@@ -1,122 +1,17 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from './assets/vite.svg'
-import heroImg from './assets/hero.png'
-import './App.css'
+import './App.css';
+import {BrowserRouter, Route, Routes} from 'react-router'
+import { Home } from './pages/Home';
+import { CreateMaterial } from './pages/CreateMaterial';
function App() {
- const [count, setCount] = useState(0)
-
- return (
- <>
-
-
-
-
Get started
-
- Edit src/App.jsx and save to test HMR
-
-
-
-
-
-
-
-
-
-
-
Documentation
-
Your questions, answered
-
-
-
-
-
Connect with us
-
Join the Vite community
-
-
-
-
-
-
- >
- )
+ return (
+ <>
+
+ } />
+ } />
+
+ >
+ );
}
export default App
diff --git a/front/src/assets/hero.png b/front/src/assets/hero.png
deleted file mode 100644
index 02251f4..0000000
Binary files a/front/src/assets/hero.png and /dev/null differ
diff --git a/front/src/assets/react.svg b/front/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/front/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/front/src/assets/vite.svg b/front/src/assets/vite.svg
deleted file mode 100644
index 5101b67..0000000
--- a/front/src/assets/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/front/src/components/Header.tsx b/front/src/components/Header.tsx
new file mode 100644
index 0000000..4fd04ce
--- /dev/null
+++ b/front/src/components/Header.tsx
@@ -0,0 +1,13 @@
+import logo from '../images/logo.png';
+
+export function Header() {
+ return (
+
+
+

+
+ Header
+
+
+ );
+}
\ No newline at end of file
diff --git a/front/src/images/logo.png b/front/src/images/logo.png
new file mode 100644
index 0000000..fdcf2fd
Binary files /dev/null and b/front/src/images/logo.png differ
diff --git a/front/src/index.css b/front/src/index.css
index 2c84af0..e69de29 100644
--- a/front/src/index.css
+++ b/front/src/index.css
@@ -1,111 +0,0 @@
-:root {
- --text: #6b6375;
- --text-h: #08060d;
- --bg: #fff;
- --border: #e5e4e7;
- --code-bg: #f4f3ec;
- --accent: #aa3bff;
- --accent-bg: rgba(170, 59, 255, 0.1);
- --accent-border: rgba(170, 59, 255, 0.5);
- --social-bg: rgba(244, 243, 236, 0.5);
- --shadow:
- rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
-
- --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
- --heading: system-ui, 'Segoe UI', Roboto, sans-serif;
- --mono: ui-monospace, Consolas, monospace;
-
- font: 18px/145% var(--sans);
- letter-spacing: 0.18px;
- color-scheme: light dark;
- color: var(--text);
- background: var(--bg);
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-
- @media (max-width: 1024px) {
- font-size: 16px;
- }
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --text: #9ca3af;
- --text-h: #f3f4f6;
- --bg: #16171d;
- --border: #2e303a;
- --code-bg: #1f2028;
- --accent: #c084fc;
- --accent-bg: rgba(192, 132, 252, 0.15);
- --accent-border: rgba(192, 132, 252, 0.5);
- --social-bg: rgba(47, 48, 58, 0.5);
- --shadow:
- rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
- }
-
- #social .button-icon {
- filter: invert(1) brightness(2);
- }
-}
-
-body {
- margin: 0;
-}
-
-#root {
- width: 1126px;
- max-width: 100%;
- margin: 0 auto;
- text-align: center;
- border-inline: 1px solid var(--border);
- min-height: 100svh;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
-}
-
-h1,
-h2 {
- font-family: var(--heading);
- font-weight: 500;
- color: var(--text-h);
-}
-
-h1 {
- font-size: 56px;
- letter-spacing: -1.68px;
- margin: 32px 0;
- @media (max-width: 1024px) {
- font-size: 36px;
- margin: 20px 0;
- }
-}
-h2 {
- font-size: 24px;
- line-height: 118%;
- letter-spacing: -0.24px;
- margin: 0 0 8px;
- @media (max-width: 1024px) {
- font-size: 20px;
- }
-}
-p {
- margin: 0;
-}
-
-code,
-.counter {
- font-family: var(--mono);
- display: inline-flex;
- border-radius: 4px;
- color: var(--text-h);
-}
-
-code {
- font-size: 15px;
- line-height: 135%;
- padding: 4px 8px;
- background: var(--code-bg);
-}
diff --git a/front/src/main.jsx b/front/src/main.jsx
index b9a1a6d..560babb 100644
--- a/front/src/main.jsx
+++ b/front/src/main.jsx
@@ -2,9 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
+import { BrowserRouter } from 'react-router'
createRoot(document.getElementById('root')).render(
-
+
+
+
,
)
diff --git a/front/src/pages/CreateMaterial.jsx b/front/src/pages/CreateMaterial.jsx
new file mode 100644
index 0000000..354210e
--- /dev/null
+++ b/front/src/pages/CreateMaterial.jsx
@@ -0,0 +1,97 @@
+import { useEffect, useState } from "react";
+import { Header } from "../components/Header";
+import { BASE_URL } from "./Home";
+
+export function CreateMaterial() {
+ const [types, setTypes] = useState([]);
+ const [measures, setMeasures] = useState([]);
+ const [name, setName] = useState();
+ const [type, setType] = useState();
+ const [measure, setMeasure] = useState();
+ const [price, setPrice] = useState();
+ const [left, setLeft] = useState();
+ const [inSet, setInSet] = useState();
+
+ const fetchTypes = async () => {
+ const res = await fetch(BASE_URL + 'types');
+ const data = (await res.json()).data;
+
+ setTypes(data);
+ }
+
+ const fetchMeasures = async () => {
+ const res = await fetch(BASE_URL + 'measures');
+ const data = (await res.json()).data;
+
+ setMeasures(data);
+ }
+
+ const onSubmit = () => {
+ fetch(BASE_URL + "create", {
+ method: "POST",
+ body: JSON.stringify({
+ name,
+ one_price: price,
+ type,
+ measure,
+ count_in_set: inSet,
+ left
+ })
+ });
+ }
+
+ useEffect(() => {
+ fetchTypes();
+ fetchMeasures();
+ }, []);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/front/src/pages/Home.jsx b/front/src/pages/Home.jsx
new file mode 100644
index 0000000..b1901ef
--- /dev/null
+++ b/front/src/pages/Home.jsx
@@ -0,0 +1,123 @@
+import { useEffect, useState } from "react";
+import { Header } from "../components/Header";
+import { NavLink } from "react-router";
+
+export const BASE_URL = "http://localhost:3000/";
+
+export function Home() {
+ const [materials, setMaterials] = useState([]);
+ const [search, setSearch] = useState("");
+ const [priceSort, setPriceSort] = useState(null);
+
+ const fetchMaterials = async () => {
+ const res = await fetch(BASE_URL + 'material');
+ let data = (await res.json()).data;
+
+ if (search) {
+ data = data.filter(filterBySearch);
+ }
+
+ if (priceSort) {
+ data = data.sort(sortByPrice);
+ }
+
+ setMaterials(data);
+ }
+
+ useEffect(() => {
+ fetchMaterials();
+ }, [search, priceSort]);
+
+ const filterBySearch = (material) => {
+ return material.name.toLowerCase().includes(search);
+ }
+
+ const sortByPrice = (a, b) => {
+ if (!priceSort) return 0;
+
+ const aPrice = calcPrice(a);
+ const bPrice = calcPrice(b);
+
+ if (priceSort === 'desc') {
+ return Number.parseFloat(aPrice) - Number.parseFloat(bPrice);
+ }
+
+ if (priceSort === 'asc') {
+ return Number.parseFloat(bPrice) - Number.parseFloat(aPrice);
+ }
+ }
+
+ const calcPrice = (material) => {
+ const neededCount = material.minimal - material.left;
+
+ if (neededCount <= 0) {
+ return 0;
+ }
+
+ const needePrice = Math.ceil(neededCount / material.count_in_set) *material.count_in_set * material.one_price;
+
+ return needePrice;
+ }
+
+ const changeSort = () => {
+ if (!priceSort) {
+ setPriceSort('asc');
+ };
+
+ if (priceSort === 'asc') {
+ setPriceSort('desc');
+ }
+
+ if (priceSort === 'desc') {
+ setPriceSort(null);
+ };
+ }
+
+ const currentSort = () => {
+ if (!priceSort) return 'Пусто';
+
+ if (priceSort === 'asc') return 'ASC';
+ if (priceSort === 'desc') return 'DESC';
+ }
+
+
+
+ return (
+ <>
+
+
+
+
Список материалов
+
+
+
+
+
+
+
+
+ {materials.map((material) => (
+
+
+ {material.type} | {material.name}
+ Минимальное количество: {material.minimal} {material.measure_name}
+ Количество на складе: {material.left} {material.measure_name}
+ Цена: {material.one_price} р / Единица измерения: {material.measure_name}
+
+
+ Стоимость партии: {new Intl.NumberFormat('ru-RU').format(calcPrice(material))} р
+
+ ))}
+
+
+ >
+ )
+}
Connect with us
-Join the Vite community
---
-
-
- GitHub
-
-
- -
-
-
- Discord
-
-
- -
-
-
- X.com
-
-
- -
-
-
- Bluesky
-
-
-
-