Fixed copmonents
This commit is contained in:
parent
08f7d13f01
commit
2ad78834f3
@ -1,7 +1,7 @@
|
||||
const path = require('path');
|
||||
const path = require("path");
|
||||
module.exports = {
|
||||
public: path.resolve(__dirname, '../public'),
|
||||
src: path.resolve(__dirname, '../src'),
|
||||
build: path.resolve(__dirname, '../build'),
|
||||
'@node_modules': path.resolve(__dirname, '../node_modules'),
|
||||
public: path.resolve(__dirname, "../public"),
|
||||
src: path.resolve(__dirname, "../src"),
|
||||
build: path.resolve(__dirname, "../build"),
|
||||
"@node_modules": path.resolve(__dirname, "../node_modules"),
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
const { merge } = require('webpack-merge');
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
||||
.BundleAnalyzerPlugin;
|
||||
const BundleAnalyzerPlugin =
|
||||
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
||||
|
||||
const prod = require('./prod');
|
||||
const prod = require("./prod");
|
||||
|
||||
module.exports = merge(prod, {
|
||||
plugins: [new BundleAnalyzerPlugin()]
|
||||
plugins: [new BundleAnalyzerPlugin()],
|
||||
});
|
@ -1,14 +1,14 @@
|
||||
const paths = require('../paths');
|
||||
const paths = require("../paths");
|
||||
|
||||
const webpack = require('webpack');
|
||||
const {merge} = require('webpack-merge');
|
||||
const webpack = require("webpack");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const common = require('./common');
|
||||
const common = require("./common");
|
||||
|
||||
module.exports = merge(common, {
|
||||
target : 'web',
|
||||
mode: 'development',
|
||||
devtool: 'eval-cheap-source-map',
|
||||
target: "web",
|
||||
mode: "development",
|
||||
devtool: "eval-cheap-source-map",
|
||||
|
||||
devServer: {
|
||||
compress: true,
|
||||
@ -17,7 +17,6 @@ module.exports = merge(common, {
|
||||
historyApiFallback: true,
|
||||
// open: true,
|
||||
port: 3000,
|
||||
|
||||
},
|
||||
plugins: [new webpack.HotModuleReplacementPlugin()]
|
||||
plugins: [new webpack.HotModuleReplacementPlugin()],
|
||||
});
|
@ -1,26 +1,25 @@
|
||||
const paths = require('../paths');
|
||||
const {merge} = require('webpack-merge');
|
||||
const common = require('./common');
|
||||
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const paths = require("../paths");
|
||||
const { merge } = require("webpack-merge");
|
||||
const common = require("./common");
|
||||
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
target :'browserslist',
|
||||
mode: "production",
|
||||
target: "browserslist",
|
||||
entry: {
|
||||
index: {
|
||||
import: `${paths.src}/index.js`,
|
||||
dependOn: ['react', 'helpers']
|
||||
dependOn: ["react", "helpers"],
|
||||
},
|
||||
react: ['react', 'react-dom', 'prop-types'],
|
||||
helpers: ['immer', 'nanoid']
|
||||
react: ["react", "react-dom", "prop-types"],
|
||||
helpers: ["immer", "nanoid"],
|
||||
},
|
||||
devtool: false,
|
||||
output: {
|
||||
filename: 'js/[name].[hash:8].bundle.js',
|
||||
publicPath: '/',
|
||||
assetModuleFilename: '[hash][ext][query]'
|
||||
filename: "js/[name].[hash:8].bundle.js",
|
||||
publicPath: "/",
|
||||
assetModuleFilename: "[hash][ext][query]",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
@ -29,34 +28,32 @@ module.exports = merge(common, {
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {importLoaders: 1}
|
||||
loader: "css-loader",
|
||||
options: { importLoaders: 1 },
|
||||
},
|
||||
'postcss-loader',
|
||||
'sass-loader'
|
||||
]
|
||||
"postcss-loader",
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
type: 'asset/resource'
|
||||
test: /\.(jpe?g|png|gif|svg|webp)$/i,
|
||||
type: "asset/resource",
|
||||
// type: 'asset'
|
||||
},
|
||||
|
||||
]
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].[contenthash].css',
|
||||
chunkFilename: '[id].css'
|
||||
filename: "[name].[contenthash].css",
|
||||
chunkFilename: "[id].css",
|
||||
}),
|
||||
|
||||
],
|
||||
optimization: {
|
||||
runtimeChunk: 'single'
|
||||
runtimeChunk: "single",
|
||||
},
|
||||
performance: {
|
||||
hints: 'warning',
|
||||
hints: "warning",
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000
|
||||
}
|
||||
maxAssetSize: 512000,
|
||||
},
|
||||
});
|
@ -1,19 +1,19 @@
|
||||
import React from 'react'
|
||||
import React from "react";
|
||||
|
||||
import './achievement.scss'
|
||||
import "./achievement.scss";
|
||||
|
||||
export const Achievement = ({ achievement }) => {
|
||||
return (
|
||||
<div className='achievement'>
|
||||
<div className='achievement__icon'>
|
||||
<img src={achievement.img} alt='achievement' />
|
||||
<div className="achievement">
|
||||
<div className="achievement__icon">
|
||||
<img src={achievement.img} alt="achievement" />
|
||||
</div>
|
||||
<div className='achievement__popup'>
|
||||
<div className='achievement__title'>{achievement.title}</div>
|
||||
<div className='achievement__description'>
|
||||
<div className="achievement__popup">
|
||||
<div className="achievement__title">{achievement.title}</div>
|
||||
<div className="achievement__description">
|
||||
{achievement.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import { calendarHelper, currentMonthAndDay } from "./calendarHelper";
|
||||
|
||||
import ellipse from "../../images/ellipse.png";
|
||||
import rectangle from "../../images/rectangle__calendar.png";
|
||||
import calendarIcon from "../../images/calendar_icon.png";
|
||||
import calendarIcon from "../../images/calendar.svg";
|
||||
|
||||
import moment from "moment";
|
||||
import "moment/locale/ru";
|
||||
|
@ -18,7 +18,6 @@ import { apiRequest } from "../../api/request";
|
||||
import { createMarkup } from "../../helper";
|
||||
|
||||
import gitImgItem from "../../images/gitItemImg.svg";
|
||||
|
||||
import rectangle from "../../images/rectangle_secondPage.png";
|
||||
import front from "../../images/front-end.webp";
|
||||
import back from "../../images/back-end.webp";
|
@ -1,8 +1,8 @@
|
||||
.control-card{
|
||||
.control-card {
|
||||
max-width: 353px;
|
||||
width: 100%;
|
||||
padding: 35px 45px 15px 30px;
|
||||
background: #FFFFFF;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
@ -56,18 +56,18 @@
|
||||
margin-bottom: 0;
|
||||
|
||||
span {
|
||||
color: #52B709;
|
||||
color: #52b709;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
&Link {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #DDEEC6;
|
||||
background: #ddeec6;
|
||||
border-radius: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 33px 32px 25px 28px;
|
||||
background: #FFFFFF;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
@ -18,7 +18,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -43,7 +42,7 @@
|
||||
p {
|
||||
max-width: 181px;
|
||||
margin-bottom: 0;
|
||||
color: #6F6F6F;
|
||||
color: #6f6f6f;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
@ -55,7 +54,7 @@
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #DDEEC6;
|
||||
background: #ddeec6;
|
||||
border-radius: 50px;
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,8 @@ import { selectProfiles } from "../../redux/outstaffingSlice";
|
||||
|
||||
import { urlForLocal } from "../../helper";
|
||||
|
||||
import male from "../../images/medium_male.png";
|
||||
import rectangle from "../../images/rectangle_secondPage.png";
|
||||
import cursorImg from "../../images/cursorImg.png";
|
||||
import cursorImg from "../../images/cursorImg.svg";
|
||||
|
||||
import "./description.scss";
|
||||
|
@ -1,49 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import logo from '../../images/logoGuild.png'
|
||||
import vk from '../../images/vkLogo.svg'
|
||||
import tg from '../../images/tgFooter.png'
|
||||
import email from '../../images/emailLogo.svg'
|
||||
|
||||
import './footer.scss'
|
||||
|
||||
export const Footer = () => {
|
||||
return (
|
||||
<footer>
|
||||
<div className='container'>
|
||||
<div className='footer'>
|
||||
<div className='footer__top'>
|
||||
<img src={logo} alt='logo' />
|
||||
<p>Подберем и документально оформим IT-специалистов, после чего передадим исполнителей под ваше руководство.
|
||||
Вы получаете полное управление над сотрудниками, имея возможность контролировать и заменять IT штат.</p>
|
||||
<div className='footer__copyright'>
|
||||
© {new Date().getFullYear()} - Все права защищены
|
||||
</div>
|
||||
</div>
|
||||
<div className='footer__bottom'>
|
||||
<div className='footer__social'>
|
||||
<div className='footer__social__icons'>
|
||||
<a>
|
||||
<img src={vk} alt='vk' />
|
||||
</a>
|
||||
<a>
|
||||
<img src={tg} alt='tg' />
|
||||
</a>
|
||||
</div>
|
||||
<p>Войти в команду</p>
|
||||
</div>
|
||||
<div className='footer__info'>
|
||||
<div className='footer__mail'>
|
||||
<a>
|
||||
<img src={email} alt='email' />
|
||||
</a>
|
||||
<p>office@itguild.info</p>
|
||||
</div>
|
||||
<a className='footer__policy'>Политика конфиденциальности</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
53
src/components/Footer/Footer.jsx
Normal file
53
src/components/Footer/Footer.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
|
||||
import logo from "../../images/logoGuild.png";
|
||||
import vk from "../../images/vkLogo.svg";
|
||||
import tg from "../../images/tgFooter.png";
|
||||
import email from "../../images/emailLogo.svg";
|
||||
|
||||
import "./footer.scss";
|
||||
|
||||
export const Footer = () => {
|
||||
return (
|
||||
<footer>
|
||||
<div className="container">
|
||||
<div className="footer">
|
||||
<div className="footer__top">
|
||||
<img src={logo} alt="logo" />
|
||||
<p>
|
||||
Подберем и документально оформим IT-специалистов, после чего
|
||||
передадим исполнителей под ваше руководство. Вы получаете полное
|
||||
управление над сотрудниками, имея возможность контролировать и
|
||||
заменять IT штат.
|
||||
</p>
|
||||
<div className="footer__copyright">
|
||||
© {new Date().getFullYear()} - Все права защищены
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer__bottom">
|
||||
<div className="footer__social">
|
||||
<div className="footer__social__icons">
|
||||
<a>
|
||||
<img src={vk} alt="vk" />
|
||||
</a>
|
||||
<a>
|
||||
<img src={tg} alt="tg" />
|
||||
</a>
|
||||
</div>
|
||||
<p>Войти в команду</p>
|
||||
</div>
|
||||
<div className="footer__info">
|
||||
<div className="footer__mail">
|
||||
<a>
|
||||
<img src={email} alt="email" />
|
||||
</a>
|
||||
<p>office@itguild.info</p>
|
||||
</div>
|
||||
<a className="footer__policy">Политика конфиденциальности</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
@ -4,7 +4,6 @@ footer {
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
||||
&__top {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
@ -21,7 +20,7 @@ footer {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #5B6871;
|
||||
color: #5b6871;
|
||||
|
||||
@media (max-width: 620px) {
|
||||
margin-left: 0;
|
||||
@ -82,7 +81,7 @@ footer {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #5B6871;
|
||||
color: #5b6871;
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +89,7 @@ footer {
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
color: #5B6871;
|
||||
color: #5b6871;
|
||||
margin-left: 150px;
|
||||
|
||||
@media (max-width: 720px) {
|
||||
@ -98,7 +97,7 @@ footer {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #5B6871;
|
||||
color: #5b6871;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
@ -115,101 +114,4 @@ footer {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
//margin-top: -3rem;
|
||||
//
|
||||
//&__left {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
//}
|
||||
//
|
||||
//&__description {
|
||||
// padding: 0 100px 0 34px;
|
||||
//
|
||||
// span {
|
||||
// color: #18586e;
|
||||
// font-family: 'GT Eesti Pro Display';
|
||||
// font-size: 1.6em;
|
||||
// font-weight: 400;
|
||||
// font-style: normal;
|
||||
// letter-spacing: normal;
|
||||
// line-height: 16.81px;
|
||||
// text-align: left;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//&__icon {
|
||||
// text-align: end;
|
||||
//
|
||||
// img {
|
||||
// margin-left: 20px;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//&__right {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// align-items: left;
|
||||
//}
|
||||
//
|
||||
//&__phone {
|
||||
// color: #003b65;
|
||||
// font-family: 'CeraPro';
|
||||
// font-size: 2.1em;
|
||||
// letter-spacing: normal;
|
||||
// line-height: 25px;
|
||||
// text-align: left;
|
||||
//}
|
||||
//
|
||||
//&__working-hours {
|
||||
// color: #003b65;
|
||||
// font-family: 'CeraPro';
|
||||
// font-size: 1.2em;
|
||||
// font-weight: 400;
|
||||
// font-style: normal;
|
||||
// letter-spacing: normal;
|
||||
// line-height: normal;
|
||||
// margin-left: 24px;
|
||||
//}
|
||||
//
|
||||
//&__copyright {
|
||||
// padding: 1rem 1rem 1rem 5.6rem;
|
||||
// font-family: 'Muller';
|
||||
// font-weight: 300;
|
||||
// font-size: 1.2em;
|
||||
//}
|
||||
}
|
||||
|
||||
//@media (max-width: 1199px) {
|
||||
// .footer {
|
||||
// &__left {
|
||||
// margin-bottom: 20px;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@media (max-width: 575.98px) {
|
||||
// .footer {
|
||||
// &__left {
|
||||
// margin-top: 80px;
|
||||
// }
|
||||
//
|
||||
// &__description {
|
||||
// padding: 0;
|
||||
// margin-left: 10px;
|
||||
//
|
||||
// span {
|
||||
// font-size: 1.2em;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// &__icon {
|
||||
// img {
|
||||
// margin-left: 10px;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// &__right {
|
||||
// margin-bottom: 20px;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
@ -1,134 +0,0 @@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {useParams, useNavigate} from 'react-router-dom'
|
||||
import {Loader} from '../Loader/Loader'
|
||||
import PhoneInput from 'react-phone-input-2'
|
||||
import 'react-phone-input-2/lib/style.css'
|
||||
import './form.scss'
|
||||
|
||||
import {apiRequest} from "../../api/request";
|
||||
|
||||
import Swal from 'sweetalert2'
|
||||
import withReactContent from 'sweetalert2-react-content'
|
||||
|
||||
const SweetAlert = withReactContent(Swal);
|
||||
|
||||
const Form = () => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const urlParams = useParams();
|
||||
|
||||
const [status, setStatus] = useState(null);
|
||||
const [data, setData] = useState({
|
||||
email: '',
|
||||
phone: '',
|
||||
comment: ''
|
||||
});
|
||||
const [isFetching, setIsFetching] = useState(false);
|
||||
|
||||
const handleModal = (status) => {
|
||||
SweetAlert.fire({
|
||||
text: status !== 200 || 201
|
||||
? 'Какие-то неполадки =('
|
||||
: 'Форма отправлена',
|
||||
preConfirm: () =>
|
||||
status !== 200 || 201 ? () => {
|
||||
setStatus(null)
|
||||
} : () => {
|
||||
setStatus(null);
|
||||
navigate(`/candidate/${urlParams.id}`)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (status) {
|
||||
handleModal(status)
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const {id, value} = e.target;
|
||||
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
[id]: value
|
||||
}))
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setIsFetching(true);
|
||||
const formData = new FormData();
|
||||
formData.append('profile_id', urlParams.id);
|
||||
formData.append('Email', data.email);
|
||||
formData.append('phone', data.phone);
|
||||
formData.append('comment', data.comment);
|
||||
|
||||
apiRequest('/interview-request/create-interview-request', {
|
||||
method: 'POST',
|
||||
params: {
|
||||
profile_id: urlParams.id,
|
||||
...data
|
||||
}
|
||||
}).then((res) => {
|
||||
setStatus(res);
|
||||
setIsFetching(false)
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='row'>
|
||||
<div className='col-sm-12'>
|
||||
<form className='form' id='test'>
|
||||
<label htmlFor='email'>Емейл:</label>
|
||||
<input
|
||||
onChange={handleChange}
|
||||
id='email'
|
||||
name='Email'
|
||||
type='email'
|
||||
placeholder='Емейл'
|
||||
value={data.email}
|
||||
/>
|
||||
|
||||
<label htmlFor='phone'>Номер телефона:</label>
|
||||
<PhoneInput
|
||||
id='phone'
|
||||
name='Phone'
|
||||
country={'ru'}
|
||||
value={data.phone}
|
||||
onChange={(e) =>
|
||||
handleChange({target: {value: e, id: 'phone'}})
|
||||
}
|
||||
/>
|
||||
{/* <input
|
||||
onChange={handleChange}
|
||||
id="phone"
|
||||
type="text"
|
||||
name="Phone"
|
||||
placeholder="Телефон"
|
||||
value={data.phone}
|
||||
/> */}
|
||||
|
||||
<textarea
|
||||
onChange={handleChange}
|
||||
id='comment'
|
||||
rows='5'
|
||||
cols='40'
|
||||
name='Comment'
|
||||
placeholder='Оставьте комментарий'
|
||||
value={data.comment}
|
||||
></textarea>
|
||||
|
||||
<button onClick={handleSubmit} className='form__btn' type='submit'>
|
||||
{isFetching ? <Loader/> : 'Отправить'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default Form
|
125
src/components/Form/Form.jsx
Normal file
125
src/components/Form/Form.jsx
Normal file
@ -0,0 +1,125 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import PhoneInput from "react-phone-input-2";
|
||||
|
||||
import { apiRequest } from "../../api/request";
|
||||
import { Loader } from "../Loader/Loader";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
|
||||
import "react-phone-input-2/lib/style.css";
|
||||
import "./form.scss";
|
||||
|
||||
const SweetAlert = withReactContent(Swal);
|
||||
|
||||
const Form = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const urlParams = useParams();
|
||||
|
||||
const [status, setStatus] = useState(null);
|
||||
const [data, setData] = useState({
|
||||
email: "",
|
||||
phone: "",
|
||||
comment: "",
|
||||
});
|
||||
const [isFetching, setIsFetching] = useState(false);
|
||||
|
||||
const handleModal = (status) => {
|
||||
SweetAlert.fire({
|
||||
text:
|
||||
status !== 200 || 201 ? "Какие-то неполадки =(" : "Форма отправлена",
|
||||
preConfirm: () =>
|
||||
status !== 200 || 201
|
||||
? () => {
|
||||
setStatus(null);
|
||||
}
|
||||
: () => {
|
||||
setStatus(null);
|
||||
navigate(`/candidate/${urlParams.id}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (status) {
|
||||
handleModal(status);
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { id, value } = e.target;
|
||||
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
[id]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setIsFetching(true);
|
||||
const formData = new FormData();
|
||||
formData.append("profile_id", urlParams.id);
|
||||
formData.append("Email", data.email);
|
||||
formData.append("phone", data.phone);
|
||||
formData.append("comment", data.comment);
|
||||
|
||||
apiRequest("/interview-request/create-interview-request", {
|
||||
method: "POST",
|
||||
params: {
|
||||
profile_id: urlParams.id,
|
||||
...data,
|
||||
},
|
||||
}).then((res) => {
|
||||
setStatus(res);
|
||||
setIsFetching(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<form className="form" id="test">
|
||||
<label htmlFor="email">Емейл:</label>
|
||||
<input
|
||||
onChange={handleChange}
|
||||
id="email"
|
||||
name="Email"
|
||||
type="email"
|
||||
placeholder="Емейл"
|
||||
value={data.email}
|
||||
/>
|
||||
|
||||
<label htmlFor="phone">Номер телефона:</label>
|
||||
<PhoneInput
|
||||
id="phone"
|
||||
name="Phone"
|
||||
country={"ru"}
|
||||
value={data.phone}
|
||||
onChange={(e) =>
|
||||
handleChange({ target: { value: e, id: "phone" } })
|
||||
}
|
||||
/>
|
||||
|
||||
<textarea
|
||||
onChange={handleChange}
|
||||
id="comment"
|
||||
rows="5"
|
||||
cols="40"
|
||||
name="Comment"
|
||||
placeholder="Оставьте комментарий"
|
||||
value={data.comment}
|
||||
></textarea>
|
||||
|
||||
<button onClick={handleSubmit} className="form__btn" type="submit">
|
||||
{isFetching ? <Loader /> : "Отправить"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Form;
|
@ -7,22 +7,22 @@
|
||||
margin: 0 0 -5px 29px;
|
||||
}
|
||||
|
||||
&__icon-question {
|
||||
|
||||
}
|
||||
&__title {
|
||||
font-weight: 700;
|
||||
@include adaptiv-value("font-size", 28, 22, 1);
|
||||
line-height: 79%;
|
||||
color: #1458dd;
|
||||
}
|
||||
|
||||
&__body {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: block;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin: 0 0 13px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
word-break: break-word;
|
||||
background: #ffffff;
|
||||
|
@ -1,15 +0,0 @@
|
||||
import React from "react";
|
||||
import {LogoutButton} from "../LogoutButton/LogoutButton";
|
||||
|
||||
import './header.scss'
|
||||
|
||||
export const Header = () => {
|
||||
return (
|
||||
<div className='container header'>
|
||||
<h2>
|
||||
<span>Аутстаффинг</span> it-персонала
|
||||
</h2>
|
||||
<LogoutButton/>
|
||||
</div>
|
||||
)
|
||||
};
|
16
src/components/Header/Header.jsx
Normal file
16
src/components/Header/Header.jsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
import { LogoutButton } from "../LogoutButton/LogoutButton";
|
||||
|
||||
import "./header.scss";
|
||||
|
||||
export const Header = () => {
|
||||
return (
|
||||
<div className="container header">
|
||||
<h2>
|
||||
<span>Аутстаффинг</span> it-персонала
|
||||
</h2>
|
||||
<LogoutButton />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -9,7 +9,7 @@
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: #52b709;
|
||||
font-family: 'GT Eesti Pro Display', sans-serif;
|
||||
font-family: "GT Eesti Pro Display", sans-serif;
|
||||
font-size: 5em;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
|
@ -1,12 +0,0 @@
|
||||
import SVGLoader from 'react-loader-spinner'
|
||||
import './loader.scss'
|
||||
import React from "react";
|
||||
|
||||
|
||||
export const Loader = ({width = 50, height = 50, style}) => {
|
||||
return (
|
||||
<div className='loader'>
|
||||
<SVGLoader type='Circles' color={style ? style : `#fff`} height={height} width={width}/>
|
||||
</div>
|
||||
)
|
||||
};
|
17
src/components/Loader/Loader.jsx
Normal file
17
src/components/Loader/Loader.jsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import SVGLoader from "react-loader-spinner";
|
||||
|
||||
import "./loader.scss";
|
||||
|
||||
export const Loader = ({ width = 50, height = 50, style }) => {
|
||||
return (
|
||||
<div className="loader">
|
||||
<SVGLoader
|
||||
type="Circles"
|
||||
color={style ? style : `#fff`}
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -5,6 +5,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
path {
|
||||
fill: #6aaf5c;
|
||||
|
@ -1,33 +0,0 @@
|
||||
import React, {useState} from 'react'
|
||||
import {useNavigate} from 'react-router-dom'
|
||||
import {useSelector} from 'react-redux'
|
||||
import {useLogout} from "../../hooks/useLogout";
|
||||
|
||||
import {Loader} from '../Loader/Loader'
|
||||
|
||||
import {getRole} from '../../redux/roleSlice'
|
||||
|
||||
import './logoutButton.scss'
|
||||
|
||||
|
||||
export const LogoutButton = () => {
|
||||
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
||||
|
||||
const userRole = useSelector(getRole);
|
||||
const navigate = useNavigate();
|
||||
const {logout} = useLogout();
|
||||
|
||||
return (
|
||||
<button
|
||||
className='logout-button'
|
||||
onClick={() => {
|
||||
setIsLoggingOut(true);
|
||||
logout();
|
||||
setIsLoggingOut(false);
|
||||
navigate(userRole === 'ROLE_DEV' ? '/authdev' : '/auth')
|
||||
}}
|
||||
>
|
||||
{isLoggingOut ? <Loader/> : 'Выйти'}
|
||||
</button>
|
||||
)
|
||||
};
|
31
src/components/LogoutButton/LogoutButton.jsx
Normal file
31
src/components/LogoutButton/LogoutButton.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { useLogout } from "../../hooks/useLogout";
|
||||
import { Loader } from "../Loader/Loader";
|
||||
import { getRole } from "../../redux/roleSlice";
|
||||
|
||||
import "./logoutButton.scss";
|
||||
|
||||
export const LogoutButton = () => {
|
||||
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
||||
|
||||
const userRole = useSelector(getRole);
|
||||
const navigate = useNavigate();
|
||||
const { logout } = useLogout();
|
||||
|
||||
return (
|
||||
<button
|
||||
className="logout-button"
|
||||
onClick={() => {
|
||||
setIsLoggingOut(true);
|
||||
logout();
|
||||
setIsLoggingOut(false);
|
||||
navigate(userRole === "ROLE_DEV" ? "/authdev" : "/auth");
|
||||
}}
|
||||
>
|
||||
{isLoggingOut ? <Loader /> : "Выйти"}
|
||||
</button>
|
||||
);
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
.logout-button {
|
||||
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
@ -14,7 +13,7 @@
|
||||
background-color: #6aaf5c;
|
||||
color: #ffffff;
|
||||
border: 3px solid #6aaf5c;
|
||||
font-family: 'Muller', sans-serif;
|
||||
font-family: "Muller", sans-serif;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
|
@ -3,7 +3,6 @@ import { useSelector, useDispatch } from "react-redux";
|
||||
|
||||
import OutstaffingBlock from "../OutstaffingBlock/OutstaffingBlock";
|
||||
import TagSelect from "../Select/TagSelect";
|
||||
|
||||
import {
|
||||
selectTags,
|
||||
getPositionId,
|
@ -1,118 +0,0 @@
|
||||
import React from 'react'
|
||||
import OutsideClickHandler from 'react-outside-click-handler'
|
||||
import {useDispatch, useSelector} from 'react-redux'
|
||||
import {
|
||||
selectItems,
|
||||
selectedItems,
|
||||
profiles,
|
||||
} from '../../redux/outstaffingSlice'
|
||||
|
||||
import {apiRequest} from "../../api/request";
|
||||
|
||||
import './outstaffingBlock.scss'
|
||||
|
||||
|
||||
const handlePositionClick = (
|
||||
{
|
||||
dispatch,
|
||||
positionId,
|
||||
isSelected,
|
||||
onSelect,
|
||||
apiRequest
|
||||
}) => {
|
||||
if (isSelected) {
|
||||
apiRequest('/profile', {
|
||||
params: {
|
||||
limit: 1000
|
||||
},
|
||||
}).then((profileArr) => {
|
||||
dispatch(profiles(profileArr));
|
||||
dispatch(selectedItems([]));
|
||||
onSelect(positionId)
|
||||
})
|
||||
} else {
|
||||
apiRequest('/profile', {
|
||||
params: {
|
||||
limit: '1000',
|
||||
position_id: positionId
|
||||
},
|
||||
}).then((res) => {
|
||||
dispatch(profiles(res));
|
||||
dispatch(selectedItems([]));
|
||||
onSelect(positionId)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const OutstaffingBlock = (
|
||||
{
|
||||
dataTags = [],
|
||||
selected,
|
||||
img,
|
||||
header,
|
||||
positionId,
|
||||
isSelected,
|
||||
onSelect
|
||||
}) => {
|
||||
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const itemsArr = useSelector(selectItems);
|
||||
|
||||
|
||||
const handleBlockClick = (item, id) => {
|
||||
if (!itemsArr.find((el) => item === el.value)) {
|
||||
dispatch(selectedItems([...itemsArr, {id, value: item, label: item}]))
|
||||
}
|
||||
};
|
||||
|
||||
let classes;
|
||||
|
||||
dataTags.forEach((el) => {
|
||||
if (el.name === 'skills_back') {
|
||||
classes = 'back'
|
||||
} else if (el.name === 'skills_design') {
|
||||
classes = 'des'
|
||||
} else if (el.name === 'skills_front') {
|
||||
classes = 'front'
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<OutsideClickHandler onOutsideClick={() => isSelected && onSelect(null)}>
|
||||
<div className={`outstaffing-block${isSelected ? ' outstaffing-block__selected' : ''}`}>
|
||||
<div className={`outstaffing-block__img ${selected ? ' outstaffing-block__border' : ''}`}
|
||||
onClick={() => handlePositionClick(
|
||||
{
|
||||
dispatch,
|
||||
positionId,
|
||||
isSelected,
|
||||
onSelect,
|
||||
apiRequest
|
||||
})
|
||||
}>
|
||||
<h3>{header}</h3>
|
||||
<img className={classes} src={img} alt='img'/>
|
||||
</div>
|
||||
<div className={`${selected ? 'outstaffing-block__mobile--block' : 'outstaffing-block__mobile--none'}`} >
|
||||
<p className='outstaffing-block__text'># Популярный стек</p>
|
||||
{dataTags && (
|
||||
<ul className='outstaffing-block__items'>
|
||||
{dataTags.map((item) => (
|
||||
<li
|
||||
key={item.id}
|
||||
onClick={() => handleBlockClick(item.value, item.id)}
|
||||
>
|
||||
{item.value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</OutsideClickHandler>
|
||||
)
|
||||
};
|
||||
|
||||
export default OutstaffingBlock
|
126
src/components/OutstaffingBlock/OutstaffingBlock.jsx
Normal file
126
src/components/OutstaffingBlock/OutstaffingBlock.jsx
Normal file
@ -0,0 +1,126 @@
|
||||
import React from "react";
|
||||
import OutsideClickHandler from "react-outside-click-handler";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
selectItems,
|
||||
selectedItems,
|
||||
profiles,
|
||||
} from "../../redux/outstaffingSlice";
|
||||
|
||||
import { apiRequest } from "../../api/request";
|
||||
|
||||
import "./outstaffingBlock.scss";
|
||||
|
||||
const handlePositionClick = ({
|
||||
dispatch,
|
||||
positionId,
|
||||
isSelected,
|
||||
onSelect,
|
||||
apiRequest,
|
||||
}) => {
|
||||
if (isSelected) {
|
||||
apiRequest("/profile", {
|
||||
params: {
|
||||
limit: 1000,
|
||||
},
|
||||
}).then((profileArr) => {
|
||||
dispatch(profiles(profileArr));
|
||||
dispatch(selectedItems([]));
|
||||
onSelect(positionId);
|
||||
});
|
||||
} else {
|
||||
apiRequest("/profile", {
|
||||
params: {
|
||||
limit: "1000",
|
||||
position_id: positionId,
|
||||
},
|
||||
}).then((res) => {
|
||||
dispatch(profiles(res));
|
||||
dispatch(selectedItems([]));
|
||||
onSelect(positionId);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const OutstaffingBlock = ({
|
||||
dataTags = [],
|
||||
selected,
|
||||
img,
|
||||
header,
|
||||
positionId,
|
||||
isSelected,
|
||||
onSelect,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const itemsArr = useSelector(selectItems);
|
||||
|
||||
const handleBlockClick = (item, id) => {
|
||||
if (!itemsArr.find((el) => item === el.value)) {
|
||||
dispatch(selectedItems([...itemsArr, { id, value: item, label: item }]));
|
||||
}
|
||||
};
|
||||
|
||||
let classes;
|
||||
|
||||
dataTags.forEach((el) => {
|
||||
if (el.name === "skills_back") {
|
||||
classes = "back";
|
||||
} else if (el.name === "skills_design") {
|
||||
classes = "des";
|
||||
} else if (el.name === "skills_front") {
|
||||
classes = "front";
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<OutsideClickHandler onOutsideClick={() => isSelected && onSelect(null)}>
|
||||
<div
|
||||
className={`outstaffing-block${
|
||||
isSelected ? " outstaffing-block__selected" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`outstaffing-block__img ${
|
||||
selected ? " outstaffing-block__border" : ""
|
||||
}`}
|
||||
onClick={() =>
|
||||
handlePositionClick({
|
||||
dispatch,
|
||||
positionId,
|
||||
isSelected,
|
||||
onSelect,
|
||||
apiRequest,
|
||||
})
|
||||
}
|
||||
>
|
||||
<h3>{header}</h3>
|
||||
<img className={classes} src={img} alt="img" />
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
selected
|
||||
? "outstaffing-block__mobile--block"
|
||||
: "outstaffing-block__mobile--none"
|
||||
}`}
|
||||
>
|
||||
<p className="outstaffing-block__text"># Популярный стек</p>
|
||||
{dataTags && (
|
||||
<ul className="outstaffing-block__items">
|
||||
{dataTags.map((item) => (
|
||||
<li
|
||||
key={item.id}
|
||||
onClick={() => handleBlockClick(item.value, item.id)}
|
||||
>
|
||||
{item.value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</OutsideClickHandler>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutstaffingBlock;
|
@ -1,15 +1,15 @@
|
||||
body {
|
||||
font-family: 'LabGrotesque', sans-serif !important;
|
||||
font-family: "LabGrotesque", sans-serif !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1160px !important;
|
||||
}
|
||||
.catalog {
|
||||
background: #F1F1F1;
|
||||
background: #f1f1f1;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
font-family: 'LabGrotesque', sans-serif;
|
||||
font-family: "LabGrotesque", sans-serif;
|
||||
padding-top: 23px;
|
||||
|
||||
&__title {
|
||||
@ -37,7 +37,7 @@ body {
|
||||
}
|
||||
|
||||
& > p {
|
||||
font-family: 'GT Eesti Pro Display';
|
||||
font-family: "GT Eesti Pro Display";
|
||||
font-size: 1.2em;
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
@ -60,7 +60,7 @@ body {
|
||||
right: 13%;
|
||||
top: 30%;
|
||||
max-width: 130px;
|
||||
font-family: 'GT Eesti Pro Display';
|
||||
font-family: "GT Eesti Pro Display";
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
@ -85,7 +85,7 @@ body {
|
||||
|
||||
&__items {
|
||||
li {
|
||||
font-family: 'GT Eesti Pro Display';
|
||||
font-family: "GT Eesti Pro Display";
|
||||
font-size: 1.8em;
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
|
@ -1,16 +0,0 @@
|
||||
import React from 'react'
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
import './profileBreadcrumbs.scss'
|
||||
|
||||
export const ProfileBreadcrumbs = ({ links }) => {
|
||||
return (
|
||||
<div className='profileBreadcrumbs'>
|
||||
{links.map((link, index) => {
|
||||
return <Link key={index} to={link.link}>{link.name}</Link>
|
||||
})
|
||||
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
18
src/components/ProfileBreadcrumbs/ProfileBreadcrumbs.jsx
Normal file
18
src/components/ProfileBreadcrumbs/ProfileBreadcrumbs.jsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import "./profileBreadcrumbs.scss";
|
||||
|
||||
export const ProfileBreadcrumbs = ({ links }) => {
|
||||
return (
|
||||
<div className="profileBreadcrumbs">
|
||||
{links.map((link, index) => {
|
||||
return (
|
||||
<Link key={index} to={link.link}>
|
||||
{link.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
a {
|
||||
color: #5B6871;
|
||||
color: #5b6871;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
@ -31,11 +31,11 @@
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
content: "";
|
||||
background-image: url("../../images/BreadcrumbsArrow.png");
|
||||
background-repeat: no-repeat;
|
||||
width: 7px;
|
||||
height:10px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
right: -14px;
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Link, Navigate } from "react-router-dom";
|
||||
|
||||
import { getReports } from "../Calendar/calendarHelper";
|
||||
import { Link, Navigate } from "react-router-dom";
|
||||
import moment from "moment";
|
||||
|
||||
import { ProfileCalendarComponent } from "./ProfileCalendarComponent";
|
||||
import { Loader } from "../Loader/Loader";
|
||||
import { ProfileHeader } from "../ProfileHeader/ProfileHeader";
|
||||
import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs";
|
||||
import { ProfileBreadcrumbs } from "../ProfileBreadcrumbs/ProfileBreadcrumbs";
|
||||
import { Footer } from "../Footer/Footer";
|
||||
import { Navigation } from "../Navigation/Navigation";
|
||||
import { ViewReport } from "../../pages/ViewReport/ViewReport";
|
||||
|
||||
import { urlForLocal } from "../../helper";
|
||||
|
||||
import { apiRequest } from "../../api/request";
|
||||
import { getProfileInfo } from "../../redux/outstaffingSlice";
|
||||
import {
|
@ -1,23 +1,25 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import arrow from "../../images/arrowCalendar.png";
|
||||
import rectangle from "../../images/rectangle__calendar.png";
|
||||
import calendarIcon from "../../images/calendar_icon.png";
|
||||
import moment from "moment";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import {
|
||||
setReportDate,
|
||||
setRequestDate,
|
||||
setSendRequest,
|
||||
} from "../../redux/reportSlice";
|
||||
import {
|
||||
calendarHelper,
|
||||
currentMonthAndDay,
|
||||
getReports,
|
||||
hourOfNum,
|
||||
} from "../Calendar/calendarHelper";
|
||||
import {
|
||||
setReportDate,
|
||||
setRequestDate,
|
||||
setSendRequest,
|
||||
} from "../../redux/reportSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import ShortReport from "../ShortReport/ShortReport";
|
||||
|
||||
import arrow from "../../images/arrowCalendar.png";
|
||||
import rectangle from "../../images/rectangle__calendar.png";
|
||||
import calendarIcon from "../../images/calendar.svg";
|
||||
import moment from "moment";
|
||||
|
||||
import "moment/locale/ru";
|
||||
import "./../Calendar/calendarComponent.scss";
|
||||
|
||||
|
@ -1,81 +1,100 @@
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {useSelector} from 'react-redux'
|
||||
import {Link, Navigate, useNavigate} from 'react-router-dom'
|
||||
import DatePicker, { registerLocale } from "react-datepicker"
|
||||
import {getCorrectDate, getCreatedDate, hourOfNum} from '../Calendar/calendarHelper'
|
||||
import ru from "date-fns/locale/ru"
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Link, Navigate, useNavigate } from "react-router-dom";
|
||||
import DatePicker, { registerLocale } from "react-datepicker";
|
||||
import {
|
||||
getCorrectDate,
|
||||
getCreatedDate,
|
||||
hourOfNum,
|
||||
} from "../Calendar/calendarHelper";
|
||||
import ru from "date-fns/locale/ru";
|
||||
registerLocale("ru", ru);
|
||||
|
||||
import {Loader} from '../Loader/Loader'
|
||||
import {Footer} from "../Footer/Footer";
|
||||
import {ProfileHeader} from "../ProfileHeader/ProfileHeader";
|
||||
import {ProfileBreadcrumbs} from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs"
|
||||
import { Loader } from "../Loader/Loader";
|
||||
import { Footer } from "../Footer/Footer";
|
||||
import { ProfileHeader } from "../ProfileHeader/ProfileHeader";
|
||||
import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs";
|
||||
|
||||
import {apiRequest} from "../../api/request";
|
||||
import { apiRequest } from "../../api/request";
|
||||
|
||||
import {getReportDate} from '../../redux/reportSlice'
|
||||
import { getReportDate } from "../../redux/reportSlice";
|
||||
|
||||
import calendarIcon from '../../images/calendar_icon.png'
|
||||
import ellipse from '../../images/ellipse.png'
|
||||
import remove from '../../images/remove.png'
|
||||
import calendarIcon from "../../images/calendar.svg";
|
||||
import ellipse from "../../images/ellipse.png";
|
||||
import remove from "../../images/remove.png";
|
||||
import arrow from "../../images/right-arrow.png";
|
||||
|
||||
import './reportForm.scss'
|
||||
import "./reportForm.scss";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { Navigation } from '../Navigation/Navigation'
|
||||
import { Navigation } from "../Navigation/Navigation";
|
||||
|
||||
const ReportForm = () => {
|
||||
if(localStorage.getItem('role_status') === '18') {
|
||||
return <Navigate to="/profile" replace/>
|
||||
if (localStorage.getItem("role_status") === "18") {
|
||||
return <Navigate to="/profile" replace />;
|
||||
}
|
||||
const navigate= useNavigate();
|
||||
const navigate = useNavigate();
|
||||
const reportDate = useSelector(getReportDate);
|
||||
|
||||
useEffect(() => {
|
||||
initListeners()
|
||||
}, [])
|
||||
initListeners();
|
||||
}, []);
|
||||
|
||||
const [isFetching, setIsFetching] = useState(false);
|
||||
const [reportSuccess, setReportSuccess] = useState('');
|
||||
const [startDate, setStartDate] = useState(reportDate ? new Date (reportDate._d) : new Date());
|
||||
const [reportSuccess, setReportSuccess] = useState("");
|
||||
const [startDate, setStartDate] = useState(
|
||||
reportDate ? new Date(reportDate._d) : new Date()
|
||||
);
|
||||
const [datePickerOpen, setDatePickerOpen] = useState(false);
|
||||
|
||||
const [inputs, setInputs] = useState([{task: '', hours_spent: '', minutes_spent: 0}]);
|
||||
const [troublesInputValue, setTroublesInputValue] = useState('');
|
||||
const [scheduledInputValue, setScheduledInputValue] = useState('');
|
||||
const [inputs, setInputs] = useState([
|
||||
{ task: "", hours_spent: "", minutes_spent: 0 },
|
||||
]);
|
||||
const [troublesInputValue, setTroublesInputValue] = useState("");
|
||||
const [scheduledInputValue, setScheduledInputValue] = useState("");
|
||||
|
||||
const addInput = () => {
|
||||
setInputs((prev) => [...prev, {task: '', hours_spent: '', minutes_spent: 0}])
|
||||
setInputs((prev) => [
|
||||
...prev,
|
||||
{ task: "", hours_spent: "", minutes_spent: 0 },
|
||||
]);
|
||||
};
|
||||
|
||||
const initListeners = () => {
|
||||
document.addEventListener('click', closeByClickingOut)
|
||||
}
|
||||
document.addEventListener("click", closeByClickingOut);
|
||||
};
|
||||
|
||||
const closeByClickingOut = (event) => {
|
||||
const path = event.path || (event.composedPath && event.composedPath())
|
||||
const path = event.path || (event.composedPath && event.composedPath());
|
||||
|
||||
if (event && !path.find((div) => div.classList && (div.classList.contains('report-form__block-img') || div.classList.contains('react-datepicker-popper')))) {
|
||||
setDatePickerOpen(false)
|
||||
}
|
||||
if (
|
||||
event &&
|
||||
!path.find(
|
||||
(div) =>
|
||||
div.classList &&
|
||||
(div.classList.contains("report-form__block-img") ||
|
||||
div.classList.contains("react-datepicker-popper"))
|
||||
)
|
||||
) {
|
||||
setDatePickerOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const totalHours = inputs.reduce((a, b) => a + b.hours_spent, 0);
|
||||
|
||||
const deleteInput = (indexRemove) => {
|
||||
setInputs((prev) => prev.filter((el, index) => index !== indexRemove))
|
||||
setInputs((prev) => prev.filter((el, index) => index !== indexRemove));
|
||||
};
|
||||
|
||||
const handler = () => {
|
||||
for (let input of inputs) {
|
||||
if(!input.task || !input.hours_spent) {
|
||||
setReportSuccess('Заполните задачи');
|
||||
setTimeout(() => setReportSuccess(''), 2000)
|
||||
return
|
||||
if (!input.task || !input.hours_spent) {
|
||||
setReportSuccess("Заполните задачи");
|
||||
setTimeout(() => setReportSuccess(""), 2000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
apiRequest('/reports/create', {
|
||||
method: 'POST',
|
||||
apiRequest("/reports/create", {
|
||||
method: "POST",
|
||||
data: {
|
||||
tasks: inputs,
|
||||
difficulties: troublesInputValue,
|
||||
@ -84,156 +103,223 @@ const ReportForm = () => {
|
||||
status: 1,
|
||||
},
|
||||
}).then((res) => {
|
||||
setReportSuccess('Отчет отправлен');
|
||||
setReportSuccess("Отчет отправлен");
|
||||
setTimeout(() => {
|
||||
setReportSuccess('')
|
||||
navigate('/profile/calendar');
|
||||
}, 1000)
|
||||
setReportSuccess("");
|
||||
navigate("/profile/calendar");
|
||||
}, 1000);
|
||||
setInputs(() => []);
|
||||
setTroublesInputValue('');
|
||||
setScheduledInputValue('');
|
||||
setTroublesInputValue("");
|
||||
setScheduledInputValue("");
|
||||
setIsFetching(false);
|
||||
setInputs(() => [{task: '', hours_spent: '', minutes_spent: 0}]);
|
||||
})
|
||||
setInputs(() => [{ task: "", hours_spent: "", minutes_spent: 0 }]);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section className='report-form'>
|
||||
<ProfileHeader/>
|
||||
<section className="report-form">
|
||||
<ProfileHeader />
|
||||
<Navigation />
|
||||
<div className='container'>
|
||||
<ProfileBreadcrumbs links={[{name: 'Главная', link: '/profile'},
|
||||
{name: 'Ваша отчетность', link: '/profile/calendar'},
|
||||
{name: 'Страница добавления нового отчета', link: '/report'}]}
|
||||
<div className="container">
|
||||
<ProfileBreadcrumbs
|
||||
links={[
|
||||
{ name: "Главная", link: "/profile" },
|
||||
{ name: "Ваша отчетность", link: "/profile/calendar" },
|
||||
{ name: "Страница добавления нового отчета", link: "/report" },
|
||||
]}
|
||||
/>
|
||||
<h2 className='summary__title'>Ваши отчеты - <span>добавить отчет</span></h2>
|
||||
<h2 className="summary__title">
|
||||
Ваши отчеты - <span>добавить отчет</span>
|
||||
</h2>
|
||||
<div>
|
||||
<div className='report__head'>
|
||||
<Link className='calendar__back' to={`/profile/calendar`}>
|
||||
<img src={arrow} alt=''/><p>Вернуться</p>
|
||||
<div className="report__head">
|
||||
<Link className="calendar__back" to={`/profile/calendar`}>
|
||||
<img src={arrow} alt="" />
|
||||
<p>Вернуться</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='report-form__content'>
|
||||
<div className='report-form__block'>
|
||||
<div className='report-form__block-title'>
|
||||
<div className="report-form__content">
|
||||
<div className="report-form__block">
|
||||
<div className="report-form__block-title">
|
||||
<h2>Добавление отчета за день</h2>
|
||||
<h3>Дата заполнения отчета:</h3>
|
||||
</div>
|
||||
<div className='report-form__block-img' onClick={() => setDatePickerOpen(true)}>
|
||||
<div
|
||||
className="report-form__block-img"
|
||||
onClick={() => setDatePickerOpen(true)}
|
||||
>
|
||||
<img
|
||||
className='report-form__calendar-icon'
|
||||
className="report-form__calendar-icon"
|
||||
src={calendarIcon}
|
||||
alt=''
|
||||
alt=""
|
||||
/>
|
||||
{getCorrectDate(startDate)}
|
||||
</div>
|
||||
<DatePicker
|
||||
className='datePicker'
|
||||
className="datePicker"
|
||||
open={datePickerOpen}
|
||||
locale="ru"
|
||||
selected={startDate}
|
||||
onChange={(date) => {
|
||||
setDatePickerOpen(false)
|
||||
setStartDate(date)
|
||||
setDatePickerOpen(false);
|
||||
setStartDate(date);
|
||||
}}
|
||||
/>
|
||||
<div className='report-form__task-list'>
|
||||
<img src={ellipse} alt=''/>
|
||||
<div className="report-form__task-list">
|
||||
<img src={ellipse} alt="" />
|
||||
<span>Какие задачи были выполнены?</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row'>
|
||||
<div className='col-8'>
|
||||
<div className='report-form__task-header'>
|
||||
<p className='report-form__task-title--description'>
|
||||
<div className="row">
|
||||
<div className="col-8">
|
||||
<div className="report-form__task-header">
|
||||
<p className="report-form__task-title--description">
|
||||
Краткое описание задачи
|
||||
</p>
|
||||
<p className='report-form__task-title--hours'>Количество часов</p>
|
||||
<p className="report-form__task-title--hours">
|
||||
Количество часов
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{inputs.map((input, index) => {
|
||||
return (
|
||||
<form id={'input'} key={`input__${index}`} className='report-form__task-form'>
|
||||
<div className='report-form__task-number'>
|
||||
{index + 1}.
|
||||
</div>
|
||||
<div className='report-form__task-input report-form__task-input--description'>
|
||||
<input value={inputs[index].task} className={!input.task && reportSuccess === 'Заполните задачи' ? 'checkTask' : ''} name='text' type='text'
|
||||
onChange={e => setInputs(inputs.map((input, inputIndex) => {
|
||||
<form
|
||||
id={"input"}
|
||||
key={`input__${index}`}
|
||||
className="report-form__task-form"
|
||||
>
|
||||
<div className="report-form__task-number">{index + 1}.</div>
|
||||
<div className="report-form__task-input report-form__task-input--description">
|
||||
<input
|
||||
value={inputs[index].task}
|
||||
className={
|
||||
!input.task && reportSuccess === "Заполните задачи"
|
||||
? "checkTask"
|
||||
: ""
|
||||
}
|
||||
name="text"
|
||||
type="text"
|
||||
onChange={(e) =>
|
||||
setInputs(
|
||||
inputs.map((input, inputIndex) => {
|
||||
return index === inputIndex
|
||||
? {
|
||||
...input,
|
||||
task: e.target.value
|
||||
task: e.target.value,
|
||||
}
|
||||
: input
|
||||
}))}/>
|
||||
</div>
|
||||
<div className='report-form__task-input report-form__task-input--hours'>
|
||||
<input value={inputs[index].hours_spent} className={!input.hours_spent && reportSuccess === 'Заполните задачи' ? 'checkTask' : ''} name='number' type='number' min='1'
|
||||
onChange={e => setInputs(inputs.map((input, inputIndex) => {
|
||||
return index === inputIndex
|
||||
? {
|
||||
...input,
|
||||
hours_spent: Number(e.target.value)
|
||||
}
|
||||
: input
|
||||
}))}/>
|
||||
</div>
|
||||
{index > 0 &&
|
||||
<div className='report-form__task-remove'>
|
||||
<img onClick={() => deleteInput(index)} src={remove} alt=''/>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
: input;
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="report-form__task-input report-form__task-input--hours">
|
||||
<input
|
||||
value={inputs[index].hours_spent}
|
||||
className={
|
||||
!input.hours_spent &&
|
||||
reportSuccess === "Заполните задачи"
|
||||
? "checkTask"
|
||||
: ""
|
||||
}
|
||||
name="number"
|
||||
type="number"
|
||||
min="1"
|
||||
onChange={(e) =>
|
||||
setInputs(
|
||||
inputs.map((input, inputIndex) => {
|
||||
return index === inputIndex
|
||||
? {
|
||||
...input,
|
||||
hours_spent: Number(e.target.value),
|
||||
}
|
||||
: input;
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{index > 0 && (
|
||||
<div className="report-form__task-remove">
|
||||
<img
|
||||
onClick={() => deleteInput(index)}
|
||||
src={remove}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className='report-form__form-add'>
|
||||
<p className='addMore' onClick={addInput}>+</p>
|
||||
<div className="report-form__form-add">
|
||||
<p className="addMore" onClick={addInput}>
|
||||
+
|
||||
</p>
|
||||
<span>Добавить еще </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='row'>
|
||||
<div className='col-12'>
|
||||
<div className='report-form__input-box'>
|
||||
<div className='report-form__troubles'>
|
||||
<img src={ellipse} alt=''/>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="report-form__input-box">
|
||||
<div className="report-form__troubles">
|
||||
<img src={ellipse} alt="" />
|
||||
<span>Какие сложности возникли?</span>
|
||||
</div>
|
||||
<input type='text' value={troublesInputValue} onChange={e => setTroublesInputValue(e.target.value)}/>
|
||||
<div className='report-form__scheduled'>
|
||||
<img src={ellipse} alt=''/>
|
||||
<input
|
||||
type="text"
|
||||
value={troublesInputValue}
|
||||
onChange={(e) => setTroublesInputValue(e.target.value)}
|
||||
/>
|
||||
<div className="report-form__scheduled">
|
||||
<img src={ellipse} alt="" />
|
||||
<span>Что планируется сделать завтра?</span>
|
||||
</div>
|
||||
<input type='text' value={scheduledInputValue} onChange={e => setScheduledInputValue(e.target.value)}/>
|
||||
<input
|
||||
type="text"
|
||||
value={scheduledInputValue}
|
||||
onChange={(e) => setScheduledInputValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col-12'>
|
||||
<div className='report-form__footer'>
|
||||
<button className='report-form__footer-btn' onClick={() => handler()}>
|
||||
{isFetching ? <Loader/> : 'Отправить'}
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="report-form__footer">
|
||||
<button
|
||||
className="report-form__footer-btn"
|
||||
onClick={() => handler()}
|
||||
>
|
||||
{isFetching ? <Loader /> : "Отправить"}
|
||||
</button>
|
||||
<p className='report-form__footer-text'>
|
||||
Всего за день : <span>{totalHours} {hourOfNum(totalHours)}</span>
|
||||
<p className="report-form__footer-text">
|
||||
Всего за день :{" "}
|
||||
<span>
|
||||
{totalHours} {hourOfNum(totalHours)}
|
||||
</span>
|
||||
</p>
|
||||
{reportSuccess &&
|
||||
<p className={`report-form__footer-done ${reportSuccess === 'Заполните задачи' ? 'errorText' : ''}`}>{reportSuccess}</p>
|
||||
}
|
||||
{reportSuccess && (
|
||||
<p
|
||||
className={`report-form__footer-done ${
|
||||
reportSuccess === "Заполните задачи" ? "errorText" : ""
|
||||
}`}
|
||||
>
|
||||
{reportSuccess}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer/>
|
||||
<Footer />
|
||||
</section>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportForm
|
||||
export default ReportForm;
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 260 B |
Binary file not shown.
Before Width: | Height: | Size: 645 B |
3
src/images/cursorImg.svg
Normal file
3
src/images/cursorImg.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.278 23.9789L9.078 15.7389L4 20.2159V2.01887C3.99999 1.64616 4.10412 1.28087 4.30065 0.964186C4.49718 0.647505 4.77829 0.392041 5.11227 0.22661C5.44626 0.0611791 5.81982 -0.00763698 6.19083 0.0279244C6.56183 0.0634857 6.91552 0.20201 7.212 0.427871L21.117 12.4359L14.5 13.1699L18.645 21.2999L13.278 23.9789ZM9.684 12.5339L14.158 21.2999L15.947 20.4059L11.4 11.4999L16.338 10.9529L5.952 1.97987L5.994 15.7899L9.684 12.5339Z" fill="#374957"/>
|
||||
</svg>
|
After Width: | Height: | Size: 556 B |
@ -14,7 +14,7 @@ import { Footer } from "../../components/Footer/Footer";
|
||||
|
||||
import { apiRequest } from "../../api/request";
|
||||
|
||||
import cursorImg from "../../images/cursorImg.png";
|
||||
import cursorImg from "../../images/cursorImg.svg";
|
||||
|
||||
import "./partnerRequests.scss";
|
||||
import { Navigation } from "../../components/Navigation/Navigation";
|
||||
|
Loading…
Reference in New Issue
Block a user