feat(): restructure
This commit is contained in:
278
app/src/App.css
Normal file
278
app/src/App.css
Normal file
@@ -0,0 +1,278 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-size {
|
||||
min-width: 50%
|
||||
}
|
||||
|
||||
.modal-size-res {
|
||||
min-width: 40%
|
||||
}
|
||||
|
||||
.ant-upload-list-picture-card-container, .ant-upload.ant-upload-select-picture-card{
|
||||
width: 95px!important;
|
||||
height: 95px!important;
|
||||
}
|
||||
|
||||
|
||||
/* custome andt upload */
|
||||
.ant-upload-list-item-actions .anticon-eye,.ant-upload-list-item-actions .ant-upload-list-item-card-actions-btn{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-upload-list-item-info {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ant-upload-list-item.ant-upload-list-item-done.ant-upload-list-item-list-type-picture-card, .ant-upload-list-item.ant-upload-list-item-error.ant-upload-list-item-list-type-picture-card {
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.boder-right {
|
||||
border-right: 1px solid rgb(242, 242, 242);
|
||||
}
|
||||
|
||||
img.opacity_img_click {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
img.opacity_img_click.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.img_check {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
background-size: contain;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.img_check_training {
|
||||
width: 120px;
|
||||
max-width: 100%;
|
||||
height: 120px;
|
||||
background-size: contain;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.img-checkbox>span {
|
||||
background: 0 0;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #9a9caf8c;
|
||||
}
|
||||
|
||||
.img-checkbox>span:after {
|
||||
border: solid #00c5dc;
|
||||
}
|
||||
|
||||
.img-checkbox>span {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.img-checkbox>input:checked~span {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.img-checkbox>span:after {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: 0px;
|
||||
margin-top: -20px;
|
||||
width: 15px;
|
||||
height: 25px;
|
||||
border-width: 0 2px 2px 0!important;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* pagination */
|
||||
.pagination li a {
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-moz-justify-content: center;
|
||||
-ms-justify-content: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-moz-align-items: center;
|
||||
-ms-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
height: 2.25rem;
|
||||
min-width: 2.25rem;
|
||||
vertical-align: middle;
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
font-size: 1rem;
|
||||
line-height: 1rem;
|
||||
font-weight: 400;
|
||||
border: 0;
|
||||
}
|
||||
.pagination li.active a {
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
background-color: #5867dd;
|
||||
border-color: #5867dd;
|
||||
}
|
||||
.pagination li {
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
.pagination li.disabled a {
|
||||
color: #6c757d;
|
||||
pointer-events: none;
|
||||
cursor: auto;
|
||||
background-color: #fff;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
.pagination {
|
||||
float: right;
|
||||
}
|
||||
|
||||
button.close {
|
||||
color: red;
|
||||
border: 1px solid red;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 35px;
|
||||
height: 30px;
|
||||
background-color: red;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button.close:hover {
|
||||
color: white
|
||||
}
|
||||
|
||||
.disable-btn-upload {
|
||||
color: #00000040;
|
||||
border-color: #d9d9d9;
|
||||
background: #f5f5f5;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.disable-btn-upload:hover, .disable-btn-upload:focus {
|
||||
color: #00000040;
|
||||
border-color: #d9d9d9;
|
||||
background: #f5f5f5;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.login__page {
|
||||
background: url("/public/assets/images/bg1.jpeg") top left repeat;
|
||||
|
||||
background-color: #eee;
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
bottom: 0;
|
||||
background-repeat: cover;
|
||||
background-position: center;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
}
|
||||
.login__page .login__form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
.login__page .login__form .login-form-button {
|
||||
width: 100%;
|
||||
}
|
||||
.login__page .login__form .login__text {
|
||||
margin-bottom: 30px;
|
||||
font-weight: 800;
|
||||
font-size: 30px;
|
||||
color: #0695e2;
|
||||
text-align: center;
|
||||
}
|
||||
.login__page .login__form .login__sec {
|
||||
background: #d3d3d3 de;
|
||||
padding: 50px 50px 30px 50px;
|
||||
border-radius: 10px;
|
||||
width: 400px;
|
||||
}
|
||||
.login__page .login__form .login__sec h2::after {
|
||||
content: " ";
|
||||
width: 100px;
|
||||
height: 5px;
|
||||
background: #0695e2;
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
border-radius: 3px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.login__page .login__form .login__sec .login__error {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.login__page .login__form .input__Cus {
|
||||
padding: 10px 15px !important;
|
||||
outline: none !important;
|
||||
border: 1px solid transparent !important;
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
.login__page .login__form .login-form-button {
|
||||
background-color: #0695e2 !important;
|
||||
border-radius: 10px !important;
|
||||
height: 50px !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
23
app/src/App.js
Normal file
23
app/src/App.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
import ListItem from './components/List/ListItem';
|
||||
import Login from './components/Login/Login';
|
||||
import 'antd/dist/antd.css';
|
||||
import "./App.css";
|
||||
import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="Container">
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate replace to="/list-famous" />} />
|
||||
<Route path='/login' element={<Login />} />
|
||||
<Route path="/list-famous" element={<ListItem />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
311
app/src/components/List/ListItem.js
Normal file
311
app/src/components/List/ListItem.js
Normal file
@@ -0,0 +1,311 @@
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Tooltip } from 'antd';
|
||||
import axios from 'axios';
|
||||
import Modaledit from 'components/Modal/ModalEdit';
|
||||
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';
|
||||
|
||||
export default function ListItem() {
|
||||
|
||||
const [data, setData] = useState(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [dataEdit, setDataEdit] = useState(null)
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const [dataSearch, setDataSearch] = useState("")
|
||||
|
||||
const [activePage, setActivePage] = useState(1)
|
||||
const [totalItems, setTotalItems] = useState(10)
|
||||
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(1)
|
||||
}, [])
|
||||
|
||||
const onClickEdit = (data) => {
|
||||
setDataEdit(data)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const onDelete = async (data) => {
|
||||
let dataPost = {
|
||||
obj_id: data._id,
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
birthday: data.birthday,
|
||||
gender: data.gender,
|
||||
is_deleted : 1
|
||||
}
|
||||
try {
|
||||
const result = await axios({
|
||||
method: 'POST',
|
||||
url: `${HOST}/api/famous_persons/insert_or_update`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// 'Authorization': token,
|
||||
},
|
||||
data: dataPost,
|
||||
})
|
||||
if (result.data.status === 10000) {
|
||||
swal({
|
||||
icon: 'success',
|
||||
title: 'Thành công',
|
||||
text: 'Xoá thành công',
|
||||
buttons: false,
|
||||
})
|
||||
getData(activePage)
|
||||
} else if (result.data.status === 10004) {
|
||||
swal("Thất bại", "Xoá thất bại!", "error");
|
||||
} else {
|
||||
swal("Thất bại", "Xoá thất bại!", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const handlePageChange = (pageNumber) => {
|
||||
setActivePage(pageNumber)
|
||||
getData(pageNumber)
|
||||
}
|
||||
|
||||
const getData = async (page) => {
|
||||
try {
|
||||
const result = await axios({
|
||||
method: 'POST',
|
||||
url: `${HOST}/api/famous_persons/search`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// 'Authorization': token
|
||||
},
|
||||
data: {
|
||||
index: page,
|
||||
item_per_page: itemsPerPage,
|
||||
search_data: dataSearch
|
||||
},
|
||||
})
|
||||
if (result.data.status === 10000) {
|
||||
setData(result.data.data)
|
||||
setTotalItems(result.data.count)
|
||||
setLoading(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleOnKeyDown = e => {
|
||||
if (e.key === 'Enter') {
|
||||
getData(1);
|
||||
}
|
||||
}
|
||||
|
||||
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 listImg
|
||||
if (value.sample_images.length > 0) {
|
||||
listImg = <img key={index} style={{ width: "80px", height: "80px" }} alt={value.sample_images.length > 0 ? value.sample_images[0] : ""} src={`${value.sample_images.length > 0 && value.image_host + value.sample_images[0]}`} />
|
||||
} else {
|
||||
listImg = <Avatar shape="square" size={80} icon={<UserOutlined />} />
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{listImg}</td>
|
||||
<td>{value.name}</td>
|
||||
<td>{gender}</td>
|
||||
<td>{value.birthday !== "" ? momment(value.birthday).format("DD-MM-YYYY") : ""}</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={"Xoá"}>
|
||||
<button
|
||||
onClick={f => {
|
||||
f.preventDefault();
|
||||
swal({
|
||||
// title: "Are you sure?",
|
||||
text: "Bạn có chắc muốn xoá " + value.name,
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
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)
|
||||
getData(1)
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
setDataSearch("")
|
||||
getData(1)
|
||||
}
|
||||
|
||||
if (!data) return <></>
|
||||
return (
|
||||
<div>
|
||||
{showModal && <Modaledit data={dataEdit} onHide={onCloseModal} show={showModal} />}
|
||||
<div className="m-portlet m-portlet--mobile pb-3">
|
||||
<div className="m-portlet__head">
|
||||
<div className="m-portlet__head-caption">
|
||||
<div className="m-portlet__head-title">
|
||||
<h3 className="m-portlet__head-text">
|
||||
Danh sách người nổi tiếng
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-portlet__body pt-2">
|
||||
<div className="row pl-4">
|
||||
<div className="row p-3 col-xl-11">
|
||||
<div className="col-lg-2 p-0 m--margin-bottom-10-tablet-and-mobile">
|
||||
<input type="text"
|
||||
onKeyPress={(e) => handleOnKeyDown(e)}
|
||||
onChange={(e) => {
|
||||
setDataSearch(e.target.value)
|
||||
}}
|
||||
value={dataSearch}
|
||||
id="inputSearch" className="form-control m-input"
|
||||
placeholder="Họ tên..."
|
||||
data-col-index={0}
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
getData(1)
|
||||
}}
|
||||
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="m-portlet__head-tools col-xl-1 d-flex align-items-center">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
let dataEdit = {
|
||||
obj_id: "",
|
||||
id: "",
|
||||
name: "",
|
||||
birthday: "",
|
||||
gender: "",
|
||||
sample_images: [],
|
||||
image_host: ""
|
||||
};
|
||||
setDataEdit(dataEdit)
|
||||
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 mới</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/*begin: Datatable */}
|
||||
<table className="table table-striped- table-bordered table-hover table-checkable" id="m_table_1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ảnh</th>
|
||||
<th>Họ tên</th>
|
||||
<th>Giới tính</th>
|
||||
<th>Ngày sinh</th>
|
||||
<th>Thao tác</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{tableRows(data)}</tbody>
|
||||
</table>
|
||||
<PulseLoader
|
||||
// css={override}
|
||||
sizeUnit={"px"}
|
||||
size={12}
|
||||
margin={'2px'}
|
||||
color={'#36D7B7'}
|
||||
loading={loading}
|
||||
/>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
128
app/src/components/Login/Login.js
Normal file
128
app/src/components/Login/Login.js
Normal file
@@ -0,0 +1,128 @@
|
||||
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';
|
||||
|
||||
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');
|
||||
}
|
||||
})
|
||||
.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);
|
||||
};
|
||||
|
||||
|
||||
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>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
414
app/src/components/Modal/ModalEdit.js
Normal file
414
app/src/components/Modal/ModalEdit.js
Normal file
@@ -0,0 +1,414 @@
|
||||
import { UploadOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Button as ButtonAntd, DatePicker, Form, Input, Radio, Upload } 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 Modaledit = (props) => {
|
||||
const { show, onHide, data } = props;
|
||||
const [crrImages, setCrrImages] = useState([]);
|
||||
|
||||
const [form] = Form.useForm()
|
||||
|
||||
const fileInput = useRef(null);
|
||||
|
||||
const [birthday, setBirthday] = useState(moment())
|
||||
const [crrData, setCrrData] = useState(null);
|
||||
|
||||
const [checkDeleteMulti, setCheckDeleteMulti] = useState(false);
|
||||
const [crrIdx, setCrrIdx] = useState(0)
|
||||
const [listChecked, setListChecked] = useState({ url: [] });
|
||||
|
||||
const [disableBtn, setDisableBtn] = useState(true);
|
||||
|
||||
const [listImgUpload, setListImgUpload] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setCrrData(data);
|
||||
setCrrImages(data.sample_images)
|
||||
setBirthday(data.birthday !== "" ? moment(data.birthday) : null)
|
||||
setDisableBtn(data._id ? false : true)
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
const click_handle = async () => {
|
||||
let dataPost = {
|
||||
obj_id: crrData._id ? crrData._id : "",
|
||||
id: crrData.id,
|
||||
name: crrData.name,
|
||||
birthday: crrData.birthday,
|
||||
gender: crrData.gender ? crrData.gender : ""
|
||||
}
|
||||
const result = await axios({
|
||||
method: 'POST',
|
||||
url: `${HOST}/api/famous_persons/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',
|
||||
buttons: false,
|
||||
})
|
||||
} else {
|
||||
swal({
|
||||
icon: 'success',
|
||||
title: 'Thành công',
|
||||
text: 'Thêm mới thành công',
|
||||
buttons: false,
|
||||
})
|
||||
setDisableBtn(false)
|
||||
}
|
||||
} else if (result.data.status === 10004) {
|
||||
swal("Thất bại", "Sửa thông tin thất bại!", "error");
|
||||
} else {
|
||||
swal("Thất bại", "Sửa thông tin thất bại!", "error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const bulletedImg = crrImages.map((value, index) => {
|
||||
let renderImg = value.includes("data:image") ? value : data.image_host + value
|
||||
return (
|
||||
<div className="img-res col-md-4 pl-0 mb-3" key={index + 1}>
|
||||
<div style={{ 'height': 80 }}>
|
||||
{
|
||||
checkDeleteMulti === false ?
|
||||
<img 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"
|
||||
defaultValue={value}
|
||||
name="img_checked"
|
||||
checked={listChecked.url.indexOf(value) === -1 ? false : true}
|
||||
onClick={e => {
|
||||
handleCheckedImg(e, value)
|
||||
}} readOnly />
|
||||
<img alt="" src={renderImg} className="opacity_img img_check" />
|
||||
<span />
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const onChangeFile = async (event) => {
|
||||
const file = event.target.files[0]
|
||||
const base64 = await convertBase64(file)
|
||||
let imgUplpad = [...crrImages]
|
||||
imgUplpad.push(base64)
|
||||
setCrrImages(imgUplpad)
|
||||
setCheckDeleteMulti(false)
|
||||
}
|
||||
|
||||
const onClickUpload = () => {
|
||||
if (crrImages.length >= 3) {
|
||||
swal("Cảnh báo", "Số lượng ảnh tải lên tối đa là 3", "warning")
|
||||
} else {
|
||||
fileInput.current.click()
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = (event) => {
|
||||
|
||||
}
|
||||
|
||||
const onUpload = async (file) => {
|
||||
console.log(file)
|
||||
const base64 = await convertBase64(file)
|
||||
|
||||
let dataUploadImg = {
|
||||
obj_id: crrData._id ? crrData._id : "",
|
||||
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 => {
|
||||
console.log(data)
|
||||
if (data.status === 10000) {
|
||||
let listImg = [...crrImages]
|
||||
listImg.unshift(data.data.toString())
|
||||
setCrrImages(listImg)
|
||||
setCheckDeleteMulti(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const uploadImage = async (options) => {
|
||||
const { file } = options;
|
||||
const base64 = await convertBase64(file)
|
||||
|
||||
let dataUploadImg = {
|
||||
obj_id: crrData._id ? crrData._id : "",
|
||||
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) {
|
||||
let listImg = [...crrImages]
|
||||
listImg.unshift(data.data.toString())
|
||||
setCrrImages(listImg)
|
||||
setCheckDeleteMulti(false)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
const convertBase64 = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.readAsDataURL(file)
|
||||
fileReader.onload = () => {
|
||||
resolve(fileReader.result);
|
||||
}
|
||||
fileReader.onerror = (error) => {
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const deleteFaceMulti = () => {
|
||||
let listDelImg = listChecked.url
|
||||
fetch(`${HOST}/api/face_images/ignore_face`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// 'Authorization': token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
person_obj_id: crrData._id ? crrData._id : "",
|
||||
images: listDelImg
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 10000) {
|
||||
let listDetele = crrImages.filter(item => !listDelImg.includes(item));
|
||||
setCrrImages(listDetele)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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") })
|
||||
}
|
||||
|
||||
if (!crrData) return <></>;
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
animation={false}
|
||||
size="lg"
|
||||
// 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>
|
||||
{crrData &&
|
||||
<Modal.Body>
|
||||
<div id="formUpdateMeeting">
|
||||
<div className="m-widget14 p-0">
|
||||
<div className="row">
|
||||
<div className="col-md-7 boder-right d-flex flex-column justify-content-between ">
|
||||
<div className="row justify-content-center pb-3">
|
||||
{
|
||||
crrImages[crrIdx] ?
|
||||
<Avatar shape="square" src={crrImages[crrIdx].includes("data:image") ? crrImages[crrIdx] : data.image_host + crrImages[crrIdx]} size={180} icon={<UserOutlined />} /> :
|
||||
<Avatar shape="square" size={180} icon={<UserOutlined />} />
|
||||
}
|
||||
|
||||
</div>
|
||||
<div className="row mr-4 ml-4">
|
||||
{
|
||||
crrImages.length > 0
|
||||
?
|
||||
<div className="col-md-12 mt-2 mb-2 p-0">
|
||||
<label className="m-checkbox m-checkbox--success mt-3 ml-1">
|
||||
<input type="checkbox"
|
||||
onClick={(e) => {
|
||||
setCheckDeleteMulti(!checkDeleteMulti)
|
||||
setListChecked({
|
||||
url: [],
|
||||
})
|
||||
}}
|
||||
checked={checkDeleteMulti} /> <div style={{ 'paddingTop': '2px' }}>Chọn</div>
|
||||
<span />
|
||||
</label>
|
||||
{
|
||||
checkDeleteMulti === true
|
||||
?
|
||||
<div style={{ 'display': 'inline-flex' }} className="pull-right">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (listChecked.url.length > 0) {
|
||||
swal({
|
||||
title: "Bạn chắc chắn muốn xóa ảnh này",
|
||||
icon: "warning",
|
||||
buttons: ["Huỷ", "Oke!"],
|
||||
dangerMode: false,
|
||||
})
|
||||
.then((willDelete) => {
|
||||
if (!willDelete) throw null;
|
||||
deleteFaceMulti();
|
||||
})
|
||||
} else {
|
||||
swal("Cảnh báo!", "Bạn chưa chọn ảnh để xóa", "warning");
|
||||
}
|
||||
}}
|
||||
className="btn btn-sm btn-danger width-mobie--100 m-loader--light m-loader--right" id="btn_deleteMulti">
|
||||
Xóa ảnh đã chọn
|
||||
</button>
|
||||
</div>
|
||||
:
|
||||
""
|
||||
}
|
||||
</div>
|
||||
:
|
||||
""
|
||||
}
|
||||
<div className="row col-md-12 pr-0 image_capture_small pull-left">
|
||||
{
|
||||
bulletedImg
|
||||
}
|
||||
|
||||
</div>
|
||||
<Upload
|
||||
customRequest = {uploadImage}
|
||||
accept="image/*"
|
||||
showUploadList={false}
|
||||
disabled={disableBtn}
|
||||
>
|
||||
<ButtonAntd className={`${disableBtn ? "disable-btn-upload" : ""}`} icon={<UploadOutlined />}>Tải ảnh lên</ButtonAntd>
|
||||
</Upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-5 d-flex flex-column justify-content-between">
|
||||
<Form
|
||||
id="formEdit"
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={() => click_handle()}
|
||||
// onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
initialValues={{
|
||||
name: crrData.name
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="Họ tên"
|
||||
name="name"
|
||||
rules={[{ required: true, message: '' }]}
|
||||
>
|
||||
<Input placeholder="Họ tên" value={crrData.name} onChange={e => UserHandle(e)} name='name' />
|
||||
</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={birthday !== null ? moment(birthday, 'DD-MM-YYYY') : null}
|
||||
format={'DD-MM-YYYY'}
|
||||
onChange={e => onChangeBirthday(e)}
|
||||
placeholder="Ngày sinh"
|
||||
/>
|
||||
</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 Modaledit;
|
||||
7
app/src/config/index.js
Normal file
7
app/src/config/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var _HOST = ""
|
||||
|
||||
_HOST = process.env.REACT_APP_HOST
|
||||
|
||||
export const HOST = _HOST
|
||||
|
||||
|
||||
13
app/src/index.css
Normal file
13
app/src/index.css
Normal file
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
22
app/src/index.js
Normal file
22
app/src/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import 'moment/locale/vi';
|
||||
import viVN from 'antd/lib/locale/vi_VN';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<ConfigProvider locale={viVN}>
|
||||
<App />
|
||||
</ConfigProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
13
app/src/reportWebVitals.js
Normal file
13
app/src/reportWebVitals.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
Reference in New Issue
Block a user