Merge pull request 'update man hinh user' (#7) from feature/login-role into dev
Reviewed-on: huyt/fsi_project#7
This commit is contained in:
commit
c9780d4a31
|
@ -21,10 +21,12 @@
|
|||
"react-file-reader": "^1.1.4",
|
||||
"react-js-pagination": "^3.0.3",
|
||||
"react-loading-overlay": "^1.0.1",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-scripts": "5.0.0",
|
||||
"react-select": "^5.2.1",
|
||||
"react-spinners": "^0.11.0",
|
||||
"react-switch": "^6.0.0",
|
||||
"sweetalert": "^2.1.2",
|
||||
"web-vitals": "^2.1.2"
|
||||
},
|
||||
|
|
BIN
app/public/img/BI_Logo.png
Normal file
BIN
app/public/img/BI_Logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
107
app/src/App.css
107
app/src/App.css
|
@ -366,3 +366,110 @@ button.close:hover {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
.login-block {
|
||||
/* background-image: url("/img/Bg_login.jpg"); */
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 50px 0;
|
||||
margin-top: 150px;
|
||||
}
|
||||
.login-sec {
|
||||
padding: 50px 30px 10px;
|
||||
position: relative;
|
||||
}
|
||||
.login-sec .copy-text {
|
||||
position: absolute;
|
||||
width: 80%;
|
||||
bottom: 20px;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
.login-sec .copy-text i {
|
||||
color: #feb58a;
|
||||
}
|
||||
.login-sec .copy-text a {
|
||||
color: #e36262;
|
||||
}
|
||||
.login-sec h2 {
|
||||
margin-bottom: 30px;
|
||||
font-weight: 800;
|
||||
font-size: 30px;
|
||||
color: #0695e2;
|
||||
}
|
||||
.login-sec h2:after {
|
||||
content: " ";
|
||||
width: 100px;
|
||||
height: 5px;
|
||||
background: #0695e2;
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
margin-top: 20px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.login-sec ul li a {
|
||||
padding: 8px !important;
|
||||
border-top-right-radius: 3px !important;
|
||||
border-top-left-radius: 3px !important;
|
||||
}
|
||||
.login-sec ul li a.active {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.banner-sec {
|
||||
background-size: cover;
|
||||
min-height: 300px;
|
||||
border-radius: 0 10px 10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
.banner-text {
|
||||
width: 70%;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.banner-text h2 {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.banner-text h2:after {
|
||||
content: " ";
|
||||
width: 100px;
|
||||
height: 5px;
|
||||
background: #fff;
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.banner-text p {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.container {
|
||||
box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.08);
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.container_form_infor {
|
||||
box-shadow: 0 1px 15px 1px rgba(69, 65, 78, 0.08);
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.carousel-inner {
|
||||
border-radius: 0 10px 10px 0;
|
||||
}
|
||||
.carousel-caption {
|
||||
text-align: left;
|
||||
left: 5%;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
background: #0695e2;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-family: inherit !important;
|
||||
}
|
|
@ -3,38 +3,41 @@ import ListItem from './components/List/ListItem';
|
|||
import Login from './components/Login/Login';
|
||||
import LabelImage from './components/LabelImg/LabelImage';
|
||||
import SearchImage from './components/SearchImg/SearchImage';
|
||||
import User from './components/User/User';
|
||||
import ImportImage from './components/ImportImg/ImportImage';
|
||||
import Test from './components/Test/Test';
|
||||
import MenuBar from './components/layouts/MenuBar'
|
||||
import Header from './components/layouts/Header'
|
||||
import 'antd/dist/antd.css';
|
||||
import "./App.css";
|
||||
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from './store';
|
||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<Switch>
|
||||
<div className="m-grid m-grid--hor m-grid--root m-page">
|
||||
<Switch>
|
||||
<Route path="/login" exact render={() => <Login />} />
|
||||
<div className="m-grid m-grid--hor m-grid--root m-page">
|
||||
<Header />
|
||||
<MenuBar />
|
||||
<div>
|
||||
{/* <Redirect exact from='/' to='/search-image' /> */}
|
||||
<Route exact path='/' component={SearchImage} />
|
||||
<Route path='/login' component={Login} />
|
||||
<Route path='/import-image' component={ImportImage} />
|
||||
<Route path='/user' component={User} />
|
||||
<Route path='/search-image' component={SearchImage} />
|
||||
<Route path='/label-image' component={LabelImage} />
|
||||
<Route path='/test' component={Test} />
|
||||
<Route path="/list-famous" component={ListItem} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Switch>
|
||||
</Switch>
|
||||
|
||||
</Router>
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
15
app/src/actions/isLogin/index.js
Normal file
15
app/src/actions/isLogin/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
export const LOGIN = 'LOGIN';
|
||||
export const LOGOUT = 'LOGOUT';
|
||||
export function login(access_token){
|
||||
return {
|
||||
type: LOGIN,
|
||||
payload:{
|
||||
access_token: access_token
|
||||
}
|
||||
}
|
||||
}
|
||||
export function logout(){
|
||||
return {
|
||||
type: LOGOUT
|
||||
}
|
||||
}
|
9
app/src/actions/role/index.js
Normal file
9
app/src/actions/role/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const ROLE = 'ROLE';
|
||||
export function role(role){
|
||||
return {
|
||||
type: ROLE,
|
||||
payload:{
|
||||
role: role
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,128 +1,157 @@
|
|||
import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Input, Typography } from 'antd';
|
||||
import axios from 'axios';
|
||||
// import { dispatchBox, dispatchToken } from 'features/Login/loginSlice';
|
||||
import React, { useState } from 'react';
|
||||
// import { useDispatch } from 'react-redux';
|
||||
// import { useHistory } from "react-router-dom";
|
||||
import { HOST } from '../../config/index';
|
||||
import React, { Component } from 'react';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import swal from 'sweetalert';
|
||||
import $ from 'jquery';
|
||||
import { HOST } from '../../config';
|
||||
import Store from '../../store';
|
||||
import { login } from '../../actions/isLogin';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const Login = () => {
|
||||
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// const dispatch = useDispatch();
|
||||
|
||||
// let history = useHistory();
|
||||
|
||||
const onFinish = async (values) => {
|
||||
setLoading(true);
|
||||
const dataReq = {
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
}
|
||||
|
||||
await axios({
|
||||
method: 'POST',
|
||||
url: `${HOST}/api/login`,
|
||||
headers: {},
|
||||
data: dataReq
|
||||
}).then((res) => {
|
||||
if (res.data.status === 10000) {
|
||||
// dispatch(dispatchToken({ token: 'Bearer ' + res.data.access_token }));
|
||||
// dispatch(dispatchBox({ rule: res.data.user_info.rule }))
|
||||
setError('');
|
||||
localStorage.setItem('refresh_token', 'Bearer ' + res.data.refresh_token);
|
||||
localStorage.setItem('access_token', 'Bearer ' + res.data.access_token);
|
||||
localStorage.setItem('rule', res.data.user_info.rule);
|
||||
|
||||
// history.push('/preferential-management')
|
||||
} else {
|
||||
setError('Sai tài khoản hoặc mật khẩu');
|
||||
class Login extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: 0,
|
||||
loadingbtn: false,
|
||||
datalogin: {
|
||||
email: "",
|
||||
password: "",
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
setError('Vui lòng kiểm tra lại đường truyền mạng')
|
||||
console.error(err);
|
||||
}
|
||||
localStorage.clear();
|
||||
}
|
||||
|
||||
CheckLogin = async (event) => {
|
||||
var form = $("#formLoginCheck")
|
||||
if (form[0].checkValidity() === false) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
form.addClass('was-validated')
|
||||
} else {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
loadingbtn: true
|
||||
}, () => {
|
||||
if (this.state.datalogin.password.length < 6) {
|
||||
this.setState({
|
||||
error: 1,
|
||||
loadingbtn: false
|
||||
})
|
||||
} else {
|
||||
fetch(`${HOST}/api/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(this.state.datalogin)
|
||||
}).then((response) => {
|
||||
if (response.status === 500) {
|
||||
this.setState({
|
||||
error: 0,
|
||||
loadingbtn: false
|
||||
})
|
||||
swal("Cảnh báo!", "Hiện tại hệ thống của chúng tôi đang nâng cấp, vui lòng đăng nhập lại sau 30 phút", "info");
|
||||
return;
|
||||
}
|
||||
return response.json()
|
||||
}).then((data) => {
|
||||
if (data.status === 10000) {
|
||||
this.setState({
|
||||
error: 0,
|
||||
loadingbtn: false
|
||||
})
|
||||
localStorage.setItem("access_token", "Bearer " + data.access_token);
|
||||
Store.dispatch(login("Bearer " + data.access_token));
|
||||
localStorage.setItem("api_url", data.api_url);
|
||||
localStorage.setItem("api2_url", data.api2_url);
|
||||
window.location.href = "/";
|
||||
// return;
|
||||
} else if (data.status === 10003) {
|
||||
this.setState({
|
||||
loadingbtn: false
|
||||
})
|
||||
swal("Cảnh báo!", "Tài khoản không tồn tại", "warning");
|
||||
} else if (data.status === 10005) {
|
||||
this.setState({
|
||||
loadingbtn: false
|
||||
})
|
||||
swal("Cảnh báo", "Tài khoản của bạn đã bị khóa, vui lòng chờ 30 phút rồi đăng nhập lại hoặc liên hệ admin", "warning");
|
||||
} else {
|
||||
this.setState({
|
||||
error: 1,
|
||||
loadingbtn: false
|
||||
})
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (error) {
|
||||
this.setState({
|
||||
loadingbtn: false
|
||||
})
|
||||
swal("Lỗi!", "Vui lòng kiểm tra lại đường truyền", "error");
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const onFinishFailed = (errorInfo) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
onEnterPress = (e) => {
|
||||
if (e.keyCode === 13 && e.shiftKey === false) {
|
||||
e.preventDefault();
|
||||
this.CheckLogin(e);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login__page">
|
||||
<div className="login__form">
|
||||
<div className="login__sec">
|
||||
<h2 className="login__text">Đăng nhập</h2>
|
||||
<Form
|
||||
layout="vertical"
|
||||
name="normal_login"
|
||||
className="login-form"
|
||||
initialValues={{
|
||||
remember: true,
|
||||
}}
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
>
|
||||
<Form.Item
|
||||
name="email"
|
||||
label="Email"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
className="input__Cus"
|
||||
prefix={<UserOutlined className="site-form-item-icon" />}
|
||||
placeholder="Email" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
label="Mật khẩu"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
className="input__Cus"
|
||||
prefix={<LockOutlined className="site-form-item-icon" />}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="login__error">
|
||||
{error && <Text strong="true" className="hide__error" type="danger">* Sai tài khoản mật khẩu </Text>}
|
||||
</div>
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={loading}
|
||||
className="login-form-button"
|
||||
>
|
||||
Đăng nhập
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
HandleLogin = (e) => {
|
||||
var datalogin = this.state.datalogin;
|
||||
datalogin[e.target.name] = e.target.value.trim();
|
||||
this.setState({ datalogin: datalogin });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section className="login-block" style={{ 'width': '100%', 'height': '100vh' }}>
|
||||
<div className="container col-md-6 col-lg-5 col-xl-3">
|
||||
<Row>
|
||||
<Col md={4} className="login-sec col-md-12">
|
||||
|
||||
<form id="formLoginCheck">
|
||||
<div className="text-center pb-5" key="1">
|
||||
<a className="m-brand__logo-wrapper">
|
||||
<img alt="" src="/img/BI_Logo.png" style={{ 'width': '200px' }} />
|
||||
</a>
|
||||
</div>
|
||||
<div className="login-form" id="formLogin">
|
||||
<label id="label_check" className={"form-check-label pb-2" + (this.state.error === 1 ? "" : " d-none")}>
|
||||
<div className="text-danger">* Sai email hoặc mật khẩu</div>
|
||||
</label>
|
||||
< div className="form-group">
|
||||
<label htmlFor="exampleInputEmail1" className="text-uppercase">Email</label>
|
||||
<input type="text" name="email" id="exampleInputEmail1" className="form-control" value={this.state.datalogin.email} placeholder="Nhập email..." required onKeyDown={this.onEnterPress} onChange={(e) => {
|
||||
this.HandleLogin(e)
|
||||
}} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="exampleInputPassword1" className="text-uppercase">Mật Khẩu</label>
|
||||
<input type="password" name="password" value={this.state.datalogin.password} className="form-control" placeholder="Nhập mật khẩu..." required onKeyDown={this.onEnterPress} onChange={(e) => {
|
||||
this.HandleLogin(e)
|
||||
}} />
|
||||
</div>
|
||||
<div className="form-check pl-0 text-center pb-5 pt-4">
|
||||
<button className={"btn btn-login m-loader--light m-loader--right " + (this.state.loadingbtn === true && ' m-loader')} disabled={this.state.loadingbtn} id="btnLogin" style={{ 'width': '150px' }} onClick={(event) => {
|
||||
this.CheckLogin(event);
|
||||
}}>Đăng Nhập</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
</section >
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Login;
|
|
@ -30,6 +30,8 @@ const ModalEditLabel = (props) => {
|
|||
|
||||
const [dateImage, setDateImg] = useState("")
|
||||
|
||||
const [dataUpload, setDataUpload] = useState([])
|
||||
|
||||
const [hostImg, setHostImg] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -124,7 +126,7 @@ const ModalEditLabel = (props) => {
|
|||
<div style={{ 'height': 80 }}>
|
||||
{
|
||||
checkDeleteMulti === false ?
|
||||
<img alt="" src={renderImg} onClick={() => { setCrrIdx(index) }} className={"opacity_img_click img_check " + (crrIdx === index ? 'active' : '')} />
|
||||
<img style={{objectFit: "contain"}} alt="" src={renderImg} onClick={() => { setCrrIdx(index) }} className={"opacity_img_click img_check " + (crrIdx === index ? 'active' : '')} />
|
||||
:
|
||||
<label className="m-checkbox img-checkbox m-checkbox-day pl-0 mb-0" style={{ 'width': '100%' }}>
|
||||
<input type="checkbox"
|
||||
|
@ -134,7 +136,7 @@ const ModalEditLabel = (props) => {
|
|||
onClick={e => {
|
||||
handleCheckedImg(e, value)
|
||||
}} readOnly />
|
||||
<img alt="" src={renderImg} className="opacity_img img_check" />
|
||||
<img style={{objectFit: "contain"}} alt="" src={renderImg} className="opacity_img img_check" />
|
||||
<span />
|
||||
</label>
|
||||
}
|
||||
|
@ -143,13 +145,16 @@ const ModalEditLabel = (props) => {
|
|||
)
|
||||
})
|
||||
|
||||
const checkLength= (file,fileList) => {
|
||||
if (crrImages.length + fileList.length > 3) {
|
||||
swal("Cảnh báo", "Bạn chỉ được tải lên tối đa 3 ảnh!", "warning");
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const uploadImage = async (options) => {
|
||||
if (crrImages.length >= 3) {
|
||||
swal("Cảnh báo", "Bạn chỉ được tải lên tối đa 3 ảnh!", "warning");
|
||||
return
|
||||
} else {
|
||||
const { file } = options;
|
||||
const base64 = await convertBase64(file)
|
||||
|
||||
|
@ -158,32 +163,54 @@ const ModalEditLabel = (props) => {
|
|||
base64_image_list: [base64.split(',')[1]]
|
||||
}
|
||||
|
||||
fetch(`${HOST}/api/face_images/famous_person`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// 'Authorization': token
|
||||
},
|
||||
body: JSON.stringify(dataUploadImg)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 10000) {
|
||||
setHostImg(data.image_host)
|
||||
let listImg = [...crrImages]
|
||||
listImg.unshift(data.data.toString())
|
||||
setCrrImages(listImg)
|
||||
setCheckDeleteMulti(false)
|
||||
} else if (data.status === 10003) {
|
||||
if (data.message === "Too many face in image") {
|
||||
swal("Thất bại", "Ảnh có nhiều khuôn mặt!", "error");
|
||||
let promises = [];
|
||||
promises.push(
|
||||
axios
|
||||
.post(`${HOST}/api/face_images/famous_person`, dataUploadImg, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
)
|
||||
await Promise.all(promises)
|
||||
.then((data) => {
|
||||
let success = false
|
||||
let manyFace = false
|
||||
let noFace = false
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const element = data[i];
|
||||
if (element.data.status === 10000) {
|
||||
let listImg = dataUpload
|
||||
setHostImg(element.data.image_host)
|
||||
listImg.unshift(element.data.data.toString())
|
||||
setDataUpload(...dataUpload)
|
||||
success = true
|
||||
} else if (element.data.status === 10003 && element.data.message === "Too many face in image") {
|
||||
manyFace = true
|
||||
} else if (element.data.status === 10003 && element.data.message === "No face in image") {
|
||||
noFace = true
|
||||
} else {
|
||||
swal("Thất bại", "Ảnh không hợp lệ", "error");
|
||||
success = false
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
let originData = crrImages
|
||||
originData.unshift(dataUpload[0].toString())
|
||||
let arrSet = [...new Set(originData)]
|
||||
setCrrImages(arrSet)
|
||||
setCheckDeleteMulti(false)
|
||||
} else if (manyFace) {
|
||||
swal("Thất bại", "Ảnh có nhiều khuôn mặt!", "error");
|
||||
} else if (noFace) {
|
||||
swal("Thất bại", "Ảnh không có khuôn mặt!", "error");
|
||||
} else {
|
||||
swal("Thất bại", "Lỗi hệ thống!", "error");
|
||||
}
|
||||
})
|
||||
}
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
@ -260,7 +287,7 @@ const ModalEditLabel = (props) => {
|
|||
<div className="row justify-content-center pb-3">
|
||||
{
|
||||
crrImages[crrIdx] ?
|
||||
<Avatar shape="square" src={crrImages[crrIdx].includes("data:image") ? crrImages[crrIdx] : hostImg +dateImage+"/"+ crrImages[crrIdx]} size={180} icon={<UserOutlined />} /> :
|
||||
<img alt="" style={{objectFit: "contain", width: "180px", height: "180px"}} src={crrImages[crrIdx].includes("data:image") ? crrImages[crrIdx] : hostImg +dateImage+"/"+ crrImages[crrIdx]} size={180} icon={<UserOutlined />} /> :
|
||||
<Avatar shape="square" size={180} icon={<UserOutlined />} />
|
||||
}
|
||||
|
||||
|
@ -323,11 +350,14 @@ const ModalEditLabel = (props) => {
|
|||
<Upload
|
||||
customRequest = {uploadImage}
|
||||
accept="image/*"
|
||||
multiple={true}
|
||||
beforeUpload={checkLength}
|
||||
showUploadList={false}
|
||||
disabled={disableBtn}
|
||||
>
|
||||
<ButtonAntd className={`${disableBtn ? "disable-btn-upload" : ""}`} icon={<UploadOutlined />}>Tải ảnh lên</ButtonAntd>
|
||||
</Upload>
|
||||
{!disableBtn && <div className="pl-2 text-danger d-flex align-items-center">* Giới hạn 3 ảnh</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ const Modaledit = (props) => {
|
|||
|
||||
const [hostImg, setHostImg] = useState('');
|
||||
|
||||
const [dataUpload, setDataUpload] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
setCrrData(data);
|
||||
setCrrImages(data.sample_images)
|
||||
|
@ -124,7 +126,7 @@ const Modaledit = (props) => {
|
|||
<div style={{ 'height': 80 }}>
|
||||
{
|
||||
checkDeleteMulti === false ?
|
||||
<img alt="" src={renderImg} onClick={() => { setCrrIdx(index) }} className={"opacity_img_click img_check " + (crrIdx === index ? 'active' : '')} />
|
||||
<img style={{objectFit: "contain"}} alt="" src={renderImg} onClick={() => { setCrrIdx(index) }} className={"opacity_img_click img_check " + (crrIdx === index ? 'active' : '')} />
|
||||
:
|
||||
<label className="m-checkbox img-checkbox m-checkbox-day pl-0 mb-0" style={{ 'width': '100%' }}>
|
||||
<input type="checkbox"
|
||||
|
@ -134,7 +136,7 @@ const Modaledit = (props) => {
|
|||
onClick={e => {
|
||||
handleCheckedImg(e, value)
|
||||
}} readOnly />
|
||||
<img alt="" src={renderImg} className="opacity_img img_check" />
|
||||
<img style={{objectFit: "contain"}} alt="" src={renderImg} className="opacity_img img_check" />
|
||||
<span />
|
||||
</label>
|
||||
}
|
||||
|
@ -143,13 +145,14 @@ const Modaledit = (props) => {
|
|||
)
|
||||
})
|
||||
|
||||
|
||||
const checkLength= (file,fileList) => {
|
||||
if (crrImages.length + fileList.length > 3) {
|
||||
swal("Cảnh báo", "Bạn chỉ được tải lên tối đa 3 ảnh!", "warning");
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const uploadImage = async (options) => {
|
||||
if (crrImages.length >= 3) {
|
||||
swal("Cảnh báo", "Bạn chỉ được tải lên tối đa 3 ảnh!", "warning");
|
||||
return
|
||||
} else {
|
||||
const { file } = options;
|
||||
const base64 = await convertBase64(file)
|
||||
|
||||
|
@ -158,32 +161,54 @@ const Modaledit = (props) => {
|
|||
base64_image_list: [base64.split(',')[1]]
|
||||
}
|
||||
|
||||
fetch(`${HOST}/api/face_images/famous_person`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// 'Authorization': token
|
||||
},
|
||||
body: JSON.stringify(dataUploadImg)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 10000) {
|
||||
setHostImg(data.image_host)
|
||||
let listImg = [...crrImages]
|
||||
listImg.unshift(data.data.toString())
|
||||
setCrrImages(listImg)
|
||||
setCheckDeleteMulti(false)
|
||||
} else if (data.status === 10003) {
|
||||
if (data.message === "Too many face in image") {
|
||||
swal("Thất bại", "Ảnh có nhiều khuôn mặt!", "error");
|
||||
let promises = [];
|
||||
promises.push(
|
||||
axios
|
||||
.post(`${HOST}/api/face_images/famous_person`, dataUploadImg, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
)
|
||||
await Promise.all(promises)
|
||||
.then((data) => {
|
||||
let success = false
|
||||
let manyFace = false
|
||||
let noFace = false
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const element = data[i];
|
||||
if (element.data.status === 10000) {
|
||||
let listImg = dataUpload
|
||||
setHostImg(element.data.image_host)
|
||||
listImg.unshift(element.data.data.toString())
|
||||
setDataUpload(...dataUpload)
|
||||
success = true
|
||||
} else if (element.data.status === 10003 && element.data.message === "Too many face in image") {
|
||||
manyFace = true
|
||||
} else if (element.data.status === 10003 && element.data.message === "No face in image") {
|
||||
noFace = true
|
||||
} else {
|
||||
swal("Thất bại", "Ảnh không hợp lệ", "error");
|
||||
success = false
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
let originData = crrImages
|
||||
originData.unshift(dataUpload[0].toString())
|
||||
let arrSet = [...new Set(originData)]
|
||||
setCrrImages(arrSet)
|
||||
setCheckDeleteMulti(false)
|
||||
} else if (manyFace) {
|
||||
swal("Thất bại", "Ảnh có nhiều khuôn mặt!", "error");
|
||||
} else if (noFace) {
|
||||
swal("Thất bại", "Ảnh không có khuôn mặt!", "error");
|
||||
} else {
|
||||
swal("Thất bại", "Lỗi hệ thống!", "error");
|
||||
}
|
||||
})
|
||||
}
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
@ -260,8 +285,8 @@ const Modaledit = (props) => {
|
|||
<div className="row justify-content-center pb-3">
|
||||
{
|
||||
crrImages[crrIdx] ?
|
||||
<Avatar shape="square" src={crrImages[crrIdx].includes("data:image") ? crrImages[crrIdx] : hostImg +dateImage+"/"+ crrImages[crrIdx]} size={180} icon={<UserOutlined />} /> :
|
||||
<Avatar shape="square" size={180} icon={<UserOutlined />} />
|
||||
<img alt="" style={{objectFit: "contain", width: "180px", height: "180px"}} src={crrImages[crrIdx].includes("data:image") ? crrImages[crrIdx] : hostImg +dateImage+"/"+ crrImages[crrIdx]} size={180} icon={<UserOutlined />} /> :
|
||||
<Avatar shape="square" size={180} icon={<UserOutlined />} />
|
||||
}
|
||||
|
||||
</div>
|
||||
|
@ -323,11 +348,14 @@ const Modaledit = (props) => {
|
|||
<Upload
|
||||
customRequest = {uploadImage}
|
||||
accept="image/*"
|
||||
multiple={true}
|
||||
beforeUpload={checkLength}
|
||||
showUploadList={false}
|
||||
disabled={disableBtn}
|
||||
>
|
||||
<ButtonAntd className={`${disableBtn ? "disable-btn-upload" : ""}`} icon={<UploadOutlined />}>Tải ảnh lên</ButtonAntd>
|
||||
</Upload>
|
||||
{!disableBtn && <div className="pl-2 text-danger d-flex align-items-center">* Giới hạn 3 ảnh</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
117
app/src/components/Modal/ModalPassword.js
Normal file
117
app/src/components/Modal/ModalPassword.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
import { Form, Input } from 'antd';
|
||||
import axios from 'axios';
|
||||
import 'moment/locale/vi';
|
||||
import React from 'react';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
import swal from 'sweetalert';
|
||||
import { HOST } from '../../config/index';
|
||||
|
||||
const ModalPassword = (props) => {
|
||||
const { show, onHide, obj_id } = props;
|
||||
|
||||
const [form] = Form.useForm()
|
||||
|
||||
const click_handle = async () => {
|
||||
let dataPost = {password: form.getFieldValue('password')}
|
||||
console.log(dataPost, obj_id)
|
||||
const result = await axios.patch(`${HOST}/api/users/${obj_id}/password`, { password: form.getFieldValue('password') })
|
||||
if (result.data.status === 10000) {
|
||||
swal({
|
||||
icon: 'success',
|
||||
title: 'Thành công',
|
||||
text: 'Thay đổi mật khẩu thành công',
|
||||
timer: 1500,
|
||||
buttons: false,
|
||||
})
|
||||
onHide()
|
||||
} else if (result.data.status === 10002) {
|
||||
swal("Thất bại", "Lỗi hệ thống!", "error");
|
||||
} else {
|
||||
swal("Thất bại", "Thay đổi mật khẩu thất bại!", "error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
animation={false}
|
||||
size="md"
|
||||
// keyboard={false}
|
||||
// dialogClassName={`${window.innerWidth >= 1920 ? "modal-size-res" : "modal-size"}`}
|
||||
aria-labelledby="contained-modal-title-vcenter"
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title id="contained-modal-title-vcenter">Đổi mật khẩu
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body>
|
||||
<div id="formUpdateMeeting">
|
||||
<div className="m-widget14 p-0">
|
||||
<div className="row">
|
||||
<div className="col-md-12 d-flex flex-column justify-content-between">
|
||||
<Form
|
||||
id="formEdit"
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={() => click_handle()}
|
||||
autoComplete="off"
|
||||
initialValues={{
|
||||
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="password"
|
||||
label="Mật khẩu mới"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="confirm"
|
||||
label="Xác nhận mật khẩu"
|
||||
dependencies={['password']}
|
||||
hasFeedback
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || getFieldValue('password') === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('Mật khẩu không khớp'));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="accent" className={"m-loader--light m-loader--right "}
|
||||
// disabled={loading}
|
||||
onClick={() => form.submit()}
|
||||
>Lưu</Button>
|
||||
<Button variant="secondary" onClick={onHide}>Đóng</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalPassword;
|
0
app/src/components/Modal/ModalRole.js
Normal file
0
app/src/components/Modal/ModalRole.js
Normal file
298
app/src/components/Modal/ModalUser.js
Normal file
298
app/src/components/Modal/ModalUser.js
Normal file
|
@ -0,0 +1,298 @@
|
|||
import { UploadOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Button as ButtonAntd, DatePicker, Form, Input, Radio, Upload, Select,Switch } from 'antd';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import viVN from 'antd/lib/locale/vi_VN';
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/vi';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
import swal from 'sweetalert';
|
||||
import { HOST } from '../../config/index';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
||||
const ModalUser = (props) => {
|
||||
const { show, onHide, data } = props;
|
||||
const [crrImages, setCrrImages] = useState([]);
|
||||
|
||||
const [form] = Form.useForm()
|
||||
|
||||
|
||||
const [birthday, setBirthday] = useState(moment())
|
||||
const [crrData, setCrrData] = useState(null);
|
||||
|
||||
const [selectedRole, setSelectedRole] = useState([])
|
||||
const [active, setActive] = useState(false)
|
||||
|
||||
const [checkDeleteMulti, setCheckDeleteMulti] = useState(false);
|
||||
const [crrIdx, setCrrIdx] = useState(0)
|
||||
const [listChecked, setListChecked] = useState({ url: [] });
|
||||
|
||||
const [disableBtn, setDisableBtn] = useState(true);
|
||||
|
||||
const [dateImage, setDateImg] = useState("")
|
||||
|
||||
const [dataUpload, setDataUpload] = useState([])
|
||||
|
||||
const [hostImg, setHostImg] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
setCrrData(data);
|
||||
console.log(data)
|
||||
// setCrrImages(data.sample_images)
|
||||
// setBirthday(data.birthday !== "" ? moment(data.birthday) : null)
|
||||
// setDisableBtn(data._id ? false : true)
|
||||
// setHostImg(data.image_host)
|
||||
// return () => {
|
||||
// setCrrData(null);
|
||||
// }
|
||||
}, [data]);
|
||||
|
||||
|
||||
// const handleCheckedImg = (event, value) => {
|
||||
// let newListChecked = { ...listChecked }
|
||||
// if (newListChecked.url.indexOf(event.target.value) === -1) {
|
||||
// newListChecked.url.push(event.target.value)
|
||||
// } else {
|
||||
// var i = newListChecked.url.indexOf(event.target.value);
|
||||
// if (i !== -1) {
|
||||
// newListChecked.url.splice(i, 1);
|
||||
// }
|
||||
// }
|
||||
// setListChecked(newListChecked)
|
||||
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// if (crrImages.length > 0) {
|
||||
// let crrDateImg = crrImages[crrIdx]
|
||||
// let getDateImg = crrDateImg !== "" && crrDateImg.split("_")
|
||||
// let dataImg = getDateImg.length > 0 && getDateImg[1].slice(0,6)
|
||||
// setDateImg(dataImg)
|
||||
// }
|
||||
// },[crrImages,crrIdx])
|
||||
|
||||
const click_handle = async () => {
|
||||
let dataPost = {
|
||||
obj_id: crrData?._id ? crrData._id : "",
|
||||
username: crrData.username,
|
||||
full_name: crrData.full_name,
|
||||
password: crrData.password,
|
||||
is_deleted: 0,
|
||||
birthday: crrData.birthday,
|
||||
gender: crrData?.gender ? crrData.gender : "",
|
||||
phone_number: crrData?.phone_number ? crrData?.phone_number : "",
|
||||
role_id_list: selectedRole
|
||||
}
|
||||
const result = await axios({
|
||||
method: 'POST',
|
||||
url: `${HOST}/api/users/insert_or_update`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// 'Authorization': token,
|
||||
},
|
||||
data: dataPost,
|
||||
})
|
||||
if (result.data.status === 10000) {
|
||||
if (crrData._id) {
|
||||
swal({
|
||||
icon: 'success',
|
||||
title: 'Thành công',
|
||||
text: 'Sửa thông tin thành công',
|
||||
timer: 1500,
|
||||
buttons: false,
|
||||
})
|
||||
} else {
|
||||
swal({
|
||||
icon: 'success',
|
||||
title: 'Thành công',
|
||||
text: 'Thêm mới thành công',
|
||||
timer: 1500,
|
||||
buttons: false,
|
||||
})
|
||||
onHide()
|
||||
}
|
||||
} else if (result.data.status === 10002) {
|
||||
swal("Thất bại", "Lỗi hệ thống!", "error");
|
||||
} else if (result.data.status === 10004) {
|
||||
swal("Thất bại", "Tài khoản đã tồn tại!", "error");
|
||||
} else if (result.data.status === 10003) {
|
||||
swal("Thất bại", "Tài khoản không được để trống!", "error");
|
||||
} else {
|
||||
if (crrData._id) {
|
||||
swal("Thất bại", "Sửa thông tin thất bại!", "error");
|
||||
} else {
|
||||
swal("Thất bại", "Thêm mới thất bại!", "error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const UserHandle = (e) => {
|
||||
setCrrData({ ...crrData, [e.target.name]: e.target.value })
|
||||
}
|
||||
|
||||
const onChangeBirthday = (date) => {
|
||||
setBirthday(date)
|
||||
setCrrData({ ...crrData, birthday: moment(date).format("YYYY-MM-DD") })
|
||||
}
|
||||
|
||||
const listOptions = [];
|
||||
for (let i = 10; i < 36; i++) {
|
||||
listOptions.push(<Option key={i.toString(36) + i + "key"}>{i.toString(36) + i + "value"}</Option>);
|
||||
}
|
||||
|
||||
const handleChangeSelect = (value) => {
|
||||
setSelectedRole(value)
|
||||
}
|
||||
|
||||
const onChangeActive = (value) => {
|
||||
setActive(value)
|
||||
}
|
||||
|
||||
if (!crrData) return <></>;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
animation={false}
|
||||
size="md"
|
||||
// keyboard={false}
|
||||
// dialogClassName={`${window.innerWidth >= 1920 ? "modal-size-res" : "modal-size"}`}
|
||||
aria-labelledby="contained-modal-title-vcenter"
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title id="contained-modal-title-vcenter">{crrData?._id ? "Sửa thông tin" : "Thêm mới"}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body>
|
||||
<div id="formUpdateMeeting">
|
||||
<div className="m-widget14 p-0">
|
||||
<div className="row">
|
||||
<div className="col-md-12 d-flex flex-column justify-content-between">
|
||||
<Form
|
||||
id="formEdit"
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={() => click_handle()}
|
||||
// onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
initialValues={{
|
||||
full_name: crrData?.full_name,
|
||||
gender: crrData?.gender,
|
||||
username: crrData?.username,
|
||||
role_id_list: crrData?.role_id_list,
|
||||
phone_number: crrData?.phone_number
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="Tên đăng nhập"
|
||||
name="username"
|
||||
rules={[{
|
||||
required: true,
|
||||
message: ""
|
||||
},{
|
||||
pattern: /^(?=.{5,20}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+$/,
|
||||
message: 'Tên đăng nhập có độ dài từ 5-20 ký tự,không có khoảng trắng và ký tự đặc biệt',
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input autoComplete="new-username" placeholder="Tên đăng nhập" value={crrData?.name} onChange={e => UserHandle(e)} name='username' />
|
||||
</Form.Item>
|
||||
{!crrData?._id &&
|
||||
<Form.Item
|
||||
label="Mật khẩu"
|
||||
name="password"
|
||||
rules={[{ required: true, message: '' }]}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder="Mật khẩu" value={crrData?.password} onChange={e => UserHandle(e)} name='password' />
|
||||
</Form.Item>
|
||||
}
|
||||
|
||||
<Form.Item
|
||||
label="Họ tên"
|
||||
name="full_name"
|
||||
rules={[{ required: true, message: '' }]}
|
||||
>
|
||||
<Input placeholder="Họ tên" value={crrData?.full_name} onChange={e => UserHandle(e)} name='full_name' />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Số điện thoại"
|
||||
name="phone_number"
|
||||
>
|
||||
<Input placeholder="Điện thoại" value={crrData?.name} onChange={e => UserHandle(e)} name='phone_number' />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="radio-group" label="Giới tính">
|
||||
<Radio.Group name="gender" onChange={e => UserHandle(e)} defaultValue={crrData?.gender}>
|
||||
<Radio name="gender" value={"1"}>Nam</Radio>
|
||||
<Radio name="gender" value={"2"}>Nữ</Radio>
|
||||
<Radio name="gender" value={"3"}>Giới tính khác</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Ngày sinh"
|
||||
name="birthday"
|
||||
>
|
||||
<DatePicker className="form-control m-input"
|
||||
locale={viVN}
|
||||
defaultValue={crrData?.birthday ? moment(birthday, 'DD-MM-YYYY') : null}
|
||||
format={'DD-MM-YYYY'}
|
||||
onChange={e => onChangeBirthday(e)}
|
||||
placeholder="Ngày sinh"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* <Form.Item
|
||||
label="Trạng thái"
|
||||
name="is_deleted"
|
||||
>
|
||||
<Switch defaultChecked={active} onChange={onChangeActive} />
|
||||
</Form.Item> */}
|
||||
|
||||
<Form.Item
|
||||
label="Quyền"
|
||||
name="role_id_list"
|
||||
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
style={{ width: '100%' }}
|
||||
placeholder="Chọn quyền"
|
||||
defaultValue={[]}
|
||||
onChange={handleChangeSelect}
|
||||
>
|
||||
{listOptions}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
</Form>
|
||||
{/* <div className="row d-flex justify-content-end mr-1">
|
||||
<Button variant="accent" className={"m-loader--light m-loader--right "}
|
||||
// disabled={loading}
|
||||
onClick={() => form.submit()}
|
||||
>Lưu</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="accent" className={"m-loader--light m-loader--right "}
|
||||
// disabled={loading}
|
||||
onClick={() => form.submit()}
|
||||
>Lưu</Button>
|
||||
<Button variant="secondary" onClick={onHide}>Đóng</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalUser;
|
432
app/src/components/User/User.js
Normal file
432
app/src/components/User/User.js
Normal file
|
@ -0,0 +1,432 @@
|
|||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Tooltip } from 'antd';
|
||||
import axios from 'axios';
|
||||
import ModalUser from '../Modal/ModalUser';
|
||||
import ModalPassword from '../Modal/ModalPassword';
|
||||
import momment from 'moment';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Pagination from "react-js-pagination";
|
||||
import { PulseLoader } from 'react-spinners';
|
||||
import { HOST } from '../../config/index';
|
||||
import swal from 'sweetalert';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import Switch from "react-switch";
|
||||
import Select from "react-select";
|
||||
|
||||
const initialDataPost = {
|
||||
index: 1,
|
||||
item_per_page: 5,
|
||||
search_data: "",
|
||||
is_deleted: -1,
|
||||
}
|
||||
|
||||
export default function User() {
|
||||
|
||||
const [data, setData] = useState(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [showModalPassword, setShowModalPassword] = useState(false)
|
||||
const [dataEdit, setDataEdit] = useState(null)
|
||||
|
||||
|
||||
const [optionSelect, setOptionSelect] = useState([
|
||||
{value: -1, label: "Tất cả"},
|
||||
{value: 0, label:"Hoạt động"},
|
||||
{value: 1, label: "Không hoạt động"},
|
||||
])
|
||||
|
||||
const [valueSelected, setValueSelected] = useState({value: -1, label:"Tất cả"})
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const [activePage, setActivePage] = useState(1)
|
||||
const [totalItems, setTotalItems] = useState(0)
|
||||
|
||||
const [offset, setOffset] = useState(0)
|
||||
|
||||
const [dataPost, setDataPost] = useState(initialDataPost)
|
||||
const itemsPerPage = 5
|
||||
|
||||
const [token, setToken] = useState('');
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
if (location.search) {
|
||||
let path = location.search.replace('?', '').split('&');
|
||||
let obj = {}
|
||||
path.forEach(value => {
|
||||
let arr = value.split('=');
|
||||
obj[arr[0]] = arr[1]
|
||||
})
|
||||
setToken(decodeURIComponent(obj.token))
|
||||
setToken((obj.token))
|
||||
}
|
||||
}, [location])
|
||||
|
||||
useEffect(() => {
|
||||
getData()
|
||||
}, [])
|
||||
|
||||
const onClickEdit = (data) => {
|
||||
setDataEdit(data)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const onClickPassword = (data) => {
|
||||
setDataEdit(data._id)
|
||||
setShowModalPassword(true)
|
||||
}
|
||||
|
||||
const onDelete = async (value) => {
|
||||
try {
|
||||
const result = await axios({
|
||||
method: 'DELETE',
|
||||
url: `${HOST}/api/users/${value._id}`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// 'Authorization': token,
|
||||
},
|
||||
})
|
||||
if (result.data.status === 10000) {
|
||||
if (data.length === 1) {
|
||||
setActivePage(activePage - 1)
|
||||
setDataPost({ ...dataPost, index: activePage - 1 })
|
||||
}
|
||||
swal({
|
||||
icon: 'success',
|
||||
title: 'Thành công',
|
||||
text: 'Xoá thành công',
|
||||
timer: 1500,
|
||||
buttons: false,
|
||||
})
|
||||
getData(dataPost)
|
||||
} else {
|
||||
swal("Thất bại", "Xoá thất bại!", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
FilterItem()
|
||||
}, [activePage])
|
||||
|
||||
const FilterItem = () => {
|
||||
const offset = (activePage - 1) * itemsPerPage;
|
||||
setOffset(offset)
|
||||
}
|
||||
|
||||
const handlePageChange = (pageNumber) => {
|
||||
setActivePage(pageNumber);
|
||||
const data = {
|
||||
...dataPost,
|
||||
index: pageNumber,
|
||||
}
|
||||
getData(data);
|
||||
}
|
||||
|
||||
const getData = async (data) => {
|
||||
try {
|
||||
const result = await axios({
|
||||
method: 'POST',
|
||||
url: `${HOST}/api/users/search`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// 'Authorization': token
|
||||
},
|
||||
data: data ? data : dataPost,
|
||||
})
|
||||
if (result.data.status === 10000) {
|
||||
setData(result.data.data)
|
||||
setTotalItems(result.data.count)
|
||||
setLoading(false)
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleOnKeyDown = e => {
|
||||
if (e.key === 'Enter') {
|
||||
setActivePage(1)
|
||||
getData(dataPost);
|
||||
}
|
||||
}
|
||||
|
||||
const tableRows = (dataAll) => dataAll.map((value, index) => {
|
||||
var gender = ""
|
||||
if (value.gender === "1") {
|
||||
gender = "Nam"
|
||||
} else if (value.gender === "2") {
|
||||
gender = "Nữ"
|
||||
} else if (value.gender === "3") {
|
||||
gender = "Giới tính khác"
|
||||
} else {
|
||||
gender = ""
|
||||
}
|
||||
|
||||
var checked = true;
|
||||
if (value.is_deteted === 1) {
|
||||
checked = true
|
||||
|
||||
}
|
||||
if (value.is_deteted === 0) {
|
||||
checked = false
|
||||
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{(index + offset + 1)}</td>
|
||||
<td>{value.username}</td>
|
||||
<td>{value.full_name}</td>
|
||||
<td>{gender}</td>
|
||||
<td>{value.birthday !== "" ? momment(value.birthday).format("DD-MM-YYYY") : ""}</td>
|
||||
<td>{value.phone_number}</td>
|
||||
<td>
|
||||
{
|
||||
value.is_deleted === 1
|
||||
?
|
||||
<span className="m-badge m-badge--secondary m-badge--wide">Không hoạt động</span>
|
||||
:
|
||||
<span className="m-badge m-badge--accent m-badge--wide">Hoạt động</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<Tooltip placement="top" title={"Sửa"}>
|
||||
<button
|
||||
onClick={() => onClickEdit(value)}
|
||||
className="m-portlet__nav-link btn m-btn m-btn--hover-warning m-btn--icon m-btn--icon-only m-btn--pill" data-tip data-for="Edit" >
|
||||
<i className="la la-edit" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={"Đổi mật khẩu"}>
|
||||
<button
|
||||
onClick={() => onClickPassword(value)}
|
||||
className="m-portlet__nav-link btn m-btn m-btn--hover-warning m-btn--icon m-btn--icon-only m-btn--pill" data-tip data-for="Edit" >
|
||||
<i className="fa flaticon-lock" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={"Xoá"}>
|
||||
<button
|
||||
onClick={f => {
|
||||
f.preventDefault();
|
||||
swal({
|
||||
// title: "Are you sure?",
|
||||
text: "Bạn có chắc muốn xoá " + value.full_name,
|
||||
icon: "warning",
|
||||
buttons: ["Huỷ", "Xoá"],
|
||||
dangerMode: true,
|
||||
})
|
||||
.then(willDelete => {
|
||||
if (willDelete) {
|
||||
onDelete(value);
|
||||
}
|
||||
});
|
||||
}}
|
||||
className="m-portlet__nav-link btn m-btn m-btn--hover-danger m-btn--icon m-btn--icon-only m-btn--pill" data-tip data-for="Edit" >
|
||||
<i className="la la-trash" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
const onCloseModal = () => {
|
||||
setShowModal(false)
|
||||
// setActivePage(activePage)
|
||||
const data = {
|
||||
...dataPost,
|
||||
index: activePage,
|
||||
}
|
||||
getData(data);
|
||||
}
|
||||
|
||||
const changeHandleFilter = (e) => {
|
||||
setDataPost({ ...dataPost, is_deleted: e.value })
|
||||
setValueSelected(e)
|
||||
}
|
||||
const onCloseModalPassword = () => {
|
||||
setShowModalPassword(false)
|
||||
const data = {
|
||||
...dataPost,
|
||||
index: activePage,
|
||||
}
|
||||
getData(data);
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
setActivePage(1)
|
||||
setDataPost({ ...dataPost, search_data: "" })
|
||||
getData(initialDataPost)
|
||||
setValueSelected({ value: -1, label: "Tất cả" })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="m-grid__item m-grid__item--fluid m-wrapper">
|
||||
<div className="m-content p-2">
|
||||
<div className="m-portlet m-portlet--tab mb-0">
|
||||
<div className="m-portlet__head p-3">
|
||||
<div className="m-portlet__head-caption pl-3">
|
||||
<div className="m-portlet__head-title">
|
||||
<h3 className="m-portlet__head-text">
|
||||
Quản lí người dùng
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-portlet__head-tools pr-3">
|
||||
<ul className="m-portlet__nav">
|
||||
<li className="m-portlet__nav-item">
|
||||
{/* {
|
||||
this.state.dataRole.indexOf(this.state.type + '/' + nameTab + ':insert_or_update') !== -1
|
||||
? */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
let defaultData = {
|
||||
birthday: "",
|
||||
// company_id: "1"
|
||||
full_name: "",
|
||||
gender: "1",
|
||||
is_deleted: 0,
|
||||
phone_number: "",
|
||||
role_id_list: [],
|
||||
username: ""
|
||||
}
|
||||
setDataEdit(defaultData)
|
||||
setShowModal(true)
|
||||
}}
|
||||
className="btn btn-accent m-btn m-btn--custom m-btn--icon m-btn--pill m-btn--air">
|
||||
<span>
|
||||
<i className="la la-plus" />
|
||||
<span>Thêm</span>
|
||||
</span>
|
||||
</button>
|
||||
{/* :
|
||||
""
|
||||
} */}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-portlet__body m-portlet__body--no-padding">
|
||||
<div className="row m-row--no-padding m-row--col-separator-xl">
|
||||
<div className="col-xl-12">
|
||||
<div className="m-widget1 col-xl-12 mx-auto">
|
||||
<div className="row col-md-12 ml-0 mr-0 p-0 mb-2">
|
||||
<div className="col-lg-2 pl-0 m--margin-bottom-10-tablet-and-mobile">
|
||||
<input type="text"
|
||||
onKeyPress={(e) => handleOnKeyDown(e)}
|
||||
onChange={(e) => {
|
||||
setDataPost({ ...dataPost, search_data: e.target.value })
|
||||
}}
|
||||
value={dataPost.search_data}
|
||||
id="inputSearch"
|
||||
className="form-control m-input"
|
||||
placeholder="Tên đăng nhập..."
|
||||
data-col-index={0} />
|
||||
</div>
|
||||
<div className="form-group m-form__group col-xl-2">
|
||||
<Select
|
||||
value={valueSelected}
|
||||
onChange={changeHandleFilter}
|
||||
options={optionSelect}
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
setActivePage(1)
|
||||
getData(dataPost)
|
||||
}}
|
||||
className="btn btn-accent m-btn m-btn--icon" id="m_search">
|
||||
<span>
|
||||
<i className="la la-search" />
|
||||
<span>Tìm kiếm</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="pl-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
reset()
|
||||
}}
|
||||
className="btn btn-secondary m-btn m-btn--icon" id="m_reset">
|
||||
<span>
|
||||
<i className="la la-rotate-left" />
|
||||
<span>Tải lại</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="table-responsive text-nowrap">
|
||||
<table className="table table-bordered table-hover table-checkable dataTable no-footer dtr-inline collapsed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ 'verticalAlign': 'middle', 'width': '80px' }}>STT</th>
|
||||
<th style={{ 'verticalAlign': 'middle', 'width': '240px' }}>Tên đăng nhập</th>
|
||||
<th style={{ 'verticalAlign': 'middle', 'width': '280px' }}>Họ tên</th>
|
||||
<th style={{ 'verticalAlign': 'middle' }}>Giới tính</th>
|
||||
<th style={{ 'verticalAlign': 'middle' }}>Ngày sinh</th>
|
||||
<th style={{ 'verticalAlign': 'middle' }}>Số điện thoại</th>
|
||||
<th style={{ 'verticalAlign': 'middle' }}>Trạng thái</th>
|
||||
<th style={{ 'verticalAlign': 'middle', 'width': '150px' }}>Thao tác</th>
|
||||
{/* {
|
||||
this.state.dataRole.indexOf(this.state.type + '/' + nameTab + ':insert_or_update') !== -1
|
||||
||
|
||||
this.state.dataRole.indexOf(this.state.type + '/' + nameTab + ':delete') !== -1
|
||||
?
|
||||
<th style={{ 'verticalAlign': 'middle', 'width': '150px' }}>{language[this.props.indexLanguage].textTable.action}</th>
|
||||
:
|
||||
""
|
||||
} */}
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data && tableRows(data)}
|
||||
</tbody>
|
||||
</table>
|
||||
{/* <PulseLoader
|
||||
css={override}
|
||||
sizeUnit={"px"}
|
||||
size={12}
|
||||
margin={'2px'}
|
||||
color={'#36D7B7'}
|
||||
loading={this.state.loading}
|
||||
/> */}
|
||||
</div>
|
||||
{/* <span>
|
||||
Showing {this.state.showFirst} to {this.state.showLast} of {this.state.totalLength} entries
|
||||
</span> */}
|
||||
{showModal && <ModalUser data={dataEdit} onHide={onCloseModal} show={showModal} />}
|
||||
{showModalPassword && <ModalPassword obj_id={dataEdit} onHide={onCloseModalPassword} show={showModalPassword} />}
|
||||
<Pagination
|
||||
prevPageText='Trang trước'
|
||||
nextPageText='Trang sau'
|
||||
firstPageText='Trang đầu'
|
||||
lastPageText='Trang cuối'
|
||||
activePage={activePage}
|
||||
itemsCountPerPage={itemsPerPage}
|
||||
totalItemsCount={totalItems}
|
||||
pageRangeDisplayed={5}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -61,22 +61,77 @@ class Header extends Component {
|
|||
</div>
|
||||
</div>
|
||||
<div className="m-stack__item m-stack__item--middle m-stack__item--left m-header-head" id="m_header_nav">
|
||||
<div className="m-stack m-stack--ver m-stack--desktop">
|
||||
<div className="m-stack__item m-stack__item--middle m-stack__item--fit">
|
||||
{/* BEGIN: Aside Left Toggle */}
|
||||
<a className="m-aside-left-toggler m-aside-left-toggler--left m_aside_left_toggler" onClick={() => this.onClickOpen()}>
|
||||
<span />
|
||||
</a>
|
||||
{/* END: Aside Left Toggle */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-stack__item m-stack__item--middle m-stack__item--center" style={{ 'width': '160px' }}>
|
||||
<a href="/" className="m-brand m-brand--desktop">
|
||||
{/* <img alt="" src={this.state.logo_link} style={{ 'width': '100%' }} /> */}
|
||||
</a>
|
||||
</div>
|
||||
<div className="m-stack m-stack--ver m-stack--desktop">
|
||||
<div className="m-stack__item m-stack__item--middle m-stack__item--fit">
|
||||
<a className="m-aside-left-toggler m-aside-left-toggler--left m_aside_left_toggler" onClick={() => this.onClickOpen()}>
|
||||
<span />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-stack__item m-stack__item--middle m-stack__item--center" style={{ 'width': '160px' }}>
|
||||
<a href="/" className="m-brand m-brand--desktop">
|
||||
{/* <img alt="" src="/img/BI_Logo.png" className="logo_img" /> */}
|
||||
</a>
|
||||
</div>
|
||||
<div className="m-stack__item m-stack__item--right">
|
||||
<div id="m_header_topbar" className="m-topbar m-stack m-stack--ver m-stack--general width-100">
|
||||
<div className="m-stack__item m-topbar__nav-wrapper">
|
||||
|
||||
<ul className="m-topbar__nav m-nav m-nav--inline width-100 ml-0">
|
||||
<li className="m-nav__item m-topbar__user-profile m-topbar__user-profile--img">
|
||||
<div className="m-nav__link m-dropdown__toggle">
|
||||
<div className="row">
|
||||
{/* <div className="m-topbar__welcome" style={{ 'lineHeight': '50px','color':'#b1b1b5' }}>Company: </div> */}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li className="m-nav__item m-dropdown m-dropdown--medium m-dropdown--arrow m-dropdown--align-right m-dropdown--mobile-full-width m-dropdown--skin-light icon_logout" m-dropdown-toggle="click">
|
||||
<a href="#" className="m-nav__link m-dropdown__toggle">
|
||||
<span className="m-topbar__userpic">
|
||||
<img src="/img/photo-placeholder.png" className="m--img-rounded m--marginless m--img-centered" alt="" />
|
||||
</span>
|
||||
<span className="m-nav__link-icon m-topbar__usericon m--hide">
|
||||
<span className="m-nav__link-icon-wrapper"><i className="flaticon-user-ok" /></span>
|
||||
</span>
|
||||
</a>
|
||||
<div className="m-dropdown__wrapper">
|
||||
<span className="m-dropdown__arrow m-dropdown__arrow--right m-dropdown__arrow--adjust" />
|
||||
<div className="m-dropdown__inner">
|
||||
<div className="m-dropdown__header m--align-center">
|
||||
<div className="m-card-user m-card-user--skin-light">
|
||||
<div className="m-card-user__pic">
|
||||
<img src="/img/photo-placeholder.png" className="m--img-rounded m--marginless" alt="" />
|
||||
</div>
|
||||
{/* <div class="m-card-user__details">
|
||||
<span class="m-card-user__name m--font-weight-500">{this.state.user !== null && this.state.user.first_name + ' ' + this.state.user.last_name}</span>
|
||||
<a href="#/" class="m-card-user__email m--font-weight-300 m-link">{this.state.user !== null && this.state.user.email}</a>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-dropdown__body">
|
||||
<div className="m-dropdown__content">
|
||||
<ul className="m-nav m-nav--skin-light">
|
||||
<li className="m-nav__separator m-nav__separator--fit">
|
||||
</li>
|
||||
<li className="m-nav__item">
|
||||
<a className="btn m-btn--pill btn-secondary m-btn m-btn--custom m-btn--label-brand m-btn--bolder"
|
||||
// onClick={() => {
|
||||
// localStorage.removeItem("access_token");
|
||||
// window.location.href = "/login";
|
||||
// }}
|
||||
>Đăng Xuất</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
@ -78,6 +78,28 @@ class MenuBar extends Component {
|
|||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="m-menu__item m-menu__item--submenu" aria-haspopup="true" m-menu-submenu-toggle="hover">
|
||||
<NavLink to="/user" className={"m-menu__link " + active} activeClassName="m-menu__item--active" onClick={() => this.onClickClose()}>
|
||||
<i className="m-menu__link-icon flaticon-users" />
|
||||
<span className="m-menu__link-title">
|
||||
<span className="m-menu__link-wrap">
|
||||
<span className="m-menu__link-text">Quản lí người dùng</span>
|
||||
</span>
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="m-menu__item m-menu__item--submenu" aria-haspopup="true" m-menu-submenu-toggle="hover">
|
||||
<NavLink to="/role" className={"m-menu__link " + active} activeClassName="m-menu__item--active" onClick={() => this.onClickClose()}>
|
||||
<i className="m-menu__link-icon flaticon-map" />
|
||||
<span className="m-menu__link-title">
|
||||
<span className="m-menu__link-wrap">
|
||||
<span className="m-menu__link-text">Quyền</span>
|
||||
</span>
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="m-menu__item m-menu__item--submenu" aria-haspopup="true" m-menu-submenu-toggle="hover">
|
||||
<NavLink to="/test" className={"m-menu__link " + active} activeClassName="m-menu__item--active" onClick={() => this.onClickClose()}>
|
||||
<i className="m-menu__link-icon flaticon-edit" />
|
||||
|
|
7
app/src/reducers/index.js
Normal file
7
app/src/reducers/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import {combineReducers} from 'redux';
|
||||
import role from './role'
|
||||
import isLogin from './isLogin';
|
||||
export default combineReducers({
|
||||
role,
|
||||
isLogin
|
||||
});
|
23
app/src/reducers/isLogin/index.js
Normal file
23
app/src/reducers/isLogin/index.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import {LOGIN, LOGOUT } from '../../actions/isLogin';
|
||||
|
||||
let access_token = localStorage.getItem('access_token');
|
||||
const initialState = {
|
||||
isLogin: access_token === null ? false : true ,
|
||||
access_token: access_token
|
||||
};
|
||||
export default function isLogin(state = initialState, action) {
|
||||
switch(action.type){
|
||||
case LOGIN:
|
||||
return{
|
||||
isLogin: true,
|
||||
access_token: action.payload.access_token
|
||||
};
|
||||
case LOGOUT:
|
||||
return{
|
||||
isLogin: false,
|
||||
access_token: null
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
15
app/src/reducers/role/index.js
Normal file
15
app/src/reducers/role/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import {ROLE} from '../../actions/role';
|
||||
|
||||
const initialState = {
|
||||
role: null
|
||||
};
|
||||
export default function role(state = initialState, action) {
|
||||
switch(action.type){
|
||||
case ROLE:
|
||||
return{
|
||||
role: action.payload.role
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
3
app/src/store/index.js
Normal file
3
app/src/store/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { createStore } from 'redux';
|
||||
import reducers from '../reducers';
|
||||
export default createStore(reducers);
|
|
@ -1049,7 +1049,7 @@
|
|||
core-js-pure "^3.20.2"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
|
||||
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
|
||||
|
@ -1849,6 +1849,14 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.0":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/html-minifier-terser@^6.0.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
|
||||
|
@ -1948,6 +1956,16 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-redux@^7.1.20":
|
||||
version "7.1.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.22.tgz#0eab76a37ef477cc4b53665aeaf29cb60631b72a"
|
||||
integrity sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-transition-group@^4.4.0":
|
||||
version "4.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e"
|
||||
|
@ -5151,7 +5169,7 @@ history@^4.9.0:
|
|||
tiny-warning "^1.0.0"
|
||||
value-equal "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1:
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
|
@ -8503,7 +8521,7 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.3.2, react-is@^16.6.0, react-
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^17.0.1:
|
||||
react-is@^17.0.1, react-is@^17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
@ -8566,6 +8584,18 @@ react-property@2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136"
|
||||
integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
|
||||
|
||||
react-redux@^7.2.6:
|
||||
version "7.2.6"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa"
|
||||
integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
"@types/react-redux" "^7.1.20"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
|
||||
react-refresh@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
|
||||
|
@ -8675,6 +8705,13 @@ react-spinners@^0.11.0:
|
|||
dependencies:
|
||||
"@emotion/react" "^11.1.4"
|
||||
|
||||
react-switch@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-switch/-/react-switch-6.0.0.tgz#bd4a2dea08f211b8a32e55e8314fd44bc1ec947e"
|
||||
integrity sha512-QV3/6eRK5/5epdQzIqvDAHRoGLbCv/wDpHUi6yBMXY1Xco5XGuIZxvB49PHoV1v/SpEgOCJLD/Zo43iic+aEIw==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-transition-group@^2.5.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
|
||||
|
@ -8775,6 +8812,13 @@ redent@^3.0.0:
|
|||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
redux@^4.0.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
|
||||
integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
|
||||
regenerate-unicode-properties@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326"
|
||||
|
|
Loading…
Reference in New Issue
Block a user