adding teacher bulk student register

This commit is contained in:
Dimas Atmodjo 2024-12-05 15:33:24 +07:00
parent 1c0c2ba39c
commit 5cc0a00a4b
4 changed files with 810 additions and 1 deletions

View File

@ -42,7 +42,7 @@
height: 30px; height: 30px;
position: absolute; position: absolute;
top: calc(50% - 30px); top: calc(50% - 30px);
left: calc(100% - 15px); left: calc(100% - 12px);
padding: 0; padding: 0;
} }
@ -112,6 +112,13 @@
.teacher-main-layout{
height: calc(100vh - 58px);
overflow-y: auto;
}
.modal-admin .modal-header, .modal-admin .modal-header,
.modal-admin .modal-body{ .modal-admin .modal-body{

View File

@ -0,0 +1,236 @@
import { useState, useEffect } from 'react';
import studentService from '../services/serviceStudents';
const useStudents = () => {
const [students, setStudents] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [fileImport, setFileImport] = useState(null);
const [selectedStudent, setSelectedStudent] = useState(null);
const [formData, setFormData] = useState({
fullName: '',
nisn: '',
email: '',
pass: '',
confirm_pass: '',
});
const [show, setShow] = useState(false);
const [showLoader, setShowLoader] = useState(false);
const [loaderState, setLoaderState] = useState({ loading: false, successMessage: '', title: '', description: '', confirmAction: false });
const resetForm = () =>{
setFormData({
fullName: '',
nisn: '',
email: '',
pass: '',
confirm_pass: '',
});
}
const resetImportFile = () => {setFileImport(null)}
const createStudent = async (data) => {
try {
const newStudent ={
NAME_USERS: data.fullName,
NISN: data.nisn,
};
const createdStudent = await studentService.createData(newStudent);
setStudents((prevStudents) => [...prevStudents, createdStudent.payload]);
} catch (err) {
setError(err);
}finally{
resetForm();
setLoaderState(prev => ({
...prev,
loading: false,
successMessage: 'Your new entry has been successfully created and saved.'
}));
}
};
const importRegister = async () => {
const fileData = new FormData();
const file = fileImport;
fileData.append('file', file);
console.log(file);
try {
const response = await studentService.registerImport(fileData);
console.log(response.data);
} catch (err) {
setError(err);
}finally{
resetForm();
setLoaderState(prev => ({
...prev,
loading: false,
successMessage: 'Your new entry has been successfully created and saved.'
}));
}
}
const editStudent = async (id, data) => {
const formData = new FormData();
formData.append('NAME_USERS', data.fullName);
formData.append('EMAIL', data.email);
formData.append('NISN', data.nisn);
try {
const student = await studentService.updateData(id, formData);
setStudents((prevStudents) =>
prevStudents.map((s) => (s.ID === id ? student.payload : s))
);
} catch (err) {
setError(err);
}finally{
setLoaderState(prev => ({
...prev,
loading: false,
successMessage: 'Your data has been successfully updated.'
}));
}
};
const deleteStudent = async (id) => {
handleShowLoader('Deleted', '', true)
try {
await studentService.deleteData(id);
} catch (err) {
setError(err);
}finally{
setStudents((prevStudents) => prevStudents.filter((s) => s.ID !== id));
setLoaderState(prev => ({
...prev,
loading: false,
successMessage: 'Your data has been successfully deleted.'
}));
}
};
const handleFormChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleFileChange = (e) => {
setFileImport(e.target.files[0]);
};
const handleShow = (data) => {
setSelectedStudent(data);
setFormData({
fullName: data?.NAME_USERS || '',
nisn: data?.NISN || '',
email: data?.EMAIL || ''
});
setShow(true);
};
const handleClose = () => {setShow(false); resetForm(); setSelectedStudent(null)};
const handleCloseLoader = () => setShowLoader(false);
const handleShowLoader = (title, description, loading = false, successMessage = '', confirmAction = false) => {
setLoaderState({ title, description, loading, successMessage, confirmAction });
setShowLoader(true);
};
const [search, setSearch] = useState("");
const [sort, setSort] = useState("");
const [page, setCurrentPage] = useState(1);
const [limit, setLimit] = useState(7);
const [totalPages, setTotalPages] = useState(0);
const [totalData, setTotalData] = useState(null);
const fetchData = async () => {
setLoading(true);
try {
const data = await studentService.fetchData(search, sort, page, limit);
setTotalPages(data.payload.totalPages);
setTotalData(data.payload.totalStudents);
setStudents(data.payload.students);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
const handleSerachChange = () => {
fetchData();
setCurrentPage(1);
}
const handlePageChange = (pages) => {
setCurrentPage(pages);
}
const handleLimitsChange = (e) => {
setLimit(e.target.value);
setCurrentPage(1);
}
useEffect(() => {
fetchData();
}, [page, limit]);
const handleDownloadTemplate = async () => {try {
const response = await studentService.getTemplate();
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 = 'template.xls';
link.click();
URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Error downloading file:', error);
alert('Gagal mengunduh file');
}
}
return {
students,
loading,
error,
selectedStudent,
formData,
createStudent,
editStudent,
deleteStudent,
setSelectedStudent,
setFormData,
handleFormChange,
resetForm,
show,
setShow,
handleShow,
handleClose,
showLoader,
handleShowLoader,
handleCloseLoader,
loaderState,
setLoaderState,
totalPages,
totalData,
setSearch,
page,
handlePageChange,
handleLimitsChange,
handleSerachChange,
handleFileChange,
handleDownloadTemplate,
importRegister,
fileImport,
resetImportFile
};
};
export default useStudents;

View File

@ -0,0 +1,87 @@
import axiosInstance from '../../../../utils/axiosInstance';
const fetchData= async (search, sort, page, limit) => {
try {
const response = await axiosInstance.get(`/user/student?search=${search}&sort=${sort}&page=${page}&limit=${limit}`);
return response.data;
} catch (error) {
console.error('Error fetching sections:', error);
throw error;
}
};
const getStudentById = async (id) => {
try {
const response = await axiosInstance.get(`/student/${id}`);
return response.data;
} catch (error) {
console.error(`Error fetching student with ID ${id}:`, error);
throw error;
}
};
const createData = async (studentData) => {
try {
const response = await axiosInstance.post(`/admin/register/student`, studentData);
return response.data;
} catch (error) {
console.error('Error adding student:', error);
throw error;
}
};
const updateData = async (id, studentData) => {
try {
const response = await axiosInstance.put(`/user/update/${id}`, studentData);
return response.data;
} catch (error) {
console.error(`Error updating student with ID ${id}:`, error);
throw error;
}
};
const deleteData = async (id) => {
try {
const response = await axiosInstance.delete(`/user/delete/${id}`);
return response.data;
} catch (error) {
console.error(`Error deleting student with ID ${id}:`, error);
throw error;
}
};
const getTemplate = async () => {
const configs = {
headers: {
Authorization: localStorage.getItem('token')
},
responseType: 'blob',
};
try {
const response = await axiosInstance.get(`/user/sendExcelExample`, configs);
return response.data;
} catch (error) {
console.error(`Error get file:`, error);
throw error;
}
};
const registerImport = async (file) => {
try {
const response = await axiosInstance.post(`/admin/register/student/csv`, file);
return response.data;
} catch (error) {
console.error('Error adding student:', error);
throw error;
}
};
export default{
fetchData,
getStudentById,
createData,
updateData,
deleteData,
getTemplate,
registerImport
};

View File

@ -0,0 +1,479 @@
import React, { useState } from 'react';
import { Table, Row, Col, Nav, Tab, Button, Form, InputGroup, Modal, Spinner } from 'react-bootstrap';
import useStudents from '../hooks/useStudents';
import ModalOperation from '../../../../components/ui/adminMessageModal/ModalOperation';
import TablePaginate from '../../../../components/ui/TablePaginate';
const ManageStudents = () => {
const {
students,
loading,
error,
selectedStudent,
formData,
createStudent,
editStudent,
deleteStudent,
setSelectedStudent,
setFormData,
handleFormChange,
resetForm,
show,
setShow,
handleShow,
handleClose,
showLoader,
handleShowLoader,
handleCloseLoader,
loaderState,
setLoaderState,
page,
totalData,
totalPages,
setSearch,
handlePageChange,
handleLimitsChange,
handleSerachChange,
handleFileChange,
handleDownloadTemplate,
importRegister,
fileImport,
resetImportFile
} = useStudents();
const handleCreate = async (e) => {
handleShowLoader('Created', '', true)
e.preventDefault();
await createStudent(formData);
};
const handleImport = async (e) => {
handleShowLoader('Created', '', true)
e.preventDefault();
await importRegister();
};
const handleUpdate = async (e) => {
handleClose();
handleShowLoader('Updated', '', true)
e.preventDefault();
await editStudent(selectedStudent.ID, formData);
};
const handleDelete = async (id) => {
handleShowLoader('Deleted', 'Are you sure you want to delete this student?', false, '', true);
const confirmDelete = async () => {
handleCloseLoader();
await deleteStudent(id);
}
setLoaderState(prev => ({ ...prev, handleConfirm: confirmDelete }));
}
const handleDragOver = (e) => {
e.preventDefault(); // Mencegah browser membuka file
e.stopPropagation();
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
const droppedFiles = e.dataTransfer.files; // Ambil file dari drag event
if (droppedFiles.length) {
// Kirim file ke input
handleFileChange({ target: { files: droppedFiles } });
}
};
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
e.target.classList.add('dragging');
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
e.target.classList.remove('dragging');
};
if (error) {
<>{error}</>
}
return (
<div className='admin-students'>
<h2 className='page-title'>Manage your students to classes!</h2>
<p className='page-desc'>Easily add students to their classes and organize them efficiently.</p>
<Tab.Container id="left-tabs-example" defaultActiveKey="detail" onSelect={resetForm}>
<Row className='mb-45'>
<Col xs={12}>
<Nav variant="pills" className='col-tabs'>
<Nav.Item>
<Nav.Link eventKey="detail">View Entries</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="create">Create Data</Nav.Link>
</Nav.Item>
</Nav>
</Col>
</Row>
<Row className='mb-45'>
<Col xs={12} className='col-tabs-content'>
<Tab.Content>
<Tab.Pane eventKey="detail">
<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(); handleSerachChange(); }}>
<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>Email Address</th>
<th className='text-center'>Action</th>
</tr>
</thead>
<tbody>
{loading?(
<tr>
<td colSpan={5} 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((student, index) => (
<tr key={student.ID}>
<td>{index + 1}</td>
<td>{student.NISN}</td>
<td>{student.NAME_USERS}</td>
<td>{student.EMAIL}</td>
<td className='text-center action-col d-flex justify-content-center'>
<Button size='sm' className='btn-edit' onClick={() => handleShow(student)}>
<i className="bi bi-pencil-square"></i>
</Button>
<Button size='sm' className='btn-delete' onClick={() => handleDelete(student.ID)}>
<i className="bi bi-trash3"></i>
</Button>
</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={handleLimitsChange}
style={{ width: '50px' }}
>
<option value="7">7</option>
<option value="10">10</option>
<option value="20">20</option>
</Form.Select>
<small>of {totalData}</small>
</div>
<TablePaginate
totalPages={totalPages}
currentPage={page}
onPageChange={handlePageChange}
/>
</div>
</div>
</div>
</Tab.Pane>
<Tab.Pane eventKey="create">
<div className="cards mb-4">
<div className="cards-title">
<h4>Add Student Data</h4>
</div>
<div className="cards-body">
<Form onSubmit={handleCreate}>
<Row className="mb-2">
<Form.Group as={Col} controlId="formGridNISN">
<Form.Label>NISN<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text id="basic-addon1"><i className="bi bi-123"></i></InputGroup.Text>
<Form.Control
name="nisn"
value={formData.nisn || ''}
onChange={handleFormChange}
placeholder="Enter NISN"
required
/>
</InputGroup>
</Form.Group>
<Form.Group as={Col} controlId="formGridName">
<Form.Label>Full Name<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text id="basic-addon1"><i className="bi bi-person"></i></InputGroup.Text>
<Form.Control
name="fullName"
value={formData.fullName || ''}
onChange={handleFormChange}
placeholder="Enter Full Name"
required
/>
</InputGroup>
</Form.Group>
</Row>
{/* <Row className="mb-2">
<Form.Group as={Col} controlId="formGridRole">
<Form.Label>Role<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon disabled">
<InputGroup.Text id="basic-addon1"><i className="bi bi-person-vcard"></i></InputGroup.Text>
<Form.Control
disabled
value="Student"
aria-label="role"
aria-describedby="basic-addon1"
/>
</InputGroup>
</Form.Group>
<Form.Group as={Col} controlId="formGridName">
<Form.Label>Full Name<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text id="basic-addon1"><i className="bi bi-person"></i></InputGroup.Text>
<Form.Control
name="fullName"
value={formData.fullName || ''}
onChange={handleFormChange}
placeholder="Enter Full Name"
/>
</InputGroup>
</Form.Group>
</Row>
<Row className="mb-2">
<Form.Group as={Col} controlId="formGridNISN">
<Form.Label>NISN<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text id="basic-addon1"><i className="bi bi-123"></i></InputGroup.Text>
<Form.Control
name="nisn"
value={formData.nisn || ''}
onChange={handleFormChange}
placeholder="Enter NISN"
/>
</InputGroup>
</Form.Group>
<Form.Group as={Col} controlId="formGridEmail">
<Form.Label>Email Address<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text id="basic-addon1"><i className="bi bi-envelope"></i></InputGroup.Text>
<Form.Control
name="email"
value={formData.email || ''}
onChange={handleFormChange}
placeholder="Enter Email Address"
/>
</InputGroup>
</Form.Group>
</Row>
<Row className="mb-2">
<Form.Group as={Col} controlId="formGridPassword">
<Form.Label>Password<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text id="basic-addon1"><i className="bi bi-key"></i></InputGroup.Text>
<Form.Control
type='password'
name="pass"
value={formData.pass || ''}
onChange={handleFormChange}
placeholder="Create Password"
/>
</InputGroup>
</Form.Group>
<Form.Group as={Col} controlId="formGridConfirm">
<Form.Label>Confirm Password<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text id="basic-addon1"><i className="bi bi-key-fill"></i></InputGroup.Text>
<Form.Control
type='password'
name="confirm_pass"
value={formData.confirm_pass || ''}
onChange={handleFormChange}
placeholder="Confirm Password"
/>
</InputGroup>
</Form.Group>
</Row> */}
<div className="d-flex justify-content-end">
<Button variant="outline-blue" type="reset" className='ms-auto py-2 rounded-35'
onClick={resetForm}
>
reset
</Button>
<Button variant="blue" type="submit" className='ms-2 py-2 px-5 rounded-35'
// onClick={handleCreate}
>
Add
</Button>
</div>
</Form>
</div>
</div>
<div className="cards">
<div className="cards-title d-flex justify-content-between aligb-items-center">
<h4>Add Student Data via Excel</h4>
<span className='text-blue cursor-pointer' onClick={() => {handleDownloadTemplate()}}><i className="bi bi-download me-2"></i>Download Template Excel</span>
</div>
<div className="cards-body">
<Form onSubmit={handleImport}>
<Row className="mb-2">
<Form.Control
id='fileUpload'
accept='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
type='file'
name="file"
onChange={handleFileChange}
placeholder="Masukan File"
className='d-none'
/>
<label
htmlFor="fileUpload"
className={`drop-zone rounded-3 ${fileImport ? `active` : ``}`}
onDragOver={handleDragOver}
onDrop={handleDrop}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
<h5 className={`m-0 ${fileImport ? `text-green fs-3` : `text-grey`}`}>{fileImport ? fileImport.name : "Upload Excel file here"}</h5>
<span className={`fs-14p ${fileImport ? `text-green` : `text-grey`}`}>{fileImport ? "Your file is ready to import" : "Please make sure your Excel file follows the template."}</span>
</label>
</Row>
<div className="d-flex justify-content-end">
<Button variant="outline-blue" type="reset" className='ms-auto py-2 rounded-35'
onClick={resetImportFile}
>
reset
</Button>
<Button variant="blue" type="submit" className='ms-2 py-2 px-5 rounded-35' disabled={fileImport ? false : true}>
Import
</Button>
</div>
</Form>
</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>Edit Student</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={handleUpdate}>
<Form.Group as={Col} controlId="modalGridRole" className='mb-3'>
<Form.Label>Role<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon disabled">
<InputGroup.Text><i className="bi bi-person-vcard"></i></InputGroup.Text>
<Form.Control
disabled
value="Student"
aria-label="role"
aria-describedby="basic-addon1"
/>
</InputGroup>
</Form.Group>
<Form.Group as={Col} controlId="modalGridName" className='mb-3'>
<Form.Label>Full Name<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text><i className="bi bi-person"></i></InputGroup.Text>
<Form.Control
placeholder="Enter Full Name"
name="fullName"
value={formData.fullName || ''}
onChange={handleFormChange}
/>
</InputGroup>
</Form.Group>
<Form.Group as={Col} controlId="modalGridNisn" className="mb-3">
<Form.Label>NISN<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text><i className="bi bi-123"></i></InputGroup.Text>
<Form.Control
placeholder="Enter NISN"
name="nisn"
value={formData.nisn || ''}
onChange={handleFormChange}
/>
</InputGroup>
</Form.Group>
<Form.Group as={Col} controlId="modalGridEmail" className="mb-3">
<Form.Label>Email Address<sup className='text-red fw-bold'>*</sup></Form.Label>
<InputGroup className="mb-2 input-group-icon">
<InputGroup.Text><i className="bi bi-envelope"></i></InputGroup.Text>
<Form.Control
placeholder="Enter Email Address"
name="email"
value={formData.email || ''}
onChange={handleFormChange}
/>
</InputGroup>
</Form.Group>
<div className="d-flex justify-content-end">
<Button variant="blue" type="submit" className='py-2 px-5 w-100 rounded-35'>
Update
</Button>
</div>
</Form>
</Modal.Body>
</Modal>
<ModalOperation
show={showLoader}
handleClose={handleCloseLoader}
title={loaderState.title}
description={loaderState.description}
loading={loaderState.loading}
successMessage={loaderState.successMessage}
confirmAction={loaderState.confirmAction}
handleConfirm={loaderState.handleConfirm}
/>
</div>
);
};
export default ManageStudents;