Compare commits

..

61 Commits
master ... uat

Author SHA1 Message Date
Quy_FE b1674a9ce9 Feat : chart v1 2 months ago
Quy_FE 1fb26ff5a8 Feat : tooltip cho phần biểu đồ 2 months ago
Quy_FE 1b1bfb83fc Feat : disable các button 2 months ago
Quy_FE 8027fcaafc Style : fix layout 2 months ago
Quy_FE 5bb67fe957 Merge branch 'uat' into quy_fe 2 months ago
Quy_FE 7643a94564 Style : reponsive màn thống kê của PGD 2 months ago
Quy_FE df5c7b1d69 Merge branch 'uat' into quy_fe 2 months ago
Quy_FE 0c99db8cb9 Merge branch 'uat' of http://git.gkcorp.com.vn:16000/HoangLaoTa/gk_se_web_report into uat 2 months ago
Quy_FE dd7373eb8a Refactor : fix chọn tất cả 2 months ago
Quy_FE 1ab9f80b98 Refactor : tách biệt SGD và PGD 2 months ago
Quy_FE 4d053c0ad9 Feat : async data chart 3 months ago
Quy_FE ee2b4c9e51 Style : resposive chart 3 months ago
Quy_FE 844bf6e782 Merge branch 'uat' into quy_fe 3 months ago
Quy_FE 4bdae89f43 Style : resposive chart 3 months ago
Quy_FE 1704b78058 Feat : xử lý chart bar 3 months ago
Quy_FE 4131312cff Feat : fix lỗi bỏ áp dụng 3 months ago
Quy_FE 1dc72d120b Feat : scroll mà chart 3 months ago
Quy_FE 7ee4e325cf Feat : box search text giáo viên 3 months ago
Quy_FE 943e3facd5 Feat : tách code, thêm mới button quay lại 3 months ago
Quy_FE e092dc64b1 Feat : fix back button 3 months ago
Quy_FE 4ccc74e332 Feat : button quay lại 3 months ago
Quy_FE 8c13a2c108 Merge branch 'quy_fe' into uat 3 months ago
Quy_FE 86a9f2f437 Merge branch 'uat' into quy_fe 3 months ago
Quy_FE 4606c42c7a Feat : nút quay lại 3 months ago
Quy_FE a88f014c12 Feat : thêm scroll, thay style cho trang chủ sgd 3 months ago
Quy_FE 81205da08c Style : button-custom 3 months ago
Quy_FE ca037c6267 Feat : logic bật tắt chỉ tiêu 3 months ago
Quy_FE 60d2c077b8 Feat : scroll load more sgd 3 months ago
Quy_FE 3a1d7d4009 Feat : handle button change month 3 months ago
Quy_FE 239e0734b3 Feat : chart style 3 months ago
Quy_FE d8cb4ae59b Feat : fix lỗi chọn học kỳ 3 months ago
Quy_FE c1d0b7ce41 Feat : sửa tên của hiệu trưởng 3 months ago
minh android c4682bb61c Merge branch 'quy_fe' of http://git.gkcorp.com.vn:16000/HoangLaoTa/gk_se_web_report into uat 3 months ago
Quy_FE 46dce555d5 Feat : sửa lại logic scroll 3 months ago
Quy_FE 16d12050c7 Feat : fix lỗi chart, khia báo tất cả 3 months ago
minh android 4af9ce6629 Merge branch 'uat' of http://git.gkcorp.com.vn:16000/HoangLaoTa/gk_se_web_report into uat 3 months ago
minh android 90ff3c8dc5 Merge branch 'quy_fe' of http://git.gkcorp.com.vn:16000/HoangLaoTa/gk_se_web_report into uat 3 months ago
Quy_FE b30181e1e2 Merge branch 'uat' of http://git.gkcorp.com.vn:16000/HoangLaoTa/gk_se_web_report into uat 3 months ago
Quy_FE 62fac2ec8a Feat : tối ưu handleScroll 3 months ago
Quy_FE 87b55b0e01 feat : lỗi màn hình trắng 3 months ago
Quy_FE daff2332c2 feat : export execl 3 months ago
Quy_FE a4d4169148 feat : scroll danh sách khối 3 months ago
Quy_FE b888e82d88 Feat : thay đổi vị trí endpoint 3 months ago
Quy_FE 48c6143610 fix : sửa lỗi login 3 months ago
Quy_FE 4c329ae25e Feat : reset scroll và check login 3 months ago
Quy_FE 20d1e5c553 Feat : Xuất execl ds lớp, fix lỗi lọc 3 months ago
Quy_FE 4c0830abbf Fix : lỗi phân quyền, thêm mới limit 3 months ago
minh android 2958cf1483 remove configConstants 3 months ago
Quy_FE 53ca639f1b Feat : thêm trường mới execl 3 months ago
Quy_FE 4bcf807247 Feat : get date tiêu chí cho role hiệu trưởng và giáo viên 3 months ago
Quy_FE 505c4ef3eb Style : sửa text header hiệu trưởng 3 months ago
Quy_FE 84e1502e89 Feat : thêm limit admin, criteria 3 months ago
Quy_FE b1fbbb9124 Feat : limit get ds Lớp 3 months ago
Quy_FE db60b90bc4 Feat : nút back, fix logic giữ fill 3 months ago
Quy_FE 768b2317bc feat : sửa text, disable nút, back mh cài đặt chỉ tiêu 3 months ago
Quy_FE ec162f6e45 Feat : mh lọc back lại vẫn giữ fill lọc cũ 3 months ago
Quy_FE d9bbcd2850 Feat : fix lỗi quyền truy cập,mh admin, Lọc 3 months ago
Quy_FE f3aff8b648 feat : bỏ onBluer và quên mật khẩu 3 months ago
minh android 0f15dfe3ec Merge branch 'uat' of http://git.gkcorp.com.vn:16000/HoangLaoTa/gk_se_web_report into uat 3 months ago
minh android e2ecabf447 pack 3 months ago
minh android 7d0967a370 bo fix cung 3 months ago
  1. 11
      src/App.js
  2. 1
      src/_components/Auth/InputDate/index.js
  3. 12
      src/_components/Auth/InputDate/index.scss
  4. 2
      src/_components/Auth/InputText/index.scss
  5. 19
      src/_components/Header/header.style.scss
  6. 33
      src/_components/Header/index.js
  7. 136
      src/_components/boxChart/BoxDoughnutBarChart.js
  8. 39
      src/_components/boxChart/BoxDoughnutChar.js
  9. 78
      src/_components/boxChart/boxChart.scss
  10. 58
      src/_components/chart/RDoughnutChart.js
  11. 31
      src/_components/chart/RPieChart.js
  12. 40
      src/_components/chart/VerticalBarChart.js
  13. 51
      src/_components/renderIcon/index.js
  14. 2
      src/_constants/common.js
  15. 2
      src/_constants/config.js
  16. 2
      src/_constants/index.js
  17. 192
      src/_screens/criteria/criteria-manage/index.js
  18. 5
      src/_screens/criteria/criteria-setting/criteriaSetting.style.scss
  19. 190
      src/_screens/criteria/criteria-setting/index.js
  20. 3
      src/_screens/home/admin/adminHome.style.scss
  21. 171
      src/_screens/home/admin/index.js
  22. 5
      src/_screens/home/detail-grade/detailGrade.style.scss
  23. 95
      src/_screens/home/detail-grade/index.js
  24. 28
      src/_screens/home/detail-room-education/detailRoomEducation.style.scss
  25. 244
      src/_screens/home/detail-room-education/index.js
  26. 37
      src/_screens/home/education-department/educationDepartmentHome.style.scss
  27. 188
      src/_screens/home/education-department/index.js
  28. 139
      src/_screens/home/headmaster/index.js
  29. 37
      src/_screens/home/outstanding-teacher/index.js
  30. 3
      src/_screens/home/outstanding-teacher/outstandingTeacher.style.scss
  31. 131
      src/_screens/home/teacher/index.js
  32. 31
      src/_screens/login/index.js

@ -22,10 +22,12 @@ import DetailRoomEducation from "./_screens/home/detail-room-education";
import EducationDepartmentHome from "./_screens/home/education-department";
import { Alert } from "./_components/Alert";
import OutstandingTeacher from "./_screens/home/outstanding-teacher";
import { TYPE_DISPATCH } from "./_constants/common";
function App() {
const dispatch = useDispatch();
const authentication = useSelector((state) => state.authentication);
// let authentication
const alert = useSelector((state) => state.alert);
String.prototype.capitalize = function () {
@ -43,7 +45,14 @@ function App() {
dispatch(alertActions?.clear());
});
}, []);
console.log("authen: ", authentication);
useEffect(() => {
if(authentication?.user?.role === "student"||authentication?.user?.role === "parent")
dispatch({
type: TYPE_DISPATCH.RESET_AUTHENTICATION,
});
}, [])
return (
<div className="rel">
<div className="loading hide">

@ -72,6 +72,7 @@ const InputDate = (props) => {
}`}
selected={props.value}
onChange={(date) => changeValue(date)}
disabledKeyboardNavigation
// showMonthDropdown
showYearDropdown
showMonthYearPicker={!!props?.isMonthPicker}

@ -53,6 +53,18 @@ $border-color: #4a4848;
}
}
.react-datepicker {
&__month-text {
&--selected,
&--in-selecting-range,
&--in-range {
border-radius: 8px !important;
}
}
}
input {
height: 100%;
border: none;

@ -98,7 +98,7 @@ $border-color: #4a4848;
.icon_label {
width: 31px;
margin-right: 8px;
// margin-right: 8px;
@include screen_mobile {
margin-right: 0 !important;

@ -16,6 +16,25 @@
font-weight: 700;
color: #4d4d4d;
}
.header-back-button {
height: 40px;
border-radius: 20px;
border: none;
font-size: 16px;
color: #fff;
font-family: 'Myriadpro-SemiBold';
background-color: var(--button-bg-color);
display: flex;
justify-content: center;
align-items: center;
min-width: 64px;
white-space: nowrap;
}
.header-back-button:hover {
background-color: #c07a05;
}
}
.header-right-side {

@ -1,27 +1,54 @@
import { useSelector } from "react-redux";
import "./header.style.scss";
import { configConstants } from "../../_constants";
import PrimaryButton from "../Button/PrimaryButton";
import { renderIconButtonLeft } from "../renderIcon";
import { history } from "../../_helpers";
export default function Header({ icon, title, subtitles = [] }) {
export default function Header({ icon, title, subtitles = [], manager = false, isBack = false }) {
const authentication = useSelector((state) => state.authentication);
const { fullname, organization_name, role } = authentication?.user || {};
const hasFullName = fullname || organization_name;
const handleFullName = () =>{
let fullName
if(manager){
fullName = 'Giám đốc ' + hasFullName
}else{
fullName = role === "organization_admin"
? `${organization_name ? 'Hiệu trưởng ' : ''}${hasFullName}`
: fullname;
}
return fullName
}
return (
<div className="header-container">
<div className="header-left-side">
{isBack && (
<div style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
<button className="header-back-button" onClick={()=>{
history.goBack()
}}>
<div style={{marginRight: 8, paddingBottom: 4}}>{renderIconButtonLeft()}</div>
</button>
</div>
)}
{icon}
<p className="header-title">
{title}
{!!subtitles?.length &&
subtitles?.map((item, index) => (
<span key={index} style={{ fontWeight: 400 }}>
<span className="header-title">{" > "}</span>
<span className="header-title">{" - "}</span>
{item}
</span>
))}
</p>
</div>
<div className="header-right-side">
<p className="header-name">{authentication?.user?.fullname}</p>
<p className="header-name">{handleFullName()}</p>
<img
src={!!authentication?.user?.avatar ? (configConstants.BASE_URL + authentication?.user?.avatar) : '/assets/imgs/avatar_auth.png'}
className="header-avatar"

@ -1,76 +1,140 @@
import { Subtitles } from '@material-ui/icons'
import { PRIMARY_COLOR } from '../../_constants/common'
import RDoughnutChart from '../chart/RDoughnutChart'
import './boxChart.scss'
import { VerticalBarChart } from '../chart/VerticalBarChart'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import InputDate from '../Auth/InputDate'
import { renderIconButton, renderIconButtonLeft, renderIconDate } from '../renderIcon'
export default function BoxDoughnutBarChart({dataDoughnut = [], dataBarChart = [], labelsBarChart = [], dateBarChat, onSetDateBarChart, titleDoughnut, subtitleDoughnut, titleLine, subTitleLine}) {
const Btn = ({ icon, isDisabled = false, onClick }) => {
return (
<div className='d-flex justify-content-center align-items-center'>
<button className={`d-flex custom-button ${isDisabled ? "button-disable" : ""}`} onClick={onClick} disabled={isDisabled}>
<div>{icon}</div>
</button>
</div>
);
};
export default function BoxDoughnutBarChart({
dataDoughnut = [],
dataBarChart = [],
labelsBarChart = [],
dateBarChat,
onSetDateBarChart,
titleDoughnut,
subtitleDoughnut,
titleLine,
subTitleLine,
}) {
const [month, setMonth] = useState(dateBarChat)
const [monthError, setMonthError] = useState('')
const [current, target] = dataDoughnut
const changeMonth = (date) => {
const handleMonthChange = (date) => {
setMonth(date)
!!onSetDateBarChart && onSetDateBarChart(date)
}
const _dataDoughnut = (dataDoughnut?.[0] / dataDoughnut?.[1] === 1 || dataDoughnut?.[0] > dataDoughnut?.[1]) ? [dataDoughnut?.[0]] : [dataDoughnut?.[0], dataDoughnut?.[1] - dataDoughnut?.[0]]
const renderIconDate = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 20 20">
<path
fill="#00bbb5"
d="M5.673 0a.7.7 0 0 1 .7.7v1.309h7.517v-1.3a.7.7 0 0 1 1.4 0v1.3H18a2 2 0 0 1 2 1.999v13.993A2 2 0 0 1 18 20H2a2 2 0 0 1-2-1.999V4.008a2 2 0 0 1 2-1.999h2.973V.699a.7.7 0 0 1 .7-.699M1.4 7.742v10.259a.6.6 0 0 0 .6.6h16a.6.6 0 0 0 .6-.6V7.756zm5.267 6.877v1.666H5v-1.666zm4.166 0v1.666H9.167v-1.666zm4.167 0v1.666h-1.667v-1.666zm-8.333-3.977v1.666H5v-1.666zm4.166 0v1.666H9.167v-1.666zm4.167 0v1.666h-1.667v-1.666zM4.973 3.408H2a.6.6 0 0 0-.6.6v2.335l17.2.014V4.008a.6.6 0 0 0-.6-.6h-2.71v.929a.7.7 0 0 1-1.4 0v-.929H6.373v.92a.7.7 0 0 1-1.4 0z"
/>
</svg>
)
}
return (
<div className="box-chart-container">
const renderDoughnutSection = () => (
<div style={{borderBottom: '1px solid #c4c4c4', paddingBottom: '1.6rem', flex: 0.38, display: 'flex', flexDirection: 'column'}}>
{!!titleDoughnut && <p className='box-chart-title'>{titleDoughnut}</p>}
{!!subtitleDoughnut && <p className='box-chart-subtitle'>{subtitleDoughnut}</p>}
{titleDoughnut && <p className='box-chart-title'>{titleDoughnut}</p>}
{subtitleDoughnut && <p className='box-chart-subtitle'>{subtitleDoughnut}</p>}
<div className='d-flex flex-1'>
<div className='doughnut-chart-content flex-1'>
<RDoughnutChart data={_dataDoughnut} />
<RDoughnutChart data={dataDoughnut} />
</div>
<div className='origin-vertical justify-content-center align-item-center flex-1'>
<p style={{fontSize: '1.8rem'}}>{dataDoughnut?.[0] + '/' + dataDoughnut?.[1]}</p>
<p style={{color: PRIMARY_COLOR, fontSize: '2.2rem', fontWeight: 800}}>{Math.round(dataDoughnut?.[0]/dataDoughnut?.[1] * 100) + '%'}</p>
<div className='origin-vertical justify-content-center align-item-center flex-1' style={{cursor:'pointer'}}>
<div className="tooltip">
<p
style={{color: PRIMARY_COLOR, fontSize: '2.2rem', fontWeight: 800}}
>
{current}
<span className="tooltiptext" style={{color: PRIMARY_COLOR}}>Số lượng đã tham gia: {current}</span>
</p>
</div>
<div className="tooltip">
<p
style={{color: '#01AEF0', fontSize: '2.2rem', fontWeight: 800}}
>
{target > 0 ? target : '...'}
<span className="tooltiptext" style={{color: '#01AEF0'}}>Tổng số: {target > 0 ? target : '...'}</span>
</p>
</div>
</div>
</div>
</div>
)
const renderBarSection = () => (
<div className='d-flex' style={{marginTop: '1.2rem', flexDirection: 'column', flex: 0.62}}>
{!!titleLine && <p className='box-chart-title'>{titleLine}</p>}
{!!subTitleLine && <p className='box-chart-subtitle'>{subTitleLine}</p>}
<div className='flex-1' style={{padding: '1.6rem 0'}}>
<VerticalBarChart data={dataBarChart} labels={labelsBarChart}/>
{titleLine && <p className='box-chart-title'>{titleLine}</p>}
{subTitleLine && <p className='box-chart-subtitle'>{subTitleLine}</p>}
{dataBarChart.length > 0 ? (
<div className='chart-container'>
<div className='flex-1' style={{ padding: '1.6rem 0', height: '100%'}}>
<VerticalBarChart data={dataBarChart} labels={labelsBarChart} />
</div>
</div>
) : (
<div className="d-flex justify-content-center align-items-center flex-1">
Không dữ liệu để hiển thị
</div>
)}
</div>
)
const handleClickNext = () => {
const newMonth = month.getMonth() + 1;
const newYear = month.getFullYear();
if (newMonth > 11) {
setMonth(new Date(newYear + 1, 0, 1));
} else {
setMonth(new Date(newYear, newMonth, 1));
}
};
const handleClickPrev = () => {
const newMonth = month.getMonth() - 1;
const newYear = month.getFullYear();
if (newMonth < 0) {
setMonth(new Date(newYear - 1, 11, 1));
} else {
setMonth(new Date(newYear, newMonth, 1));
}
};
useEffect(() => {
onSetDateBarChart?.(month);
}, [month]);
const isDisabledNext = new Date(month).getMonth() === new Date().getMonth() && new Date(month).getFullYear() === new Date().getFullYear();
return (
<div className="box-chart-container">
{renderDoughnutSection()}
{renderBarSection()}
<div className='d-flex justify-content-center align-items-center' style={{marginTop:'1.2rem'}}>
<Btn icon={renderIconButtonLeft()} isDisabled={false} onClick={handleClickPrev} />
<div className='d-flex justify-content-center align-item-center' style={{width: 140, alignSelf: 'center'}}>
{/* {renderArrowLeft()}
<span style={{padding: '0 2rem', fontSize: '1.8rem'}}>Tháng 11</span>
{renderArrowRight()} */}
<InputDate
maxDate={new Date()}
label={''}
styleContainer={{flex: 1}}
value={month}
setValue={changeMonth}
setValue={handleMonthChange}
name="month"
renderLabelIcon={renderIconDate}
errorText={monthError}
errorAbsolute={true}
// placeholder={"Chọn ngày sinh"}
popperPlacement='top'
typeErrText='underAbsolute'
isMonthPicker
/>
</div>
<Btn icon={renderIconButton()} isDisabled={isDisabledNext} onClick={handleClickNext} />
</div>
</div>
)
}

@ -3,15 +3,44 @@ import RDoughnutChart from '../chart/RDoughnutChart'
import './boxChart.scss'
export default function BoxDoughnutChart({data = [], title, propsContainer}) {
const _data = (data?.[0] / data?.[1] === 1 || data?.[0] > data?.[1]) ? [data?.[0]] : [data?.[0], data?.[1] - data?.[0]]
const [current, target] = data
// const chartData = (current / target === 1 || current > target) ? [current] : [current, target - current];
const renderStats = () => {
return (
<>
<div style={{cursor:'pointer',display: 'flex', flexDirection: 'column', alignItems:'center'}}>
<div className="tooltip">
<p
style={{color: PRIMARY_COLOR, fontSize: '2.2rem', fontWeight: 800}}
>
{current}
<span className="tooltiptext" style={{color: PRIMARY_COLOR}}>Số lượng đã tham gia: {current}</span>
</p>
</div>
<div className="tooltip">
<p
style={{color: '#01AEF0', fontSize: '2.2rem', fontWeight: 800}}
>
{target > 0 ? target : '...'}
<span className="tooltiptext" style={{color: '#01AEF0'}}>Tổng số: {target > 0 ? target : '...'}</span>
</p>
</div>
</div>
</>
)
}
return (
<div className="box-chart-container" {...propsContainer}>
<p className='box-chart-subtitle'>{title}</p>
<p className='box-chart-subtitle' style={{ marginBottom: '1.2rem'}}>{title}</p>
<div className='d-flex flex-1'>
<div className='doughnut-chart-content flex-1'><RDoughnutChart data={_data} /></div>
<div className='doughnut-chart-content flex-1'>
<RDoughnutChart data={data} />
</div>
<div className='origin-vertical justify-content-center align-item-center flex-1'>
<p style={{fontSize: '1.8rem'}}>{data?.[0] + '/' + data?.[1]}</p>
<p style={{color: PRIMARY_COLOR, fontSize: '2.2rem', fontWeight: 800}}>{Math.round(data?.[0]/data?.[1] * 100) + '%'}</p>
{renderStats()}
</div>
</div>
</div>

@ -7,6 +7,14 @@
display: flex;
flex-direction: column;
@media (min-width: 1600px) and (max-width: 1750px) {
width: 300px;
}
@media (max-width: 1599px) {
max-width: 260px;
}
.box-chart-title {
font-size: 2rem;
font-weight: 700;
@ -19,5 +27,73 @@
font-weight: 700;
}
.doughnut-chart-content {}
// .doughnut-chart-content {
// min-width: 120px;
// }
.custom-button {
padding: 8px;
height: auto;
border-radius: 20px;
border: none;
font-size: 16px;
color: #fff;
font-family: 'Myriadpro-SemiBold';
background-color: var(--button-bg-color);
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
width: max-content;
margin: 0 10px;
}
.custom-button:hover {
background-color: #c07a05;
}
.button-disable {
cursor: not-allowed;
/* background: #70707070 !important; */
background: #c1c1c1 !important;
pointer-events: none;
}
.chart-container {
// overflow-x: scroll;
height: 100%;
// width: 300px;
// @media (max-width: 1750px) {
// max-width: 240px;
// }
}
}
.tooltip {
position: relative;
display: inline-block;
.tooltiptext {
visibility: hidden;
width: max-content;
background-color: #fff;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
font-size: 1.4rem;
}
&:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
}

@ -1,65 +1,55 @@
import React from 'react';
import { Chart } from 'react-chartjs-2';
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
import { PRIMARY_COLOR } from '../../_constants/common';
import { Chart as ChartJS, registerables } from 'chart.js';
ChartJS.register(ArcElement, Tooltip, Legend);
// const data = {
// labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
// datasets: [
// {
// label: '# of Votes',
// data: [12, 19, 3, 5, 2, 3],
// backgroundColor: [
// 'rgba(255, 99, 132, 0.2)',
// 'rgba(54, 162, 235, 0.2)',
// 'rgba(255, 206, 86, 0.2)',
// 'rgba(75, 192, 192, 0.2)',
// 'rgba(153, 102, 255, 0.2)',
// 'rgba(255, 159, 64, 0.2)',
// ],
// borderColor: [
// 'rgba(255, 99, 132, 1)',
// 'rgba(54, 162, 235, 1)',
// 'rgba(255, 206, 86, 1)',
// 'rgba(75, 192, 192, 1)',
// 'rgba(153, 102, 255, 1)',
// 'rgba(255, 159, 64, 1)',
// ],
// borderWidth: 1,
// },
// ],
// };
ChartJS.register(...registerables);
export default function RDoughnutChart({data = [], ...other}) {
if (!data?.length) {
return null
}
const total = data[1] === 0 ? 1 : data[0] + data[1];
const percentageData = [
(data[0] / total) * 100,
(data[1] / total) * 100,
];
const adjustedData = (percentageData[0] === 0 && percentageData[1] === 0) || percentageData[1] === percentageData[0] ? [0, 1] : percentageData;
return <Chart
type='doughnut'
data={{
labels: [],
labels: ['Số tham gia',"Tổng số"],
datasets: [
{
label: '',
data,
data: adjustedData,
backgroundColor: [
PRIMARY_COLOR, '#01AEF0'
],
borderWidth: 0
},
],
hoverOffset: 4
}}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
enabled: false
}
callbacks: {
label: function (tooltipItem) {
const label = tooltipItem.label || '';
const value = data[tooltipItem.dataIndex];
return `${label}: ${value}`;
},
},
position: 'nearest',
z: 1000,
},
legend: false,
}
}}
{...other}

@ -1,35 +1,8 @@
import React from 'react';
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
import { Pie } from 'react-chartjs-2';
import { Chart as ChartJS, registerables } from 'chart.js';
ChartJS.register(ArcElement, Tooltip, Legend);
// const data = {
// labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
// datasets: [
// {
// label: '# of Votes',
// data: [12, 19, 3, 5, 2, 3],
// backgroundColor: [
// 'rgba(255, 99, 132, 0.2)',
// 'rgba(54, 162, 235, 0.2)',
// 'rgba(255, 206, 86, 0.2)',
// 'rgba(75, 192, 192, 0.2)',
// 'rgba(153, 102, 255, 0.2)',
// 'rgba(255, 159, 64, 0.2)',
// ],
// borderColor: [
// 'rgba(255, 99, 132, 1)',
// 'rgba(54, 162, 235, 1)',
// 'rgba(255, 206, 86, 1)',
// 'rgba(75, 192, 192, 1)',
// 'rgba(153, 102, 255, 1)',
// 'rgba(255, 159, 64, 1)',
// ],
// borderWidth: 1,
// },
// ],
// };
ChartJS.register(...registerables);
export default function RPieChart({data, ...other}) {
return <Pie data={data} {...other} />;

@ -1,24 +1,9 @@
import React from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import { Bar } from 'react-chartjs-2';
import { PRIMARY_COLOR } from '../../_constants/common';
import { Chart as ChartJS, registerables } from 'chart.js';
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
ChartJS.register(...registerables);
export function VerticalBarChart({data = [], labels = []}) {
if (!data?.length) {
@ -31,6 +16,8 @@ export function VerticalBarChart({data = [], labels = []}) {
}
})
labels = labels?.map(week => week.split(" "));
const options = {
responsive: true,
maintainAspectRatio: false,
@ -38,19 +25,30 @@ export function VerticalBarChart({data = [], labels = []}) {
legend: {
display: false
},
// title: {
// display: true,
// text: 'Tuần',
// align: 'start',
// position: 'bottom',
// },
datalabels: {
anchor: 'end', // Position of the labels (start, end, center, etc.)
align: 'end', // Alignment of the labels (start, end, center, etc.)
color: 'blue', // Color of the labels
anchor: 'end',
align: 'end',
color: 'blue',
font: {
weight: 'bold',
},
formatter: function (value, context) {
return value; // Display the actual data value
return value;
}
}
},
scales: {
x: {
ticks: {
maxRotation: 0,
},
},
y: {
max: maxValue < 5 ? 5 : maxValue < 20 ? maxValue + 5 : maxValue + 10,
ticks: {

@ -139,3 +139,54 @@ export const renderIconSearchInput = () => {
</svg>
);
};
export const renderIconButton = (width = 24, height = 24) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path stroke-dasharray="20" stroke-dashoffset="20" d="M3 12h17.5"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="20;0"/></path><path stroke-dasharray="12" stroke-dashoffset="12" d="M21 12l-7 7M21 12l-7 -7"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="12;0"/></path></g></svg>
)
}
export const renderIconButtonLeft = (width = 24, height = 24) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
>
<g fill="none" stroke="#fff" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" transform="rotate(180 12 12)">
<path strokeDasharray="20" strokeDashoffset="20" d="M3 12h17.5">
<animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="20;0" />
</path>
<path strokeDasharray="12" strokeDashoffset="12" d="M21 12l-7 7M21 12l-7 -7">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="12;0" />
</path>
</g>
</svg>
);
};
export const renderIconDate = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 20 20">
<path
fill="#00bbb5"
d="M5.673 0a.7.7 0 0 1 .7.7v1.309h7.517v-1.3a.7.7 0 0 1 1.4 0v1.3H18a2 2 0 0 1 2 1.999v13.993A2 2 0 0 1 18 20H2a2 2 0 0 1-2-1.999V4.008a2 2 0 0 1 2-1.999h2.973V.699a.7.7 0 0 1 .7-.699M1.4 7.742v10.259a.6.6 0 0 0 .6.6h16a.6.6 0 0 0 .6-.6V7.756zm5.267 6.877v1.666H5v-1.666zm4.166 0v1.666H9.167v-1.666zm4.167 0v1.666h-1.667v-1.666zm-8.333-3.977v1.666H5v-1.666zm4.166 0v1.666H9.167v-1.666zm4.167 0v1.666h-1.667v-1.666zM4.973 3.408H2a.6.6 0 0 0-.6.6v2.335l17.2.014V4.008a.6.6 0 0 0-.6-.6h-2.71v.929a.7.7 0 0 1-1.4 0v-.929H6.373v.92a.7.7 0 0 1-1.4 0z"
/>
</svg>
)
export const renderIconBook = () => {
return (
<svg
width="29"
height="29"
viewBox="0 0 29 29"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.2359 2.71875L3.12973 6.05873H3.11342C2.91223 6.05873 0.90625 6.11492 0.90625 8.35789C0.90625 9.33256 1.33853 10.0589 1.33853 10.0589L10.5614 25.4842C11.4432 26.8889 13.316 26.0094 13.316 26.0094L27.4585 19.1196L26.9292 18.5827C26.8046 17.9379 26.7321 16.7439 27.6075 15.8548C27.627 15.8349 27.6307 15.8077 27.6411 15.7828L28.0938 15.6011L15.2359 2.71875ZM15.6115 5.66044L18.0869 8.35517L8.84727 11.7387L6.66502 8.30578L15.6115 5.66044ZM10.2343 23.1719L2.12153 9.60172C2.11791 9.59673 1.8125 9.05389 1.8125 8.35789C1.8125 7.4752 2.24478 7.13944 2.66755 7.02208L11.9444 21.4931C11.2461 21.7953 10.4658 22.4859 10.2343 23.1719ZM26.4679 17.2826C26.4385 17.4911 26.4235 17.6941 26.4267 17.8844L17.1426 22.291L26.4598 18.3371C26.4688 18.4096 26.477 18.4816 26.4883 18.5469L14.587 24.2952L26.5821 18.9751L26.5894 19.0036L13.316 25.3533C13.3096 25.3573 12.7038 25.6795 12.0966 25.6795C11.4967 25.6795 11.1229 25.3714 10.9525 24.7379C10.5143 23.1103 13.0038 22.1877 13.0414 22.1741L26.8087 16.2178C26.7099 16.399 26.6415 16.5839 26.5844 16.7674L17.845 20.8814L26.4679 17.2826Z"
fill="white"
/>
</svg>
);
};

@ -96,7 +96,7 @@ export const getListMonthBySemester = (semester) => {
{ label: "Tháng 10", value: 10 },
{ label: "Tháng 11", value: 11 },
{ label: "Tháng 12", value: 12 },
{ label: `Tháng 1 (${currentYear + 1})`, value: 1 },
{ label: `Tháng 1 (${currentYear})`, value: 1 },
];
case 2:
return [

@ -1,4 +1,4 @@
const enviroment = "";
const enviroment = ".dev";
export const configConstants = {
API_URL: "",

@ -1,5 +1,5 @@
export * from "./config";
export * from "./configConstants";
// export * from "./configConstants";
export * from "./path";
export * from "./alerts";
export * from "./user";

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import Header from "../../../_components/Header";
import {
renderIconSearchInput,
@ -13,13 +13,13 @@ import { history } from "../../../_helpers/history";
import { PATH } from "../../../_constants/path";
import { apiCaller } from "../../../_helpers";
import { configConstants } from "../../../_constants";
import { listStatusCriteria } from "../../../_constants/common";
import { listStatusCriteria, TYPE_DISPATCH } from "../../../_constants/common";
import moment from "moment";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { alertActions } from "../../../_actions";
export default function CriteriaManage() {
const [searchText, setSearchText] = useState();
const [searchText, setSearchText] = useState('');
const [listIdSelected, setListIdSelected] = useState([]);
const [listData, setListData] = useState([]);
const [listProvince, setListProvince] = useState([
@ -32,13 +32,17 @@ export default function CriteriaManage() {
]);
const [districtSelect, setDistrictSelect] = useState(listDistrict[0]);
const [isLoadingDistrict, setIsLoadingDistrict] = useState(false);
const [statusCriteriaSelected, setStatusCriteriaSelected] = useState(
listStatusCriteria[0],
);
const [statusCriteriaSelected, setStatusCriteriaSelected] = useState(listStatusCriteria[0]);
const [isLoading, setIsLoading] = useState(false);
const [isFiltered, setIsFiltered] = useState(false);
const [endPointSave, setendPointSave] = useState(null)
const [isLoadMoreOnline, setLoadMoreOnline] = useState(true);
const [limitOnline] = useState(10);
const [offsetOnline, setOffsetOnline] = useState(0);
const [isEndOnlineClasses, setIsEndOnlineClasses] = useState(false);
const [isCheckAll,setIsCheckAll] = useState(false)
const listRef = useRef(null);
const dispatch = useDispatch();
const changeProvince = (item) => {
setProvinceSelect(item);
setDistrictSelect({ value: "", label: "Lọc theo huyện" });
@ -53,6 +57,21 @@ export default function CriteriaManage() {
setDistrictSelect(item);
};
const scrollToTop = () => {
if (listRef.current) {
listRef.current.scrollTo({
top: 0,
behavior: 'smooth',
});
}
};
useEffect(() => {
if(isCheckAll){
handleSelectAll()
}
}, [listData])
const handleSelectAll = () => {
if (listData?.every((item) => listIdSelected?.includes(item?.school_id))) {
setListIdSelected((prev) =>
@ -68,6 +87,7 @@ export default function CriteriaManage() {
newListIdSelected.push(item?.school_id);
});
setListIdSelected(newListIdSelected);
setIsCheckAll(true)
};
const handleSelectItem = (item) => {
@ -87,6 +107,7 @@ export default function CriteriaManage() {
);
return;
}
saveCurrentState()
history.push(
PATH.criteria.setting +
`?schoolList=${encodeURIComponent(JSON.stringify(listIdSelected))}`,
@ -117,10 +138,8 @@ export default function CriteriaManage() {
}),
);
setListIdSelected([]);
await setSearchText("");
await changeProvince(listProvince[0]);
await setStatusCriteriaSelected(listStatusCriteria[0]);
getDataCriteria();
setIsEndOnlineClasses(false)
await getDataCriteria(true);
}
} catch (err) {
dispatch(
@ -140,27 +159,41 @@ export default function CriteriaManage() {
) {
return;
}
scrollToTop()
setOffsetOnline(0)
setLoadMoreOnline(true)
setIsEndOnlineClasses(false)
setIsFiltered(true);
getDataCriteria(true);
setListIdSelected([])
setIsCheckAll(false)
};
const getDataCriteria = async (isFilter = false) => {
try {
setIsLoading(true);
const endPoint =
`/report/api_report/getOrganizationAndCriteria` +
(isFilter
? `?province_id=${
!!provinceSelect?.value ? provinceSelect?.value : ""
const queryParams = [];
const addParam = (key, value) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.push(`${key}=${value}`);
}
${!!districtSelect?.value ? `&district_id=${districtSelect?.value}` : ""}
${
!!statusCriteriaSelected?.value
? `&status=${statusCriteriaSelected?.value}`
: ""
};
addParam('province_id', provinceSelect?.value);
addParam('district_id', districtSelect?.value);
if (statusCriteriaSelected?.value !== undefined && statusCriteriaSelected?.value !== null) {
const statusValue = statusCriteriaSelected.value === '0'
? JSON.stringify(statusCriteriaSelected.value)
: statusCriteriaSelected.value;
addParam('status', statusValue);
}
${!!searchText ? `&school_name=${searchText}` : ""}`
: "");
addParam('school_name', searchText);
const endPoint = `/report/api_report/getOrganizationAndCriteria${
isFilter && queryParams.length ? `?${queryParams.join("&")}` : ""
}`;
setendPointSave(endPoint)
const res = await apiCaller(endPoint, "GET");
if (res?.status) {
setListData(res?.data);
@ -232,9 +265,114 @@ export default function CriteriaManage() {
useEffect(() => {
getProvinceList();
// getDataCriteria();
}, []);
const isDisabled = () =>
!(
searchText ||
provinceSelect.value ||
districtSelect.value ||
statusCriteriaSelected.value
);
useEffect(async() => {
const savedState = sessionStorage.getItem('criteriaManageState');
if (savedState) {
const state = JSON.parse(savedState);
setSearchText(state.searchText);
setListIdSelected(state.listIdSelected);
setProvinceSelect(state.provinceSelect);
setDistrictSelect(state.districtSelect);
setStatusCriteriaSelected(state.statusCriteriaSelected);
setendPointSave(state.endPointSave)
if(state.endPointSave){
const res = await apiCaller(state.endPointSave, "GET");
if (res?.status) {
setListData(res?.data);
setIsFiltered(true);
setListIdSelected([]);
}
}
sessionStorage.removeItem('criteriaManageState');
}
}, []);
const saveCurrentState = () => {
const currentState = {
searchText,
listIdSelected,
provinceSelect,
districtSelect,
statusCriteriaSelected,
endPointSave,
};
sessionStorage.setItem('criteriaManageState', JSON.stringify(currentState));
};
const handleScroll = (e) => {
if (
e.target.scrollHeight - e.target.scrollTop < e.target.clientHeight + 5 &&
isLoadMoreOnline &&
!isLoading
) {
if(listData.length < limitOnline) return
onLoadMoreClasses();
}
};
// Load More Classes for Teacher
const onLoadMoreClasses = async () => {
let offsetOnlineMore = offsetOnline + limitOnline;
let listNext = [];
setIsLoading(true);
try {
if (!isEndOnlineClasses) {
const queryParams = [];
const addParam = (key, value) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.push(`${key}=${value}`);
}
};
addParam('province_id', provinceSelect?.value);
addParam('district_id', districtSelect?.value);
if (statusCriteriaSelected?.value !== undefined && statusCriteriaSelected?.value !== null) {
const statusValue = statusCriteriaSelected.value === '0'
? JSON.stringify(statusCriteriaSelected.value)
: statusCriteriaSelected.value;
addParam('status', statusValue);
}
addParam('school_name', searchText);
const endPoint = `/report/api_report/getOrganizationAndCriteria?limit=${limitOnline}&offset=${offsetOnlineMore}${
queryParams.length ? `&${queryParams.join("&")}` : ""
}`;
const res = await apiCaller(endPoint, "GET");
if (!res.data){
setIsEndOnlineClasses(true);
} else {
listNext = res?.data;
setOffsetOnline(offsetOnline + limitOnline);
if (res?.data?.length < limitOnline) {
setLoadMoreOnline(false);
if (res?.data?.length == 0) setLoadMoreOnline(false);
setIsEndOnlineClasses(true);
}
}
} else {
setIsEndOnlineClasses(true);
}
let listPrev = listData;
setListData(listPrev?.concat(listNext));
} catch (e) {
} finally {
setIsLoading(false);
}
};
return (
<div className="flex-1">
<Header
@ -274,7 +412,7 @@ export default function CriteriaManage() {
className={"criteria-manage-item"}
/>
<div className="criteria-manage-filter-action">
<PrimaryButton onClick={handleFilter}>Áp dụng</PrimaryButton>
<PrimaryButton isDisabled={isDisabled()} onClick={handleFilter}>Áp dụng</PrimaryButton>
</div>
</div>
<div className="criteria-manage-list-container">
@ -314,7 +452,7 @@ export default function CriteriaManage() {
onClick={handleSelectAll}
placement="right"
/>
<div className="criteria-manage-list scrollbar-custom">
<div ref={listRef} onScroll={handleScroll} className="criteria-manage-list scrollbar-custom">
{listData?.map((item, index) => {
return (
<div className="criteria-manage-item">

@ -94,6 +94,7 @@
display: flex;
align-items: center;
gap: 32px;
width: max-content;
.criteria-setting-item-detail-label {
display: flex;
@ -120,8 +121,8 @@
border-radius: 4px;
padding: 0;
margin-right: 1px;
height: 2.8rem;
width: 60px;
height: 2.4rem;
width: 40px;
font-size: 1.8rem;
text-align: center;
color: var(--primary-color);

@ -12,6 +12,10 @@ import InputText from "../../../_components/Auth/InputText";
import { DEFAULT_SETTING_CRITERIA } from "../../../_constants/common";
import { alertActions } from "../../../_actions";
const textTilte = 'Mức độ hoàn thành tiêu chí'
const textContent = 'Mức độ hoàn thành tiêu chí được tính bằng tiêu chí có mức hoàn thành thấp nhất trong 2 tiêu chí số lần giao bài trong tháng và tỷ lệ học sinh làm bài được giao trong tháng'
export default function CriteriaSetting() {
const search = history?.location?.search;
const params = new URLSearchParams(search);
@ -20,36 +24,130 @@ export default function CriteriaSetting() {
const dispatch = useDispatch()
const [criteriaTarget, setCriteriaTarget] = useState(DEFAULT_SETTING_CRITERIA);
const [showAlert, setShowAlert] = useState(false);
const [alertMessage, setAlertMessage] = useState("");
const [isApply, setIsApply] = useState(false)
const schoolList = isJsonString(decodeURIComponent(_schoolList))
? JSON.parse(decodeURIComponent(_schoolList))
: decodeURIComponent(_schoolList);
function AlertSuccess({ message, onComplete, notShowComplete }) {
function close() {
onComplete();
dispatch(alertActions.clear());
history.goBack();
}
if (notShowComplete) {
return null;
} else {
return (
<div
id="modal-center"
className="uk-flex-top uk-modal uk-flex uk-open"
uk-modal=""
>
<div className="uk-modal-dialog uk-modal-body uk-margin-auto-vertical root-alert">
<p className="text-center">{message}</p>
<div className="form-sunE-button root-alert-button">
<button className="btn-line-blue" onClick={close}>
Đóng
</button>
</div>
</div>
</div>
);
}
}
const isValid = () => {
if (criteriaTarget.assign.enable) {
if (!criteriaTarget.studentDone.enable) {
return (
!(criteriaTarget.assign.target1 &&
criteriaTarget.assign.target2 &&
criteriaTarget.assign.target1 < criteriaTarget.assign.target2)
);
} else {
return (
!(criteriaTarget.assign.target1 &&
criteriaTarget.assign.target2 &&
criteriaTarget.assign.target1 < criteriaTarget.assign.target2 &&
criteriaTarget.studentDone.target1 != null &&
criteriaTarget.studentDone.target2 != null &&
criteriaTarget.studentDone.target1 < criteriaTarget.studentDone.target2)
);
}
} else if (criteriaTarget.studentDone.enable) {
return (
!(criteriaTarget.studentDone.target1 != null &&
criteriaTarget.studentDone.target2 != null &&
criteriaTarget.studentDone.target1 < criteriaTarget.studentDone.target2)
);
} else {
return true;
}
};
const saveCriteriaSetting = async () => {
try {
const dataSave = {
school_list: schoolList,
school_list: schoolList || [authentication?.user?.organization_id],
assign_number_active: criteriaTarget.assign.enable ? 1 : 0,
assign_number_target_1: criteriaTarget.assign.target1,
assign_number_target_2: criteriaTarget.assign.target2,
student_done_active: criteriaTarget.studentDone.enable ? 1 : 0,
student_done_target_1: criteriaTarget.studentDone.target1,
student_done_target_2: criteriaTarget.studentDone.target2,
};
let res;
if (isApply) {
res = await handleApply(dataSave);
} else {
res = await handleNonApply(dataSave);
}
const res = await apiCaller(
"/report/api_report/assignCriteriaToOrganization",
"PUT",
dataSave,
);
if (res?.status) {
dispatch(alertActions.success({
message: res?.msg
}))
setAlertMessage(res?.msg);
setShowAlert(true);
}
} catch (err) {
console.log("err: ", err);
console.error("Error: ", err);
}
};
const handleApply = async (dataSave) => {
if (isValid()) {
return await apiCaller(
"/report/api_report/removeCriteriaFromOrganization",
"PUT",
{ school_list: schoolList || [authentication?.user?.organization_id] }
);
} else {
return await apiCaller(
"/report/api_report/assignCriteriaToOrganization",
"PUT",
dataSave
);
}
};
const handleNonApply = async (dataSave) => {
if (!isValid()) {
return await apiCaller(
"/report/api_report/assignCriteriaToOrganization",
"PUT",
dataSave
);
} else {
setAlertMessage("Bạn chưa thực hiện bất kỳ thay đổi nào");
setShowAlert(true);
return null;
}
};
const getData = async () => {
try {
@ -59,6 +157,7 @@ export default function CriteriaSetting() {
"GET",
);
if (res?.status) {
setIsApply(res?.data?.status_criteria ==='1')
setCriteriaTarget({
assign: {
target1: res?.data?.assign_number_target_1 || DEFAULT_SETTING_CRITERIA.assign.target1,
@ -77,9 +176,34 @@ export default function CriteriaSetting() {
}
};
useEffect(() => {
useEffect( async () => {
if (schoolList?.length === 1) {
getData();
}else if(authentication?.user){
try {
const res = await apiCaller(
"/report/api_report/getCriteriaByOrganization?organization_id=" +
authentication?.user?.organization_id,
"GET",
);
if (res?.status) {
setIsApply(res?.data?.status_criteria ==='1')
setCriteriaTarget({
assign: {
target1: res?.data?.assign_number_target_1 || DEFAULT_SETTING_CRITERIA.assign.target1,
target2: res?.data?.assign_number_target_2 || DEFAULT_SETTING_CRITERIA.assign.target2,
enable: !!(res?.data?.assign_number_active == 1),
},
studentDone: {
target1: res?.data?.student_done_target_1 || DEFAULT_SETTING_CRITERIA.studentDone.target1,
target2: res?.data?.student_done_target_2 || DEFAULT_SETTING_CRITERIA.studentDone.target2,
enable: !!(res?.data?.student_done_active == 1),
}
})
}
} catch (err) {
console.log("err: ", err);
}
}
}, []);
@ -97,6 +221,11 @@ export default function CriteriaSetting() {
);
};
const onlyRead = ()=>{
const isRead = authentication?.user?.role==='supper_admin'
return !isRead
}
const renderRightItem = ({ title, desc, valueName, unit, key, maxLengthInput }) => {
return (
<div
@ -115,7 +244,6 @@ export default function CriteriaSetting() {
},
})
}
// disabled={true}
/>
</div>
<div style={{ width: "100%" }}>
@ -128,18 +256,21 @@ export default function CriteriaSetting() {
<span>Hoàn thành mức 1:</span>
</div>
<div className="criteria-setting-item-detail-value">
{`${valueName} < `}
{`${valueName} ít hơn `}
<input
value={criteriaTarget[key].target1}
onChange={(e) => {
if (!!maxLengthInput && e.target.value.length > maxLengthInput) {
const value = e.target.value;
if (!/^[1-9]\d*$/.test(value) && value !== "") {
return;
}else if (!!maxLengthInput && e.target.value.length > maxLengthInput) {
return;
}
setCriteriaTarget({
...criteriaTarget,
[key]: {
...criteriaTarget[key],
target1: e.target.value,
target1: value,
},
});
}}
@ -154,15 +285,18 @@ export default function CriteriaSetting() {
<span>Hoàn thành mức 2:</span>
</div>
<div className="criteria-setting-item-detail-value">
{` ${valueName} từ `}
<span>
{criteriaTarget[key].target1}
{unit}
</span>
{` < ${valueName} < `}
{' đến ít hơn '}
<input
value={criteriaTarget[key].target2}
onChange={(e) => {
if (!!maxLengthInput && e.target.value.length > maxLengthInput) {
if (!/^[1-9]\d*$/.test(e.target.value) && e.target.value !== "") {
return;
}else if (!!maxLengthInput && e.target.value.length > maxLengthInput) {
return;
}
setCriteriaTarget({
@ -184,7 +318,7 @@ export default function CriteriaSetting() {
<span>Hoàn thành mức 3:</span>
</div>
<div className="criteria-setting-item-detail-value">
{`${valueName} > `}
{`${valueName} lớn hơn hoặc bằng `}
<span>
{criteriaTarget[key].target2}
{unit}
@ -207,12 +341,10 @@ export default function CriteriaSetting() {
<div className="criteria-setting-container bg-sub-main-img">
<div className="criteria-setting-left-side">
<p className="criteria-setting-left-title">
MỨC ĐỘ CĐS SUNDAY ENGLISH
{textTilte.toLocaleUpperCase()}
</p>
<p className="criteria-setting-left-desc">
Mức độ CĐS Sunday English được tính bằng tiêu chí mức hoàn
thành thấp nhất trong 2 tiêu chí số lần giao bài trong tháng tỷ
lệ học sinh làm bài được giao trong tháng.
{textContent}
</p>
<div className="criteria-setting-left-img">
<img src="/assets/imgs/book_criteria_setting.png" />
@ -222,13 +354,13 @@ export default function CriteriaSetting() {
<div className="criteria-setting-right-content">
{renderRightItem({
title: "Số lần giao bài trong tháng",
desc: "(Bao gồm các lần giao bài có thời gian kết thúc trong tháng đó)",
valueName: "Số lần giao",
desc: "Gồm các lần giao bài có thời gian kết thúc trong tháng đó",
valueName: "Số lần giao ",
key: "assign",
})}
{renderRightItem({
title: "Tỷ lệ học sinh làm bài được giao trong tháng",
desc: "(Tỷ lệ học sinh làm bài được tính bằng số học sinh đã hoàn thành tất cả các bài tập được giao trong tháng (trừ những bài chưa đến hạn) trên tổng số học sinh của lớp. Học sinh để quá hạn bài tập trong tháng nhưng làm bài trước khi hết tháng thì vẫn được tính là hoàn thành bài tập).",
desc: "Tỷ lệ học sinh làm bài được tính bằng số học sinh đã hoàn thành tất cả các bài tập được giao trong tháng (trừ những bài chưa đến hạn) trên tổng số học sinh của lớp. Học sinh để quá hạn bài tập trong tháng nhưng làm bài trước khi hết tháng thì vẫn được tính là hoàn thành bài tập.",
valueName: "Tỉ lệ làm bài",
unit: "%",
key: "studentDone",
@ -244,13 +376,19 @@ export default function CriteriaSetting() {
>
Quay lại
</PrimaryButton>
{authentication?.user?.role === USER_ROLE.ADMIN && (
{(authentication?.user?.role === USER_ROLE.ADMIN || authentication?.user?.role === USER_ROLE.HEADMASTER) && (
<PrimaryButton onClick={saveCriteriaSetting}>Lưu</PrimaryButton>
)}
</div>
</div>
</div>
</div>
{showAlert && (
<AlertSuccess
message={alertMessage}
onComplete={() => setShowAlert(false)}
/>
)}
</div>
);
}

@ -7,7 +7,7 @@
overflow: auto;
.admin-home-left-side {
width: 30%;
min-width: 340px;
border-right: 1px solid #4d4d4d;
.admin-home-form-select {
@ -25,6 +25,7 @@
.admin-home-search-input {
border-radius: 40px;
height: 46px;
min-width: 340px;
@include screen_pc_sm {
height: 34px;

@ -9,7 +9,7 @@ import PrimaryButton from "../../../_components/Button/PrimaryButton";
import RateStar from "../../../_components/RateStar";
import RootRadioGroup from "../../../_components/RootRadioGroup";
import { LIST_TYPE_FILTER_ADMIN_HOME } from "../../../_constants/common";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import InputText from "../../../_components/Auth/InputText";
import { apiCaller, history } from "../../../_helpers";
import { configConstants, PATH } from "../../../_constants";
@ -37,18 +37,36 @@ export default function AdminHome() {
const [isLoading, setIsLoading] = useState(false);
const [listSchool, setListSchool] = useState([]);
const [isSearched, setIsSearched] = useState(false);
const [endPointSave, setendPointSave] = useState(null)
const [isLoadMoreOnline, setLoadMoreOnline] = useState(true);
const [limitOnline] = useState(10);
const [offsetOnline, setOffsetOnline] = useState(0);
const [isEndOnlineClasses, setIsEndOnlineClasses] = useState(false);
const listRef = useRef(null);
const dispatch = useDispatch();
const isFilterSchool = typeFilter === LIST_TYPE_FILTER_ADMIN_HOME[0]?.value;
const getSearchText = () => (isFilterSchool ? searchTextSchool : searchTextTeacher);
const setSearchText = isFilterSchool ? setSearchTextSchool : setSearchTextTeacher;
const getData = async () => {
try {
setIsLoading(true);
const endPoint = `/api_organization/get_organization?province_id=${
provinceSelect?.value
}${
!!districtSelect?.value ? `&district_id=${districtSelect?.value}` : ""
}${!!searchTextSchool ? `&name_school_find=${searchTextSchool}` : ""}`;
let queryParams = []
if(provinceSelect?.value){
queryParams.push(`province_id=${provinceSelect?.value}`)
}
if(districtSelect?.value){
queryParams.push(`district_id=${districtSelect?.value}`)
}
if(!!searchTextSchool){
queryParams.push(`name_school_find=${searchTextSchool}`)
}
const endPoint = `/api_organization/get_organization${
queryParams.length ? `?${queryParams.join("&")}` : ""
}`;
setendPointSave(endPoint)
const res = await apiCaller(endPoint, "GET");
setIsLoading(false);
if (res?.status) {
@ -60,6 +78,15 @@ export default function AdminHome() {
}
};
const scrollToTop = () => {
if (listRef.current) {
listRef.current.scrollTo({
top: 0,
behavior: 'smooth',
});
}
};
const getDataTeacher = async () => {
try {
setIsLoading(true);
@ -67,13 +94,15 @@ export default function AdminHome() {
const res = await apiCaller(endPoint, "GET");
setIsLoading(false);
if (res?.status) {
history.push(
replacePathParams(PATH.home.detailTeacher, {
teacherId: res?.data?.[0]?.teacher_id,
}) +
"?teacher_name=" +
encodeURIComponent(res?.data?.[0]?.teacher_name),
);
const teacherId = res?.data?.[0]?.teacher_id;
const teacherName = encodeURIComponent(res?.data?.[0]?.teacher_name);
const detailPath = replacePathParams(PATH.home.detailTeacher, { teacherId});
history.push({
pathname: detailPath,
search: `?teacher_name=${teacherName}`,
state: { isBack: true }
});
}
} catch (err) {
setIsLoading(false);
@ -161,13 +190,24 @@ export default function AdminHome() {
};
const handleClickSchoolItem = (item) => {
history.push(replacePathParams(PATH.home.detailSchool, {schoolId: item?.organization_id}) +
"?school_name=" +
encodeURIComponent(item?.organization_name));
saveCurrentState()
const schoolId = item?.organization_id;
const schoolName = encodeURIComponent(item?.organization_name);
const detailPath = replacePathParams(PATH.home.detailSchool, { schoolId});
history.push({
pathname: detailPath,
search: `?school_name=${schoolName}`,
state: { isBack: true }
});
};
const handleSubmit = () => {
if (isFilterSchool) {
scrollToTop()
setOffsetOnline(0)
setLoadMoreOnline(true)
setIsEndOnlineClasses(false)
setIsSearched(true);
getData();
return;
@ -187,10 +227,7 @@ export default function AdminHome() {
};
const validateParam = () => {
if (isFilterSchool) {
return !!provinceSelect.value || !!searchTextSchool;
}
return true;
return !!provinceSelect.value || !!getSearchText();
};
useEffect(() => {
@ -198,6 +235,92 @@ export default function AdminHome() {
getProvinceList();
}, []);
useEffect(async() => {
const savedState = sessionStorage.getItem('adminState');
if (savedState) {
const state = JSON.parse(savedState);
setSearchTextSchool(state.searchTextSchool);
// setProvinceSelect(state.provinceSelect);
changeProvince(state.provinceSelect)
setDistrictSelect(state.districtSelect);
setendPointSave(state.endPointSave)
if(state.endPointSave){
const res = await apiCaller(state.endPointSave, "GET");
if (res?.status) {
setListSchool(res?.data);
}
}
sessionStorage.removeItem('adminState');
}
}, []);
const saveCurrentState = () => {
const currentState = {
searchTextSchool,
provinceSelect,
districtSelect,
endPointSave,
};
sessionStorage.setItem('adminState', JSON.stringify(currentState));
};
const handleScroll = (e) => {
if (
e.target.scrollHeight - e.target.scrollTop < e.target.clientHeight + 5 &&
isLoadMoreOnline &&
!isLoading
) {
if(listSchool.length < limitOnline) return
onLoadMoreClasses();
}
};
// Load More Classes for Teacher
const onLoadMoreClasses = async () => {
let offsetOnlineMore = offsetOnline + limitOnline;
let concatListSide = [];
setIsLoading(true);
try {
if (!isEndOnlineClasses) {
let queryParams = []
if(provinceSelect?.value){
queryParams.push(`province_id=${provinceSelect?.value}`)
}
if(districtSelect?.value){
queryParams.push(`district_id=${districtSelect?.value}`)
}
if(!!searchTextSchool){
queryParams.push(`name_school_find=${searchTextSchool}`)
}
const endPoint = `/api_organization/get_organization?limit=${limitOnline}&offset=${offsetOnlineMore}
${
queryParams.length ? `&${queryParams.join("&")}` : ""
}`;
const res = await apiCaller(endPoint, "GET");
if(!res?.data){
setIsEndOnlineClasses(true);
} else {
concatListSide = res?.data;
setOffsetOnline(offsetOnline + limitOnline);
if (res?.data?.length < limitOnline) {
setLoadMoreOnline(false);
if (res?.data?.length == 0) setLoadMoreOnline(false);
setIsEndOnlineClasses(true);
}
}
} else {
setIsEndOnlineClasses(true);
}
let listClassSide = listSchool;
setListSchool(listClassSide?.concat(concatListSide));
} catch (e) {
} finally {
setIsLoading(false);
}
};
return (
<div className="flex-1">
<Header icon={renderIconHome({ color: "#4D4D4D" })} title={"Trang chủ"} />
@ -217,10 +340,8 @@ export default function AdminHome() {
</div>
<InputText
className="admin-home-search-input"
value={isFilterSchool ? searchTextSchool : searchTextTeacher}
setValue={
isFilterSchool ? setSearchTextSchool : setSearchTextTeacher
}
value={getSearchText()}
setValue={setSearchText}
type="text"
name="searchText"
placeholder={
@ -269,7 +390,7 @@ export default function AdminHome() {
</PrimaryButton>
)}
</div>
<div className="admin-home-list-school admin-home-right-p-h scrollbar-custom">
<div ref={listRef} onScroll={handleScroll} className="admin-home-list-school admin-home-right-p-h scrollbar-custom">
{listSchool.map((item, index) => {
return (
<div

@ -31,6 +31,11 @@
.detail-grade-list-action {
align-self: flex-end;
}
.arrow-button{
display: flex;
justify-content: space-between;
width: 100%;
}
.detail-grade-list-class {
margin-top: 16px;

@ -7,7 +7,7 @@ import { renderIconHome } from "../../../_components/renderIcon";
import { apiCaller, history } from "../../../_helpers";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { defaultMonthYearSemester, getListMonthBySemester, LIST_SCHOOL_YEAR, LIST_SEMESTER } from "../../../_constants/common";
import { configConstants } from "../../../_constants";
import { exportExcel } from "../../../_helpers/utils";
@ -29,6 +29,11 @@ export default function DetailGrade() {
const [month, setMonth] = useState(grade?.filterGrade?.month);
const [listClass, setListClass] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isLoadMoreOnline, setLoadMoreOnline] = useState(true);
const [limitOnline] = useState(10);
const [offsetOnline, setOffsetOnline] = useState(0);
const [isEndOnlineClasses, setIsEndOnlineClasses] = useState(false);
const listRef = useRef(null);
const schoolName = !!school_name ? decodeURIComponent(school_name) : '';
const gradeName = !!grade_name ? decodeURIComponent(grade_name) : '';
@ -37,8 +42,20 @@ export default function DetailGrade() {
setSemester(item);
setMonth(getListMonthBySemester(item?.value)?.[0]);
};
const scrollToTop = () => {
if (listRef.current) {
listRef.current.scrollTo({
top: 0,
behavior: 'smooth',
});
}
};
const handleFilter = () => {
setOffsetOnline(0)
setLoadMoreOnline(true)
setIsEndOnlineClasses(false)
scrollToTop()
getData();
};
@ -51,10 +68,9 @@ export default function DetailGrade() {
"Tiêu chí giao bài",
"Tỷ lệ học sinh làm bài",
"Tiêu chí tỷ lệ học sinh làm bài",
"Khối",
"Trường",
// "Huyện",
// "Tỉnh",
"Huyện",
"Tỉnh",
"Tháng",
"học kỳ",
"Niên khóa",
@ -67,10 +83,9 @@ export default function DetailGrade() {
item?.assign_number_level || 0,
`${item?.student_done_per || 0}%`,
item?.student_done_per_level || 0,
gradeName || '',
schoolName || '',
// "Huyện",
// "Tỉnh",
item?.organization_name || '',
item?.district ||'',
item?.province ||'',
!!month?.value
? month.value
: getListMonthBySemester(semester.value)
@ -120,12 +135,68 @@ export default function DetailGrade() {
getData();
}, [])
const handleScroll = (e) => {
if (
e.target.scrollHeight - e.target.scrollTop < e.target.clientHeight + 5 &&
isLoadMoreOnline &&
!isLoading
) {
if(listClass.length < limitOnline) return
onLoadMoreClasses();
}
};
// Load More Classes for Teacher
const onLoadMoreClasses = async () => {
let offsetOnlineMore = offsetOnline + limitOnline;
let listNext = [];
setIsLoading(true);
try {
if (!isEndOnlineClasses) {
const endPoint = `/report/api_report/getListClassInGradeOrganization?school_id=${
schoolId
}&grade_id=${
gradeId
}${
!!schoolYear?.value ? `&year=${schoolYear?.value}` : ""
}${
!!semester?.value ? `&semester=${semester?.value}` : ""
}${
!!month?.value ? `&month=${month?.value}` : ""
}&limit=${limitOnline}&offset=${offsetOnlineMore}`;
const res = await apiCaller(endPoint, "GET");
if(!res.data){
setIsEndOnlineClasses(true);
} else {
listNext = res?.data;
setOffsetOnline(offsetOnline + limitOnline);
if (res?.data?.length < limitOnline) {
setLoadMoreOnline(false);
if (res?.data?.length == 0) setLoadMoreOnline(false);
setIsEndOnlineClasses(true);
}
}
} else {
setIsEndOnlineClasses(true);
}
let listPrev = listClass;
setListClass(listPrev?.concat(listNext));
} catch (e) {
} finally {
setIsLoading(false);
}
};
return (
<div className="flex-1">
<Header
icon={renderIconHome({ color: "#4D4D4D" })}
title={schoolName}
subtitles={[gradeName]}
isBack={true}
/>
<div className="container-page-header container-page-sidebar">
<div className="detail-grade-container bg-main-img">
@ -153,9 +224,11 @@ export default function DetailGrade() {
</div>
<div className="detail-grade-right-side">
<div className="detail-grade-list-action detail-grade-right-p-h">
<div>
{!!listClass?.length && <PrimaryButton onClick={handleExport}>Xuất excel</PrimaryButton>}
</div>
<div className="detail-grade-list-class detail-grade-right-p-h scrollbar-custom">
</div>
<div ref={listRef} onScroll={handleScroll} className="detail-grade-list-class detail-grade-right-p-h scrollbar-custom">
{!isLoading && !listClass?.length && (
<p style={{fontSize: '1.8rem', fontWeight: 700}}>
Không lớp nào
@ -204,6 +277,10 @@ export default function DetailGrade() {
);
})}
</div>
{/* <div className="arrow-button">
<PrimaryButton isDisabled={true} onClick={()=>{}}>{'<'}</PrimaryButton>
<PrimaryButton onClick={()=>{}}>{'>'}</PrimaryButton>
</div> */}
</div>
</div>
</div>

@ -4,7 +4,7 @@
display: flex;
.detail-room-education-left-side {
width: 40%;
width: 43%;
border-right: 1px solid #c7c7c7;
display: flex;
flex-direction: column;
@ -36,13 +36,37 @@
flex-direction: column;
height: 100%;
.detail-room-education-header {
width: 100%;
.detail-room-education-list-header {
justify-content: space-around;
margin: 12px;
gap: 12px;
.criteria-manage-search-input {
height: 46px;
border-radius: 40px;
@include screen_pc_sm {
height: 36px;
}
}
.criteria-manage-item {
min-width: fit-content;
flex: 1;
}
}
}
.detail-room-education-list {
flex: 1;
gap: 20px;
display: flex;
flex-direction: column;
padding-bottom: 16px;
padding-left: 32px;
padding-left: 16px;
@include screen_pc_sm {
gap: 16px;

@ -1,22 +1,26 @@
import { useEffect, useState } from "react";
import Header from "../../../_components/Header";
import { renderIconHome } from "../../../_components/renderIcon";
import { renderIconButton, renderIconHome, renderIconSearchInput } from "../../../_components/renderIcon";
import RateStar from "../../../_components/RateStar";
import { defaultMonthYearSemester, getListMonthBySemester, LIST_SCHOOL_YEAR, LIST_SEMESTER, PRIMARY_COLOR } from "../../../_constants/common";
import { configConstants, PATH } from "../../../_constants";
import RootSelect from "../../../_components/RootSelect";
import PrimaryButton from "../../../_components/Button/PrimaryButton";
import { useSelector } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import './detailRoomEducation.style.scss'
import BoxDoughnutChart from "../../../_components/boxChart/BoxDoughnutChar";
import BoxDoughnutBarChart from "../../../_components/boxChart/BoxDoughnutBarChart";
import { apiCaller, history } from "../../../_helpers";
import { replacePathParams } from "../../../_helpers/utils";
import { exportExcel, replacePathParams } from "../../../_helpers/utils";
import $ from "jquery";
import { useParams } from "react-router-dom";
import { useLocation, useParams } from "react-router-dom";
import InputText from "../../../_components/Auth/InputText";
import { alertActions } from "../../../_actions";
export default function DetailRoomEducation() {
const {idRoom} = useParams()
const location = useLocation();
const isBack = location.state?.isBack;
const grade = useSelector((state) => state.grade);
const authentication = useSelector((state) => state.authentication);
const [dateStudentChart, setDateStudentChart] = useState(new Date())
@ -34,6 +38,16 @@ export default function DetailRoomEducation() {
const [isLoadingStatisticCircle, setIsLoadingStatisticCircle] = useState(false);
const [isLoadingStudentChart, setIsLoadingStudentChart] = useState(false);
const [isLoadingTeacherChart, setIsLoadingTeacherChart] = useState(false);
const [isFilterChanged, setIsFilterChanged] = useState(false);
const [isLoadMoreOnline, setLoadMoreOnline] = useState(true);
const [limitOnline] = useState(10);
const [offsetOnline, setOffsetOnline] = useState(0);
const [isEndOnlineClasses, setIsEndOnlineClasses] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [searchText, setSearchText] = useState('');
const dispatch = useDispatch();
const isSGD = authentication?.user?.organization_name?.toLowerCase().includes('sở');
const getListOrganization = async () => {
try {
@ -185,23 +199,41 @@ export default function DetailRoomEducation() {
}
const goToDetailSchool = (item) => {
history.push(replacePathParams(PATH.home.detailSchool, {schoolId: item?.organization_id}) +
"?school_name=" +
encodeURIComponent(item?.organization_name));
const schoolId = item?.organization_id;
const schoolName = encodeURIComponent(item?.organization_name);
const detailPath = replacePathParams(PATH.home.detailSchool, { schoolId});
history.push({
pathname: detailPath,
search: `?school_name=${schoolName}`,
state: { isBack: true }
});
}
useEffect(() => {
getDataStatisticCircle()
getListOrganization()
}, [])
const fetchData = async () => {
await getDataStatisticCircle();
await getListOrganization();
};
fetchData();
}, []);
useEffect(() => {
getDataStudentChart()
}, [dateStudentChart])
const fetchStudentData = async () => {
await getDataStudentChart();
};
fetchStudentData();
}, [dateStudentChart]);
useEffect(() => {
getDataTeacherChart()
}, [dateTeacherChart])
const fetchTeacherData = async () => {
await getDataTeacherChart();
};
fetchTeacherData();
}, [dateTeacherChart]);
useEffect(() => {
if (isLoadingListOrganization || isLoadingStatisticCircle || isLoadingStudentChart || isLoadingTeacherChart) {
@ -211,23 +243,135 @@ export default function DetailRoomEducation() {
}
}, [isLoadingListOrganization, isLoadingStatisticCircle, isLoadingStudentChart, isLoadingTeacherChart])
const renderIconButton = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path stroke-dasharray="20" stroke-dashoffset="20" d="M3 12h17.5"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="20;0"/></path><path stroke-dasharray="12" stroke-dashoffset="12" d="M21 12l-7 7M21 12l-7 -7"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="12;0"/></path></g></svg>
const handleScroll = (e) => {
if (
e.target.scrollHeight - e.target.scrollTop < e.target.clientHeight + 5 &&
isLoadMoreOnline &&
!isLoading
) {
if(listOrganization.length < limitOnline) return
onLoadMoreClasses();
}
};
// Load More Classes for Teacher
const onLoadMoreClasses = async () => {
let offsetOnlineMore = offsetOnline + limitOnline;
let listNext = [];
setIsLoading(true);
try {
if (!isEndOnlineClasses) {
const endPoint = `/report/api_report/getListOrganizationFromDistrictOrganization?organization_id=${
idRoom
}${
!!schoolYear?.value ? `&year=${schoolYear?.value}` : ""
}${
!!semester?.value ? `&semester=${semester?.value}` : ""
}${
!!month?.value ? `&month=${month?.value}` : ""
}&limit=${limitOnline}&offset=${offsetOnlineMore}`;
const res = await apiCaller(
endPoint,
"GET",
{},
null,
true,
configConstants.API_URL_SETEST,
false
)
// const endPoint = `/report/api_report/getOrganizationAndCriteria?limit=${limitOnline}&offset=${offsetOnlineMore}${
// queryParams.length ? `&${queryParams.join("&")}` : ""
// }`;
// const res = await apiCaller(endPoint, "GET");
if (!res.data){
setIsEndOnlineClasses(true);
} else {
listNext = res?.data;
setOffsetOnline(offsetOnline + limitOnline);
if (res?.data?.length < limitOnline) {
setLoadMoreOnline(false);
if (res?.data?.length == 0) setLoadMoreOnline(false);
setIsEndOnlineClasses(true);
}
}
} else {
setIsEndOnlineClasses(true);
}
let listPrev = listOrganization;
setListOrganization(listPrev?.concat(listNext));
} catch (e) {
} finally {
setIsLoading(false);
}
};
const handleExport = () => {
const listHeader = [
'Stt',
'Tên trường',
'Tiêu chí giao bài',
'Tiêu chí tỷ lệ học sinh làm bài',
'Mức độ hoàn thành tiêu chí',
'Huyện',
'Tỉnh'
];
const listData = listOrganization.map((item, index) => [
index + 1,
item.organization_name || '',
item.criteria_level || 0,
item.student_done_per_level || 0,
item.assign_number_level || 0,
item.district || '',
item.province || '',
]);
exportExcel(listHeader, listData, `Danh sách trường.xlsx`);
};
const getDataTeacher = async () => {
try {
setIsLoading(true);
const endPoint = `/api_teacher/get_teacher_info?search=${searchText}`;
const res = await apiCaller(endPoint, "GET");
setIsLoading(false);
if (res?.status) {
const teacherId = res?.data?.[0]?.teacher_id;
const teacherName = encodeURIComponent(res?.data?.[0]?.teacher_name);
const detailPath = replacePathParams(PATH.home.detailTeacher, { teacherId});
history.push({
pathname: detailPath,
search: `?teacher_name=${teacherName}`,
state: { isBack: true }
});
}
} catch (err) {
setIsLoading(false);
dispatch(
alertActions.error({
message: err?.toString(),
}),
);
}
};
const handleSubmit = () =>{
getDataTeacher()
}
return (
<div className="flex-1">
<Header
icon={renderIconHome({ color: "#4D4D4D" })}
title={"Phòng giáo dục huyện Giao Thủy"}
title={"Phòng giáo dục " + (isSGD ? `${authentication.user.province}` : `${authentication.user.district}`)}
manager={true}
isBack={isBack}
/>
<div className="container-page-header container-page-sidebar">
<div className="detail-room-education-container bg-sub-main-green-img">
<div className="detail-room-education-left-side">
<div className="detail-room-education-statistic-container">
<div className="d-flex flex-1 gap-16" style={{ flexDirection: 'column'}}>
{data ?
(<>
<div className="flex flex-m gap-16" style={{height: '23%'}}>
<BoxDoughnutChart data={[data?.school_join, data?.total_school]} title={'Số trường đã tham gia'} />
<BoxDoughnutChart data={[data?.class_join, data?.total_class]} title={'Số lớp đã tham gia'} />
@ -254,6 +398,10 @@ export default function DetailRoomEducation() {
subTitleLine={"Số học sinh làm bài trong tuần"}
/>
</div>
</>)
:
"Không có dữ liệu để hiển thị"
}
</div>
<div className="d-flex justify-content-center" style={{marginTop: '3rem'}}>
<PrimaryButton className="d-flex" style={{textDecoration: 'underline'}} onClick={goToOutstandingTeacher}>
@ -265,31 +413,71 @@ export default function DetailRoomEducation() {
</div>
<div className="detail-room-education-right-side">
<div className="detail-room-education-list-container">
<div className="detail-room-education-header">
<span style={{fontSize: '2rem', fontWeight: 700, padding: '0 3.2rem'}}>Danh sách trường</span>
<div className="flex gap-16 align-item-center" style={{padding: '1rem 3.2rem'}}>
<div className="d-flex detail-room-education-list-header">
<InputText
className="criteria-manage-search-input criteria-manage-item"
value={searchText}
setValue={setSearchText}
type="text"
name="searchText"
placeholder={"Nhập email hoặc số điện thoại giáo viên"}
renderLabelIcon={renderIconSearchInput}
onKeyUp={(e) => {
if (e.which == 13 && !isFilterSchool) {
handleSubmit();
}
}}
/>
<PrimaryButton
isDisabled={searchText.length === 0}
onClick={handleSubmit}
>
{"Tìm kiếm"}
</PrimaryButton>
<PrimaryButton isDisabled={listOrganization.length === 0} onClick={handleExport}>
Xuất excel
</PrimaryButton>
</div>
</div>
<div className="flex gap-16 align-item-center" style={{padding: '1rem'}}>
<RootSelect
data={LIST_SCHOOL_YEAR}
value={schoolYear}
setValue={setSchoolYear}
style={{flex: 1}}
setValue={(value) => {
setSchoolYear(value);
setIsFilterChanged(true);
}}
style={{flex: 1, minWidth: '200px', padding:'0 10px'}}
/>
<RootSelect
data={LIST_SEMESTER}
value={semester}
setValue={changeSemester}
style={{flex: 0.5}}
setValue={(value) => {
changeSemester(value);
setIsFilterChanged(true);
}}
style={{flex: 0.5, minWidth: '151px', padding:'0 10px'}}
/>
<RootSelect
data={getListMonthBySemester(semester.value)}
value={month}
setValue={setMonth}
style={{flex: 0.5}}
setValue={(value) => {
setMonth(value);
setIsFilterChanged(true);
}}
style={{flex: 0.5, minWidth: '150px', padding:'0 10px'}}
/>
<PrimaryButton onClick={handleFilter}>
<PrimaryButton isDisabled={!isFilterChanged} onClick={() => {
handleFilter();
setIsFilterChanged(false);
}}>
Áp dụng
</PrimaryButton>
</div>
<div className="detail-room-education-list detail-room-education-right-p-h scrollbar-custom">
<div onScroll={handleScroll} className="detail-room-education-list detail-room-education-right-p-h scrollbar-custom">
{!isLoadingListOrganization && !listOrganization?.length && (
<p style={{fontSize: '1.8rem', fontWeight: 700}}>
Không trường nào
@ -300,7 +488,7 @@ export default function DetailRoomEducation() {
<div className="detail-room-education-item" key={index} onClick={() => goToDetailSchool(item)}>
<div className="detail-room-education-item-content">
<div className="detail-room-education-avatar">
<img src={configConstants.BASE_URL + item?.avatar} />
<img src={item?.avatar ? configConstants.BASE_URL + item?.avatar : "/assets/imgs/avatar_school.png"} />
</div>
<div className="detail-room-education-detail">
<div className="detail-room-education-info">

@ -26,10 +26,43 @@
flex-direction: column;
background-color: #fff;
// .search-input {
// border-radius: 40px;
// height: 46px;
// width: 50%;
// @include screen_pc_sm {
// height: 34px;
// }
// }
.education-department-home-header{
width: 100%;
padding: 0 32px;
.detail-education-department-list-header{
justify-content: space-around;
margin: 1.2rem 0;
gap: 32px;
@media (max-width: 1650px) {
flex-direction: column;
}
.criteria-manage-search-input {
height: 46px;
border-radius: 40px;
@include screen_pc_sm {
height: 36px;
}
}
.criteria-manage-item {
min-width: fit-content;
flex: 1;
}
}
}
.education-department-home-note {
font-size: 2rem;
font-weight: 700;
padding-left: 3.2rem;
padding-bottom: 1.2rem;
}
@ -38,6 +71,8 @@
display: flex;
flex-direction: column;
padding: 0 3.2rem;
max-height: 740px;
padding-top: 1.2rem;
.education-department-home-list-room {
flex: 1;

@ -2,6 +2,8 @@ import { useEffect, useState } from "react";
import InputText from "../../../_components/Auth/InputText";
import Header from "../../../_components/Header";
import {
renderIconBook,
renderIconButton,
renderIconHome,
renderIconSearchInput,
} from "../../../_components/renderIcon";
@ -14,7 +16,8 @@ import PrimaryButton from "../../../_components/Button/PrimaryButton";
import { apiCaller, history } from "../../../_helpers";
import { PATH } from "../../../_constants";
import { replacePathParams } from "../../../_helpers/utils";
import { useSelector } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { alertActions } from "../../../_actions";
export default function EducationDepartmentHome() {
const authentication = useSelector((state) => state.authentication);
@ -24,6 +27,15 @@ export default function EducationDepartmentHome() {
const [dataStudentChart, setDataStudentChart] = useState([])
const [dataTeacherChart, setDataTeacherChart] = useState([])
const [listOrganization, setListOrganization] = useState([])
const [searchText, setSearchText] = useState('')
const [isLoadMoreOnline, setLoadMoreOnline] = useState(true);
const [limitOnline] = useState(10);
const [offsetOnline, setOffsetOnline] = useState(0);
const [isEndOnlineClasses, setIsEndOnlineClasses] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const dispatch = useDispatch();
const isSGD = authentication?.user?.organization_name?.toLowerCase().includes('sở');
const getDataOrganization = async () => {
try {
@ -76,55 +88,127 @@ export default function EducationDepartmentHome() {
}
const goToDetailRoomEducation = (item) => {
history.push(replacePathParams(PATH.home.detailRoomEducation, {idRoom: item?.id}))
const idRoom = item?.id;
const detailPath = replacePathParams(PATH.home.detailRoomEducation, { idRoom });
history.push({
pathname: detailPath,
state: { isBack: true },
});
}
useEffect(() => {
getDataOrganization()
getDataStatisticCircle()
}, [])
const fetchData = async () => {
await getDataOrganization();
await getDataStatisticCircle();
};
fetchData();
}, []);
useEffect(() => {
getDataStudentChart()
}, [dateStudentChart])
const fetchStudentData = async () => {
await getDataStudentChart();
};
fetchStudentData();
}, [dateStudentChart]);
useEffect(() => {
getDataTeacherChart()
}, [dateTeacherChart])
const fetchTeacherData = async () => {
await getDataTeacherChart();
};
const renderIconBook = () => {
return (
<svg
width="29"
height="29"
viewBox="0 0 29 29"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.2359 2.71875L3.12973 6.05873H3.11342C2.91223 6.05873 0.90625 6.11492 0.90625 8.35789C0.90625 9.33256 1.33853 10.0589 1.33853 10.0589L10.5614 25.4842C11.4432 26.8889 13.316 26.0094 13.316 26.0094L27.4585 19.1196L26.9292 18.5827C26.8046 17.9379 26.7321 16.7439 27.6075 15.8548C27.627 15.8349 27.6307 15.8077 27.6411 15.7828L28.0938 15.6011L15.2359 2.71875ZM15.6115 5.66044L18.0869 8.35517L8.84727 11.7387L6.66502 8.30578L15.6115 5.66044ZM10.2343 23.1719L2.12153 9.60172C2.11791 9.59673 1.8125 9.05389 1.8125 8.35789C1.8125 7.4752 2.24478 7.13944 2.66755 7.02208L11.9444 21.4931C11.2461 21.7953 10.4658 22.4859 10.2343 23.1719ZM26.4679 17.2826C26.4385 17.4911 26.4235 17.6941 26.4267 17.8844L17.1426 22.291L26.4598 18.3371C26.4688 18.4096 26.477 18.4816 26.4883 18.5469L14.587 24.2952L26.5821 18.9751L26.5894 19.0036L13.316 25.3533C13.3096 25.3573 12.7038 25.6795 12.0966 25.6795C11.4967 25.6795 11.1229 25.3714 10.9525 24.7379C10.5143 23.1103 13.0038 22.1877 13.0414 22.1741L26.8087 16.2178C26.7099 16.399 26.6415 16.5839 26.5844 16.7674L17.845 20.8814L26.4679 17.2826Z"
fill="white"
/>
</svg>
fetchTeacherData();
}, [dateTeacherChart]);
const getDataTeacher = async () => {
try {
setIsLoading(true);
const endPoint = `/api_teacher/get_teacher_info?search=${searchText}`;
const res = await apiCaller(endPoint, "GET");
setIsLoading(false);
if (res?.status) {
const teacherId = res?.data?.[0]?.teacher_id;
const teacherName = encodeURIComponent(res?.data?.[0]?.teacher_name);
const detailPath = replacePathParams(PATH.home.detailTeacher, { teacherId});
history.push({
pathname: detailPath,
search: `?teacher_name=${teacherName}`,
state: { isBack: true }
});
}
} catch (err) {
setIsLoading(false);
dispatch(
alertActions.error({
message: err?.toString(),
}),
);
}
};
const renderIconButton = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path stroke-dasharray="20" stroke-dashoffset="20" d="M3 12h17.5"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="20;0"/></path><path stroke-dasharray="12" stroke-dashoffset="12" d="M21 12l-7 7M21 12l-7 -7"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="12;0"/></path></g></svg>
)
const handleSubmit = () =>{
getDataTeacher()
}
const handleScroll = (e) => {
if (
e.target.scrollHeight - e.target.scrollTop < e.target.clientHeight + 5 &&
isLoadMoreOnline &&
!isLoading
) {
if(listOrganization.length < limitOnline) return
onLoadMoreClasses();
}
};
// Load More
const onLoadMoreClasses = async () => {
let offsetOnlineMore = offsetOnline + limitOnline;
let listNext = [];
setIsLoading(true);
try {
if (!isEndOnlineClasses) {
const endPoint = `/report/api_report/listChildOrganization?organization_id=${authentication?.user?.organization_id}?limit=${limitOnline}&offset=${offsetOnlineMore}`;
const res = await apiCaller(endPoint, "GET");
if (!res.data){
setIsEndOnlineClasses(true);
} else {
listNext = res?.data;
setOffsetOnline(offsetOnline + limitOnline);
if (res?.data?.length < limitOnline) {
setLoadMoreOnline(false);
if (res?.data?.length == 0) setLoadMoreOnline(false);
setIsEndOnlineClasses(true);
}
}
} else {
setIsEndOnlineClasses(true);
}
let listPrev = listOrganization;
setListOrganization(listPrev?.concat(listNext));
} catch (e) {
} finally {
setIsLoading(false);
}
};
return (
<div className="flex-1">
<Header
icon={renderIconHome({ color: "#4D4D4D" })}
title={"Sở giáo dục tỉnh Nam Định"}
title={"Phòng giáo dục " + (isSGD ? `${authentication.user.province}` : `${authentication.user.district}`)}
manager={true}
/>
<div className="container-page-header container-page-sidebar">
<div className="education-department-home-container bg-sub-main-green-img">
<div className="education-department-statistic-container">
{!!data && <div className="d-flex gap-16" style={{height: '80%'}}>
{!!data ? <div className="d-flex gap-16" style={{height: '90%'}}>
<div className="education-department-statistic-col gap-16">
<BoxDoughnutChart data={[data?.district_join, data?.total_district]} title={'Số huyện đã tham gia'} />
<BoxDoughnutChart data={[data?.school_join, data?.total_school]} title={'Số trường đã tham gia'} />
@ -154,21 +238,52 @@ export default function EducationDepartmentHome() {
subTitleLine={"Số học sinh làm bài trong tuần"}
/>
</div>
</div>}
</div>
:
<div className="d-flex justify-content-center">
Không dữ liệu để hiển thị
</div>
}
<div className="d-flex justify-content-center" style={{marginTop: '3rem'}}>
<PrimaryButton className="d-flex" style={{textDecoration: 'underline'}} onClick={goToOutstandingTeacher}>
<PrimaryButton style={{textDecoration: 'underline'}} onClick={goToOutstandingTeacher}>
Top 10 giáo viên tiêu biểu
<div style={{marginLeft: 8, paddingBottom: 4}}>{renderIconButton()}</div>
</PrimaryButton>
</div>
</div>
<div className="education-department-home-list-container">
<div className="education-department-home-header">
<p className="education-department-home-note">
Danh sách phòng giáo dục
</p>
<div className="education-department-home-list-content scrollbar-custom">
<div className="detail-education-department-list-header">
<InputText
className="criteria-manage-search-input criteria-manage-item"
value={searchText}
setValue={setSearchText}
type="text"
name="searchText"
placeholder={"Nhập email hoặc số điện thoại giáo viên"}
renderLabelIcon={renderIconSearchInput}
onKeyUp={(e) => {
if (e.which == 13 && !isFilterSchool) {
handleSubmit();
}
}}
/>
<div style={{paddingTop: 10}}>
<PrimaryButton
isDisabled={searchText.length === 0}
onClick={handleSubmit}
>
{"Tìm kiếm"}
</PrimaryButton>
</div>
</div>
</div>
<div onScroll={handleScroll} className="education-department-home-list-content scrollbar-custom">
<div className="education-department-home-list-room">
{listOrganization.map((item, index) => (
{listOrganization.length > 0 ? listOrganization.map((item, index) => (
<div
key={index}
className="education-department-home-item-room"
@ -181,7 +296,12 @@ export default function EducationDepartmentHome() {
{item?.name}
</p>
</div>
))}
))
:
<div className="d-flex justify-content-center">
Không phòng giáo dục nào
</div>
}
</div>
</div>
</div>

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import PrimaryButton from "../../../_components/Button/PrimaryButton";
import Header from "../../../_components/Header";
import RateStar from "../../../_components/RateStar";
@ -13,7 +13,7 @@ import {
TYPE_DISPATCH,
} from "../../../_constants/common";
import { apiCaller, history } from "../../../_helpers";
import { useParams } from "react-router-dom";
import { useLocation, useParams } from "react-router-dom";
import { exportExcel, replacePathParams } from "../../../_helpers/utils";
import { configConstants, PATH } from "../../../_constants";
import { useDispatch, useSelector } from "react-redux";
@ -28,7 +28,8 @@ export default function HeadmasterHome() {
const params = new URLSearchParams(search);
const school_name = params.get("school_name");
const {schoolId} = useParams();
const location = useLocation();
const isBack = location.state?.isBack;
const authentication = useSelector((state) => state.authentication);
const grade = useSelector((state) => state.grade);
const dispatch = useDispatch()
@ -42,13 +43,31 @@ export default function HeadmasterHome() {
const [listTeacher, setListTeacher] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [criteriaSchool, setCriteriaSchool] = useState();
const [isLoadMoreOnline, setLoadMoreOnline] = useState(true);
const [limitOnline] = useState(10);
const [offsetOnline, setOffsetOnline] = useState(0);
const [isEndOnlineClasses, setIsEndOnlineClasses] = useState(false);
const listRef = useRef(null);
const schoolName = !!schoolId
? !!school_name ? decodeURIComponent(school_name) : ''
: authentication?.user?.organization_name;
: `${authentication?.user?.organization_name}`;
const id = !!schoolId ? schoolId : authentication?.user?.organization_id;
const scrollToTop = () => {
if (listRef.current) {
listRef.current.scrollTo({
top: 0,
behavior: 'smooth',
});
}
};
const changeSemester = (item) => {
setOffsetOnline(0)
setLoadMoreOnline(true)
setIsEndOnlineClasses(false)
scrollToTop()
setSemester(item);
setMonth(getListMonthBySemester(item?.value)?.[0]);
};
@ -57,39 +76,37 @@ export default function HeadmasterHome() {
const listHeader = [
"STT",
"Tên giáo viên",
"Email",
"Số điện thoại",
"Tiêu chí giao bài",
"Tiêu chí tỷ lệ học sinh làm bài",
// "Trường",
// "Huyện",
// "Tỉnh",
// "Tháng",
// "học kỳ",
// "Niên khóa",
"Mức độ hoàn thiện tiêu chí",
"Tên Trường",
"Huyện",
"Tỉnh",
"Tháng",
"học kỳ",
"Niên khóa",
];
const listData = listTeacher.map((item, index) => [
index + 1,
item?.teacher_name,
item?.email || "",
item?.phone || "",
item?.criteria_teacher?.assign_number_level || 0,
item?.criteria_teacher?.student_done_per_level || 0,
// "Trường",
// "Huyện",
// "Tỉnh",
// !!month?.value
// ? month.value
// : getListMonthBySemester(semester.value)
// ?.filter((item) => !!item?.value)
// ?.map((item) => item?.value)
// ?.join(", "),
// !!semester?.value
// ? semester.value
// : LIST_SEMESTER?.filter((item) => !!item?.value)
// ?.map((item) => item?.value)
// ?.join(", "),
// schoolYear?.value,
item?.criteria_teacher?.criteria_level || 0,
item?.organization_name || '',
item?.district ||'',
item?.province ||'',
!!month?.value
? month.value
: getListMonthBySemester(semester.value)
?.filter((item) => !!item?.value)
?.map((item) => item?.value)
?.join(", "),
!!semester?.value
? semester.value
: LIST_SEMESTER?.filter((item) => !!item?.value)
?.map((item) => item?.value)
?.join(", "),
schoolYear?.value,
]);
exportExcel(
listHeader,
@ -99,13 +116,15 @@ export default function HeadmasterHome() {
};
const handleClickTeacherItem = (item) => {
history.push(
replacePathParams(PATH.home.detailTeacher, {
teacherId: item?.teacher_id,
}) +
"?teacher_name=" +
encodeURIComponent(item?.teacher_name),
);
const teacherId = item?.teacher_id;
const teacherName = encodeURIComponent(item?.teacher_name);
const detailPath = replacePathParams(PATH.home.detailTeacher, { teacherId });
history.push({
pathname: detailPath,
search: `?teacher_name=${teacherName}`,
state: { isBack: true },
});
}
const handleClickGradeItem = (item) => {
@ -147,6 +166,51 @@ export default function HeadmasterHome() {
}
}
const handleScroll = (e) => {
if (
e.target.scrollHeight - e.target.scrollTop < e.target.clientHeight + 5 &&
isLoadMoreOnline &&
!isLoading
) {
if(listTeacher.length < limitOnline) return
onLoadMoreClasses();
}
};
// Load More Classes for Teacher
const onLoadMoreClasses = async () => {
let offsetOnlineMore = offsetOnline + limitOnline;
let listNext = [];
setIsLoading(true);
try {
if (!isEndOnlineClasses) {
const endPoint = `/report/api_report/teacherByOrganizationId?organization_id=${id}&limit=${limitOnline}&offset=${offsetOnlineMore}`;
const res = await apiCaller(endPoint, "GET");
if (!res.data){
setIsEndOnlineClasses(true);
} else {
listNext = res?.data;
setOffsetOnline(offsetOnline + limitOnline);
if (res?.data?.length < limitOnline) {
setLoadMoreOnline(false);
if (res?.data?.length == 0) setLoadMoreOnline(false);
setIsEndOnlineClasses(true);
}
}
} else {
setIsEndOnlineClasses(true);
}
let listPrev = listTeacher;
setListTeacher(listPrev?.concat(listNext));
} catch (e) {
} finally {
setIsLoading(false);
}
};
const getDataCriteriaSchool = async () => {
try {
setIsLoading(true);
@ -193,6 +257,7 @@ export default function HeadmasterHome() {
<Header
icon={renderIconHome({ color: "#4D4D4D" })}
title={schoolName}
isBack={isBack}
/>
<div className="container-page-header container-page-sidebar">
<div className="headmaster-home-container bg-main-img">
@ -282,7 +347,7 @@ export default function HeadmasterHome() {
</div>
</div>
) : (
<div className="headmaster-home-list-teacher headmaster-home-right-p-h scrollbar-custom">
<div ref={listRef} onScroll={handleScroll} className="headmaster-home-list-teacher headmaster-home-right-p-h scrollbar-custom">
{!isLoading && !listTeacher?.length && (
<p style={{fontSize: '1.8rem', fontWeight: 700}}>
Không giáo viên nào

@ -3,13 +3,14 @@ import Header from "../../../_components/Header";
import { renderIconHome } from "../../../_components/renderIcon";
import RateStar from "../../../_components/RateStar";
import { defaultMonthYearSemester, getListMonthBySemester, LIST_SCHOOL_YEAR, LIST_SEMESTER } from "../../../_constants/common";
import { configConstants } from "../../../_constants";
import { configConstants, PATH } from "../../../_constants";
import RootSelect from "../../../_components/RootSelect";
import PrimaryButton from "../../../_components/Button/PrimaryButton";
import { useSelector } from "react-redux";
import './outstandingTeacher.style.scss'
import { apiCaller } from "../../../_helpers";
import { apiCaller, history } from "../../../_helpers";
import { useParams } from "react-router-dom";
import { replacePathParams } from "../../../_helpers/utils";
export default function OutstandingTeacher() {
@ -24,6 +25,8 @@ export default function OutstandingTeacher() {
const [firstTeacher, setFirstTeacher] = useState();
const [listTeacher, setListTeacher] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const isSGD = authentication?.user?.organization_name?.toLowerCase().includes('sở');
const getData = async () => {
setIsLoading(true)
@ -55,11 +58,25 @@ export default function OutstandingTeacher() {
getData()
}, [])
const handleClickTeacherItem = (item) => {
const teacherId = item?.teacher_id;
const teacherName = encodeURIComponent(item?.teacher_name);
const detailPath = replacePathParams(PATH.home.detailTeacher, { teacherId });
history.push({
pathname: detailPath,
search: `?teacher_name=${teacherName}`,
state: { isBack: true },
});
}
return (
<div className="flex-1">
<Header
icon={renderIconHome({ color: "#4D4D4D" })}
title={"Top 10 giáo viên tiêu biểu - Tỉnh Nam Định"}
title={"Top 10 giáo viên tiêu biểu - " + (isSGD ? `${authentication.user.province}` : `${authentication.user.district}`)}
isBack={true}
manager={true}
/>
<div className="container-page-header container-page-sidebar">
<div className="outstanding-teacher-container bg-outstanding-img">
@ -87,15 +104,15 @@ export default function OutstandingTeacher() {
Áp dụng
</PrimaryButton>
</div>
{!!firstTeacher && <div className="outstanding-teacher-best-container">
{!!firstTeacher && <div className="outstanding-teacher-best-container" >
<div className="outstanding-teacher-best-avatar-content">
<div className="outstanding-teacher-best-avatar-box">
<img className="outstanding-teacher-best-crown" src="/assets/imgs/crown_warning.png" />
<img className="outstanding-teacher-best-avatar" src={configConstants.BASE_URL + firstTeacher?.avatar} />
<img className="outstanding-teacher-best-crown" style={{cursor: 'default', zIndex: -1}} src="/assets/imgs/crown_warning.png" />
<img className="outstanding-teacher-best-avatar" src={configConstants.BASE_URL + firstTeacher?.avatar} onClick={() => handleClickTeacherItem(firstTeacher)} />
</div>
</div>
<div className="outstanding-teacher-best-info">
<span className="outstanding-teacher-best-name">{firstTeacher?.teacher_name}</span>
<span className="outstanding-teacher-best-name" onClick={() => handleClickTeacherItem(firstTeacher)}>{firstTeacher?.teacher_name}</span>
<div className="flex flex-m" style={{gap: '1.6rem', width: '100%', marginTop: '1rem'}}>
<div className="flex-1 outstanding-teacher-best-address">{firstTeacher?.district_name}</div>
<div className="flex-1 outstanding-teacher-best-address">{firstTeacher?.school_name}</div>
@ -130,7 +147,7 @@ export default function OutstandingTeacher() {
<div className="outstanding-teacher-list">
{!isLoading && !listTeacher?.length && (
<p style={{fontSize: '1.8rem', fontWeight: 700}}>
Không có giáo viên nào
Không còn giáo viên nào để hiển thị
</p>
)}
{listTeacher.map((item, index) => {
@ -147,10 +164,10 @@ export default function OutstandingTeacher() {
{(index + 2) + '. ' + item?.teacher_name}
</p>
<p className="outstanding-teacher-info-text">
{item?.email}
{item?.district_name}
</p>
<p className="outstanding-teacher-info-text">
{item?.phone}
{item?.school_name}
</p>
</div>
<div className="outstanding-teacher-criteria">

@ -39,6 +39,7 @@
display: flex;
align-items: center;
justify-content: center;
max-height: 290px;
.outstanding-teacher-best-avatar-box {
height: 90%;
@ -62,6 +63,7 @@
object-fit: cover;
border-radius: 50%;
box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
cursor: pointer;
}
}
@ -78,6 +80,7 @@
font-size: 2.4rem;
font-weight: 700;
align-self: center;
cursor: pointer;
}
.outstanding-teacher-best-address {

@ -15,17 +15,19 @@ import {
listAlphabet,
PRIMARY_COLOR,
} from "../../../_constants/common";
import { useEffect, useState } from "react";
import { useEffect, useState,useRef } from "react";
import { apiCaller, history } from "../../../_helpers";
import { configConstants } from "../../../_constants";
import { exportExcel } from "../../../_helpers/utils";
import { useParams } from "react-router-dom";
import { useLocation, useParams } from "react-router-dom";
export default function TeacherHome() {
const search = history?.location?.search;
const params = new URLSearchParams(search);
const teacher_name = params.get("teacher_name");
const { teacherId } = useParams();
const location = useLocation();
const isBack = location.state?.isBack;
const authentication = useSelector((state) => state.authentication);
const [schoolYear, setSchoolYear] = useState(
defaultMonthYearSemester.schoolYear,
@ -35,22 +37,44 @@ export default function TeacherHome() {
const [criteria, setCriteria] = useState({});
const [listClass, setListClass] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isLoadMoreOnline, setLoadMoreOnline] = useState(true);
const [limitOnline] = useState(10);
const [offsetOnline, setOffsetOnline] = useState(0);
const [isEndOnlineClasses, setIsEndOnlineClasses] = useState(false);
const [totalClass, setTotalClass] = useState(0)
const [totalAssign, settotalAssign] = useState(0)
const listRef = useRef(null)
const teacherName = !!teacherId
? !!teacher_name ? decodeURIComponent(teacher_name) : ''
: authentication?.user?.fullname;
const id = !!teacherId ? teacherId : authentication?.user?.id;
const scrollToTop = () => {
if (listRef.current) {
listRef.current.scrollTo({
top: 0,
behavior: 'smooth',
});
}
};
const getData = async () => {
try {
setIsLoading(true);
const endPoint = `/api_exercise_report/teacher_report_summary?teacher_id=${id}&year=${
schoolYear?.value
}${!!semester?.value ? `&semester=${semester?.value}` : ""}${
!!month?.value ? `&month=${month?.value}` : ""
}`;
let queryParams = []
if(!!semester?.value){
queryParams.push(`&semester=${semester?.value}`)
}
if(!!month?.value){
queryParams.push(`month=${month?.value}`)
}
const endPoint = `/api_exercise_report/teacher_report_summary?teacher_id=${id}&year=${schoolYear?.value}${queryParams.length ? `${queryParams.join("&")}` : ""}`;
const res = await apiCaller(endPoint, "GET");
if (res?.status) {
setTotalClass(res?.total_class);
settotalAssign(res?.total_assign);
setCriteria(res?.criteria_teacher);
setListClass(res?.data);
}
@ -75,10 +99,27 @@ export default function TeacherHome() {
};
const handleFilter = () => {
scrollToTop()
setOffsetOnline(0)
setLoadMoreOnline(true)
setIsEndOnlineClasses(false)
setListClass([])
getData();
};
const handleExport = () => {
const handleExport = async () => {
let queryParams = []
if(!!semester?.value){
queryParams.push(`&semester=${semester?.value}`)
}
if(!!month?.value){
queryParams.push(`month=${month?.value}`)
}
const endPoint =
`/api_exercise_report/teacher_report_summary?teacher_id=${id}&year=${schoolYear?.value.trim()}&detail=true` +
(queryParams.length > 0 ? `&${queryParams.join("&")}` : "");
const res = await apiCaller(endPoint, "GET");
if(res?.status){
const listHeader = [
"STT",
"Tên lớp",
@ -87,14 +128,14 @@ export default function TeacherHome() {
"Tiêu chí giao bài",
"Tỷ lệ học sinh làm bài",
"Tiêu chí tỷ lệ học sinh làm bài",
// "Trường",
// "Huyện",
// "Tỉnh",
"Trường",
"Huyện",
"Tỉnh",
"Tháng",
"học kỳ",
"Niên khóa",
];
const listData = listClass.map((item, index) => [
const listData = res.data.map((item, index) => [
index + 1,
item?.class_name,
teacherName,
@ -102,9 +143,9 @@ export default function TeacherHome() {
item?.assign_number_level || 0,
`${item?.student_done_per || 0}%`,
item?.student_done_per_level || 0,
// "Trường",
// "Huyện",
// "Tỉnh",
item?.organization_name || '',
item?.organization_district ||'',
item?.organization_province ||'',
!!month?.value
? month.value
: getListMonthBySemester(semester.value)
@ -123,17 +164,71 @@ export default function TeacherHome() {
listData,
`Danh sách lớp của giáo viên ${teacherName}.xlsx`,
);
}
};
useEffect(() => {
getData();
}, []);
const handleScroll = (e) => {
if (
e.target.scrollHeight - e.target.scrollTop < e.target.clientHeight + 5 &&
isLoadMoreOnline &&
!isLoading
) {
if(listClass.length < limitOnline) return
onLoadMoreClasses();
}
};
// Load More Classes for Teacher
const onLoadMoreClasses = async () => {
let offsetOnlineMore = offsetOnline + limitOnline;
let concatListSide = [];
setIsLoading(true);
try {
if (!isEndOnlineClasses) {
let queryParams = []
if(!!semester?.value){
queryParams.push(`&semester=${semester?.value}`)
}
if(!!month?.value){
queryParams.push(`month=${month?.value}`)
}
const endPoint =
`/api_exercise_report/teacher_report_summary?teacher_id=${id}&year=${schoolYear?.value.trim()}&limit=${limitOnline}&offset=${offsetOnlineMore}` +
(queryParams.length > 0 ? `&${queryParams.join("&")}` : "");
const res = await apiCaller(endPoint, "GET");
if (!res.data){
setIsEndOnlineClasses(true);
} else {
concatListSide = res?.data;
setOffsetOnline(offsetOnline + limitOnline);
if (res?.data?.length < limitOnline) {
setLoadMoreOnline(false);
if (res?.data?.length == 0) setLoadMoreOnline(false);
setIsEndOnlineClasses(true);
}
}
} else {
setIsEndOnlineClasses(true);
}
let listClassSide = listClass;
setListClass(listClassSide?.concat(concatListSide));
} catch (e) {
} finally {
setIsLoading(false);
}
};
return (
<div className="flex-1">
<Header
icon={renderIconHome({ color: "#4D4D4D" })}
title={"Danh sách lớp của giáo viên " + teacherName}
isBack={isBack}
/>
<div className="container-page-header container-page-sidebar">
<div className="teacher-home-container bg-main-img">
@ -165,13 +260,13 @@ export default function TeacherHome() {
<div className="teacher-home-info-assign-box">
<img src="/assets/imgs/icon_group.png" />
<span style={{fontWeight: 700}}>{"Tổng số lớp: "}
<span style={{color: PRIMARY_COLOR}}>{listClass?.length}</span>
<span style={{color: PRIMARY_COLOR}}>{totalClass}</span>
</span>
</div>
<div className="teacher-home-info-assign-box">
<img src="/assets/imgs/icon_assign_class.png" />
<span style={{fontWeight: 700}}>{"Tổng số lần giao: "}
<span style={{color: PRIMARY_COLOR}}>{countAss(listClass)}</span>
<span style={{color: PRIMARY_COLOR}}>{totalAssign}</span>
</span>
</div>
</div>
@ -205,7 +300,7 @@ export default function TeacherHome() {
<PrimaryButton onClick={handleExport}>Xuất excel</PrimaryButton>
)}
</div>
<div className="teacher-home-list-class teacher-home-right-p-h scrollbar-custom">
<div ref={listRef} onScroll={handleScroll} className="teacher-home-list-class teacher-home-right-p-h scrollbar-custom">
{listClass?.map((item) => {
return (
<div className="teacher-home-item-class" key={item?.class_id}>

@ -75,12 +75,25 @@ export default function Login() {
dataSubmit,
);
if (res?.status) {
if(res.data_user.role === "student"||res.data_user.role === "parent"){
dispatch({
type: TYPE_DISPATCH.RESET_AUTHENTICATION,
});
dispatch(
alertActions.error({
message: 'Bạn không có quyền truy cập',
}),
);
return false
// persistor.purge();
// window.location.href = PATH.login;
}
handleLoginSuccess(res);
}
} catch (err) {
dispatch(
alertActions.error({
message: err?.toString(),
message: err.toString()
}),
);
}
@ -160,9 +173,9 @@ export default function Login() {
required={true}
renderLabelIcon={renderIconUsername}
errorText={usernameErr}
onBlur={() => {
onBlurField("username");
}}
// onBlur={() => {
// onBlurField("username");
// }}
setErrorText={setUsernameErr}
autoFocus={true}
/>
@ -176,9 +189,9 @@ export default function Login() {
renderLabelIcon={renderIconPassword}
errorText={passwordErr}
setErrorText={setPasswordErr}
onBlur={() => {
onBlurField("password");
}}
// onBlur={() => {
// onBlurField("password");
// }}
onFocus={() => setPassword(password.trim())}
/>
<PrimaryButton
@ -188,9 +201,9 @@ export default function Login() {
>
Đăng nhập
</PrimaryButton>
<a href={PATH.forgetPassword} className="login-forget-password">
{/* <a href={PATH.forgetPassword} className="login-forget-password">
Bạn không nhớ mật khẩu, vui lòng click vào đây!
</a>
</a> */}
</div>
</div>
</form>

Loading…
Cancel
Save