diff --git a/app/package.json b/app/package.json
index eba885a..666c029 100644
--- a/app/package.json
+++ b/app/package.json
@@ -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"
},
diff --git a/app/public/img/BI_Logo.png b/app/public/img/BI_Logo.png
new file mode 100644
index 0000000..4a5f4f2
Binary files /dev/null and b/app/public/img/BI_Logo.png differ
diff --git a/app/src/App.css b/app/src/App.css
index 611c57d..72768df 100644
--- a/app/src/App.css
+++ b/app/src/App.css
@@ -365,4 +365,111 @@ button.close:hover {
height: 60px;
}
+}
+
+.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;
}
\ No newline at end of file
diff --git a/app/src/App.js b/app/src/App.js
index a85cc1a..511cda2 100644
--- a/app/src/App.js
+++ b/app/src/App.js
@@ -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 (
-
+
);
}
diff --git a/app/src/actions/isLogin/index.js b/app/src/actions/isLogin/index.js
new file mode 100644
index 0000000..a78075a
--- /dev/null
+++ b/app/src/actions/isLogin/index.js
@@ -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
+ }
+}
diff --git a/app/src/actions/role/index.js b/app/src/actions/role/index.js
new file mode 100644
index 0000000..aaa42f5
--- /dev/null
+++ b/app/src/actions/role/index.js
@@ -0,0 +1,9 @@
+export const ROLE = 'ROLE';
+export function role(role){
+ return {
+ type: ROLE,
+ payload:{
+ role: role
+ }
+ }
+}
diff --git a/app/src/components/Login/Login.js b/app/src/components/Login/Login.js
index c18c8ac..e4b109d 100644
--- a/app/src/components/Login/Login.js
+++ b/app/src/components/Login/Login.js
@@ -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);
+ }
+ }
+ HandleLogin = (e) => {
+ var datalogin = this.state.datalogin;
+ datalogin[e.target.name] = e.target.value.trim();
+ this.setState({ datalogin: datalogin });
+ }
- return (
-
-
-
-
Đăng nhập
-
- }
- placeholder="Email" />
-
-
- }
- type="password"
- placeholder="Password"
- />
-
-
- {error && * Sai tài khoản mật khẩu }
-
-
-
-
-
+ render() {
+ return (
+
-
-
-
-
- );
+
+ )
+ }
}
-
-export default Login;
\ No newline at end of file
+export default Login;
diff --git a/app/src/components/Modal/ModaEditLabel.js b/app/src/components/Modal/ModaEditLabel.js
index 9cc403f..9f4e9c0 100644
--- a/app/src/components/Modal/ModaEditLabel.js
+++ b/app/src/components/Modal/ModaEditLabel.js
@@ -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) => {
{
checkDeleteMulti === false ?
-

{ setCrrIdx(index) }} className={"opacity_img_click img_check " + (crrIdx === index ? 'active' : '')} />
+

{ setCrrIdx(index) }} className={"opacity_img_click img_check " + (crrIdx === index ? 'active' : '')} />
:
}
@@ -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");
- } else {
- swal("Thất bại", "Ảnh không hợp lệ", "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 {
+ 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) => {
{
crrImages[crrIdx] ?
-
} /> :
+
)
} /> :
} />
}
@@ -323,11 +350,14 @@ const ModalEditLabel = (props) => {
}>Tải ảnh lên
+ {!disableBtn &&
* Giới hạn 3 ảnh
}
diff --git a/app/src/components/Modal/ModalEdit.js b/app/src/components/Modal/ModalEdit.js
index 2bb8b0b..1cba733 100644
--- a/app/src/components/Modal/ModalEdit.js
+++ b/app/src/components/Modal/ModalEdit.js
@@ -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) => {
{
checkDeleteMulti === false ?
-

{ setCrrIdx(index) }} className={"opacity_img_click img_check " + (crrIdx === index ? 'active' : '')} />
+

{ setCrrIdx(index) }} className={"opacity_img_click img_check " + (crrIdx === index ? 'active' : '')} />
:
}
@@ -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");
- } else {
- swal("Thất bại", "Ảnh không hợp lệ", "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 {
+ 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) => {
{
crrImages[crrIdx] ?
-
} /> :
-
} />
+
)
} /> :
+
} />
}
@@ -323,11 +348,14 @@ const Modaledit = (props) => {
}>Tải ảnh lên
+ {!disableBtn &&
* Giới hạn 3 ảnh
}
diff --git a/app/src/components/Modal/ModalPassword.js b/app/src/components/Modal/ModalPassword.js
new file mode 100644
index 0000000..12d05bf
--- /dev/null
+++ b/app/src/components/Modal/ModalPassword.js
@@ -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 (
+ = 1920 ? "modal-size-res" : "modal-size"}`}
+ aria-labelledby="contained-modal-title-vcenter"
+ >
+
+ Đổi mật khẩu
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ModalPassword;
diff --git a/app/src/components/Modal/ModalRole.js b/app/src/components/Modal/ModalRole.js
new file mode 100644
index 0000000..e69de29
diff --git a/app/src/components/Modal/ModalUser.js b/app/src/components/Modal/ModalUser.js
new file mode 100644
index 0000000..adf1e3b
--- /dev/null
+++ b/app/src/components/Modal/ModalUser.js
@@ -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();
+ }
+
+ const handleChangeSelect = (value) => {
+ setSelectedRole(value)
+ }
+
+ const onChangeActive = (value) => {
+ setActive(value)
+ }
+
+ if (!crrData) return <>>;
+
+ return (
+ = 1920 ? "modal-size-res" : "modal-size"}`}
+ aria-labelledby="contained-modal-title-vcenter"
+ >
+
+ {crrData?._id ? "Sửa thông tin" : "Thêm mới"}
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ModalUser;
diff --git a/app/src/components/User/User.js b/app/src/components/User/User.js
new file mode 100644
index 0000000..23e2711
--- /dev/null
+++ b/app/src/components/User/User.js
@@ -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 (
+
+ {(index + offset + 1)} |
+ {value.username} |
+ {value.full_name} |
+ {gender} |
+ {value.birthday !== "" ? momment(value.birthday).format("DD-MM-YYYY") : ""} |
+ {value.phone_number} |
+
+ {
+ value.is_deleted === 1
+ ?
+ Không hoạt động
+ :
+ Hoạt động
+ }
+ |
+
+
+
+
+
+
+
+
+
+
+ |
+
+ )
+ })
+
+
+ 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 (
+
+
+
+
+
+
+
+ Quản lí người dùng
+
+
+
+
+
+ -
+ {/* {
+ this.state.dataRole.indexOf(this.state.type + '/' + nameTab + ':insert_or_update') !== -1
+ ? */}
+
+ {/* :
+ ""
+ } */}
+
+
+
+
+
+
+
+
+
+
+ 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} />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ STT |
+ Tên đăng nhập |
+ Họ tên |
+ Giới tính |
+ Ngày sinh |
+ Số điện thoại |
+ Trạng thái |
+ Thao tác |
+ {/* {
+ this.state.dataRole.indexOf(this.state.type + '/' + nameTab + ':insert_or_update') !== -1
+ ||
+ this.state.dataRole.indexOf(this.state.type + '/' + nameTab + ':delete') !== -1
+ ?
+ {language[this.props.indexLanguage].textTable.action} |
+ :
+ ""
+ } */}
+
+
+
+
+ {data && tableRows(data)}
+
+
+ {/*
*/}
+
+ {/*
+ Showing {this.state.showFirst} to {this.state.showLast} of {this.state.totalLength} entries
+ */}
+ {showModal &&
}
+ {showModalPassword &&
}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/app/src/components/layouts/Header.js b/app/src/components/layouts/Header.js
index 84a82df..34c47d6 100644
--- a/app/src/components/layouts/Header.js
+++ b/app/src/components/layouts/Header.js
@@ -6,11 +6,11 @@ class Header extends Component {
constructor(props) {
super(props);
this.state = {
-
+
};
-
+
}
-
+
onClickOpen = () => {
$('#m_aside_left').addClass('m-aside-left--on')
@@ -30,11 +30,11 @@ class Header extends Component {
)
}
componentDidUpdate(prevProps, prevState) {
-
+
}
render() {
-
+
return (
diff --git a/app/src/components/layouts/MenuBar.js b/app/src/components/layouts/MenuBar.js
index 40f37a8..29728eb 100644
--- a/app/src/components/layouts/MenuBar.js
+++ b/app/src/components/layouts/MenuBar.js
@@ -78,6 +78,28 @@ class MenuBar extends Component {
+
+ this.onClickClose()}>
+
+
+
+ Quản lí người dùng
+
+
+
+
+
+
+ this.onClickClose()}>
+
+
+
+ Quyền
+
+
+
+
+
this.onClickClose()}>
diff --git a/app/src/reducers/index.js b/app/src/reducers/index.js
new file mode 100644
index 0000000..f658bdb
--- /dev/null
+++ b/app/src/reducers/index.js
@@ -0,0 +1,7 @@
+import {combineReducers} from 'redux';
+import role from './role'
+import isLogin from './isLogin';
+export default combineReducers({
+ role,
+ isLogin
+});
\ No newline at end of file
diff --git a/app/src/reducers/isLogin/index.js b/app/src/reducers/isLogin/index.js
new file mode 100644
index 0000000..285511c
--- /dev/null
+++ b/app/src/reducers/isLogin/index.js
@@ -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;
+ }
+}
diff --git a/app/src/reducers/role/index.js b/app/src/reducers/role/index.js
new file mode 100644
index 0000000..b0a6a58
--- /dev/null
+++ b/app/src/reducers/role/index.js
@@ -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;
+ }
+}
diff --git a/app/src/store/index.js b/app/src/store/index.js
new file mode 100644
index 0000000..058ef0a
--- /dev/null
+++ b/app/src/store/index.js
@@ -0,0 +1,3 @@
+import { createStore } from 'redux';
+import reducers from '../reducers';
+export default createStore(reducers);
diff --git a/app/yarn.lock b/app/yarn.lock
index 90fa3f3..1cec3e3 100644
--- a/app/yarn.lock
+++ b/app/yarn.lock
@@ -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"