4 + промежуточная 5

This commit is contained in:
Вячеслав Иванов 2023-11-27 18:15:43 +04:00
parent 056bab6b14
commit a8937930eb
44 changed files with 2732 additions and 534 deletions

View File

@ -30,7 +30,7 @@
</div>
<div class="navbar-collapse collapse justify-content-end" id="navbarNav">
<div class="navbar-nav">
<a class="btn custom-btn" href="personalAccountLogin.html">Войти</a>
<a class="btn custom-btn" href="personalAccountLogin.html">Администратор</a>
<a class="btn btn-warning" href="basket.html">Корзина</a>
</div>
</div>
@ -39,9 +39,11 @@
</header>
<main class="container-fluid p-2">
<h1 class="text-warning text-center font-weight-bold">Панель администратора</h1>
<div class="text-center">
<div class="btn-group" role="group">
<a class="btn btn-warning" href="/page-edit.html">Добавить товар</a>
</div>
</div>
<div>
<h2 class="text-warning text-center font-weight-bold" style="padding-top: 10px;">Таблица данных</h2>
<table id="items-table" class="table table-striped">

File diff suppressed because one or more lines are too long

View File

@ -74,6 +74,7 @@
class="fw-bold text-body"><u>Регистрация</u></a></p>
<p class="text-center"><a class="fw-bold text-body" href="Administrator.html">Администратор</a></p>
<p class="text-center"><a class="fw-bold text-body" href="AdminDop.html">Администратор2</a></p>
</form>
</div>

19
lab4/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"
}
}

1714
lab4/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,38 @@
{
"name": "lab4",
"name": "lec4",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"rest": "json-server data.json",
"vite": "vite",
"dev": "npm-run-all --parallel rest vite",
"prod": "npm-run-all lint 'vite build' --parallel rest 'vite preview'"
},
"dependencies": {
"bootstrap": "^5.3.2",
"mdb-react-ui-kit": "^7.0.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.18.0",
"react-hot-toast": "^2.4.1",
"axios": "^1.6.1",
"bootstrap": "^5.3.2",
"react-bootstrap": "^2.9.1",
"react-bootstrap-icons": "^1.10.3",
"react-dom": "^18.2.0",
"react-router-dom": "^6.18.0"
"prop-types": "^15.8.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/react-dom": "^18.2.15",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"json-server": "^0.17.4",
"npm-run-all": "^4.1.5",
"vite": "^4.4.5"
}
}

View File

@ -1,5 +1,6 @@
import { Container, Row, Col } from 'react-bootstrap';
import BasketCard from '../components/card/BasketCard';
import { Link } from 'react-router-dom';
const Basket = () => {
return (
@ -24,7 +25,9 @@ const Basket = () => {
Сумма
</div>
</div>
<a className="btn btn-warning" style={{ marginLeft: '25px', marginBottom: '10px' }} href="makingAnOrder">К оплате</a>
<Link to='/MakingAnOrder' className="btn btn-warning" style={{ marginLeft: '25px', marginBottom: '10px' }}>
К оплате
</Link>
</Container>
);
};

View File

@ -13,7 +13,7 @@ const Contacts = () => {
<iframe
src="https://yandex.ru/map-widget/v1/?um=constructor%3A0643c92cbdf3809080e5dfb2804b473ea00af31cfabe6fee08676c59d8675f01&amp;source=constructor"
className="img-fluid"
style={{ width: '100%', height: '720px' }}
style={{ width: '100%', height: '500px' }}
title="Yandex Map"
></iframe>
</Col>

View File

@ -35,7 +35,7 @@ const MakingAnOrder = () => {
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className="mb-4" controlId="name">
<Form.Label htmlFor="name">Ваше имя</Form.Label>
<Form.Label>Ваше имя</Form.Label>
<Form.Control type="text" required />
<Form.Control.Feedback>Имя заполнено</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Имя не заполнено</Form.Control.Feedback>

View File

@ -1,5 +1,6 @@
import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
import { useState } from 'react';
import { Link } from 'react-router-dom';
const PasswordRecovery = () => {
const [validated, setValidated] = useState(false);
@ -39,15 +40,11 @@ const PasswordRecovery = () => {
</div>
<p className="text-center text-muted mb-0">
<a href="personalAccountLogin" className="fw-bold text-body">
<u>Войти</u>
</a>
<Link className="fw-bold text-body" to='/PersonalAccountLogin' style={{ color: 'black', textDecoration: 'none' }}><u>Войти</u></Link>
</p>
<p className="text-center text-muted mb-0">
<a href="personalAccountRegister" className="fw-bold text-body">
<u>Регистрация</u>
</a>
<Link className="fw-bold text-body" to='/PersonalAccountRegister' style={{ color: 'black', textDecoration: 'none' }}><u>Регистрация</u></Link>
</p>
</Form>
</Card.Body>

View File

@ -1,5 +1,6 @@
import { useState } from 'react';
import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
const PersonalAccount = () => {
const [validated, setValidated] = useState(false);
@ -24,14 +25,14 @@ const PersonalAccount = () => {
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className="mb-4" controlId="name">
<Form.Label htmlFor="name">Ваше имя</Form.Label>
<Form.Label>Ваше имя</Form.Label>
<Form.Control type="text" required />
<Form.Control.Feedback>Имя заполнено</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Имя не заполнено</Form.Control.Feedback>
</Form.Group>
<Form.Group className="mb-4" controlId="surname">
<Form.Label htmlFor="surname">Ваша фамилия</Form.Label>
<Form.Label>Ваша фамилия</Form.Label>
<Form.Control type="text" required/>
<Form.Control.Feedback>Фамилия заполнена</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Фамилия не заполнена</Form.Control.Feedback>
@ -65,9 +66,9 @@ const PersonalAccount = () => {
</div>
<div className="d-flex justify-content-center mt-2">
<a className="btn btn-outline-danger" type="button" href="/">
<Link to='/' className="btn btn-outline-danger" type="button">
Выйти
</a>
</Link>
</div>
</Form>
</Card.Body>

View File

@ -1,5 +1,6 @@
import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
import { useState } from 'react';
import { Link } from 'react-router-dom';
const PersonalAccountLogin = () => {
const [validated, setValidated] = useState(false);
@ -43,28 +44,28 @@ const PersonalAccountLogin = () => {
<p className="text-center text-muted mb-0">
Забыли пароль?{' '}
<a href="PasswordRecovery" className="fw-bold text-body">
<Link to='/PasswordRecovery' className="fw-bold text-body" style={{ color: 'black', textDecoration: 'none' }}>
<u>Восстановление пароля</u>
</a>
</Link>
</p>
<div className="d-flex justify-content-center">
<Button variant="submit" className="btn-block btn-warning text-body mb-0" href="PersonalAccount">
<Button as={Link} to='/PersonalAccount' variant="submit" className="btn-block btn-warning text-body mb-0">
Вход
</Button>
</div>
<p className="text-center text-muted mb-0">
У вас нет аккаунта?{' '}
<a href="personalAccountRegister" className="fw-bold text-body">
<Link to='/personalAccountRegister' className="fw-bold text-body" style={{ color: 'black', textDecoration: 'none' }}>
<u>Регистрация</u>
</a>
</Link>
</p>
<p className="text-center">
<a className="fw-bold text-body" href="Administrator">
<Link to='/Administrator' className="fw-bold text-body" style={{ color: 'black'}}>
Администратор
</a>
</Link>
</p>
</Form>
</Card.Body>

View File

@ -1,5 +1,6 @@
import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
import { useState } from 'react';
import { Link } from 'react-router-dom';
const PersonalAccountRegister = () => {
const [validated, setValidated] = useState(false);
@ -27,7 +28,7 @@ const PersonalAccountRegister = () => {
<Form.Control type="text" required />
<Form.Control.Feedback>Имя заполнено</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Имя не заполнено</Form.Control.Feedback>
<Form.Label htmlFor="name">Ваше имя</Form.Label>
<Form.Label>Ваше имя</Form.Label>
</Form.Group>
<Form.Group className="mb-4">
@ -63,16 +64,18 @@ const PersonalAccountRegister = () => {
</Form.Group>
<div className="d-flex justify-content-center">
<Button variant="success" type="button" className="btn-block btn-warning text-body mb-0" href="PersonalAccount">
<Link to='/PersonalAccount' style={{color: 'black', textDecoration: 'none'}}>
<Button variant="success" type="button" className="btn-block btn-warning text-body mb-0">
Регистрация
</Button>
</Link>
</div>
<p className="text-center text-muted mb-0">
У вас уже есть учетная запись?{' '}
<a href="personalAccountLogin" className="fw-bold text-body">
<Link to='/PersonalAccountLogin' className="fw-bold text-body" style={{ color: 'black', textDecoration: 'none' }}>
<u>Войдите здесь</u>
</a>
</Link>
</p>
</Form>
</Card.Body>

File diff suppressed because one or more lines are too long

26
lab5/package-lock.json generated
View File

@ -16,7 +16,7 @@
"react-bootstrap-icons": "^1.10.3",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-router-dom": "^6.18.0"
"react-router-dom": "^6.20.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
@ -956,9 +956,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0.tgz",
"integrity": "sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==",
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.0.tgz",
"integrity": "sha512-5dMOnVnefRsl4uRnAdoWjtVTdh8e6aZqgM4puy9nmEADH72ck+uXwzpJLEKE9Q6F8ZljNewLgmTfkxUrBdv4WA==",
"engines": {
"node": ">=14.0.0"
}
@ -4802,11 +4802,11 @@
}
},
"node_modules/react-router": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz",
"integrity": "sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg==",
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.0.tgz",
"integrity": "sha512-pVvzsSsgUxxtuNfTHC4IxjATs10UaAtvLGVSA1tbUE4GDaOSU1Esu2xF5nWLz7KPiMuW8BJWuPFdlGYJ7/rW0w==",
"dependencies": {
"@remix-run/router": "1.11.0"
"@remix-run/router": "1.13.0"
},
"engines": {
"node": ">=14.0.0"
@ -4816,12 +4816,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.18.0.tgz",
"integrity": "sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw==",
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.0.tgz",
"integrity": "sha512-CbcKjEyiSVpA6UtCHOIYLUYn/UJfwzp55va4yEfpk7JBN3GPqWfHrdLkAvNCcpXr8QoihcDMuk0dzWZxtlB/mQ==",
"dependencies": {
"@remix-run/router": "1.11.0",
"react-router": "6.18.0"
"@remix-run/router": "1.13.0",
"react-router": "6.20.0"
},
"engines": {
"node": ">=14.0.0"

View File

@ -12,6 +12,7 @@ const LinesForm = ({ id }) => {
validated,
handleSubmit,
handleChange,
handleStockChange,
} = useLinesItemForm(id);
const onBack = () => {
@ -27,7 +28,7 @@ const LinesForm = ({ id }) => {
return (
<>
<Form className='m-0 p-2' noValidate validated={validated} onSubmit={onSubmit}>
<LinesItemForm item={item} handleChange={handleChange} />
<LinesItemForm item={item} handleChange={handleChange} handleStockChange={handleStockChange} />
<Form.Group className='row justify-content-center m-0 mt-3'>
<Button className='col-5 col-lg-2 m-0 me-2' variant='secondary' onClick={() => onBack()}>
Назад

View File

@ -3,23 +3,23 @@ import imgPlaceholder from '../../../Images/200.png';
import Input from '../../input/Input.jsx';
import Select from '../../input/Select.jsx';
import useTypes from '../../types/hooks/TypesHook';
import useStocks from '../../types/hooks/StockHook';
import './LinesItemForm.css';
const LinesItemForm = ({ item, handleChange }) => {
const LinesItemForm = ({ item, handleChange, handleStockChange }) => {
const { types } = useTypes();
const { stocks } = useStocks();
return (
<>
<div className='text-center'>
<img id='image-preview' className='rounded' alt='placeholder'
src={item.image || imgPlaceholder} />
</div>
<Select values={types} name='typeId' label='Товары' value={item.typeId} onChange={handleChange}
<Select values={types} name='typeId' label='Товары' value={item.typeId.toString()} onChange={handleChange}
required />
<Input name='price' label='Цена' value={item.price} onChange={handleChange}
type='number' min='1000.0' step='0.50' required />
<Input name='stock' label='Акция' value={item.stock} onChange={handleChange}
type='number' min='0' step='1' />
type='number' min='1' step='1' required />
<Select values={stocks} name='stock' label='Акция' value={item.stock} onChange={handleStockChange} />
<Input name='count' label='Количество' value={item.count} onChange={handleChange}
type='number' min='1' step='1' required />
<Input name='image' label='Изображение' onChange={handleChange}
@ -30,7 +30,9 @@ const LinesItemForm = ({ item, handleChange }) => {
LinesItemForm.propTypes = {
item: PropTypes.object,
handleChange: PropTypes.func,
handleStockChange: PropTypes.func,
};
export default LinesItemForm;

View File

@ -0,0 +1,48 @@
import PropTypes from 'prop-types';
import { Button, Form } from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
import useStocksItemForm from '../hooks/StocksItemFormHook.js';
import StocksItemForm from './StocksItemForm.jsx';
const StocksForm = ({ id }) => {
const navigate = useNavigate();
const {
stocks,
validated,
handleSubmit,
handleChange,
} = useStocksItemForm(id);
const onBack = () => {
navigate(-1);
};
const onSubmit = async (event) => {
if (await handleSubmit(event)) {
onBack();
}
};
return (
<>
<Form className='m-0 p-2' noValidate validated={validated} onSubmit={onSubmit}>
<StocksItemForm item={stocks} handleChange={handleChange} />
<Form.Group className='row justify-content-center m-0 mt-3'>
<Button className='col-5 col-lg-2 m-0 me-2' variant='secondary' onClick={() => onBack()}>
Назад
</Button>
<Button className='col-5 col-lg-2 m-0 ms-2' type='submit' variant='primary'>
Сохранить
</Button>
</Form.Group>
</Form>
</>
);
};
StocksForm.propTypes = {
id: PropTypes.string,
};
export default StocksForm;

View File

@ -0,0 +1,19 @@
import PropTypes from 'prop-types';
import Input from '../../input/Input.jsx';
const StocksItemForm = ({ item, handleChange }) => {
return (
<>
<h1 className="text-warning text-center font-weight-bold">Добавление акции</h1>
<Input name='name' label='Наименование акции ' value={item.name} onChange={handleChange} required />
<Input name='value' label='Значение акции' value={item.value} max='100' onChange={handleChange} required />
</>
);
};
StocksItemForm.propTypes = {
item: PropTypes.object,
handleChange: PropTypes.func,
};
export default StocksItemForm;

View File

@ -1,11 +1,24 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import toast from 'react-hot-toast';
import getBase64FromFile from '../../utils/Base64';
import LinesApiService from '../service/LinesApiService';
import StocksApiService from '../service/StocksApiService';
import useLinesItem from './LinesItemHook';
const useLinesItemForm = (id, linesChangeHandle) => {
const { item, setItem } = useLinesItem(id);
const [stockData, setStockData] = useState(null);
useEffect(() => {
if (item.stock) {
StocksApiService.getById(item.stock)
.then(response => setStockData(response))
.catch(error => {
console.error('Ошибка при получении данных акции:', error);
setStockData(null);
});
}
}, [item.stock]);
const [validated, setValidated] = useState(false);
@ -16,10 +29,11 @@ const useLinesItemForm = (id, linesChangeHandle) => {
const getLineObject = (formData) => {
const typeId = parseInt(formData.typeId, 10);
const price = parseFloat(formData.price).toFixed(2);
const stock = formData.stock !== '' ? parseInt(formData.stock, 10) : null;
const stock = stockData.value;
const count = parseInt(formData.count, 10);
let sum;
if (stock !== null) {
if (stockData !== null) {
sum = parseFloat((price * ((100 - stock) / 100)) * count).toFixed(2);
} else {
sum = parseFloat(price * count).toFixed(2);
@ -29,7 +43,7 @@ const useLinesItemForm = (id, linesChangeHandle) => {
return {
typeId: typeId.toString(),
price: price.toString(),
stock: stock !== null ? stock.toString() : '',
stock: stock,
count: count.toString(),
sum: sum.toString(),
image,
@ -45,11 +59,22 @@ const useLinesItemForm = (id, linesChangeHandle) => {
});
};
const handleStockChange = (event) => {
setItem({
...item,
stock: event.target.value,
});
};
const handleChange = (event) => {
if (event.target.type === 'file') {
handleImageChange(event);
return;
}
if (event.target.name === 'stock') {
handleStockChange(event);
return;
}
const inputName = event.target.name;
const inputValue = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
setItem({
@ -82,6 +107,7 @@ const useLinesItemForm = (id, linesChangeHandle) => {
validated,
handleSubmit,
handleChange,
handleStockChange,
resetValidity,
};
};

View File

@ -5,9 +5,9 @@ const useLinesItem = (id) => {
const emptyItem = {
id: '',
typeId: '',
price: '0',
price: '',
stock: '',
count: '0',
count: '',
image: '',
};
const [item, setItem] = useState({ ...emptyItem });

View File

@ -0,0 +1,34 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import useModal from '../../modal/ModalHook';
import StocksApiService from '../service/StocksApiService';
const useStocksDeleteModal = (linesChangeHandle) => {
const { isModalShow, showModal, hideModal } = useModal();
const [currentId, setCurrentId] = useState(0);
const showModalDialogStock = (id) => {
showModal();
setCurrentId(id);
};
const onCloseStock = () => {
hideModal();
};
const onDeleteStock = async () => {
await StocksApiService.delete(currentId);
linesChangeHandle();
toast.success('Акция успешно удалена', { id: 'StocksTable' });
onCloseStock();
};
return {
isDeleteModalShowStock: isModalShow,
showDeleteModalStock: showModalDialogStock,
handleDeleteConfirmStock: onDeleteStock,
handleDeleteCancelStock: onCloseStock,
};
};
export default useStocksDeleteModal;

View File

@ -0,0 +1,25 @@
import { useEffect, useState } from 'react';
import StocksApiService from '../service/StocksApiService';
const useStocks = () => {
const [stocksRefresh, setStocksRefresh] = useState(false);
const [stocks, setStocks] = useState([]);
const handleStocksChange = () => setStocksRefresh(!stocksRefresh);
const getStocks = async () => {
let expand = '?_expand=';
const data = await StocksApiService.getAll(expand);
setStocks(data ?? []);
};
useEffect(() => {
getStocks();
}, [stocksRefresh]);
return {
stocks,
handleStocksChange,
};
};
export default useStocks;

View File

@ -0,0 +1,62 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import StocksApiService from '../service/StocksApiService';
import useStocksItem from './StocksItemHook.js';
const useStocksItemForm = (id, linesChangeHandle) => {
const { stocks, setStock } = useStocksItem(id);
const [validated, setValidated] = useState(null);
const resetValidity = () => {
setValidated(false);
};
const getStockObject = (formData) => {
const name = formData.name;
const value = parseInt(formData.value, 10);
return {
name: name.toString(),
value: value.toString(),
};
};
const handleChange = (event) => {
const inputName = event.target.name;
const inputValue = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
setStock({
...stocks,
[inputName]: inputValue,
});
};
const handleSubmit = async (event) => {
const form = event.currentTarget;
event.preventDefault();
event.stopPropagation();
const body = getStockObject(stocks);
if (form.checkValidity()) {
if (id === undefined) {
await StocksApiService.create(body);
} else {
await StocksApiService.update(id, body);
}
if (linesChangeHandle) linesChangeHandle();
toast.success('Акция успешно сохранена', { id: 'StocksTable' });
return true;
}
setValidated(true);
return false;
};
return {
stocks,
validated,
handleSubmit,
handleChange,
resetValidity,
};
};
export default useStocksItemForm;

View File

@ -0,0 +1,32 @@
import { useEffect, useState } from 'react';
import TypesApiService from '../service/StocksApiService';
const useStocksItem = (id) => {
const emptyStocks = {
id: '',
name: '',
stock: '',
};
const [stocks, setStock] = useState({ ...emptyStocks });
const getStock = async (stockId = undefined) => {
if (stockId && stockId > 0) {
const data = await TypesApiService.get(stockId);
setStock(data);
} else {
setStock({ ...emptyStocks });
}
};
useEffect(() => {
getStock(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
return {
stocks,
setStock,
};
};
export default useStocksItem;

View File

@ -0,0 +1,11 @@
import ApiService from '../../api/ApiService';
class StocksApiService extends ApiService {
async getById(id) {
return this.get(id);
}
}
const TypesApiService = new StocksApiService('stocks');
export default TypesApiService;

View File

@ -3,15 +3,27 @@ import { Link, useNavigate } from 'react-router-dom';
import Select from '../../input/Select.jsx';
import ModalConfirm from '../../modal/ModalConfirm.jsx';
import useLinesDeleteModal from '../hooks/LinesDeleteModalHook';
import useStocksDeleteModal from '../hooks/StocksDeleteModalHook';
import useTypeFilter from '../hooks/LinesFilterHook';
import useLines from '../hooks/LinesHook';
import LinesTable from './LinesTable.jsx';
import LinesTableRow from './LinesTableRow.jsx';
import StocksTable from './StocksTable.jsx';
import StocksTableRow from './StocksTableRow.jsx';
import useStocks from '../hooks/StocksHook.js';
const Lines = () => {
const { types, currentFilter, handleFilterChange } = useTypeFilter();
const { lines, handleLinesChange } = useLines(currentFilter);
const navigate = useNavigate();
const { stocks, handleStocksChange } = useStocks();
const {
isDeleteModalShowStock,
showDeleteModalStock,
handleDeleteConfirmStock,
handleDeleteCancelStock,
} = useStocksDeleteModal(handleStocksChange);
const {
isDeleteModalShow,
@ -28,6 +40,14 @@ const Lines = () => {
showDeleteModal(id);
};
const showEditStocksPage = (id) => {
navigate(`/PageEditStocks/${id}`);
};
const handleDeleteStock = (id) => {
showDeleteModalStock(id);
};
return (
<>
<h1 className="text-warning text-center font-weight-bold">Панель администратора</h1>
@ -36,6 +56,9 @@ const Lines = () => {
<Button as={Link} to='/pageEdit' variant='warning'>
Добавить товар
</Button>
<Button as={Link} to='/PageEditStocks' variant='warning'>
Добавить акцию
</Button>
</ButtonGroup>
</div>
<Select className='mt-2' values={types} label='Фильтр по товарам'
@ -51,9 +74,25 @@ const Lines = () => {
/>)
}
</LinesTable>
<h2 className="text-warning text-center font-weight-bold mt-2">Таблица акций</h2>
<StocksTable>
{
stocks.map((stock, index) =>
<StocksTableRow key={stock.id}
index={index} stock={stock}
onDelete={() => handleDeleteStock(stock.id)}
onEdit={() => showEditStocksPage(stock.id)}
/>)
}
</StocksTable>
<ModalConfirm show={isDeleteModalShow}
onConfirm={handleDeleteConfirm} onClose={handleDeleteCancel}
title='Удаление' message='Удалить элемент?' />
<ModalConfirm show={isDeleteModalShowStock}
onConfirm={handleDeleteConfirmStock} onClose={handleDeleteCancelStock}
title='Удаление' message='Удалить акцию?' />
</>
);
};

View File

@ -0,0 +1,27 @@
import PropTypes from 'prop-types';
import { Table } from 'react-bootstrap';
const StocksTable = ({ children }) => {
return (
<Table className='mt-2' striped responsive>
<thead>
<tr>
<th scope="col"></th>
<th scope="col" className="w-25">Имя акции</th>
<th scope="col" className="w-75">Значение акции</th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{children}
</tbody >
</Table>
);
};
StocksTable.propTypes = {
children: PropTypes.node,
};
export default StocksTable;

View File

@ -0,0 +1,30 @@
import PropTypes from 'prop-types';
import { PencilSquare, Trash3 } from 'react-bootstrap-icons';
const StocksTableRow = ({
index, stock, onDelete, onEdit
}) => {
const handleAnchorClick = (event, action) => {
event.preventDefault();
action();
};
return (
<tr>
<th scope="row">{index + 1}</th>
<td>{stock.name}</td>
<td>{stock.value}</td>
<td><a href="#" onClick={(event) => handleAnchorClick(event, onEdit)}><PencilSquare /></a></td>
<td><a href="#" onClick={(event) => handleAnchorClick(event, onDelete)}><Trash3 /></a></td>
</tr>
);
};
StocksTableRow.propTypes = {
index: PropTypes.number,
stock: PropTypes.object,
onDelete: PropTypes.func,
onEdit: PropTypes.func,
};
export default StocksTableRow;

View File

@ -0,0 +1,21 @@
import { useEffect, useState } from 'react';
import StocksApiService from '../../lines/service/StocksApiService';
const useStocks = () => {
const [stocks, setStocks] = useState([]);
const getStocks = async () => {
const data = await StocksApiService.getAll();
setStocks(data ?? []);
};
useEffect(() => {
getStocks();
}, []);
return {
stocks,
};
};
export default useStocks;

View File

@ -16,6 +16,7 @@ import Administrator from './pages/Administrator.jsx';
import Basket from './pages/Basket.jsx';
import MakingAnOrder from './pages/MakingAnOrder.jsx';
import PageEdit from './pages/PageEdit.jsx';
import PageEditStocks from './pages/PageEditStocs.jsx';
const routes = [
{
@ -66,6 +67,10 @@ const routes = [
path: '/PageEdit/:id?',
element: <PageEdit/>,
},
{
path: '/PageEditStocks/:id?',
element: <PageEditStocks/>,
}
];
const router = createBrowserRouter([

View File

@ -1,5 +1,6 @@
import { Container, Row, Col } from 'react-bootstrap';
import BasketCard from '../components/card/BasketCard';
import { Link } from 'react-router-dom';
const Basket = () => {
return (
@ -24,7 +25,9 @@ const Basket = () => {
Сумма
</div>
</div>
<a className="btn btn-warning" style={{ marginLeft: '25px', marginBottom: '10px' }} href="makingAnOrder">К оплате</a>
<Link to='/MakingAnOrder' className="btn btn-warning" style={{ marginLeft: '25px', marginBottom: '10px' }}>
К оплате
</Link>
</Container>
);
};

View File

@ -13,7 +13,7 @@ const Contacts = () => {
<iframe
src="https://yandex.ru/map-widget/v1/?um=constructor%3A0643c92cbdf3809080e5dfb2804b473ea00af31cfabe6fee08676c59d8675f01&amp;source=constructor"
className="img-fluid"
style={{ width: '100%', height: '720px' }}
style={{ width: '100%', height: '500px' }}
title="Yandex Map"
></iframe>
</Col>

View File

@ -35,7 +35,7 @@ const MakingAnOrder = () => {
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className="mb-4" controlId="name">
<Form.Label htmlFor="name">Ваше имя</Form.Label>
<Form.Label>Ваше имя</Form.Label>
<Form.Control type="text" required />
<Form.Control.Feedback>Имя заполнено</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Имя не заполнено</Form.Control.Feedback>

View File

@ -1,174 +1,7 @@
//import { useState } from 'react';
//import { Container, Col, Row, Form, Button } from 'react-bootstrap';
import { useParams } from 'react-router-dom';
import LinesForm from '../components/lines/form/LinesForm.jsx';
const PageEdit = () => {
/*const [validated, setValidated] = useState(false);
const handleSubmit = (event) => {
const form = event.currentTarget;
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
setValidated(true);
};
const [selectedItem, setSelectedItem] = useState('');
const [price, setPrice] = useState(0.00);
const [stock, setStock] = useState(0);
const [count, setCount] = useState(0);
const [imagePreview, setImagePreview] = useState('https://via.placeholder.com/200');
const handleItemChange = (e) => {
setSelectedItem(e.target.value);
};
const handlePriceChange = (e) => {
setPrice(parseFloat(e.target.value));
};
const handleStockChange = (e) => {
setStock(parseInt(e.target.value, 10));
};
const handleCountChange = (e) => {
setCount(parseInt(e.target.value, 10));
};
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
const img = new Image();
img.src = reader.result;
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const maxWidth = 200;
const maxHeight = 200;
let width = img.width;
let height = img.height;
if (width > height) {
if (width > maxWidth) {
height *= maxWidth / width;
width = maxWidth;
}
} else {
if (height > maxHeight) {
width *= maxHeight / height;
height = maxHeight;
}
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
setImagePreview(canvas.toDataURL('image/png'));
};
};
reader.readAsDataURL(file);
}
};
return (
<Container fluid className="p-2">
<h1 className="text-warning text-center font-weight-bold">Добавление товара</h1>
<Row className="text-center">
<Col>
<img id="image-preview" src={imagePreview} className="rounded rounded-circle" alt="placeholder" />
</Col>
</Row>
<Form
id="items-form"
className="needs-validation"
noValidate
validated={validated}
onSubmit={handleSubmit}
>
<Form.Group controlId="item" className="mb-2">
<Form.Label>Товары</Form.Label>
<Form.Control
as="select"
name="selected"
onChange={handleItemChange}
value={selectedItem}
required
>
</Form.Control>
<Form.Control.Feedback type="invalid">Пожалуйста, выберите товар.</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="price" className="mb-2">
<Form.Label>Цена</Form.Label>
<Form.Control
type="number"
name="price"
value={price}
min="1000.00"
step="0.50"
onChange={handlePriceChange}
required
/>
<Form.Control.Feedback type="invalid">Пожалуйста, введите цену.</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="stock" className="mb-2">
<Form.Label>Акция</Form.Label>
<Form.Control
type="number"
name="price"
value={stock}
min="0"
step="1"
onChange={handleStockChange}
required
/>
<Form.Control.Feedback type="invalid">Пожалуйста, введите акцию.</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="count" className="mb-2">
<Form.Label>Количество</Form.Label>
<Form.Control
type="number"
name="count"
value={count}
min="1"
step="1"
onChange={handleCountChange}
required
/>
<Form.Control.Feedback type="invalid">Пожалуйста, введите количество.</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="image" className="mb-2">
<Form.Label>Изображение</Form.Label>
<Form.Control
type="file"
name="image"
accept="image/*"
onChange={handleImageChange}
/>
<Form.Control.Feedback type="invalid">Пожалуйста, выберите изображение.</Form.Control.Feedback>
</Form.Group>
<Row className="mb-2">
<Col>
<Button href="Administrator" className="btn btn-secondary">Назад</Button>
<Button type="submit" className="btn btn-primary">Сохранить</Button>
</Col>
</Row>
</Form>
</Container>
);*/
const { id } = useParams();
return (

View File

@ -0,0 +1,12 @@
import { useParams } from 'react-router-dom';
import StocksForm from '../components/lines/form/StocksForm';
const PageEditStocks = () => {
const { id } = useParams();
return (
<StocksForm id={id} />
);
};
export default PageEditStocks;

View File

@ -1,5 +1,6 @@
import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
import { useState } from 'react';
import { Link } from 'react-router-dom';
const PasswordRecovery = () => {
const [validated, setValidated] = useState(false);
@ -39,15 +40,11 @@ const PasswordRecovery = () => {
</div>
<p className="text-center text-muted mb-0">
<a href="personalAccountLogin" className="fw-bold text-body">
<u>Войти</u>
</a>
<Link className="fw-bold text-body" to='/PersonalAccountLogin' style={{ color: 'black', textDecoration: 'none' }}><u>Войти</u></Link>
</p>
<p className="text-center text-muted mb-0">
<a href="personalAccountRegister" className="fw-bold text-body">
<u>Регистрация</u>
</a>
<Link className="fw-bold text-body" to='/PersonalAccountRegister' style={{ color: 'black', textDecoration: 'none' }}><u>Регистрация</u></Link>
</p>
</Form>
</Card.Body>

View File

@ -1,36 +1,18 @@
/* eslint-disable linebreak-style */
/* eslint-disable object-curly-newline */
/* eslint-disable linebreak-style */
import { useState } from 'react';
import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
const PersonalAccount = () => {
const [validated, setValidated] = useState(false);
const [formData, setFormData] = useState({
lastname: '',
firstname: '',
email: '',
date: '',
tel: '',
});
const handleSubmit = (event) => {
const form = event.currentTarget;
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
if (form.checkValidity() !== false) {
console.log(formData);
}
setValidated(true);
};
const handleChange = (event) => {
const inputName = event.target.name;
const inputValue = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
setFormData({
...formData,
[inputName]: inputValue,
});
setValidated(true);
};
return (
@ -42,42 +24,37 @@ const PersonalAccount = () => {
<h2 className="text-uppercase text-center mb-5">Личный кабинет</h2>
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className='mb-2' controlId='firstname'>
<Form.Label>Имя</Form.Label>
<Form.Control type='text' name='firstname' required
value={formData.firstname} onChange={handleChange} />
<Form.Group className="mb-4" controlId="name">
<Form.Label>Ваше имя</Form.Label>
<Form.Control type="text" required />
<Form.Control.Feedback>Имя заполнено</Form.Control.Feedback>
<Form.Control.Feedback type='invalid'>Имя не заполнено</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Имя не заполнено</Form.Control.Feedback>
</Form.Group>
<Form.Group className='mb-4' controlId='lastname'>
<Form.Label>Фамилия</Form.Label>
<Form.Control type='text' name='lastname' required
value={formData.lastname} onChange={handleChange} />
<Form.Group className="mb-4" controlId="surname">
<Form.Label>Ваша фамилия</Form.Label>
<Form.Control type="text" required/>
<Form.Control.Feedback>Фамилия заполнена</Form.Control.Feedback>
<Form.Control.Feedback type='invalid'>Фамилия не заполнена</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Фамилия не заполнена</Form.Control.Feedback>
</Form.Group>
<Form.Group className='mb-4' controlId='email'>
<Form.Label>Ваш адрес электронной почты</Form.Label>
<Form.Control type='email' name='email' placeholder='name@example.ru' required
value={formData.email} onChange={handleChange} />
<Form.Control.Feedback>E-mail заполнен</Form.Control.Feedback>
<Form.Control.Feedback type='invalid'>E-mail не заполнен</Form.Control.Feedback>
<Form.Group className="mb-4">
<Form.Label htmlFor="form3Example3cg">Ваш адрес электронной почты</Form.Label>
<Form.Control type="email" id="form3Example3cg" required />
<Form.Control.Feedback>Email заполнен</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Email не заполнен</Form.Control.Feedback>
</Form.Group>
<Form.Group className="mb-4">
<Form.Label htmlFor="form3Example4cg">Дата рождения</Form.Label>
<Form.Control type="date" id="form3Example4cg" required
value={formData.date} onChange={handleChange} />
<Form.Control type="date" id="form3Example4cg" required />
<Form.Control.Feedback>Дата рождения заполнена</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Дата рождения не заполнена</Form.Control.Feedback>
</Form.Group>
<Form.Group className="mb-2">
<Form.Label htmlFor="form3Example5cg">Номер телефона</Form.Label>
<Form.Control type="tel" id="form3Example5cg" required
value={formData.tel} onChange={handleChange} />
<Form.Control type="tel" id="form3Example5cg" required />
<Form.Control.Feedback>Номер телефона заполнен</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Номер телефона не заполнен</Form.Control.Feedback>
</Form.Group>
@ -89,9 +66,9 @@ const PersonalAccount = () => {
</div>
<div className="d-flex justify-content-center mt-2">
<a className="btn btn-outline-danger" type="button" href="/">
<Link to='/' className="btn btn-outline-danger" type="button">
Выйти
</a>
</Link>
</div>
</Form>
</Card.Body>

View File

@ -1,5 +1,6 @@
import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
import { useState } from 'react';
import { Link } from 'react-router-dom';
const PersonalAccountLogin = () => {
const [validated, setValidated] = useState(false);
@ -43,28 +44,28 @@ const PersonalAccountLogin = () => {
<p className="text-center text-muted mb-0">
Забыли пароль?{' '}
<a href="PasswordRecovery" className="fw-bold text-body">
<Link to='/PasswordRecovery' className="fw-bold text-body" style={{ color: 'black', textDecoration: 'none' }}>
<u>Восстановление пароля</u>
</a>
</Link>
</p>
<div className="d-flex justify-content-center">
<Button variant="submit" className="btn-block btn-warning text-body mb-0" href="PersonalAccount">
<Button as={Link} to='/PersonalAccount' variant="submit" className="btn-block btn-warning text-body mb-0">
Вход
</Button>
</div>
<p className="text-center text-muted mb-0">
У вас нет аккаунта?{' '}
<a href="personalAccountRegister" className="fw-bold text-body">
<Link to='/personalAccountRegister' className="fw-bold text-body" style={{ color: 'black', textDecoration: 'none' }}>
<u>Регистрация</u>
</a>
</Link>
</p>
<p className="text-center">
<a className="fw-bold text-body" href="Administrator">
<Link to='/Administrator' className="fw-bold text-body" style={{ color: 'black'}}>
Администратор
</a>
</Link>
</p>
</Form>
</Card.Body>

View File

@ -1,5 +1,6 @@
import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
import { useState } from 'react';
import { Link } from 'react-router-dom';
const PersonalAccountRegister = () => {
const [validated, setValidated] = useState(false);
@ -27,7 +28,7 @@ const PersonalAccountRegister = () => {
<Form.Control type="text" required />
<Form.Control.Feedback>Имя заполнено</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">Имя не заполнено</Form.Control.Feedback>
<Form.Label htmlFor="name">Ваше имя</Form.Label>
<Form.Label>Ваше имя</Form.Label>
</Form.Group>
<Form.Group className="mb-4">
@ -63,16 +64,18 @@ const PersonalAccountRegister = () => {
</Form.Group>
<div className="d-flex justify-content-center">
<Button variant="success" type="button" className="btn-block btn-warning text-body mb-0" href="PersonalAccount">
<Link to='/PersonalAccount' style={{color: 'black', textDecoration: 'none'}}>
<Button variant="success" type="button" className="btn-block btn-warning text-body mb-0">
Регистрация
</Button>
</Link>
</div>
<p className="text-center text-muted mb-0">
У вас уже есть учетная запись?{' '}
<a href="personalAccountLogin" className="fw-bold text-body">
<Link to='/PersonalAccountLogin' className="fw-bold text-body" style={{ color: 'black', textDecoration: 'none' }}>
<u>Войдите здесь</u>
</a>
</Link>
</p>
</Form>
</Card.Body>

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

Binary file not shown.

Binary file not shown.

Binary file not shown.