Compare commits
10 Commits
f27d98607e
...
efc1dde782
Author | SHA1 | Date | |
---|---|---|---|
|
efc1dde782 | ||
|
02886529f1 | ||
|
4680ea6701 | ||
|
da0e0f774b | ||
|
319ee611fb | ||
|
7d29222534 | ||
|
7d07651323 | ||
|
cd2be9a53a | ||
|
b3e018fa70 | ||
|
88a676f6fb |
20
Makefile
Normal file
20
Makefile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
up: docker-up
|
||||||
|
down: docker-down
|
||||||
|
restart: docker-down docker-up
|
||||||
|
init: docker-down docker-pull docker-build docker-up assets-install
|
||||||
|
|
||||||
|
docker-up:
|
||||||
|
docker compose -f ./docker-compose.yml --compatibility up -d
|
||||||
|
|
||||||
|
docker-down:
|
||||||
|
docker compose -f ./docker-compose.yml down --remove-orphans
|
||||||
|
|
||||||
|
docker-pull:
|
||||||
|
docker compose -f ./docker-compose.yml pull
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
docker compose -f ./docker-compose.yml build
|
||||||
|
|
||||||
|
assets-install:
|
||||||
|
docker compose -f ./docker-compose.yml run --rm node yarn install
|
||||||
|
docker compose -f ./docker-compose.yml run --rm node yarn run build
|
16
docker-compose.yml
Normal file
16
docker-compose.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
image: library/nginx:1-alpine
|
||||||
|
volumes:
|
||||||
|
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
- ./:/app
|
||||||
|
working_dir: /app
|
||||||
|
ports:
|
||||||
|
- ${NGINX_PORT}:80
|
||||||
|
restart: unless-stopped
|
||||||
|
node:
|
||||||
|
image: library/node:20-alpine
|
||||||
|
volumes:
|
||||||
|
- ./:/app
|
||||||
|
working_dir: /app
|
9
docker/nginx/default.conf
Normal file
9
docker/nginx/default.conf
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
index index.html;
|
||||||
|
root /app/build;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php$is_args$args;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { HeaderUi } from "../widgets/loyaout/ui/HeaderUi";
|
|||||||
import { FooterUi } from "../widgets/loyaout/ui/FooterUi";
|
import { FooterUi } from "../widgets/loyaout/ui/FooterUi";
|
||||||
import { AuctionPage } from "../pages/AuctionPage";
|
import { AuctionPage } from "../pages/AuctionPage";
|
||||||
import { AuthPage } from "../pages/AuthPage";
|
import { AuthPage } from "../pages/AuthPage";
|
||||||
|
import { PrivateRoute } from "./PrivateRoute";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BrowserRouter as Router,
|
BrowserRouter as Router,
|
||||||
@ -20,7 +21,9 @@ function App() {
|
|||||||
<div>
|
<div>
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/auction" element={<AuctionPage />} />
|
<Route path='/auction' element={<PrivateRoute/>}>
|
||||||
|
<Route path='/auction' element={<AuctionPage/>}/>
|
||||||
|
</Route>
|
||||||
<Route path="/auth" element={<AuthPage />} />
|
<Route path="/auth" element={<AuthPage />} />
|
||||||
<Route path="*" element={<Navigate to="/auth" replace />} />
|
<Route path="*" element={<Navigate to="/auth" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
7
src/app/PrivateRoute.tsx
Normal file
7
src/app/PrivateRoute.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navigate, Outlet } from 'react-router-dom';
|
||||||
|
import { getCookie } from "typescript-cookie";
|
||||||
|
|
||||||
|
export const PrivateRoute = () => {
|
||||||
|
return getCookie('authToken') ? <Outlet /> : <Navigate to="/auth" />;
|
||||||
|
}
|
18
src/entities/Ui/SearchUi/SearchUi.tsx
Normal file
18
src/entities/Ui/SearchUi/SearchUi.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Form from "react-bootstrap/Form";
|
||||||
|
|
||||||
|
import loupe from "../../../shared/images/loupe.png";
|
||||||
|
|
||||||
|
import styles from "./searchUi.module.scss";
|
||||||
|
|
||||||
|
export const SearchUi = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Найти аукцион"
|
||||||
|
/>
|
||||||
|
<img src={loupe} alt='loupe' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
src/entities/Ui/SearchUi/index.ts
Normal file
1
src/entities/Ui/SearchUi/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { SearchUi } from "./SearchUi"
|
25
src/entities/Ui/SearchUi/searchUi.module.scss
Normal file
25
src/entities/Ui/SearchUi/searchUi.module.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid gray;
|
||||||
|
max-width: 300px;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
}
|
95
src/features/auth/ui/AuthLoginFormUi/AuthLoginFormUi.tsx
Normal file
95
src/features/auth/ui/AuthLoginFormUi/AuthLoginFormUi.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { ButtonUi, ButtonUiType } from "../../../../shared/UI/ButtonUi";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import Form from 'react-bootstrap/Form';
|
||||||
|
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
import { AuthResponsePayload } from '../../../../types';
|
||||||
|
import { api } from "../../../../query/query";
|
||||||
|
import { setCookie } from "typescript-cookie";
|
||||||
|
import styles from "./authLoginForm.module.scss";
|
||||||
|
|
||||||
|
export const AuthLoginFormUi = () => {
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const schema = yup.object().shape({
|
||||||
|
username: yup.string()
|
||||||
|
.min(3, 'Минимум 3 символа!')
|
||||||
|
.max(16, 'Максимум 16 символов!')
|
||||||
|
.required('Это поле обязательное.'),
|
||||||
|
|
||||||
|
password: yup.string().required('Это обязательное поле.').min(6, 'Минимум 6 символов!'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
errors,
|
||||||
|
values,
|
||||||
|
setFieldError
|
||||||
|
} = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
validationSchema: schema,
|
||||||
|
onSubmit: async (values): Promise<AuthResponsePayload> => {
|
||||||
|
return await api.post('/authorization', {
|
||||||
|
username: values.username,
|
||||||
|
password: values.password
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.data[0]) {
|
||||||
|
setFieldError('username', res.data[0])
|
||||||
|
setFieldError('password', res.data[0])
|
||||||
|
} else {
|
||||||
|
setCookie('authToken', res.data!.authToken)
|
||||||
|
setCookie('refreshToken', res.data!.refreshToken)
|
||||||
|
navigate('/auction')
|
||||||
|
}
|
||||||
|
return res.data as AuthResponsePayload
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form noValidate className={styles.container} onSubmit={handleSubmit}>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
placeholder="username"
|
||||||
|
onChange={handleChange}
|
||||||
|
name="username"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.username}
|
||||||
|
isInvalid={!!errors.username}
|
||||||
|
/>
|
||||||
|
<Form.Control.Feedback type="invalid">
|
||||||
|
{errors.username}
|
||||||
|
</Form.Control.Feedback>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
placeholder="password"
|
||||||
|
onChange={handleChange}
|
||||||
|
value={values.password}
|
||||||
|
name="password"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
isInvalid={!!errors.password}
|
||||||
|
/>
|
||||||
|
<Form.Control.Feedback type="invalid">
|
||||||
|
{errors.password}
|
||||||
|
</Form.Control.Feedback>
|
||||||
|
</Form.Group>
|
||||||
|
<ButtonUi title={'Войти'} variant={ButtonUiType.PRIMARY} type={'submit'}/>
|
||||||
|
</Form >
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
.container {
|
||||||
|
padding: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 15px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
1
src/features/auth/ui/AuthLoginFormUi/index.ts
Normal file
1
src/features/auth/ui/AuthLoginFormUi/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { AuthLoginFormUi } from "./AuthLoginFormUi";
|
@ -1,81 +1,62 @@
|
|||||||
import React, {useState} from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
import { BreadCrumbsUi } from "../../shared/UI/BreadCrumbsUi";
|
import { BreadCrumbsUi } from "../../shared/UI/BreadCrumbsUi";
|
||||||
import {ButtonUi, ButtonUiType} from "../../shared/UI/ButtonUi";
|
import {ButtonUi, ButtonUiType} from "../../shared/UI/ButtonUi";
|
||||||
import { DefaultDropDown } from "../../entities/DefaultDropDown";
|
import { LoaderUi } from "../../shared/UI/LoaderUi/LoaderUi";
|
||||||
|
import { AuctionFilters } from "../../widgets/AuctionFilters";
|
||||||
import { DefaultPagination } from "../../entities/DefaultPagination";
|
import { DefaultPagination } from "../../entities/DefaultPagination";
|
||||||
import { AddAuctionModal } from "../../widgets/AddAuctionModal";
|
import { AddAuctionModal } from "../../widgets/AddAuctionModal";
|
||||||
import { EditAuctionModal } from "../../widgets/EditAuctionModal";
|
import { EditAuctionModal } from "../../widgets/EditAuctionModal";
|
||||||
import Form from 'react-bootstrap/Form';
|
import { SearchUi } from "../../entities/Ui/SearchUi";
|
||||||
import { AuctionItem } from "../../types";
|
|
||||||
import { filterItems } from "../../constants/data";
|
import Table from 'react-bootstrap/Table';
|
||||||
|
|
||||||
|
import { AuctionItem } from "../../types";
|
||||||
|
|
||||||
|
import { api } from "../../query/query";
|
||||||
|
|
||||||
import loupe from "../../shared/images/loupe.png"
|
|
||||||
import filterImg from "../../shared/images/filter.png"
|
|
||||||
import close from "../../shared/images/close.png"
|
|
||||||
import edit from "../../shared/images/edit.png"
|
import edit from "../../shared/images/edit.png"
|
||||||
|
|
||||||
import styles from "./auctionPage.module.scss"
|
import styles from "./auctionPage.module.scss"
|
||||||
|
|
||||||
export const AuctionPage = () => {
|
export const AuctionPage = () => {
|
||||||
|
|
||||||
const [auctionItems, setAuctionItems] = useState<AuctionItem[]>([
|
const [auctionItems, setAuctionItems] = useState<AuctionItem[]>([])
|
||||||
{
|
|
||||||
number: 1,
|
const [currentEditAuction, setCurrentEditAuction] = useState<AuctionItem>({
|
||||||
name: 'Аукцион на закупку в интересах компании ООО "Фабрика"',
|
uuid: '018c448e-c7d7-7092-b151-08f8d8bc410e',
|
||||||
receptionDate: '17.04.23-18.04.23',
|
dateCreate: "2023-12-07 16:54:17",
|
||||||
startDate: '18.04.23',
|
name: '',
|
||||||
status: 'Черновик'
|
auctionStartDate: "2023-12-22 16:03:00",
|
||||||
},
|
description: "",
|
||||||
{
|
requestsEndDate: "2023-12-21 16:03:00",
|
||||||
number: 2,
|
requestsStartDate: "2023-12-18 16:06:00",
|
||||||
name: 'Аукцион на закупку в интересах компании ООО "Пресс"',
|
status: 'Сбор заявок'
|
||||||
receptionDate: '17.04.23-18.04.23',
|
})
|
||||||
startDate: '18.04.23',
|
|
||||||
status: 'Сбор заявок'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
number: 3,
|
|
||||||
name: 'Аукцион на закупку в интересах компании ООО "Компания"',
|
|
||||||
receptionDate: '17.04.23-18.04.23',
|
|
||||||
startDate: '18.04.23',
|
|
||||||
status: 'Идут торги'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
number: 4,
|
|
||||||
name: 'Аукцион на закупку в интересах компании ООО "Кот"',
|
|
||||||
receptionDate: '17.04.23-18.04.23',
|
|
||||||
startDate: '18.04.23',
|
|
||||||
status: 'В архиве'
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const [openAddModal, setOpenAddModal] = useState(false)
|
const [openAddModal, setOpenAddModal] = useState(false)
|
||||||
|
|
||||||
const [openEditModal, setOpenEditModal] = useState(false)
|
const [loader, setLoader] = useState(false)
|
||||||
|
|
||||||
const [currentEditAuction, setCurrentEditAuction] = useState({
|
const [openEditModal, setOpenEditModal] = useState(false)
|
||||||
number: 0,
|
|
||||||
name: '',
|
|
||||||
receptionDate: '',
|
|
||||||
startDate: '',
|
|
||||||
status: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const addNewAuction = (newAction: string) => {
|
const addNewAuction = (newAction: string) => {
|
||||||
setAuctionItems((prevValue) => [...prevValue, {
|
setAuctionItems((prevValue) => [...prevValue, {
|
||||||
number: prevValue.length + 1,
|
uuid: '018c448e-c7d7-7092-b151-08f8d8bc480e',
|
||||||
|
dateCreate: "2023-12-07 16:54:17",
|
||||||
name: newAction,
|
name: newAction,
|
||||||
receptionDate: '17.04.23-18.04.23',
|
auctionStartDate: "2023-12-22 16:03:00",
|
||||||
startDate: '18.04.23',
|
description: "",
|
||||||
|
requestsEndDate: "2023-12-21 16:03:00",
|
||||||
|
requestsStartDate: "2023-12-18 16:06:00",
|
||||||
status: 'Сбор заявок'
|
status: 'Сбор заявок'
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
const editAuctionItem = (newAuctionName: string, currentEditAuctionId: number) => {
|
const editAuctionItem = (newAuctionName: string, currentEditAuctionId: string) => {
|
||||||
setAuctionItems((prevValue) => {
|
setAuctionItems((prevValue) => {
|
||||||
return prevValue.map((item) => {
|
return prevValue.map((item) => {
|
||||||
if (item.number === currentEditAuctionId) {
|
if (item.uuid === currentEditAuctionId) {
|
||||||
return {...item, name: newAuctionName}
|
return {...item, name: newAuctionName}
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
@ -83,6 +64,34 @@ export const AuctionPage = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCorrectDate = (day:string) => {
|
||||||
|
const months = [
|
||||||
|
"января",
|
||||||
|
"февраля",
|
||||||
|
"марта",
|
||||||
|
"апреля",
|
||||||
|
"мая",
|
||||||
|
"июня",
|
||||||
|
"июля",
|
||||||
|
"августа",
|
||||||
|
"сентября",
|
||||||
|
"октября",
|
||||||
|
"ноября",
|
||||||
|
"декабря",
|
||||||
|
];
|
||||||
|
return `${new Date(day).getDate()} ${
|
||||||
|
months[new Date(day).getMonth()]
|
||||||
|
} ${new Date(day).getFullYear()} года`;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoader(true)
|
||||||
|
api.get('/auctions').then((res) => {
|
||||||
|
setAuctionItems(res.data)
|
||||||
|
setLoader(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.home}>
|
<div className={styles.home}>
|
||||||
<BreadCrumbsUi links={[
|
<BreadCrumbsUi links={[
|
||||||
@ -92,57 +101,45 @@ export const AuctionPage = () => {
|
|||||||
<div className={styles.home__info}>
|
<div className={styles.home__info}>
|
||||||
<div className={styles.info__top}>
|
<div className={styles.info__top}>
|
||||||
<ButtonUi event={() => setOpenAddModal(true)} title={'+ Добавить аукцион'} variant={ButtonUiType.PRIMARY} />
|
<ButtonUi event={() => setOpenAddModal(true)} title={'+ Добавить аукцион'} variant={ButtonUiType.PRIMARY} />
|
||||||
<div className={styles.info__search}>
|
<SearchUi />
|
||||||
<Form.Control
|
|
||||||
type="text"
|
|
||||||
placeholder="Найти аукцион"
|
|
||||||
/>
|
|
||||||
<img src={loupe} alt='loupe' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.info__filters}>
|
|
||||||
<img className={styles.info__filtersImg} src={filterImg} alt="filter" />
|
|
||||||
<div className={styles.info__filters__items}>
|
|
||||||
{filterItems.map((item) => {
|
|
||||||
return <DefaultDropDown key={item.title} title={item.title} options={item.options} />
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<ButtonUi
|
|
||||||
title={'Сбросить фильтры'}
|
|
||||||
variant={ButtonUiType.PRIMARY}
|
|
||||||
img={<img src={close} alt="cross" />} />
|
|
||||||
<ButtonUi title={'Применить'} variant={ButtonUiType.INFO} />
|
|
||||||
</div>
|
</div>
|
||||||
|
<AuctionFilters />
|
||||||
<div className={styles.info__tableWrapper}>
|
<div className={styles.info__tableWrapper}>
|
||||||
<table>
|
{loader ?
|
||||||
<thead>
|
<LoaderUi animation={'border'} variant={'primary'} />
|
||||||
<tr className={styles.tableItem}>
|
:
|
||||||
<td>№</td>
|
<>
|
||||||
<td>Название аукциона</td>
|
<Table>
|
||||||
<td>Прием заявок</td>
|
<thead>
|
||||||
<td>Проведение</td>
|
<tr className={styles.tableItem}>
|
||||||
<td>Статус</td>
|
<td>№</td>
|
||||||
</tr>
|
<td>Название аукциона</td>
|
||||||
</thead>
|
<td>Прием заявок</td>
|
||||||
<tbody>
|
<td>Проведение</td>
|
||||||
{auctionItems.map((item) => {
|
<td>Статус</td>
|
||||||
return <tr className={styles.tableItem} key={item.number}>
|
</tr>
|
||||||
<td>{item.number}</td>
|
</thead>
|
||||||
<td className={styles.tableItem__name}>{item.name}</td>
|
<tbody>
|
||||||
<td>{item.receptionDate}</td>
|
{auctionItems.map((item) => {
|
||||||
<td>{item.startDate}</td>
|
return <tr className={styles.tableItem} key={item.uuid}>
|
||||||
<td>{item.status}</td>
|
<td>{item.uuid}</td>
|
||||||
<td className={styles.tableItem__edit}>
|
<td className={styles.tableItem__name}>{item.name}</td>
|
||||||
<img src={edit} alt="edit" onClick={() => {
|
<td>{getCorrectDate((item.requestsStartDate))}</td>
|
||||||
setCurrentEditAuction(item)
|
<td>{item.dateCreate}</td>
|
||||||
setOpenEditModal(true)
|
<td>{item.status}</td>
|
||||||
}}/>
|
<td className={styles.tableItem__edit}>
|
||||||
</td>
|
<img src={edit} alt="edit" onClick={() => {
|
||||||
</tr>
|
setCurrentEditAuction(item)
|
||||||
})}
|
setOpenEditModal(true)
|
||||||
</tbody>
|
}}/>
|
||||||
</table>
|
</td>
|
||||||
<DefaultPagination pageCount={10} currentPage={2} />
|
</tr>
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
<DefaultPagination pageCount={10} currentPage={2} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AddAuctionModal
|
<AddAuctionModal
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
&__info {
|
&__info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 35px 0;
|
margin: 35px 0;
|
||||||
|
min-height: 281px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
row-gap: 15px;
|
row-gap: 15px;
|
||||||
@ -22,8 +23,8 @@
|
|||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 5px;
|
width: 3px;
|
||||||
height: 10px;
|
height: 5px;
|
||||||
background: gray;
|
background: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,76 +36,8 @@
|
|||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__search {
|
|
||||||
display: flex;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 2px solid gray;
|
|
||||||
max-width: 300px;
|
|
||||||
width: 100%;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
input {
|
|
||||||
border: none;
|
|
||||||
width: 100%;
|
|
||||||
outline: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__filters {
|
|
||||||
display: flex;
|
|
||||||
padding: 10px 15px;
|
|
||||||
background-color: #2f95f2;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
min-width: 1213px;
|
|
||||||
|
|
||||||
&Img {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__items {
|
|
||||||
display: flex;
|
|
||||||
column-gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 17px;
|
|
||||||
max-width: 200px;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
column-gap: 8px;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 17px;
|
|
||||||
height: 17px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter {
|
|
||||||
&__reset {
|
|
||||||
background-color: #0668c2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__confirm {
|
|
||||||
background-color: #00529d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&__tableWrapper {
|
&__tableWrapper {
|
||||||
min-width: 1243px;
|
min-width: 1213px;
|
||||||
|
|
||||||
.tableItem {
|
.tableItem {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -119,6 +52,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
@ -129,34 +64,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
row-gap: 10px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
|
|
||||||
thead {
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
tr {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 7% 50% 16% 12% 10% 5%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody {
|
|
||||||
display: grid;
|
|
||||||
row-gap: 10px;
|
|
||||||
|
|
||||||
tr {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 7% 50% 16% 12% 10% 5%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,99 +1,15 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import { ButtonUi, ButtonUiType } from "../../shared/UI/ButtonUi";
|
import { AuthLoginFormUi } from "../../features/auth/ui/AuthLoginFormUi";
|
||||||
|
|
||||||
import Form from 'react-bootstrap/Form';
|
|
||||||
|
|
||||||
import { useFormik } from 'formik';
|
|
||||||
import * as yup from 'yup';
|
|
||||||
|
|
||||||
import { api } from "../../query/query";
|
|
||||||
import { setCookie } from "typescript-cookie";
|
|
||||||
import { AuthResponsePayload } from "../../types";
|
|
||||||
|
|
||||||
import styles from "./authPage.module.scss"
|
import styles from "./authPage.module.scss"
|
||||||
|
|
||||||
export const AuthPage = () => {
|
export const AuthPage = () => {
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const schema = yup.object().shape({
|
|
||||||
username: yup.string()
|
|
||||||
.min(3, 'Минимум 3 символа!')
|
|
||||||
.max(16, 'Максимум 16 символов!')
|
|
||||||
.required('Это поле обязательное.'),
|
|
||||||
|
|
||||||
password: yup.string().required('Это обязательное поле.').min(6, 'Минимум 6 символов!'),
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
handleSubmit,
|
|
||||||
handleChange,
|
|
||||||
handleBlur,
|
|
||||||
errors,
|
|
||||||
values,
|
|
||||||
setFieldError
|
|
||||||
} = useFormik({
|
|
||||||
initialValues: {
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
},
|
|
||||||
validationSchema: schema,
|
|
||||||
onSubmit: async (values): Promise<AuthResponsePayload> => {
|
|
||||||
return await api.post('/authorization', {
|
|
||||||
username: values.username,
|
|
||||||
password: values.password
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.data[0]) {
|
|
||||||
setFieldError('username', res.data[0])
|
|
||||||
setFieldError('password', res.data[0])
|
|
||||||
} else {
|
|
||||||
setCookie('authToken', res.data!.authToken)
|
|
||||||
setCookie('refreshToken', res.data!.refreshToken)
|
|
||||||
navigate("/auction")
|
|
||||||
}
|
|
||||||
return res.data as AuthResponsePayload
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.auth}>
|
<div className={styles.auth}>
|
||||||
<h1 className={styles.auth__title}>Аукционная площадка для проведения закупок ГК Проф-Пресс</h1>
|
<h1 className={styles.auth__title}>Аукционная площадка для проведения закупок ГК Проф-Пресс</h1>
|
||||||
<Form noValidate className={styles.auth__form} onSubmit={handleSubmit}>
|
<AuthLoginFormUi />
|
||||||
<Form.Group>
|
|
||||||
<Form.Control
|
|
||||||
type="text"
|
|
||||||
id="username"
|
|
||||||
placeholder="username"
|
|
||||||
onChange={handleChange}
|
|
||||||
name="username"
|
|
||||||
onBlur={handleBlur}
|
|
||||||
value={values.username}
|
|
||||||
isInvalid={!!errors.username}
|
|
||||||
/>
|
|
||||||
<Form.Control.Feedback type="invalid">
|
|
||||||
{errors.username}
|
|
||||||
</Form.Control.Feedback>
|
|
||||||
</Form.Group>
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Control
|
|
||||||
type="password"
|
|
||||||
id="password"
|
|
||||||
placeholder="password"
|
|
||||||
onChange={handleChange}
|
|
||||||
value={values.password}
|
|
||||||
name="password"
|
|
||||||
onBlur={handleBlur}
|
|
||||||
isInvalid={!!errors.password}
|
|
||||||
/>
|
|
||||||
<Form.Control.Feedback type="invalid">
|
|
||||||
{errors.password}
|
|
||||||
</Form.Control.Feedback>
|
|
||||||
</Form.Group>
|
|
||||||
<ButtonUi title={'Войти'} variant={ButtonUiType.PRIMARY} type={'submit'}/>
|
|
||||||
</Form >
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,4 @@
|
|||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__form {
|
|
||||||
padding: 20px 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
row-gap: 15px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { removeCookie } from 'typescript-cookie'
|
import { removeCookie, getCookie } from 'typescript-cookie'
|
||||||
|
|
||||||
export const api = axios.create({
|
export const api = axios.create({
|
||||||
baseURL: 'https://tender.prof-press.ru/api/v1/',
|
baseURL: 'https://tender.prof-press.ru/api/v1/',
|
||||||
@ -9,10 +9,16 @@ export const api = axios.create({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
api.interceptors.request.use(config => {
|
||||||
|
config.headers.authorization = getCookie('authToken') ? `Bearer ${getCookie('authToken')}` : ''
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
api.interceptors.response.use(undefined, async function (error) {
|
api.interceptors.response.use(undefined, async function (error) {
|
||||||
if (error?.response?.status === 401) {
|
if (error?.response?.status === 401) {
|
||||||
removeCookie('access-token')
|
removeCookie('authToken')
|
||||||
removeCookie('refresh-token')
|
removeCookie('refreshToken')
|
||||||
|
window.location.replace("/auth")
|
||||||
}
|
}
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
})
|
})
|
||||||
|
14
src/shared/UI/LoaderUi/LoaderUi.tsx
Normal file
14
src/shared/UI/LoaderUi/LoaderUi.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import Spinner from 'react-bootstrap/Spinner';
|
||||||
|
|
||||||
|
interface LoaderUi {
|
||||||
|
animation: 'border' | 'grow',
|
||||||
|
variant: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoaderUi:React.FC<LoaderUi> = ({animation, variant}) => {
|
||||||
|
return (
|
||||||
|
<Spinner animation={animation} variant={variant} />
|
||||||
|
)
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
export interface AuctionItem {
|
export interface AuctionItem {
|
||||||
number: number,
|
uuid: string,
|
||||||
name: string,
|
name: string,
|
||||||
receptionDate: string,
|
description: string,
|
||||||
startDate: string,
|
requestsStartDate: string,
|
||||||
|
requestsEndDate: string,
|
||||||
|
auctionStartDate: string,
|
||||||
|
dateCreate: string
|
||||||
status: string
|
status: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
src/widgets/AuctionFilters/AuctionFilters.tsx
Normal file
28
src/widgets/AuctionFilters/AuctionFilters.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { DefaultDropDown } from "../../entities/DefaultDropDown";
|
||||||
|
import { ButtonUi, ButtonUiType } from "../../shared/UI/ButtonUi";
|
||||||
|
|
||||||
|
import { filterItems } from "../../constants/data";
|
||||||
|
|
||||||
|
import filterImg from "../../shared/images/filter.png";
|
||||||
|
import close from "../../shared/images/close.png";
|
||||||
|
import styles from "./auctionFilters.module.scss";
|
||||||
|
|
||||||
|
export const AuctionFilters = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.filter}>
|
||||||
|
<img className={styles.filter__img} src={filterImg} alt="filter" />
|
||||||
|
<div className={styles.filter__items}>
|
||||||
|
{filterItems.map((item) => {
|
||||||
|
return <DefaultDropDown key={item.title} title={item.title} options={item.options} />
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<ButtonUi
|
||||||
|
title={'Сбросить фильтры'}
|
||||||
|
variant={ButtonUiType.PRIMARY}
|
||||||
|
img={<img src={close} alt="cross" />} />
|
||||||
|
<ButtonUi title={'Применить'} variant={ButtonUiType.INFO} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
36
src/widgets/AuctionFilters/auctionFilters.module.scss
Normal file
36
src/widgets/AuctionFilters/auctionFilters.module.scss
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
.filter {
|
||||||
|
display: flex;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #2f95f2;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-width: 1213px;
|
||||||
|
|
||||||
|
&__img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__items {
|
||||||
|
display: flex;
|
||||||
|
column-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 17px;
|
||||||
|
max-width: 200px;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
column-gap: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
src/widgets/AuctionFilters/index.ts
Normal file
1
src/widgets/AuctionFilters/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { AuctionFilters } from "./AuctionFilters"
|
@ -3,20 +3,13 @@ import React, {useState, useEffect} from "react";
|
|||||||
import { ButtonUi, ButtonUiType } from "../../shared/UI/ButtonUi";
|
import { ButtonUi, ButtonUiType } from "../../shared/UI/ButtonUi";
|
||||||
import Modal from 'react-bootstrap/Modal';
|
import Modal from 'react-bootstrap/Modal';
|
||||||
import Form from 'react-bootstrap/Form';
|
import Form from 'react-bootstrap/Form';
|
||||||
|
import { AuctionItem } from "../../types";
|
||||||
type auctionItem = {
|
|
||||||
number: number,
|
|
||||||
name: string,
|
|
||||||
receptionDate: string,
|
|
||||||
startDate: string,
|
|
||||||
status: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AddAuctionModalProps {
|
interface AddAuctionModalProps {
|
||||||
showModal: boolean,
|
showModal: boolean,
|
||||||
onHide: () => void,
|
onHide: () => void,
|
||||||
currentAuction: auctionItem,
|
currentAuction: AuctionItem,
|
||||||
editAuctionItem: (newAuctionName:string, currentEditAuctionId:number) => void
|
editAuctionItem: (newAuctionName:string, currentEditAuctionId:string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditAuctionModal:React.FC<AddAuctionModalProps> = ({showModal, onHide, currentAuction, editAuctionItem}) => {
|
export const EditAuctionModal:React.FC<AddAuctionModalProps> = ({showModal, onHide, currentAuction, editAuctionItem}) => {
|
||||||
@ -27,7 +20,7 @@ export const EditAuctionModal:React.FC<AddAuctionModalProps> = ({showModal, onHi
|
|||||||
}, [currentAuction])
|
}, [currentAuction])
|
||||||
|
|
||||||
const editAuction = () => {
|
const editAuction = () => {
|
||||||
editAuctionItem(newAuctionName, currentAuction.number)
|
editAuctionItem(newAuctionName, currentAuction.uuid)
|
||||||
onHide()
|
onHide()
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@ -40,7 +33,7 @@ export const EditAuctionModal:React.FC<AddAuctionModalProps> = ({showModal, onHi
|
|||||||
>
|
>
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title id="contained-modal-title-vcenter">
|
<Modal.Title id="contained-modal-title-vcenter">
|
||||||
Редактировать аукцион №{currentAuction.number}
|
Редактировать аукцион №{currentAuction.uuid}
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
|
Loading…
Reference in New Issue
Block a user