update teacher monitoring and feedback
This commit is contained in:
parent
5cc0a00a4b
commit
faccef8205
|
|
@ -10,7 +10,7 @@ const TeacherLayout = ({ children }) => {
|
|||
<div className="container-fluid dashboard-container">
|
||||
<div className="row min-h-100">
|
||||
<SideNav />
|
||||
<main className="col p-4 overflow-auto bg-light">
|
||||
<main className="col p-4 overflow-auto bg-light teacher-main-layout">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,17 +35,25 @@ const TeacherSideNav = () => {
|
|||
</div>
|
||||
<Nav className="flex-column">
|
||||
<Nav.Link as={NavLink} to="home" className="mb-2 text-white rounded">
|
||||
<i className="bi bi-house me-2"></i>
|
||||
<i className="bi bi-house-fill me-2"></i>
|
||||
<span className='text-truncate'>Home</span>
|
||||
</Nav.Link>
|
||||
<Nav.Link as={NavLink} to="class" className="mb-2 text-white rounded">
|
||||
<i className="bi bi-easel2-fill me-2"></i>
|
||||
<span className='text-truncate'>Class</span>
|
||||
</Nav.Link>
|
||||
<Nav.Link as={NavLink} to="student" className="mb-2 text-white rounded">
|
||||
<i className="bi bi-people-fill me-2"></i>
|
||||
<span className='text-truncate'>Student</span>
|
||||
</Nav.Link>
|
||||
<Nav.Link as={NavLink} to="monitoring" className="mb-2 text-white rounded">
|
||||
<i className="bi bi-kanban-fill me-2"></i>
|
||||
<span className='text-truncate'>Monitoring</span>
|
||||
</Nav.Link>
|
||||
<Nav.Link as={NavLink} to="feedback" className="mb-2 text-white rounded">
|
||||
<i className="bi bi-chat-left-text-fill me-2"></i>
|
||||
<span className='text-truncate'>Feedback</span>
|
||||
</Nav.Link>
|
||||
<Nav.Link as={NavLink} to="setting" className="mb-2 text-white rounded">
|
||||
<i className="bi bi-gear-fill me-2"></i>
|
||||
<span className='text-truncate'>Setting</span>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,17 @@ import '../../assets/styles/user.css';
|
|||
import ProtectedRoute from '../../utils/ProtectedRoute';
|
||||
import ManageClasses from './manage_classes/views/ManageClasses';
|
||||
import ClassDetail from './manage_classes/views/ClassDetail';
|
||||
import ManageStudents from './manage_students/views/ManageStudents';
|
||||
import Monitoring from './monitoring/views/Monitoring';
|
||||
import StudentProgress from './monitoring/views/StudentProgress';
|
||||
import ClassProgress from './monitoring/views/ClassProgress';
|
||||
import Feedback from './feedback/views/Feedback';
|
||||
import StudentFeedback from './feedback/views/StudentFeedback';
|
||||
import ClassFeedback from './feedback/views/ClassFeedback';
|
||||
import Setting from './setting/views/Setting';
|
||||
|
||||
import Review from '../user/review/views/Review';
|
||||
|
||||
import '../../assets/styles/teacher.css'
|
||||
|
||||
const TeacherRoutes = () => {
|
||||
|
|
@ -29,9 +36,15 @@ const TeacherRoutes = () => {
|
|||
<Route path="home" element={<Dashboard />} />
|
||||
<Route path="class" element={<ManageClasses />} />
|
||||
<Route path="class/d/:classId" element={<ClassDetail />} />
|
||||
<Route path="student" element={<ManageStudents />} />
|
||||
|
||||
<Route path="monitoring" element={<Monitoring />} />
|
||||
<Route path="monitoring/s/:progressId" element={<StudentProgress />} />
|
||||
<Route path="monitoring/c/:progressId" element={<ClassProgress />} />
|
||||
<Route path="monitoring/s/:stdLearning" element={<Review />} />
|
||||
<Route path="monitoring/c/:classId" element={<ClassProgress />} />
|
||||
|
||||
<Route path="feedback" element={<Feedback />} />
|
||||
<Route path="feedback/s/:progressId" element={<StudentFeedback />} />
|
||||
<Route path="feedback/c/:progressId" element={<ClassFeedback />} />
|
||||
<Route path="setting" element={<Setting />} />
|
||||
|
||||
</Route>
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ const TeacherDashboard = () => {
|
|||
):(
|
||||
activity.length > 0?(
|
||||
activity.map((data, index) => (
|
||||
<tr index>
|
||||
<tr key={index}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{data.NISN}</td>
|
||||
<td>{data.NAME_USERS}</td>
|
||||
|
|
|
|||
142
src/roles/teacher/feedback/hooks/useProgress.jsx
Normal file
142
src/roles/teacher/feedback/hooks/useProgress.jsx
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import progressService from '../services/serviceProgress';
|
||||
|
||||
const useProgress = () => {
|
||||
const [students, setStudents] = useState([]);
|
||||
const [classes, setClasses] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [searchStudent, setSearchStudent] = useState("");
|
||||
const [sortStudent, setSortStudent] = useState("");
|
||||
const [pageStudent, setCurrentPageStudent] = useState(1);
|
||||
const [limitStudent, setLimitStudent] = useState(20);
|
||||
const [totalPagesStudent, setTotalPagesStudent] = useState(0);
|
||||
const [totalDataStudent, setTotalDataStudent] = useState(null);
|
||||
|
||||
const [searchClass, setSearchClass] = useState("");
|
||||
const [sortClass, setSortClass] = useState("");
|
||||
const [pageClass, setCurrentPageClass] = useState(1);
|
||||
const [limitClass, setLimitClass] = useState(1000);
|
||||
const [totalPagesClass, setTotalPagesClass] = useState(0);
|
||||
const [totalDataClass, setTotalDataClass] = useState(null);
|
||||
|
||||
const [selectedId, setSelectedId] = useState('');
|
||||
const [selected, setSelected] = useState([]);
|
||||
const [show, setShow] = useState(false);
|
||||
const [loadingModal, setLoadingModal] = useState(true);
|
||||
|
||||
const fetchDataStudents = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const student = await progressService.fetchDataStudent(searchStudent, sortStudent, pageStudent, limitStudent);
|
||||
setStudents(student.payload.monitorings);
|
||||
setTotalPagesStudent(student.payload.totalPages);
|
||||
setTotalDataStudent(student.payload.totalItems);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDataClasses = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const classes = await progressService.fetchDataClass(searchClass, sortClass, pageClass, limitClass);
|
||||
setClasses(classes.payload.classes);
|
||||
setTotalDataClass(classes.payload.totalItems);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchChangeStudents = () => {
|
||||
fetchDataStudents();
|
||||
setCurrentPageStudent(1);
|
||||
}
|
||||
|
||||
const handlePageChangeStudents = (pages) => {
|
||||
setCurrentPageStudent(pages);
|
||||
}
|
||||
|
||||
const handleLimitsChangeStudents = (e) => {
|
||||
setLimitStudent(e.target.value);
|
||||
setCurrentPageStudent(1);
|
||||
}
|
||||
|
||||
const handleSearchChangeClasses = () => {
|
||||
fetchDataClasses();
|
||||
setCurrentPageClass(1);
|
||||
}
|
||||
|
||||
const handlePageChangeClasses = (pages) => {
|
||||
setCurrentPageClass(pages);
|
||||
}
|
||||
|
||||
const handleLimitsChangeClasses = (e) => {
|
||||
setLimitClass(e.target.value);
|
||||
setCurrentPageClass(1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchDataStudents();
|
||||
}, [pageStudent, limitStudent]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDataClasses();
|
||||
}, [pageClass, limitClass]);
|
||||
|
||||
const getClassTopic = async (id) => {
|
||||
setLoadingModal(true);
|
||||
try {
|
||||
const classes = await progressService.getTopicByClass(id, '', '', '', 1000);
|
||||
setSelected(classes.payload.data);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}finally{
|
||||
setLoadingModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleShow = (data) => {
|
||||
setSelectedId(data);
|
||||
getClassTopic(data);
|
||||
setShow(true);
|
||||
};
|
||||
const handleClose = () => {setShow(false); setSelected([])};
|
||||
|
||||
return {
|
||||
students,
|
||||
classes,
|
||||
loading,
|
||||
error,
|
||||
|
||||
totalPagesStudent,
|
||||
totalDataStudent,
|
||||
setSearchStudent,
|
||||
pageStudent,
|
||||
handlePageChangeStudents,
|
||||
handleLimitsChangeStudents,
|
||||
handleSearchChangeStudents,
|
||||
|
||||
totalPagesClass,
|
||||
totalDataClass,
|
||||
setSearchClass,
|
||||
pageClass,
|
||||
handlePageChangeClasses,
|
||||
handleLimitsChangeClasses,
|
||||
handleSearchChangeClasses,
|
||||
|
||||
selectedId,
|
||||
selected,
|
||||
show,
|
||||
loadingModal,
|
||||
handleShow,
|
||||
handleClose,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProgress;
|
||||
101
src/roles/teacher/feedback/hooks/useProgressClass.jsx
Normal file
101
src/roles/teacher/feedback/hooks/useProgressClass.jsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import progressService from '../services/serviceProgress';
|
||||
|
||||
const useProgressClass = (progressId) => {
|
||||
const [progress, setProgress] = useState([]);
|
||||
const [section, setSection] = useState('section');
|
||||
const [topic, setTopic] = useState('topic');
|
||||
const [name, setName] = useState('class');
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const [sort, setSort] = useState("");
|
||||
const [page, setCurrentPage] = useState(1);
|
||||
const [limit, setLimit] = useState(20);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [totalData, setTotalData] = useState(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
const [classId, topicId] = progressId.split("&");
|
||||
|
||||
const dataId = {
|
||||
ID_CLASS: classId,
|
||||
ID_TOPIC: topicId,
|
||||
};
|
||||
|
||||
try {
|
||||
const data = await progressService.fetchDataClassProgress(dataId, search, sort, page, limit);
|
||||
setProgress(data.payload.levels);
|
||||
setTotalPages(data.payload.totalPages);
|
||||
setTotalData(data.payload.totalItems);
|
||||
|
||||
setSection(data.payload.NAME_SECTION)
|
||||
setTopic(data.payload.NAME_TOPIC)
|
||||
setName(data.payload.NAME_CLASS)
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchChange = () => {
|
||||
fetchData();
|
||||
setCurrentPage(1);
|
||||
}
|
||||
|
||||
const handlePageChange = (pages) => {
|
||||
setCurrentPage(pages);
|
||||
}
|
||||
|
||||
const handleLimitsChange = (e) => {
|
||||
setLimit(e.target.value);
|
||||
setCurrentPage(1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, progressId]);
|
||||
|
||||
function formatLocalDate(isoDate) {
|
||||
const date = new Date(isoDate);
|
||||
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
timeZone: 'Asia/Jakarta'
|
||||
};
|
||||
|
||||
return new Intl.DateTimeFormat('id-ID', options).format(date)
|
||||
.replace(/\//g, '-')
|
||||
.replace(/\./g, ':') + ' WIB';
|
||||
}
|
||||
|
||||
return {
|
||||
progress,
|
||||
section,
|
||||
topic,
|
||||
name,
|
||||
nisn,
|
||||
loading,
|
||||
error,
|
||||
|
||||
totalPages,
|
||||
totalData,
|
||||
setSearch,
|
||||
page,
|
||||
handlePageChange,
|
||||
handleLimitsChange,
|
||||
handleSearchChange,
|
||||
formatLocalDate
|
||||
};
|
||||
};
|
||||
|
||||
export default useProgressClass;
|
||||
129
src/roles/teacher/feedback/hooks/useProgressStudent.jsx
Normal file
129
src/roles/teacher/feedback/hooks/useProgressStudent.jsx
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import progressService from '../services/serviceProgress';
|
||||
import jsPDF from "jspdf";
|
||||
import "jspdf-autotable";
|
||||
|
||||
const useProgressStudent = (monitoringId) => {
|
||||
const [progress, setProgress] = useState([]);
|
||||
const [section, setSection] = useState('section');
|
||||
const [topic, setTopic] = useState('topic');
|
||||
const [name, setName] = useState('name');
|
||||
const [nisn, setNisn] = useState('nisn');
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const [sort, setSort] = useState("");
|
||||
const [page, setCurrentPage] = useState(1);
|
||||
const [limit, setLimit] = useState(20);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [totalData, setTotalData] = useState(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await progressService.fetchDataStudentProgress(monitoringId, search, sort, page, limit);
|
||||
console.log(data);
|
||||
setProgress(data.payload.levels);
|
||||
setTotalPages(data.payload.totalPages);
|
||||
setTotalData(data.payload.totalItems);
|
||||
|
||||
setSection(data.payload.NAME_SECTION)
|
||||
setTopic(data.payload.NAME_TOPIC)
|
||||
setName(data.payload.NAME_USERS)
|
||||
setNisn(data.payload.NISN)
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchChange = () => {
|
||||
fetchData();
|
||||
setCurrentPage(1);
|
||||
}
|
||||
|
||||
const handlePageChange = (pages) => {
|
||||
setCurrentPage(pages);
|
||||
}
|
||||
|
||||
const handleLimitsChange = (e) => {
|
||||
setLimit(e.target.value);
|
||||
setCurrentPage(1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, monitoringId]);
|
||||
|
||||
function formatLocalDate(isoDate) {
|
||||
const date = new Date(isoDate);
|
||||
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
timeZone: 'Asia/Jakarta'
|
||||
};
|
||||
|
||||
return new Intl.DateTimeFormat('id-ID', options).format(date)
|
||||
.replace(/\//g, '-')
|
||||
.replace(/\./g, ':') + ' WIB';
|
||||
}
|
||||
|
||||
const downloadPDF = () => {
|
||||
const doc = new jsPDF();
|
||||
const pageWidth = doc.internal.pageSize.getWidth();
|
||||
|
||||
const text = "Progress Report";
|
||||
const textWidth = doc.getTextWidth(text);
|
||||
const titleCenter = (pageWidth - textWidth) / 2;
|
||||
|
||||
doc.setFontSize(14);
|
||||
doc.text(text, titleCenter, 12);
|
||||
|
||||
doc.setFontSize(12);
|
||||
doc.text(name, 14, 24);
|
||||
doc.text(nisn.toString(), 14, 30);
|
||||
doc.setFontSize(10);
|
||||
doc.text(`${section} - ${topic}`, 14, 36);
|
||||
|
||||
const columns = ["No", "Name", "Score", "Feedback", "Start Exercise", "Finish Exercise"];
|
||||
const rows = progress.map((item, index) => [(index+1), item.NAME_LEVEL, item.SCORE, item.FEEDBACK_STUDENT, formatLocalDate(item.STUDENT_START), formatLocalDate(item.STUDENT_FINISH)]);
|
||||
|
||||
doc.autoTable({
|
||||
head: [columns],
|
||||
body: rows,
|
||||
startY: 44,
|
||||
});
|
||||
|
||||
doc.save(`SEALS-report-${name}.pdf`);
|
||||
};
|
||||
|
||||
return {
|
||||
progress,
|
||||
section,
|
||||
topic,
|
||||
name,
|
||||
nisn,
|
||||
loading,
|
||||
error,
|
||||
|
||||
totalPages,
|
||||
totalData,
|
||||
setSearch,
|
||||
page,
|
||||
handlePageChange,
|
||||
handleLimitsChange,
|
||||
handleSearchChange,
|
||||
formatLocalDate,
|
||||
downloadPDF
|
||||
};
|
||||
};
|
||||
|
||||
export default useProgressStudent;
|
||||
70
src/roles/teacher/feedback/services/serviceProgress.jsx
Normal file
70
src/roles/teacher/feedback/services/serviceProgress.jsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import axiosInstance from '../../../../utils/axiosInstance';
|
||||
|
||||
const fetchDataStudent = async (search, sort, page, limit) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/monitoring/progress?search=${search}&sort=${sort}&page=${page}&limit=${limit}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching progress:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDataClass = async (search, sort, page, limit) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/class/admin?search=${search}&sort=${sort}&page=${page}&limit=${limit}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching progress:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getStudentById = async (id) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/progress/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching progress with student ID ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getTopicByClass = async (id, search, sort, page, limit) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/monitoring/class/${id}?search=${search}&sort=${sort}&page=${page}&limit=${limit}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching progress:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDataStudentProgress = async (id, search, sort, page, limit) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/monitoring/progress/${id}?search=${search}&sort=${sort}&page=${page}&limit=${limit}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching progress:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDataClassProgress = async (dataId, search, sort, page, limit) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/monitoring/class?search=${search}&sort=${sort}&page=${page}&limit=${limit}`, dataId);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching progress:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default{
|
||||
fetchDataStudent,
|
||||
fetchDataClass,
|
||||
getStudentById,
|
||||
getTopicByClass,
|
||||
fetchDataStudentProgress,
|
||||
fetchDataClassProgress
|
||||
};
|
||||
141
src/roles/teacher/feedback/views/ClassFeedback.jsx
Normal file
141
src/roles/teacher/feedback/views/ClassFeedback.jsx
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import React from 'react';
|
||||
import { Table, Row, Col, Button, Form, Dropdown, DropdownButton, Breadcrumb, Spinner } from 'react-bootstrap';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import useProgressClass from '../hooks/useProgressClass';
|
||||
import TablePaginate from '../../../../components/ui/TablePaginate';
|
||||
|
||||
const ClassFeedback = () => {
|
||||
const { progressId } = useParams();
|
||||
const {
|
||||
progress,
|
||||
section,
|
||||
topic,
|
||||
name,
|
||||
nisn,
|
||||
loading,
|
||||
error,
|
||||
|
||||
totalPages,
|
||||
totalData,
|
||||
setSearch,
|
||||
page,
|
||||
handlePageChange,
|
||||
handleLimitsChange,
|
||||
handleSearchChange,
|
||||
formatLocalDate
|
||||
} = useProgressClass(progressId);
|
||||
return (
|
||||
<div className='admin-teachers'>
|
||||
<Row className='mb-45'>
|
||||
<Col sm={6} className="d-flex align-items-center breadcrumb-con">
|
||||
<Button as={Link} className='btn btn-blue btn-square-back' to='/portal/feedback'>
|
||||
<i className="bi bi-arrow-90deg-left"></i>
|
||||
</Button>
|
||||
<Breadcrumb className='custom-breadcrumb'>
|
||||
<Breadcrumb.Item href="/portal/feedback">Monitoring Progress</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>Progress Details</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
</Col>
|
||||
<Col sm={6} className="d-flex align-items-center justify-content-end">
|
||||
<Button variant="outline-blue" type="button" className='py-2 bg-white'>
|
||||
<i className="bi bi-download me-2"></i>Download PDF
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className='mb-45'>
|
||||
<Col>
|
||||
<div className="cards">
|
||||
<div className="cards-title">
|
||||
<h4 className='mb-2'>{nisn} - {name}</h4>
|
||||
<span className='text-grey'>{section} / {topic}</span>
|
||||
</div>
|
||||
<div className="cards-body">
|
||||
<Form className="mb-3 d-flex align-items-strech" onSubmit={(e) => { e.preventDefault(); handleSearchChange(); }}>
|
||||
<Form.Control type='search'
|
||||
aria-label="Large"
|
||||
aria-describedby="inputGroup-sizing-sm"
|
||||
placeholder='Search'
|
||||
className='table-input-search mb-0 me-2 rounded-3'
|
||||
onChange={(e) => { setSearch(e.target.value); }}
|
||||
/>
|
||||
<Button type='submit' variant='blue rounded-3'>Search</Button>
|
||||
</Form>
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>NISN</th>
|
||||
<th>Full Name</th>
|
||||
<th>Level</th>
|
||||
<th>Score</th>
|
||||
<th>Start Exercise</th>
|
||||
<th>Finish Exercise</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading?(
|
||||
<tr>
|
||||
<td colSpan={6} style={{height:"20vh"}}>
|
||||
<Spinner animation="grow" variant="primary" />
|
||||
<Spinner animation="grow" variant="secondary" />
|
||||
<Spinner animation="grow" variant="success" />
|
||||
<Spinner animation="grow" variant="danger" />
|
||||
<Spinner animation="grow" variant="warning" />
|
||||
<Spinner animation="grow" variant="info" />
|
||||
</td>
|
||||
</tr>
|
||||
):(
|
||||
progress.length > 0?(
|
||||
progress.map((data, index) => (
|
||||
<tr key={index}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{data.NISN}</td>
|
||||
<td>{data.NAME_USERS}</td>
|
||||
<td>{data.NAME_LEVEL}</td>
|
||||
<td>{data.SCORE}</td>
|
||||
<td>{formatLocalDate(data.STUDENT_START)}</td>
|
||||
<td>{formatLocalDate(data.STUDENT_FINISH)}</td>
|
||||
</tr>
|
||||
))
|
||||
):(
|
||||
<tr>
|
||||
<td colSpan={6} style={{height:'20vh'}}>
|
||||
<h3>Empty Data</h3>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<div className="mt-2 w-100 d-flex justify-content-between align-items-center">
|
||||
<div className='d-flex align-items-center'>
|
||||
<small className="me-2">Item per page</small>
|
||||
<Form.Select
|
||||
size='sm'
|
||||
className='py-0 px-1 me-2'
|
||||
aria-label="Default select example"
|
||||
defaultValue='7'
|
||||
onChange={handleLimitsChange}
|
||||
style={{ width: '50px' }}
|
||||
>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</Form.Select>
|
||||
<small>of {totalData}</small>
|
||||
</div>
|
||||
<TablePaginate
|
||||
totalPages={totalPages}
|
||||
currentPage={page}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClassFeedback;
|
||||
250
src/roles/teacher/feedback/views/Feedback.jsx
Normal file
250
src/roles/teacher/feedback/views/Feedback.jsx
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Table, Row, Col, Nav, Tab, Button, Form, Card, Modal, Spinner } from 'react-bootstrap';
|
||||
import useProgress from '../hooks/useProgress';
|
||||
import TablePaginate from '../../../../components/ui/TablePaginate';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Feedback = () => {
|
||||
const {
|
||||
students,
|
||||
classes,
|
||||
loading,
|
||||
error,
|
||||
|
||||
totalPagesStudent,
|
||||
totalDataStudent,
|
||||
setSearchStudent,
|
||||
pageStudent,
|
||||
handlePageChangeStudents,
|
||||
handleLimitsChangeStudents,
|
||||
handleSearchChangeStudents,
|
||||
|
||||
totalPagesClass,
|
||||
totalDataClass,
|
||||
setSearchClass,
|
||||
pageClass,
|
||||
handlePageChangeClasses,
|
||||
handleLimitsChangeClasses,
|
||||
handleSearchChangeClasses,
|
||||
|
||||
selectedId,
|
||||
selected,
|
||||
show,
|
||||
loadingModal,
|
||||
handleShow,
|
||||
handleClose,
|
||||
} = useProgress();
|
||||
|
||||
if (error) {
|
||||
<>{error}</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='admin-students'>
|
||||
<h2 className='page-title strip'>Learning Progress</h2>
|
||||
<p className='page-desc'>Follow student activity closely and monitor their English learning journey.</p>
|
||||
<Tab.Container id="left-tabs-example" defaultActiveKey="student">
|
||||
<Row className='mb-45'>
|
||||
<Col xs={12}>
|
||||
<Nav variant="pills" className='col-tabs'>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="student">Student</Nav.Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="class">Class</Nav.Link>
|
||||
</Nav.Item>
|
||||
</Nav>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className='mb-45'>
|
||||
<Col xs={12} className='col-tabs-content'>
|
||||
<Tab.Content>
|
||||
<Tab.Pane eventKey="student">
|
||||
<div className="cards">
|
||||
<div className="cards-title">
|
||||
<h4>Student List</h4>
|
||||
</div>
|
||||
<div className="cards-body">
|
||||
<Form className="mb-3 d-flex align-items-strech" onSubmit={(e) => { e.preventDefault(); handleSearchChangeStudents(); }}>
|
||||
<Form.Control type='search'
|
||||
aria-label="Large"
|
||||
aria-describedby="inputGroup-sizing-sm"
|
||||
placeholder='Search'
|
||||
className='table-input-search mb-0 me-2 rounded-3'
|
||||
onChange={(e) => { setSearchStudent(e.target.value); }}
|
||||
/>
|
||||
<Button type='submit' variant='blue rounded-3'>Search</Button>
|
||||
</Form>
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>NISN</th>
|
||||
<th>Full Name</th>
|
||||
<th>Section</th>
|
||||
<th>Topic</th>
|
||||
<th className='text-center'>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading?(
|
||||
<tr>
|
||||
<td colSpan={6} style={{height:"20vh"}}>
|
||||
<Spinner animation="grow" variant="primary" />
|
||||
<Spinner animation="grow" variant="secondary" />
|
||||
<Spinner animation="grow" variant="success" />
|
||||
<Spinner animation="grow" variant="danger" />
|
||||
<Spinner animation="grow" variant="warning" />
|
||||
<Spinner animation="grow" variant="info" />
|
||||
</td>
|
||||
</tr>
|
||||
):(
|
||||
students.length > 0?(
|
||||
students.map((data, index) => (
|
||||
<tr key={data.ID_MONITORING}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{data.NISN}</td>
|
||||
<td>{data.NAME_USERS}</td>
|
||||
<td>{data.NAME_SECTION}</td>
|
||||
<td>{data.NAME_TOPIC}</td>
|
||||
<td className='text-center action-col'>
|
||||
<Link className='btn btn-sm btn-view' to={`s/${data.ID_MONITORING}`}><i className="bi bi-eye"></i></Link>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
):(
|
||||
<tr>
|
||||
<td colSpan={5} style={{height:'20vh'}}>
|
||||
<h3>Empty Data</h3>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<div className="mt-2 w-100 d-flex justify-content-between align-items-center">
|
||||
<div className='d-flex align-items-center'>
|
||||
<small className="me-2">Item per page</small>
|
||||
<Form.Select
|
||||
size='sm'
|
||||
className='py-0 px-1 me-2'
|
||||
aria-label="Default select example"
|
||||
defaultValue='7'
|
||||
onChange={handleLimitsChangeStudents}
|
||||
style={{ width: '50px' }}
|
||||
>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</Form.Select>
|
||||
<small>of {totalDataStudent}</small>
|
||||
</div>
|
||||
<TablePaginate
|
||||
totalPages={totalPagesStudent}
|
||||
currentPage={pageStudent}
|
||||
onPageChange={handlePageChangeStudents}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="class">
|
||||
<div className="cards">
|
||||
<div className="cards-title">
|
||||
<h4>Class List</h4>
|
||||
</div>
|
||||
<div className="cards-body">
|
||||
<Form className="mb-3 d-flex align-items-strech" onSubmit={(e) => { e.preventDefault(); handleSearchChangeClasses(); }}>
|
||||
<Form.Control type='search'
|
||||
aria-label="Large"
|
||||
aria-describedby="inputGroup-sizing-sm"
|
||||
placeholder='Search'
|
||||
className='table-input-search mb-0 me-2 rounded-3'
|
||||
onChange={(e) => { setSearchClass(e.target.value); }}
|
||||
/>
|
||||
<Button type='submit' variant='blue rounded-3'>Search</Button>
|
||||
</Form>
|
||||
<Row>
|
||||
{loading?(
|
||||
<div className='d-flex justify-content-center align-items-center' style={{height:"20vh"}}>
|
||||
<Spinner animation="grow" variant="primary" />
|
||||
<Spinner animation="grow" variant="secondary" />
|
||||
<Spinner animation="grow" variant="success" />
|
||||
<Spinner animation="grow" variant="danger" />
|
||||
<Spinner animation="grow" variant="warning" />
|
||||
<Spinner animation="grow" variant="info" />
|
||||
</div>
|
||||
):(
|
||||
classes.map((data, index) => (
|
||||
<Col xs={6} lg={4} xxl={3} className='mb-3' key={index}>
|
||||
<Card className='h-100'>
|
||||
<Card.Body>
|
||||
<Card.Title as='h2'>{data.NAME_CLASS}</Card.Title>
|
||||
<Card.Text className='mb-4 fs-14p'>
|
||||
{data.STUDENTS < data.TOTAL_STUDENT?(
|
||||
<>
|
||||
<span className='fw-bold'>{data.STUDENTS} </span>
|
||||
out of {data.TOTAL_STUDENT} students
|
||||
</>
|
||||
):(
|
||||
`${data.TOTAL_STUDENT} students`
|
||||
)}
|
||||
</Card.Text>
|
||||
{/* <Button as={Link} to={`c/${data.ID_CLASS}`} variant="warning" className='py-2 w-100 rounded-35'>See Details</Button> */}
|
||||
<Button onClick={() => {handleShow(data.ID_CLASS);}} variant="warning" className='py-2 w-100 rounded-35'>See Details</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))
|
||||
)}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Pane>
|
||||
</Tab.Content>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab.Container>
|
||||
|
||||
<Modal show={show} onHide={handleClose} className='modal-admin' size='lg' centered>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Select Topic</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{loadingModal?(
|
||||
<div className='d-flex justify-content-center align-items-center' style={{height:"20vh"}}>
|
||||
<Spinner animation="grow" variant="primary" />
|
||||
<Spinner animation="grow" variant="secondary" />
|
||||
<Spinner animation="grow" variant="success" />
|
||||
<Spinner animation="grow" variant="danger" />
|
||||
<Spinner animation="grow" variant="warning" />
|
||||
<Spinner animation="grow" variant="info" />
|
||||
</div>
|
||||
):(
|
||||
selected.length > 0?(
|
||||
<Row>
|
||||
{selected.map((data, index) => (
|
||||
<Col xs={6} lg={4} xxl={3} className='mb-3' key={index}>
|
||||
<Card className='h-100'>
|
||||
<Card.Body>
|
||||
<Card.Title as='h3'>{data.NAME_SECTION}</Card.Title>
|
||||
<Card.Text className='mb-4 fs-14p'>
|
||||
{data.NAME_TOPIC}
|
||||
</Card.Text>
|
||||
<Button as={Link} to={`c/${selectedId}&${data.ID_TOPIC}`} variant="warning" className='py-2 w-100 rounded-35'>See Details</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
):(
|
||||
<h2 className='my-3 text-center'>No Progress from this class</h2>
|
||||
)
|
||||
)}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Feedback;
|
||||
140
src/roles/teacher/feedback/views/StudentFeedback.jsx
Normal file
140
src/roles/teacher/feedback/views/StudentFeedback.jsx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import React from 'react';
|
||||
import { Table, Row, Col, Button, Form, Dropdown, DropdownButton, Breadcrumb, Spinner } from 'react-bootstrap';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import useProgressStudent from '../hooks/useProgressStudent';
|
||||
import TablePaginate from '../../../../components/ui/TablePaginate';
|
||||
|
||||
const StudentFeedback = () => {
|
||||
const { progressId } = useParams();
|
||||
const {
|
||||
progress,
|
||||
section,
|
||||
topic,
|
||||
name,
|
||||
nisn,
|
||||
loading,
|
||||
error,
|
||||
|
||||
totalPages,
|
||||
totalData,
|
||||
setSearch,
|
||||
page,
|
||||
handlePageChange,
|
||||
handleLimitsChange,
|
||||
handleSearchChange,
|
||||
formatLocalDate,
|
||||
downloadPDF
|
||||
} = useProgressStudent(progressId);
|
||||
return (
|
||||
<div className='admin-teachers'>
|
||||
<Row className='mb-45'>
|
||||
<Col sm={6} className="d-flex align-items-center breadcrumb-con">
|
||||
<Button as={Link} className='btn btn-blue btn-square-back' to='/portal/feedback'>
|
||||
<i className="bi bi-arrow-90deg-left"></i>
|
||||
</Button>
|
||||
<Breadcrumb className='custom-breadcrumb'>
|
||||
<Breadcrumb.Item href="/portal/feedback">Monitoring Progress</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>Progress Details</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
</Col>
|
||||
<Col sm={6} className="d-flex align-items-center justify-content-end">
|
||||
<Button variant="outline-blue" type="button" className='py-2 bg-white' onClick={downloadPDF}>
|
||||
<i className="bi bi-download me-2"></i>Download PDF
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className='mb-45'>
|
||||
<Col>
|
||||
<div className="cards">
|
||||
<div className="cards-title">
|
||||
<h4 className='mb-2'>{nisn} - {name}</h4>
|
||||
<span className='text-grey'>{section} / {topic}</span>
|
||||
</div>
|
||||
<div className="cards-body">
|
||||
<Form className="mb-3 d-flex align-items-strech" onSubmit={(e) => { e.preventDefault(); handleSearchChange(); }}>
|
||||
<Form.Control type='search'
|
||||
aria-label="Large"
|
||||
aria-describedby="inputGroup-sizing-sm"
|
||||
placeholder='Search'
|
||||
className='table-input-search mb-0 me-2 rounded-3'
|
||||
onChange={(e) => { setSearch(e.target.value); }}
|
||||
/>
|
||||
<Button type='submit' variant='blue rounded-3'>Search</Button>
|
||||
</Form>
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Level</th>
|
||||
<th>Score</th>
|
||||
<th>Feedback</th>
|
||||
<th>Start Exercise</th>
|
||||
<th>Finish Exercise</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading?(
|
||||
<tr>
|
||||
<td colSpan={6} style={{height:"20vh"}}>
|
||||
<Spinner animation="grow" variant="primary" />
|
||||
<Spinner animation="grow" variant="secondary" />
|
||||
<Spinner animation="grow" variant="success" />
|
||||
<Spinner animation="grow" variant="danger" />
|
||||
<Spinner animation="grow" variant="warning" />
|
||||
<Spinner animation="grow" variant="info" />
|
||||
</td>
|
||||
</tr>
|
||||
):(
|
||||
progress.length > 0?(
|
||||
progress.map((data, index) => (
|
||||
<tr key={index}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{data.NAME_LEVEL}</td>
|
||||
<td>{data.SCORE}</td>
|
||||
<td>{data.FEEDBACK_STUDENT}</td>
|
||||
<td>{formatLocalDate(data.STUDENT_START)}</td>
|
||||
<td>{formatLocalDate(data.STUDENT_FINISH)}</td>
|
||||
</tr>
|
||||
))
|
||||
):(
|
||||
<tr>
|
||||
<td colSpan={6} style={{height:'20vh'}}>
|
||||
<h3>Empty Data</h3>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<div className="mt-2 w-100 d-flex justify-content-between align-items-center">
|
||||
<div className='d-flex align-items-center'>
|
||||
<small className="me-2">Item per page</small>
|
||||
<Form.Select
|
||||
size='sm'
|
||||
className='py-0 px-1 me-2'
|
||||
aria-label="Default select example"
|
||||
defaultValue='7'
|
||||
onChange={handleLimitsChange}
|
||||
style={{ width: '50px' }}
|
||||
>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</Form.Select>
|
||||
<small>of {totalData}</small>
|
||||
</div>
|
||||
<TablePaginate
|
||||
totalPages={totalPages}
|
||||
currentPage={page}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudentFeedback;
|
||||
|
|
@ -32,14 +32,6 @@ const ClassDetail = () => {
|
|||
<h4>{nameClass}</h4>
|
||||
</div>
|
||||
<div className="cards-body">
|
||||
<div className="d-flex">
|
||||
<Form.Control
|
||||
aria-label="Large"
|
||||
aria-describedby="inputGroup-sizing-sm"
|
||||
placeholder='Search'
|
||||
className='table-input-search'
|
||||
/>
|
||||
</div>
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ const useProgress = () => {
|
|||
setLoading(true);
|
||||
try {
|
||||
const student = await progressService.fetchDataStudent(searchStudent, sortStudent, pageStudent, limitStudent);
|
||||
setStudents(student.payload.monitorings);
|
||||
console.log(student);
|
||||
setStudents(student.payload.studentActivities);
|
||||
setTotalPagesStudent(student.payload.totalPages);
|
||||
setTotalDataStudent(student.payload.totalItems);
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import progressService from '../services/serviceProgress';
|
||||
|
||||
const useProgressClass = (progressId) => {
|
||||
const useProgressClass = (classId) => {
|
||||
const [progress, setProgress] = useState([]);
|
||||
const [section, setSection] = useState('section');
|
||||
const [topic, setTopic] = useState('topic');
|
||||
const [name, setName] = useState('class');
|
||||
const [nameClass, setNameClass] = useState('class');
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
|
@ -19,22 +17,17 @@ const useProgressClass = (progressId) => {
|
|||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
const [classId, topicId] = progressId.split("&");
|
||||
|
||||
const dataId = {
|
||||
ID_CLASS: classId,
|
||||
ID_TOPIC: topicId,
|
||||
ID_CLASS: classId
|
||||
};
|
||||
|
||||
try {
|
||||
const data = await progressService.fetchDataClassProgress(dataId, search, sort, page, limit);
|
||||
setProgress(data.payload.levels);
|
||||
const data = await progressService.fetchDataClassProgress(classId, search, sort, page, limit);
|
||||
console.log(data);
|
||||
setProgress(data.payload.studentActivities);
|
||||
setTotalPages(data.payload.totalPages);
|
||||
setTotalData(data.payload.totalItems);
|
||||
|
||||
setSection(data.payload.NAME_SECTION)
|
||||
setTopic(data.payload.NAME_TOPIC)
|
||||
setName(data.payload.NAME_CLASS)
|
||||
setNameClass(data.payload.studentActivities[0].NAME_CLASS);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
|
|
@ -58,7 +51,7 @@ const useProgressClass = (progressId) => {
|
|||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, progressId]);
|
||||
}, [page, limit, classId]);
|
||||
|
||||
function formatLocalDate(isoDate) {
|
||||
const date = new Date(isoDate);
|
||||
|
|
@ -80,10 +73,7 @@ const useProgressClass = (progressId) => {
|
|||
|
||||
return {
|
||||
progress,
|
||||
section,
|
||||
topic,
|
||||
name,
|
||||
nisn,
|
||||
nameClass,
|
||||
loading,
|
||||
error,
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const useProgressStudent = (monitoringId) => {
|
|||
setLoading(true);
|
||||
try {
|
||||
const data = await progressService.fetchDataStudentProgress(monitoringId, search, sort, page, limit);
|
||||
console.log(data);
|
||||
setProgress(data.payload.levels);
|
||||
setTotalPages(data.payload.totalPages);
|
||||
setTotalData(data.payload.totalItems);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import axios from 'axios';
|
||||
import axiosInstance from '../../../../utils/axiosInstance';
|
||||
import { API_URL } from '../../../../utils/Constant';
|
||||
|
||||
const fetchDataStudent = async (search, sort, page, limit) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/monitoring/progress?search=${search}&sort=${sort}&page=${page}&limit=${limit}`);
|
||||
const response = await axiosInstance.get(`/stdLearning/activities?search=${search}&sort=${sort}&page=${page}&limit=${limit}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching progress:', error);
|
||||
|
|
@ -50,9 +52,9 @@ const fetchDataStudentProgress = async (id, search, sort, page, limit) => {
|
|||
}
|
||||
};
|
||||
|
||||
const fetchDataClassProgress = async (dataId, search, sort, page, limit) => {
|
||||
const fetchDataClassProgress = async (classId, search, sort, page, limit) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/monitoring/class?search=${search}&sort=${sort}&page=${page}&limit=${limit}`, dataId);
|
||||
const response = await axiosInstance.get(`/stdLearning/activities/class/${classId}?search=${search}&sort=${sort}&page=${page}&limit=${limit}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching progress:', error);
|
||||
|
|
|
|||
|
|
@ -5,13 +5,10 @@ import useProgressClass from '../hooks/useProgressClass';
|
|||
import TablePaginate from '../../../../components/ui/TablePaginate';
|
||||
|
||||
const ClassProgress = () => {
|
||||
const { progressId } = useParams();
|
||||
const { classId } = useParams();
|
||||
const {
|
||||
progress,
|
||||
section,
|
||||
topic,
|
||||
name,
|
||||
nisn,
|
||||
nameClass,
|
||||
loading,
|
||||
error,
|
||||
|
||||
|
|
@ -23,7 +20,7 @@ const ClassProgress = () => {
|
|||
handleLimitsChange,
|
||||
handleSearchChange,
|
||||
formatLocalDate
|
||||
} = useProgressClass(progressId);
|
||||
} = useProgressClass(classId);
|
||||
return (
|
||||
<div className='admin-teachers'>
|
||||
<Row className='mb-45'>
|
||||
|
|
@ -33,7 +30,8 @@ const ClassProgress = () => {
|
|||
</Button>
|
||||
<Breadcrumb className='custom-breadcrumb'>
|
||||
<Breadcrumb.Item href="/portal/monitoring">Monitoring Progress</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>Progress Details</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/portal/monitoring">Class Detail</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>{nameClass}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
</Col>
|
||||
<Col sm={6} className="d-flex align-items-center justify-content-end">
|
||||
|
|
@ -46,8 +44,7 @@ const ClassProgress = () => {
|
|||
<Col>
|
||||
<div className="cards">
|
||||
<div className="cards-title">
|
||||
<h4 className='mb-2'>{nisn} - {name}</h4>
|
||||
<span className='text-grey'>{section} / {topic}</span>
|
||||
<h4 className='mb-2'>Student Last Activity</h4>
|
||||
</div>
|
||||
<div className="cards-body">
|
||||
<Form className="mb-3 d-flex align-items-strech" onSubmit={(e) => { e.preventDefault(); handleSearchChange(); }}>
|
||||
|
|
@ -63,19 +60,20 @@ const ClassProgress = () => {
|
|||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>NISN</th>
|
||||
<th className='text-start'>NISN</th>
|
||||
<th>Full Name</th>
|
||||
<th className='text-center'>Class</th>
|
||||
<th>Section</th>
|
||||
<th>Topic</th>
|
||||
<th>Level</th>
|
||||
<th>Score</th>
|
||||
<th>Start Exercise</th>
|
||||
<th>Finish Exercise</th>
|
||||
<th className='text-center'>Score</th>
|
||||
<th className='text-center'>Review</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading?(
|
||||
<tr>
|
||||
<td colSpan={6} style={{height:"20vh"}}>
|
||||
<td colSpan={8} style={{height:"20vh"}}>
|
||||
<Spinner animation="grow" variant="primary" />
|
||||
<Spinner animation="grow" variant="secondary" />
|
||||
<Spinner animation="grow" variant="success" />
|
||||
|
|
@ -88,18 +86,21 @@ const ClassProgress = () => {
|
|||
progress.length > 0?(
|
||||
progress.map((data, index) => (
|
||||
<tr key={index}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{data.NISN}</td>
|
||||
<td className='text-start'>{data.NISN}</td>
|
||||
<td>{data.NAME_USERS}</td>
|
||||
<td className='text-center'>{data.NAME_CLASS ? data.NAME_CLASS : '-'}</td>
|
||||
<td>{data.NAME_SECTION}</td>
|
||||
<td>{data.NAME_TOPIC}</td>
|
||||
<td>{data.NAME_LEVEL}</td>
|
||||
<td>{data.SCORE}</td>
|
||||
<td>{formatLocalDate(data.STUDENT_START)}</td>
|
||||
<td>{formatLocalDate(data.STUDENT_FINISH)}</td>
|
||||
<td className='text-center'>{data.SCORE}</td>
|
||||
<td className='text-center action-col'>
|
||||
<Link className='btn btn-sm btn-view' to={`/portal/monitoring/s/${data.ID_STUDENT_LEARNING}`}><i className="bi bi-eye"></i></Link>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
):(
|
||||
<tr>
|
||||
<td colSpan={6} style={{height:'20vh'}}>
|
||||
<td colSpan={8} style={{height:'20vh'}}>
|
||||
<h3>Empty Data</h3>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ const Monitoring = () => {
|
|||
|
||||
return (
|
||||
<div className='admin-students'>
|
||||
<h2 className='page-title strip'>Learning Progress</h2>
|
||||
<p className='page-desc'>Follow student activity closely and monitor their English learning journey.</p>
|
||||
<h2 className='page-title'>Stay updated on student progress!</h2>
|
||||
<p className='page-desc'>Closely track student activity and monitor their English learning journey.</p>
|
||||
<Tab.Container id="left-tabs-example" defaultActiveKey="student">
|
||||
<Row className='mb-45'>
|
||||
<Col xs={12}>
|
||||
|
|
@ -78,12 +78,14 @@ const Monitoring = () => {
|
|||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>NISN</th>
|
||||
<th className='text-start'>NISN</th>
|
||||
<th>Full Name</th>
|
||||
<th className='text-center'>Class</th>
|
||||
<th>Section</th>
|
||||
<th>Topic</th>
|
||||
<th className='text-center'>Action</th>
|
||||
<th>Level</th>
|
||||
<th className='text-center'>Score</th>
|
||||
<th className='text-center'>Review</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -101,14 +103,16 @@ const Monitoring = () => {
|
|||
):(
|
||||
students.length > 0?(
|
||||
students.map((data, index) => (
|
||||
<tr key={data.ID_MONITORING}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{data.NISN}</td>
|
||||
<tr key={data.ID_STUDENT_LEARNING}>
|
||||
<td className='text-start'>{data.NISN}</td>
|
||||
<td>{data.NAME_USERS}</td>
|
||||
<td className='text-center'>{data.NAME_CLASS ? data.NAME_CLASS : '-'}</td>
|
||||
<td>{data.NAME_SECTION}</td>
|
||||
<td>{data.NAME_TOPIC}</td>
|
||||
<td>{data.NAME_LEVEL}</td>
|
||||
<td className='text-center'>{data.SCORE}</td>
|
||||
<td className='text-center action-col'>
|
||||
<Link className='btn btn-sm btn-view' to={`s/${data.ID_MONITORING}`}><i className="bi bi-eye"></i></Link>
|
||||
<Link className='btn btn-sm btn-view' to={`s/${data.ID_STUDENT_LEARNING}`}><i className="bi bi-eye"></i></Link>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
|
|
@ -190,8 +194,8 @@ const Monitoring = () => {
|
|||
`${data.TOTAL_STUDENT} students`
|
||||
)}
|
||||
</Card.Text>
|
||||
{/* <Button as={Link} to={`c/${data.ID_CLASS}`} variant="warning" className='py-2 w-100 rounded-35'>See Details</Button> */}
|
||||
<Button onClick={() => {handleShow(data.ID_CLASS);}} variant="warning" className='py-2 w-100 rounded-35'>See Details</Button>
|
||||
<Button as={Link} to={`c/${data.ID_CLASS}`} variant="warning" className='py-2 w-100 rounded-35'>See Details</Button>
|
||||
{/* <Button onClick={() => {handleShow(data.ID_CLASS);}} variant="warning" className='py-2 w-100 rounded-35'>See Details</Button> */}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
|
|
@ -211,19 +215,6 @@ const Monitoring = () => {
|
|||
<Modal.Title>Select Topic</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Row>
|
||||
<Col xs={6} lg={4} className='mb-3'>
|
||||
<Card className='h-100'>
|
||||
<Card.Body>
|
||||
<Card.Title as='h3'>Vocabulary</Card.Title>
|
||||
<Card.Text className='mb-4 fs-14p'>
|
||||
The legend of naresh
|
||||
</Card.Text>
|
||||
<Button as={Link} to={`c/${selectedId}&21a9c23e-9737-49f5-bbd5-5b0ecac3f73d`} variant="warning" className='py-2 w-100 rounded-35'>See Details</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
{loadingModal?(
|
||||
<div className='d-flex justify-content-center align-items-center' style={{height:"20vh"}}>
|
||||
<Spinner animation="grow" variant="primary" />
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const useSettings = () => {
|
|||
const formData = new FormData();
|
||||
formData.append('NAME_USERS', profile.NAME_USERS);
|
||||
formData.append('EMAIL', profile.EMAIL);
|
||||
formData.append('NISN', profile.NISN);
|
||||
formData.append('NIP', profile.NIP);
|
||||
|
||||
if (selectedImage) {
|
||||
formData.append('PICTURE', selectedImage);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import axios from 'axios';
|
||||
import { API_URL } from '../../../../utils/Constant';
|
||||
import axiosInstance from '../../../../utils/axiosInstance';
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
|
|
@ -9,7 +8,7 @@ const config = {
|
|||
|
||||
const fetchProfile = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/getMe`, config);
|
||||
const response = await axiosInstance.get(`/getMe`, config);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
|
@ -17,16 +16,8 @@ const fetchProfile = async () => {
|
|||
};
|
||||
|
||||
const updateProfile = async (id, formData) => {
|
||||
|
||||
const cfg = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
Authorization: localStorage.getItem('token')
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await axios.put(`${API_URL}/user/update/${id}`, formData, cfg);
|
||||
const response = await axiosInstance.put(`/user/update/${id}`, formData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
|
@ -34,14 +25,8 @@ const updateProfile = async (id, formData) => {
|
|||
};
|
||||
|
||||
const updatePassword = async (userId, passwordData) => {
|
||||
const cfg = {
|
||||
headers: {
|
||||
Authorization: localStorage.getItem('token'),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
try {
|
||||
const response = await axios.put(`${API_URL}/user/update/password/${userId}`, passwordData, cfg);
|
||||
const response = await axiosInstance.put(`/user/update/password/${userId}`, passwordData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Table, Row, Col, Nav, Tab, Button, Form, InputGroup, Dropdown, DropdownButton, Modal } from 'react-bootstrap';
|
||||
import { Table, Row, Col, Nav, Tab, Button, Form, InputGroup, Dropdown, DropdownButton, Modal, Spinner } from 'react-bootstrap';
|
||||
import avatar from '../../../../assets/images/default-avatar.jpg';
|
||||
import ilustration from '../../../../assets/images/illustration/changePass.png';
|
||||
import successModal from '../../../../assets/images/illustration/successModal.png';
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user