fixing teacher feedback page

This commit is contained in:
Dimas Atmodjo 2024-12-16 00:40:10 +07:00
parent f5e8b7c566
commit 866b542e12
7 changed files with 173 additions and 32 deletions

View File

@ -92,7 +92,7 @@ const useProgress = () => {
const getClassTopic = async (id) => { const getClassTopic = async (id) => {
setLoadingModal(true); setLoadingModal(true);
try { try {
const classes = await progressService.getTopicByClass(id, '', '', '', 1000); const classes = await progressService.getTopicByClass(id);
setSelected(classes.payload.data); setSelected(classes.payload.data);
} catch (err) { } catch (err) {
console.log(err); console.log(err);

View File

@ -78,12 +78,30 @@ const useProgressClass = (progressId) => {
.replace(/\./g, ':') + ' WIB'; .replace(/\./g, ':') + ' WIB';
} }
const handleDownloadCSV = async () => {
try {
const response = await progressService.getCsvProgress();
const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = `progress ${name}-${topic}.csv`;
link.click();
URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Error downloading file:', error);
alert('Gagal mengunduh file');
}
}
return { return {
progress, progress,
section, section,
topic, topic,
name, name,
nisn,
loading, loading,
error, error,
@ -94,7 +112,8 @@ const useProgressClass = (progressId) => {
handlePageChange, handlePageChange,
handleLimitsChange, handleLimitsChange,
handleSearchChange, handleSearchChange,
formatLocalDate formatLocalDate,
handleDownloadCSV
}; };
}; };

View File

@ -20,6 +20,13 @@ const useProgressStudent = (monitoringId) => {
const [totalPages, setTotalPages] = useState(0); const [totalPages, setTotalPages] = useState(0);
const [totalData, setTotalData] = useState(null); const [totalData, setTotalData] = useState(null);
const [idMonitoring, setIdMonitoring] = useState(null);
const [showFeedback, setShowFeedback] = useState(false);
const [feedback, setFeedback] = useState("");
const [successFeedback, setSuccessFeedback] = useState(false);
const [loadingFeedback, setLoadingFeedback] = useState(false);
const fetchData = async () => { const fetchData = async () => {
setLoading(true); setLoading(true);
try { try {
@ -28,7 +35,8 @@ const useProgressStudent = (monitoringId) => {
setProgress(data.payload.levels); setProgress(data.payload.levels);
setTotalPages(data.payload.totalPages); setTotalPages(data.payload.totalPages);
setTotalData(data.payload.totalItems); setTotalData(data.payload.totalItems);
setIdMonitoring(data.payload.ID_MONITORING);
setSection(data.payload.NAME_SECTION) setSection(data.payload.NAME_SECTION)
setTopic(data.payload.NAME_TOPIC) setTopic(data.payload.NAME_TOPIC)
setName(data.payload.NAME_USERS) setName(data.payload.NAME_USERS)
@ -76,7 +84,33 @@ const useProgressStudent = (monitoringId) => {
.replace(/\./g, ':') + ' WIB'; .replace(/\./g, ':') + ' WIB';
} }
const downloadPDF = () => { const handleShowFeedback = () => {
setSuccessFeedback(false);
setShowFeedback(true);
}
const handleCloseFeedback = () => {
setShowFeedback(false);
setFeedback('');
setSuccessFeedback(false);
}
const handleSubmitFeedback = async (e) => {
e.preventDefault();
setLoadingFeedback(true);
setSuccessFeedback(false);
try {
await progressService.postFeedback(idMonitoring, feedback);
setSuccessFeedback(true);
setFeedback('');
setLoadingFeedback(false);
} catch (err) {
// setError(err.message);
console.log(err.message);
}
};
const handleDownloadCSV = () => {
const doc = new jsPDF(); const doc = new jsPDF();
const pageWidth = doc.internal.pageSize.getWidth(); const pageWidth = doc.internal.pageSize.getWidth();
@ -122,7 +156,16 @@ const useProgressStudent = (monitoringId) => {
handleLimitsChange, handleLimitsChange,
handleSearchChange, handleSearchChange,
formatLocalDate, formatLocalDate,
downloadPDF handleDownloadCSV,
feedback,
setFeedback,
loadingFeedback,
successFeedback,
handleSubmitFeedback,
showFeedback,
handleShowFeedback,
handleCloseFeedback,
}; };
}; };

View File

@ -30,9 +30,9 @@ const getStudentById = async (id) => {
} }
}; };
const getTopicByClass = async (id, search, sort, page, limit) => { const getTopicByClass = async (id) => {
try { try {
const response = await axiosInstance.get(`/monitoring/class/${id}?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); const response = await axiosInstance.get(`/monitoring/class/${id}`);
return response.data; return response.data;
} catch (error) { } catch (error) {
console.error('Error fetching progress:', error); console.error('Error fetching progress:', error);
@ -52,7 +52,7 @@ const fetchDataStudentProgress = async (id, search, sort, page, limit) => {
const fetchDataClassProgress = async (dataId, search, sort, page, limit) => { const fetchDataClassProgress = async (dataId, search, sort, page, limit) => {
try { try {
const response = await axiosInstance.get(`/monitoring/class?search=${search}&sort=${sort}&page=${page}&limit=${limit}`, dataId); const response = await axiosInstance.post(`/monitoring/class?search=${search}&sort=${sort}&page=${page}&limit=${limit}`, dataId);
return response.data; return response.data;
} catch (error) { } catch (error) {
console.error('Error fetching progress:', error); console.error('Error fetching progress:', error);
@ -60,11 +60,39 @@ const fetchDataClassProgress = async (dataId, search, sort, page, limit) => {
} }
}; };
const postFeedback = async (id, feedback) => {
try {
const response = await axiosInstance.post(`/monitoring/feedback/${id}`, feedback);
return response.data;
} catch (error) {
console.error('Error post feedback:', error);
throw error;
}
};
const getCsvProgress = async () => {
const configs = {
headers: {
Authorization: localStorage.getItem('token')
},
responseType: 'blob',
};
try {
const response = await axiosInstance.get(`/monitoring/class/csv`, configs);
return response.data;
} catch (error) {
console.error(`Error get file:`, error);
throw error;
}
};
export default{ export default{
fetchDataStudent, fetchDataStudent,
fetchDataClass, fetchDataClass,
getStudentById, getStudentById,
getTopicByClass, getTopicByClass,
fetchDataStudentProgress, fetchDataStudentProgress,
fetchDataClassProgress fetchDataClassProgress,
getCsvProgress,
postFeedback
}; };

View File

@ -8,12 +8,10 @@ const ClassFeedback = () => {
const { progressId } = useParams(); const { progressId } = useParams();
const { const {
progress, progress,
name,
section, section,
topic, topic,
name,
nisn,
loading, loading,
error,
totalPages, totalPages,
totalData, totalData,
@ -22,7 +20,8 @@ const ClassFeedback = () => {
handlePageChange, handlePageChange,
handleLimitsChange, handleLimitsChange,
handleSearchChange, handleSearchChange,
formatLocalDate formatLocalDate,
handleDownloadCSV
} = useProgressClass(progressId); } = useProgressClass(progressId);
return ( return (
<div className='admin-teachers'> <div className='admin-teachers'>
@ -33,12 +32,12 @@ const ClassFeedback = () => {
</Button> </Button>
<Breadcrumb className='custom-breadcrumb'> <Breadcrumb className='custom-breadcrumb'>
<Breadcrumb.Item href="/portal/feedback">Monitoring Progress</Breadcrumb.Item> <Breadcrumb.Item href="/portal/feedback">Monitoring Progress</Breadcrumb.Item>
<Breadcrumb.Item active>Progress Details</Breadcrumb.Item> <Breadcrumb.Item active>Class Progress Details</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
</Col> </Col>
<Col sm={6} className="d-flex align-items-center justify-content-end"> <Col sm={6} className="d-flex align-items-center justify-content-end">
<Button variant="outline-blue" type="button" className='py-2 bg-white'> <Button variant="outline-blue" type="button" className='py-2 bg-white' onClick={(e) => { e.preventDefault(); handleDownloadCSV(); }}>
<i className="bi bi-download me-2"></i>Download PDF <i className="bi bi-download me-2"></i>Download CSV
</Button> </Button>
</Col> </Col>
</Row> </Row>
@ -46,7 +45,7 @@ const ClassFeedback = () => {
<Col> <Col>
<div className="cards"> <div className="cards">
<div className="cards-title"> <div className="cards-title">
<h4 className='mb-2'>{nisn} - {name}</h4> <h4 className='mb-2'>{name}</h4>
<span className='text-grey'>{section} / {topic}</span> <span className='text-grey'>{section} / {topic}</span>
</div> </div>
<div className="cards-body"> <div className="cards-body">
@ -63,11 +62,11 @@ const ClassFeedback = () => {
<Table hover> <Table hover>
<thead> <thead>
<tr> <tr>
<th>No</th> <th className='text-start'>NISN</th>
<th>NISN</th>
<th>Full Name</th> <th>Full Name</th>
<th>Level</th> <th>Level</th>
<th>Score</th> <th>Score</th>
<th>Feedback</th>
<th>Start Exercise</th> <th>Start Exercise</th>
<th>Finish Exercise</th> <th>Finish Exercise</th>
</tr> </tr>
@ -75,7 +74,7 @@ const ClassFeedback = () => {
<tbody> <tbody>
{loading?( {loading?(
<tr> <tr>
<td colSpan={6} style={{height:"20vh"}}> <td colSpan={7} style={{height:"20vh"}}>
<Spinner animation="grow" variant="primary" /> <Spinner animation="grow" variant="primary" />
<Spinner animation="grow" variant="secondary" /> <Spinner animation="grow" variant="secondary" />
<Spinner animation="grow" variant="success" /> <Spinner animation="grow" variant="success" />
@ -88,18 +87,18 @@ const ClassFeedback = () => {
progress.length > 0?( progress.length > 0?(
progress.map((data, index) => ( progress.map((data, index) => (
<tr key={index}> <tr key={index}>
<td>{index + 1}</td> <td className='text-start'>{data.NISN}</td>
<td>{data.NISN}</td>
<td>{data.NAME_USERS}</td> <td>{data.NAME_USERS}</td>
<td>{data.NAME_LEVEL}</td> <td>{data.NAME_LEVEL}</td>
<td>{data.SCORE}</td> <td>{data.SCORE}</td>
<td>{data.FEEDBACK_STUDENT ?? '-'}</td>
<td>{formatLocalDate(data.STUDENT_START)}</td> <td>{formatLocalDate(data.STUDENT_START)}</td>
<td>{formatLocalDate(data.STUDENT_FINISH)}</td> <td>{formatLocalDate(data.STUDENT_FINISH)}</td>
</tr> </tr>
)) ))
):( ):(
<tr> <tr>
<td colSpan={6} style={{height:'20vh'}}> <td colSpan={7} style={{height:'20vh'}}>
<h3>Empty Data</h3> <h3>Empty Data</h3>
</td> </td>
</tr> </tr>

View File

@ -41,8 +41,8 @@ const Feedback = () => {
return ( return (
<div className='admin-students'> <div className='admin-students'>
<h2 className='page-title strip'>Learning Progress</h2> <h2 className='page-title strip'>Feedback</h2>
<p className='page-desc'>Follow student activity closely and monitor their English learning journey.</p> <p className='page-desc'>Make students more enthusiastic with feedback from you.</p>
<Tab.Container id="left-tabs-example" defaultActiveKey="student"> <Tab.Container id="left-tabs-example" defaultActiveKey="student">
<Row className='mb-45'> <Row className='mb-45'>
<Col xs={12}> <Col xs={12}>
@ -206,13 +206,13 @@ const Feedback = () => {
</Row> </Row>
</Tab.Container> </Tab.Container>
<Modal show={show} onHide={handleClose} className='modal-admin' size='lg' centered> <Modal show={show} onHide={handleClose} className='modal-admin' size='xl' centered>
<Modal.Header closeButton> <Modal.Header closeButton>
<Modal.Title>Select Topic</Modal.Title> <Modal.Title>Select Topic</Modal.Title>
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body>
{loadingModal?( {loadingModal?(
<div className='d-flex justify-content-center align-items-center' style={{height:"20vh"}}> <div className='d-flex justify-content-center align-items-center' style={{height:"179px"}}>
<Spinner animation="grow" variant="primary" /> <Spinner animation="grow" variant="primary" />
<Spinner animation="grow" variant="secondary" /> <Spinner animation="grow" variant="secondary" />
<Spinner animation="grow" variant="success" /> <Spinner animation="grow" variant="success" />
@ -238,7 +238,9 @@ const Feedback = () => {
))} ))}
</Row> </Row>
):( ):(
<h2 className='my-3 text-center'>No Progress from this class</h2> <div className='d-flex justify-content-center align-items-center' style={{height:"179px"}}>
<h2 className='m-0 text-center'>No Progress from this class</h2>
</div>
) )
)} )}
</Modal.Body> </Modal.Body>

View File

@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import { Table, Row, Col, Button, Form, Dropdown, DropdownButton, Breadcrumb, Spinner } from 'react-bootstrap'; import { Table, Row, Col, Button, Form, Modal, Breadcrumb, Spinner } from 'react-bootstrap';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import useProgressStudent from '../hooks/useProgressStudent'; import useProgressStudent from '../hooks/useProgressStudent';
import TablePaginate from '../../../../components/ui/TablePaginate'; import TablePaginate from '../../../../components/ui/TablePaginate';
import successIllustration from '../../../../assets/images/illustration/successModal.png'
const StudentFeedback = () => { const StudentFeedback = () => {
const { progressId } = useParams(); const { progressId } = useParams();
const { const {
@ -23,7 +25,16 @@ const StudentFeedback = () => {
handleLimitsChange, handleLimitsChange,
handleSearchChange, handleSearchChange,
formatLocalDate, formatLocalDate,
downloadPDF handleDownloadCSV,
feedback,
setFeedback,
loadingFeedback,
successFeedback,
handleSubmitFeedback,
showFeedback,
handleShowFeedback,
handleCloseFeedback,
} = useProgressStudent(progressId); } = useProgressStudent(progressId);
return ( return (
<div className='admin-teachers'> <div className='admin-teachers'>
@ -38,7 +49,10 @@ const StudentFeedback = () => {
</Breadcrumb> </Breadcrumb>
</Col> </Col>
<Col sm={6} className="d-flex align-items-center justify-content-end"> <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}> <Button variant="outline-blue" type="button" className='py-2 me-3 bg-white' onClick={handleShowFeedback}>
<i className="bi bi-chat-left-text me-2"></i>Give Feedback
</Button>
<Button variant="outline-blue" type="button" className='py-2 bg-white' onClick={(e) => { e.preventDefault(); handleDownloadCSV(); }}>
<i className="bi bi-download me-2"></i>Download PDF <i className="bi bi-download me-2"></i>Download PDF
</Button> </Button>
</Col> </Col>
@ -133,6 +147,42 @@ const StudentFeedback = () => {
</div> </div>
</Col> </Col>
</Row> </Row>
<Modal centered show={showFeedback} onHide={handleCloseFeedback} size='lg'>
{successFeedback?(
<Modal.Body className='p-4 d-flex flex-column items-center'>
<h4 className='mb-4 fw-bold text-dark'>Feedback <span className='text-blue'>sent</span>!</h4>
<img src={successIllustration} alt="" />
<p className='my-3 text-muted fw-light'>Thank you for letting us know. Well investigate the issue and work on resolving it promptly.</p>
<Button variant="gd" className="py-2 px-5 mt-4 w-50 rounded-35" onClick={handleCloseFeedback}>Done</Button>
</Modal.Body>
):(
<>
<Modal.Header closeButton>
<Modal.Title>Feedback</Modal.Title>
</Modal.Header>
<Modal.Body className='p-4 d-flex flex-column items-center'>
<Form onSubmit={handleSubmitFeedback} className="w-100 p-3 border rounded-3">
<Form.Group className="mb-3" controlId="feedbackTextarea">
<Form.Label className="text-black fw-bold">What insights or comments would you like to share with the student?</Form.Label>
<Form.Control
as="textarea"
rows={5}
placeholder="Type your feedback here..."
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
required
/>
</Form.Group>
<Button variant="gd" type="submit" className="py-2 px-5 w-100 rounded-35" disabled={loadingFeedback || !feedback}>
{loadingFeedback ? <Spinner animation="border" size="sm" /> : 'SEND'}
</Button>
</Form>
</Modal.Body>
</>
)
}
</Modal>
</div> </div>
); );
}; };