Compare commits

..

2 Commits
uat ... master

Author SHA1 Message Date
minh android 7c3d118536 bo fix cung 3 months ago
minh android 32ffb3d12f link fix 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,12 +22,10 @@ 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 () {
@ -45,14 +43,7 @@ function App() {
dispatch(alertActions?.clear());
});
}, []);
useEffect(() => {
if(authentication?.user?.role === "student"||authentication?.user?.role === "parent")
dispatch({
type: TYPE_DISPATCH.RESET_AUTHENTICATION,
});
}, [])
console.log("authen: ", authentication);
return (
<div className="rel">
<div className="loading hide">

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

@ -53,18 +53,6 @@ $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,25 +16,6 @@
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,54 +1,27 @@
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 = [], manager = false, isBack = false }) {
export default function Header({ icon, title, subtitles = [] }) {
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">{handleFullName()}</p>
<p className="header-name">{authentication?.user?.fullname}</p>
<img
src={!!authentication?.user?.avatar ? (configConstants.BASE_URL + authentication?.user?.avatar) : '/assets/imgs/avatar_auth.png'}
className="header-avatar"

@ -1,140 +1,76 @@
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 { useEffect, useState } from 'react'
import { useState } from 'react'
import InputDate from '../Auth/InputDate'
import { renderIconButton, renderIconButtonLeft, renderIconDate } from '../renderIcon'
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,
}) {
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 handleMonthChange = (date) => {
const changeMonth = (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>
)
}
const renderDoughnutSection = () => (
return (
<div className="box-chart-container">
<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' 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 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>
<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>}
{dataBarChart.length > 0 ? (
<div className='chart-container'>
<div className='flex-1' style={{ padding: '1.6rem 0', height: '100%'}}>
<VerticalBarChart data={dataBarChart} labels={labelsBarChart} />
{!!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}/>
</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={handleMonthChange}
setValue={changeMonth}
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,44 +3,15 @@ import RDoughnutChart from '../chart/RDoughnutChart'
import './boxChart.scss'
export default function BoxDoughnutChart({data = [], title, propsContainer}) {
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>
</>
)
}
const _data = (data?.[0] / data?.[1] === 1 || data?.[0] > data?.[1]) ? [data?.[0]] : [data?.[0], data?.[1] - data?.[0]]
return (
<div className="box-chart-container" {...propsContainer}>
<p className='box-chart-subtitle' style={{ marginBottom: '1.2rem'}}>{title}</p>
<p className='box-chart-subtitle'>{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'>
{renderStats()}
<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>
</div>
</div>
</div>

@ -7,14 +7,6 @@
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;
@ -27,73 +19,5 @@
font-weight: 700;
}
// .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;
}
.doughnut-chart-content {}
}

@ -1,55 +1,65 @@
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(...registerables);
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,
// },
// ],
// };
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: ['Số tham gia',"Tổng số"],
labels: [],
datasets: [
{
label: '',
data: adjustedData,
data,
backgroundColor: [
PRIMARY_COLOR, '#01AEF0'
],
borderWidth: 0
},
],
hoverOffset: 4
}}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
callbacks: {
label: function (tooltipItem) {
const label = tooltipItem.label || '';
const value = data[tooltipItem.dataIndex];
return `${label}: ${value}`;
},
},
position: 'nearest',
z: 1000,
},
legend: false,
enabled: false
}
}
}}
{...other}

@ -1,8 +1,35 @@
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(...registerables);
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,
// },
// ],
// };
export default function RPieChart({data, ...other}) {
return <Pie data={data} {...other} />;

@ -1,9 +1,24 @@
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(...registerables);
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
export function VerticalBarChart({data = [], labels = []}) {
if (!data?.length) {
@ -16,8 +31,6 @@ export function VerticalBarChart({data = [], labels = []}) {
}
})
labels = labels?.map(week => week.split(" "));
const options = {
responsive: true,
maintainAspectRatio: false,
@ -25,30 +38,19 @@ export function VerticalBarChart({data = [], labels = []}) {
legend: {
display: false
},
// title: {
// display: true,
// text: 'Tuần',
// align: 'start',
// position: 'bottom',
// },
datalabels: {
anchor: 'end',
align: 'end',
color: 'blue',
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
font: {
weight: 'bold',
},
formatter: function (value, context) {
return value;
return value; // Display the actual data value
}
}
},
scales: {
x: {
ticks: {
maxRotation: 0,
},
},
y: {
max: maxValue < 5 ? 5 : maxValue < 20 ? maxValue + 5 : maxValue + 10,
ticks: {

@ -139,54 +139,3 @@ 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})`, value: 1 },
{ label: `Tháng 1 (${currentYear + 1})`, value: 1 },
];
case 2:
return [

@ -1,4 +1,4 @@
const enviroment = ".dev";
const enviroment = "";
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, useRef, useState } from "react";
import { useEffect, 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, TYPE_DISPATCH } from "../../../_constants/common";
import { listStatusCriteria } from "../../../_constants/common";
import moment from "moment";
import { useDispatch, useSelector } from "react-redux";
import { useDispatch } 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,17 +32,13 @@ 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" });
@ -57,21 +53,6 @@ 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) =>
@ -87,7 +68,6 @@ export default function CriteriaManage() {
newListIdSelected.push(item?.school_id);
});
setListIdSelected(newListIdSelected);
setIsCheckAll(true)
};
const handleSelectItem = (item) => {
@ -107,7 +87,6 @@ export default function CriteriaManage() {
);
return;
}
saveCurrentState()
history.push(
PATH.criteria.setting +
`?schoolList=${encodeURIComponent(JSON.stringify(listIdSelected))}`,
@ -138,8 +117,10 @@ export default function CriteriaManage() {
}),
);
setListIdSelected([]);
setIsEndOnlineClasses(false)
await getDataCriteria(true);
await setSearchText("");
await changeProvince(listProvince[0]);
await setStatusCriteriaSelected(listStatusCriteria[0]);
getDataCriteria();
}
} catch (err) {
dispatch(
@ -159,41 +140,27 @@ 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 queryParams = [];
const addParam = (key, value) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.push(`${key}=${value}`);
const endPoint =
`/report/api_report/getOrganizationAndCriteria` +
(isFilter
? `?province_id=${
!!provinceSelect?.value ? provinceSelect?.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);
${!!districtSelect?.value ? `&district_id=${districtSelect?.value}` : ""}
${
!!statusCriteriaSelected?.value
? `&status=${statusCriteriaSelected?.value}`
: ""
}
addParam('school_name', searchText);
const endPoint = `/report/api_report/getOrganizationAndCriteria${
isFilter && queryParams.length ? `?${queryParams.join("&")}` : ""
}`;
setendPointSave(endPoint)
${!!searchText ? `&school_name=${searchText}` : ""}`
: "");
const res = await apiCaller(endPoint, "GET");
if (res?.status) {
setListData(res?.data);
@ -265,114 +232,9 @@ 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
@ -412,7 +274,7 @@ export default function CriteriaManage() {
className={"criteria-manage-item"}
/>
<div className="criteria-manage-filter-action">
<PrimaryButton isDisabled={isDisabled()} onClick={handleFilter}>Áp dụng</PrimaryButton>
<PrimaryButton onClick={handleFilter}>Áp dụng</PrimaryButton>
</div>
</div>
<div className="criteria-manage-list-container">
@ -452,7 +314,7 @@ export default function CriteriaManage() {
onClick={handleSelectAll}
placement="right"
/>
<div ref={listRef} onScroll={handleScroll} className="criteria-manage-list scrollbar-custom">
<div className="criteria-manage-list scrollbar-custom">
{listData?.map((item, index) => {
return (
<div className="criteria-manage-item">

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

@ -12,10 +12,6 @@ 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);
@ -24,130 +20,36 @@ 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 || [authentication?.user?.organization_id],
school_list: schoolList,
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);
}
if (res?.status) {
setAlertMessage(res?.msg);
setShowAlert(true);
}
} catch (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(
const res = await apiCaller(
"/report/api_report/assignCriteriaToOrganization",
"PUT",
dataSave
dataSave,
);
if (res?.status) {
dispatch(alertActions.success({
message: res?.msg
}))
}
} catch (err) {
console.log("err: ", err);
}
};
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 {
@ -157,7 +59,6 @@ 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,
@ -176,34 +77,9 @@ export default function CriteriaSetting() {
}
};
useEffect( async () => {
useEffect(() => {
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);
}
}
}, []);
@ -221,11 +97,6 @@ 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
@ -244,6 +115,7 @@ export default function CriteriaSetting() {
},
})
}
// disabled={true}
/>
</div>
<div style={{ width: "100%" }}>
@ -256,21 +128,18 @@ export default function CriteriaSetting() {
<span>Hoàn thành mức 1:</span>
</div>
<div className="criteria-setting-item-detail-value">
{`${valueName} ít hơn `}
{`${valueName} < `}
<input
value={criteriaTarget[key].target1}
onChange={(e) => {
const value = e.target.value;
if (!/^[1-9]\d*$/.test(value) && value !== "") {
return;
}else if (!!maxLengthInput && e.target.value.length > maxLengthInput) {
if (!!maxLengthInput && e.target.value.length > maxLengthInput) {
return;
}
setCriteriaTarget({
...criteriaTarget,
[key]: {
...criteriaTarget[key],
target1: value,
target1: e.target.value,
},
});
}}
@ -285,18 +154,15 @@ 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>
{' đến ít hơn '}
{` < ${valueName} < `}
<input
value={criteriaTarget[key].target2}
onChange={(e) => {
if (!/^[1-9]\d*$/.test(e.target.value) && e.target.value !== "") {
return;
}else if (!!maxLengthInput && e.target.value.length > maxLengthInput) {
if (!!maxLengthInput && e.target.value.length > maxLengthInput) {
return;
}
setCriteriaTarget({
@ -318,7 +184,7 @@ export default function CriteriaSetting() {
<span>Hoàn thành mức 3:</span>
</div>
<div className="criteria-setting-item-detail-value">
{`${valueName} lớn hơn hoặc bằng `}
{`${valueName} > `}
<span>
{criteriaTarget[key].target2}
{unit}
@ -341,10 +207,12 @@ 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">
{textTilte.toLocaleUpperCase()}
MỨC ĐỘ CĐS SUNDAY ENGLISH
</p>
<p className="criteria-setting-left-desc">
{textContent}
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.
</p>
<div className="criteria-setting-left-img">
<img src="/assets/imgs/book_criteria_setting.png" />
@ -354,13 +222,13 @@ export default function CriteriaSetting() {
<div className="criteria-setting-right-content">
{renderRightItem({
title: "Số lần giao bài trong tháng",
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 ",
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",
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",
@ -376,19 +244,13 @@ export default function CriteriaSetting() {
>
Quay lại
</PrimaryButton>
{(authentication?.user?.role === USER_ROLE.ADMIN || authentication?.user?.role === USER_ROLE.HEADMASTER) && (
{authentication?.user?.role === USER_ROLE.ADMIN && (
<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 {
min-width: 340px;
width: 30%;
border-right: 1px solid #4d4d4d;
.admin-home-form-select {
@ -25,7 +25,6 @@
.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, useRef, useState } from "react";
import { useEffect, useState } from "react";
import InputText from "../../../_components/Auth/InputText";
import { apiCaller, history } from "../../../_helpers";
import { configConstants, PATH } from "../../../_constants";
@ -37,36 +37,18 @@ 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);
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 endPoint = `/api_organization/get_organization?province_id=${
provinceSelect?.value
}${
!!districtSelect?.value ? `&district_id=${districtSelect?.value}` : ""
}${!!searchTextSchool ? `&name_school_find=${searchTextSchool}` : ""}`;
const res = await apiCaller(endPoint, "GET");
setIsLoading(false);
if (res?.status) {
@ -78,15 +60,6 @@ export default function AdminHome() {
}
};
const scrollToTop = () => {
if (listRef.current) {
listRef.current.scrollTo({
top: 0,
behavior: 'smooth',
});
}
};
const getDataTeacher = async () => {
try {
setIsLoading(true);
@ -94,15 +67,13 @@ export default function AdminHome() {
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 }
});
history.push(
replacePathParams(PATH.home.detailTeacher, {
teacherId: res?.data?.[0]?.teacher_id,
}) +
"?teacher_name=" +
encodeURIComponent(res?.data?.[0]?.teacher_name),
);
}
} catch (err) {
setIsLoading(false);
@ -190,24 +161,13 @@ export default function AdminHome() {
};
const handleClickSchoolItem = (item) => {
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 }
});
history.push(replacePathParams(PATH.home.detailSchool, {schoolId: item?.organization_id}) +
"?school_name=" +
encodeURIComponent(item?.organization_name));
};
const handleSubmit = () => {
if (isFilterSchool) {
scrollToTop()
setOffsetOnline(0)
setLoadMoreOnline(true)
setIsEndOnlineClasses(false)
setIsSearched(true);
getData();
return;
@ -227,7 +187,10 @@ export default function AdminHome() {
};
const validateParam = () => {
return !!provinceSelect.value || !!getSearchText();
if (isFilterSchool) {
return !!provinceSelect.value || !!searchTextSchool;
}
return true;
};
useEffect(() => {
@ -235,92 +198,6 @@ 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ủ"} />
@ -340,8 +217,10 @@ export default function AdminHome() {
</div>
<InputText
className="admin-home-search-input"
value={getSearchText()}
setValue={setSearchText}
value={isFilterSchool ? searchTextSchool : searchTextTeacher}
setValue={
isFilterSchool ? setSearchTextSchool : setSearchTextTeacher
}
type="text"
name="searchText"
placeholder={
@ -390,7 +269,7 @@ export default function AdminHome() {
</PrimaryButton>
)}
</div>
<div ref={listRef} onScroll={handleScroll} className="admin-home-list-school admin-home-right-p-h scrollbar-custom">
<div className="admin-home-list-school admin-home-right-p-h scrollbar-custom">
{listSchool.map((item, index) => {
return (
<div

@ -31,11 +31,6 @@
.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, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { defaultMonthYearSemester, getListMonthBySemester, LIST_SCHOOL_YEAR, LIST_SEMESTER } from "../../../_constants/common";
import { configConstants } from "../../../_constants";
import { exportExcel } from "../../../_helpers/utils";
@ -29,11 +29,6 @@ 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) : '';
@ -42,20 +37,8 @@ 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();
};
@ -68,9 +51,10 @@ 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",
@ -83,9 +67,10 @@ export default function DetailGrade() {
item?.assign_number_level || 0,
`${item?.student_done_per || 0}%`,
item?.student_done_per_level || 0,
item?.organization_name || '',
item?.district ||'',
item?.province ||'',
gradeName || '',
schoolName || '',
// "Huyện",
// "Tỉnh",
!!month?.value
? month.value
: getListMonthBySemester(semester.value)
@ -135,68 +120,12 @@ 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">
@ -224,11 +153,9 @@ 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>
<div ref={listRef} onScroll={handleScroll} className="detail-grade-list-class detail-grade-right-p-h scrollbar-custom">
<div 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
@ -277,10 +204,6 @@ 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: 43%;
width: 40%;
border-right: 1px solid #c7c7c7;
display: flex;
flex-direction: column;
@ -36,37 +36,13 @@
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: 16px;
padding-left: 32px;
@include screen_pc_sm {
gap: 16px;

@ -1,26 +1,22 @@
import { useEffect, useState } from "react";
import Header from "../../../_components/Header";
import { renderIconButton, renderIconHome, renderIconSearchInput } from "../../../_components/renderIcon";
import { renderIconHome } 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 { useDispatch, useSelector } from "react-redux";
import { 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 { exportExcel, replacePathParams } from "../../../_helpers/utils";
import { replacePathParams } from "../../../_helpers/utils";
import $ from "jquery";
import { useLocation, useParams } from "react-router-dom";
import InputText from "../../../_components/Auth/InputText";
import { alertActions } from "../../../_actions";
import { useParams } from "react-router-dom";
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())
@ -38,16 +34,6 @@ 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 {
@ -199,41 +185,23 @@ export default function DetailRoomEducation() {
}
const goToDetailSchool = (item) => {
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 }
});
history.push(replacePathParams(PATH.home.detailSchool, {schoolId: item?.organization_id}) +
"?school_name=" +
encodeURIComponent(item?.organization_name));
}
useEffect(() => {
const fetchData = async () => {
await getDataStatisticCircle();
await getListOrganization();
};
fetchData();
}, []);
getDataStatisticCircle()
getListOrganization()
}, [])
useEffect(() => {
const fetchStudentData = async () => {
await getDataStudentChart();
};
fetchStudentData();
}, [dateStudentChart]);
getDataStudentChart()
}, [dateStudentChart])
useEffect(() => {
const fetchTeacherData = async () => {
await getDataTeacherChart();
};
fetchTeacherData();
}, [dateTeacherChart]);
getDataTeacherChart()
}, [dateTeacherChart])
useEffect(() => {
if (isLoadingListOrganization || isLoadingStatisticCircle || isLoadingStudentChart || isLoadingTeacherChart) {
@ -243,135 +211,23 @@ export default function DetailRoomEducation() {
}
}, [isLoadingListOrganization, isLoadingStatisticCircle, isLoadingStudentChart, isLoadingTeacherChart])
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 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 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 " + (isSGD ? `${authentication.user.province}` : `${authentication.user.district}`)}
manager={true}
isBack={isBack}
title={"Phòng giáo dục huyện Giao Thủy"}
/>
<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'} />
@ -398,10 +254,6 @@ 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}>
@ -413,71 +265,31 @@ 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="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'}}>
<div className="flex gap-16 align-item-center" style={{padding: '1rem 3.2rem'}}>
<RootSelect
data={LIST_SCHOOL_YEAR}
value={schoolYear}
setValue={(value) => {
setSchoolYear(value);
setIsFilterChanged(true);
}}
style={{flex: 1, minWidth: '200px', padding:'0 10px'}}
setValue={setSchoolYear}
style={{flex: 1}}
/>
<RootSelect
data={LIST_SEMESTER}
value={semester}
setValue={(value) => {
changeSemester(value);
setIsFilterChanged(true);
}}
style={{flex: 0.5, minWidth: '151px', padding:'0 10px'}}
setValue={changeSemester}
style={{flex: 0.5}}
/>
<RootSelect
data={getListMonthBySemester(semester.value)}
value={month}
setValue={(value) => {
setMonth(value);
setIsFilterChanged(true);
}}
style={{flex: 0.5, minWidth: '150px', padding:'0 10px'}}
setValue={setMonth}
style={{flex: 0.5}}
/>
<PrimaryButton isDisabled={!isFilterChanged} onClick={() => {
handleFilter();
setIsFilterChanged(false);
}}>
<PrimaryButton onClick={handleFilter}>
Áp dụng
</PrimaryButton>
</div>
<div onScroll={handleScroll} className="detail-room-education-list detail-room-education-right-p-h scrollbar-custom">
<div 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
@ -488,7 +300,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={item?.avatar ? configConstants.BASE_URL + item?.avatar : "/assets/imgs/avatar_school.png"} />
<img src={configConstants.BASE_URL + item?.avatar} />
</div>
<div className="detail-room-education-detail">
<div className="detail-room-education-info">

@ -26,43 +26,10 @@
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;
}
@ -71,8 +38,6 @@
display: flex;
flex-direction: column;
padding: 0 3.2rem;
max-height: 740px;
padding-top: 1.2rem;
.education-department-home-list-room {
flex: 1;

@ -2,8 +2,6 @@ import { useEffect, useState } from "react";
import InputText from "../../../_components/Auth/InputText";
import Header from "../../../_components/Header";
import {
renderIconBook,
renderIconButton,
renderIconHome,
renderIconSearchInput,
} from "../../../_components/renderIcon";
@ -16,8 +14,7 @@ import PrimaryButton from "../../../_components/Button/PrimaryButton";
import { apiCaller, history } from "../../../_helpers";
import { PATH } from "../../../_constants";
import { replacePathParams } from "../../../_helpers/utils";
import { useDispatch, useSelector } from "react-redux";
import { alertActions } from "../../../_actions";
import { useSelector } from "react-redux";
export default function EducationDepartmentHome() {
const authentication = useSelector((state) => state.authentication);
@ -27,15 +24,6 @@ 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 {
@ -88,127 +76,55 @@ export default function EducationDepartmentHome() {
}
const goToDetailRoomEducation = (item) => {
const idRoom = item?.id;
const detailPath = replacePathParams(PATH.home.detailRoomEducation, { idRoom });
history.push({
pathname: detailPath,
state: { isBack: true },
});
history.push(replacePathParams(PATH.home.detailRoomEducation, {idRoom: item?.id}))
}
useEffect(() => {
const fetchData = async () => {
await getDataOrganization();
await getDataStatisticCircle();
};
fetchData();
}, []);
getDataOrganization()
getDataStatisticCircle()
}, [])
useEffect(() => {
const fetchStudentData = async () => {
await getDataStudentChart();
};
fetchStudentData();
}, [dateStudentChart]);
getDataStudentChart()
}, [dateStudentChart])
useEffect(() => {
const fetchTeacherData = async () => {
await getDataTeacherChart();
};
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});
getDataTeacherChart()
}, [dateTeacherChart])
history.push({
pathname: detailPath,
search: `?teacher_name=${teacherName}`,
state: { isBack: true }
});
}
} catch (err) {
setIsLoading(false);
dispatch(
alertActions.error({
message: err?.toString(),
}),
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>
);
}
};
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);
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>
)
}
};
return (
<div className="flex-1">
<Header
icon={renderIconHome({ color: "#4D4D4D" })}
title={"Phòng giáo dục " + (isSGD ? `${authentication.user.province}` : `${authentication.user.district}`)}
manager={true}
title={"Sở giáo dục tỉnh Nam Định"}
/>
<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: '90%'}}>
{!!data && <div className="d-flex gap-16" style={{height: '80%'}}>
<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'} />
@ -238,52 +154,21 @@ export default function EducationDepartmentHome() {
subTitleLine={"Số học sinh làm bài trong tuần"}
/>
</div>
</div>
:
<div className="d-flex justify-content-center">
Không dữ liệu để hiển thị
</div>
}
</div>}
<div className="d-flex justify-content-center" style={{marginTop: '3rem'}}>
<PrimaryButton style={{textDecoration: 'underline'}} onClick={goToOutstandingTeacher}>
<PrimaryButton className="d-flex" 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="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-content scrollbar-custom">
<div className="education-department-home-list-room">
{listOrganization.length > 0 ? listOrganization.map((item, index) => (
{listOrganization.map((item, index) => (
<div
key={index}
className="education-department-home-item-room"
@ -296,12 +181,7 @@ 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, useRef, useState } from "react";
import { useEffect, 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 { useLocation, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import { exportExcel, replacePathParams } from "../../../_helpers/utils";
import { configConstants, PATH } from "../../../_constants";
import { useDispatch, useSelector } from "react-redux";
@ -28,8 +28,7 @@ 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()
@ -43,31 +42,13 @@ 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]);
};
@ -76,37 +57,39 @@ 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",
"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",
// "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,
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,
// "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,
]);
exportExcel(
listHeader,
@ -116,15 +99,13 @@ export default function HeadmasterHome() {
};
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 },
});
history.push(
replacePathParams(PATH.home.detailTeacher, {
teacherId: item?.teacher_id,
}) +
"?teacher_name=" +
encodeURIComponent(item?.teacher_name),
);
}
const handleClickGradeItem = (item) => {
@ -166,51 +147,6 @@ 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);
@ -257,7 +193,6 @@ 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">
@ -347,7 +282,7 @@ export default function HeadmasterHome() {
</div>
</div>
) : (
<div ref={listRef} onScroll={handleScroll} className="headmaster-home-list-teacher headmaster-home-right-p-h scrollbar-custom">
<div 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,14 +3,13 @@ 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, PATH } from "../../../_constants";
import { configConstants } from "../../../_constants";
import RootSelect from "../../../_components/RootSelect";
import PrimaryButton from "../../../_components/Button/PrimaryButton";
import { useSelector } from "react-redux";
import './outstandingTeacher.style.scss'
import { apiCaller, history } from "../../../_helpers";
import { apiCaller } from "../../../_helpers";
import { useParams } from "react-router-dom";
import { replacePathParams } from "../../../_helpers/utils";
export default function OutstandingTeacher() {
@ -25,8 +24,6 @@ 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)
@ -58,25 +55,11 @@ 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 - " + (isSGD ? `${authentication.user.province}` : `${authentication.user.district}`)}
isBack={true}
manager={true}
title={"Top 10 giáo viên tiêu biểu - Tỉnh Nam Định"}
/>
<div className="container-page-header container-page-sidebar">
<div className="outstanding-teacher-container bg-outstanding-img">
@ -104,15 +87,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" 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)} />
<img className="outstanding-teacher-best-crown" src="/assets/imgs/crown_warning.png" />
<img className="outstanding-teacher-best-avatar" src={configConstants.BASE_URL + firstTeacher?.avatar} />
</div>
</div>
<div className="outstanding-teacher-best-info">
<span className="outstanding-teacher-best-name" onClick={() => handleClickTeacherItem(firstTeacher)}>{firstTeacher?.teacher_name}</span>
<span className="outstanding-teacher-best-name">{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>
@ -147,7 +130,7 @@ export default function OutstandingTeacher() {
<div className="outstanding-teacher-list">
{!isLoading && !listTeacher?.length && (
<p style={{fontSize: '1.8rem', fontWeight: 700}}>
Không còn giáo viên nào để hiển thị
Không có giáo viên nào
</p>
)}
{listTeacher.map((item, index) => {
@ -164,10 +147,10 @@ export default function OutstandingTeacher() {
{(index + 2) + '. ' + item?.teacher_name}
</p>
<p className="outstanding-teacher-info-text">
{item?.district_name}
{item?.email}
</p>
<p className="outstanding-teacher-info-text">
{item?.school_name}
{item?.phone}
</p>
</div>
<div className="outstanding-teacher-criteria">

@ -39,7 +39,6 @@
display: flex;
align-items: center;
justify-content: center;
max-height: 290px;
.outstanding-teacher-best-avatar-box {
height: 90%;
@ -63,7 +62,6 @@
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;
}
}
@ -80,7 +78,6 @@
font-size: 2.4rem;
font-weight: 700;
align-self: center;
cursor: pointer;
}
.outstanding-teacher-best-address {

@ -15,19 +15,17 @@ import {
listAlphabet,
PRIMARY_COLOR,
} from "../../../_constants/common";
import { useEffect, useState,useRef } from "react";
import { useEffect, useState } from "react";
import { apiCaller, history } from "../../../_helpers";
import { configConstants } from "../../../_constants";
import { exportExcel } from "../../../_helpers/utils";
import { useLocation, useParams } from "react-router-dom";
import { 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,
@ -37,44 +35,22 @@ 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);
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 endPoint = `/api_exercise_report/teacher_report_summary?teacher_id=${id}&year=${
schoolYear?.value
}${!!semester?.value ? `&semester=${semester?.value}` : ""}${
!!month?.value ? `&month=${month?.value}` : ""
}`;
const res = await apiCaller(endPoint, "GET");
if (res?.status) {
setTotalClass(res?.total_class);
settotalAssign(res?.total_assign);
setCriteria(res?.criteria_teacher);
setListClass(res?.data);
}
@ -99,27 +75,10 @@ export default function TeacherHome() {
};
const handleFilter = () => {
scrollToTop()
setOffsetOnline(0)
setLoadMoreOnline(true)
setIsEndOnlineClasses(false)
setListClass([])
getData();
};
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 handleExport = () => {
const listHeader = [
"STT",
"Tên lớp",
@ -128,14 +87,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 = res.data.map((item, index) => [
const listData = listClass.map((item, index) => [
index + 1,
item?.class_name,
teacherName,
@ -143,9 +102,9 @@ export default function TeacherHome() {
item?.assign_number_level || 0,
`${item?.student_done_per || 0}%`,
item?.student_done_per_level || 0,
item?.organization_name || '',
item?.organization_district ||'',
item?.organization_province ||'',
// "Trường",
// "Huyện",
// "Tỉnh",
!!month?.value
? month.value
: getListMonthBySemester(semester.value)
@ -164,71 +123,17 @@ 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">
@ -260,13 +165,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}}>{totalClass}</span>
<span style={{color: PRIMARY_COLOR}}>{listClass?.length}</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}}>{totalAssign}</span>
<span style={{color: PRIMARY_COLOR}}>{countAss(listClass)}</span>
</span>
</div>
</div>
@ -300,7 +205,7 @@ export default function TeacherHome() {
<PrimaryButton onClick={handleExport}>Xuất excel</PrimaryButton>
)}
</div>
<div ref={listRef} onScroll={handleScroll} className="teacher-home-list-class teacher-home-right-p-h scrollbar-custom">
<div 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,25 +75,12 @@ 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(),
}),
);
}
@ -173,9 +160,9 @@ export default function Login() {
required={true}
renderLabelIcon={renderIconUsername}
errorText={usernameErr}
// onBlur={() => {
// onBlurField("username");
// }}
onBlur={() => {
onBlurField("username");
}}
setErrorText={setUsernameErr}
autoFocus={true}
/>
@ -189,9 +176,9 @@ export default function Login() {
renderLabelIcon={renderIconPassword}
errorText={passwordErr}
setErrorText={setPasswordErr}
// onBlur={() => {
// onBlurField("password");
// }}
onBlur={() => {
onBlurField("password");
}}
onFocus={() => setPassword(password.trim())}
/>
<PrimaryButton
@ -201,9 +188,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