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:
huyt 2022-01-14 05:01:06 +00:00
commit c9780d4a31
20 changed files with 1457 additions and 218 deletions

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -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;
}

View File

@ -3,28 +3,31 @@ 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>
<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} />
@ -34,7 +37,7 @@ function App() {
</Switch>
</Router>
</div>
</Provider>
);
}

View 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
}
}

View File

@ -0,0 +1,9 @@
export const ROLE = 'ROLE';
export function role(role){
return {
type: ROLE,
payload:{
role: role
}
}
}

View File

@ -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,
class Login extends Component {
constructor(props) {
super(props);
this.state = {
error: 0,
loadingbtn: false,
datalogin: {
email: "",
password: "",
}
}
localStorage.clear();
}
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')
CheckLogin = async (event) => {
var form = $("#formLoginCheck")
if (form[0].checkValidity() === false) {
event.preventDefault()
event.stopPropagation()
form.addClass('was-validated')
} else {
setError('Sai tài khoản hoặc mật khẩu');
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");
}
})
.catch(err => {
setError('Vui lòng kiểm tra lại đường truyền mạng')
console.error(err);
}
})
setLoading(false);
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
}
}
onEnterPress = (e) => {
if (e.keyCode === 13 && e.shiftKey === false) {
e.preventDefault();
this.CheckLogin(e);
}
}
HandleLogin = (e) => {
var datalogin = this.state.datalogin;
datalogin[e.target.name] = e.target.value.trim();
this.setState({ datalogin: datalogin });
}
render() {
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>
</div>
<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>
);
</section >
)
}
}
export default Login;

View File

@ -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',
let promises = [];
promises.push(
axios
.post(`${HOST}/api/face_images/famous_person`, dataUploadImg, {
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");
)
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>

View File

@ -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',
let promises = [];
promises.push(
axios
.post(`${HOST}/api/face_images/famous_person`, dataUploadImg, {
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");
)
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 +285,7 @@ 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 />} /> :
<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 +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>

View 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;

View File

View 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;

View 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 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>
)
}

View File

@ -63,20 +63,75 @@ class Header extends Component {
<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%' }} /> */}
{/* <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>

View File

@ -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 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" />

View File

@ -0,0 +1,7 @@
import {combineReducers} from 'redux';
import role from './role'
import isLogin from './isLogin';
export default combineReducers({
role,
isLogin
});

View 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;
}
}

View 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
View File

@ -0,0 +1,3 @@
import { createStore } from 'redux';
import reducers from '../reducers';
export default createStore(reducers);

View File

@ -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"