diff --git a/.husky/pre-commit b/.husky/pre-commit index 589d19328..16b07e518 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -6,5 +6,3 @@ npx lint-staged || exit 1 # Run lint, if it fails, the commit will be aborted npm run lint || exit 1 - -git add . diff --git a/package.json b/package.json index 236751ab4..ba201fc2e 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,7 @@ "lint-staged": { "*.{js,jsx}": [ "prettier --write", - "eslint --fix", - "git add" + "eslint --fix" ] } } diff --git a/src/App.jsx b/src/App.jsx index 99d55a675..70158ef19 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,7 +8,8 @@ import Dashboard from "./Modules/Dashboard/dashboardNotifications"; import Profile from "./Modules/Dashboard/StudentProfile/profilePage"; import LoginPage from "./pages/login"; import ForgotPassword from "./pages/forgotPassword"; -import AcademicPage from "./Modules/Academic/index"; +// import AcademicPage from "./Modules/Academic/index"; +import HostelManagement from "./Modules/Hostel-Management/index"; import ValidateAuth from "./helper/validateauth"; import FacultyProfessionalProfile from "./Modules/facultyProfessionalProfile/facultyProfessionalProfile"; import InactivityHandler from "./helper/inactivityhandler"; @@ -46,19 +47,27 @@ export default function App() { } /> + {/* + + + } + /> */} - + } /> - + } /> diff --git a/src/Modules/Academic/AcademicCalendar.jsx b/src/Modules/Academic/AcademicCalendar.jsx deleted file mode 100644 index 6674b8f15..000000000 --- a/src/Modules/Academic/AcademicCalendar.jsx +++ /dev/null @@ -1,464 +0,0 @@ -import { useState, useEffect } from "react"; -import { - Card, - Text, - Button, - Alert, - Modal, - Group, - TextInput, - Loader, - FileInput, - Table, -} from "@mantine/core"; -import axios from "axios"; -import { IconUpload } from "@tabler/icons-react"; -import { - calendarRoute, - addCalendarRoute, - editCalendarRoute, - deleteCalendarRoute, - clearCalendarRoute, - exportCalendarRoute, - importCalendarRoute, -} from "../../routes/academicRoutes"; - -function AcademicCalendar() { - const [events, setEvents] = useState([]); - const [newEvent, setNewEvent] = useState({ - description: "", - from_date: null, - to_date: null, - }); - const [editingEvent, setEditingEvent] = useState(null); - const [addModalOpen, setAddModalOpen] = useState(false); - const [error, setError] = useState(""); - const [loading, setLoading] = useState(true); - const [processing, setProcessing] = useState(false); - const [refreshTrigger, setRefreshTrigger] = useState(0); - const [file, setFile] = useState(null); - - // Fetch events - useEffect(() => { - let mounted = true; - const fetchData = async () => { - setLoading(true); - const token = localStorage.getItem("authToken"); - if (!token) { - setError("Authentication required"); - setLoading(false); - return; - } - try { - const { data } = await axios.get(calendarRoute, { - headers: { Authorization: `Token ${token}` }, - }); - if (!mounted) return; - setEvents( - Array.isArray(data) - ? data.map((e) => ({ - ...e, - from_date: e.from_date ? new Date(e.from_date) : null, - to_date: e.to_date ? new Date(e.to_date) : null, - })) - : [] - ); - } catch (err) { - if (mounted) setError("Failed to load events"); - } finally { - if (mounted) setLoading(false); - } - }; - fetchData(); - return () => { - mounted = false; - }; - }, [refreshTrigger]); - - // Format dates for display - const formatDate = (d) => - d - ? d.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }) - : ""; - - // Open modals - const handleAdd = () => { - setError(""); - setNewEvent({ description: "", from_date: null, to_date: null }); - setAddModalOpen(true); - }; - const handleEdit = (ev) => { - setError(""); - setEditingEvent(ev); - }; - - // Save edited event - const handleSaveEdit = async () => { - if ( - !editingEvent?.description || - !editingEvent?.from_date || - !editingEvent?.to_date - ) { - return setError("Please fill all fields"); - } - setProcessing(true); - try { - const token = localStorage.getItem("authToken"); - await axios.put( - editCalendarRoute, - { - ...editingEvent, - from_date: editingEvent.from_date.toISOString().slice(0, 10), - to_date: editingEvent.to_date.toISOString().slice(0, 10), - }, - { headers: { Authorization: `Token ${token}` } } - ); - setEditingEvent(null); - setRefreshTrigger((t) => t + 1); - } catch { - setError("Failed to update event"); - } finally { - setProcessing(false); - } - }; - - // Create new event - const handleAddEvent = async () => { - if ( - !newEvent.description || - !newEvent.from_date || - !newEvent.to_date - ) { - return setError("Please fill all fields"); - } - setProcessing(true); - try { - const token = localStorage.getItem("authToken"); - await axios.post( - addCalendarRoute, - { - ...newEvent, - from_date: newEvent.from_date.toISOString().slice(0, 10), - to_date: newEvent.to_date.toISOString().slice(0, 10), - }, - { headers: { Authorization: `Token ${token}` } } - ); - setAddModalOpen(false); - setRefreshTrigger((t) => t + 1); - } catch { - setError("Failed to create event"); - } finally { - setProcessing(false); - } - }; - - // Delete one event - const handleDelete = async (ev) => { - setProcessing(true); - try { - const token = localStorage.getItem("authToken"); - await axios.delete(deleteCalendarRoute, { - headers: { Authorization: `Token ${token}` }, - data: { id: ev.id }, - }); - setRefreshTrigger((t) => t + 1); - } catch { - setError("Failed to delete event"); - } finally { - setProcessing(false); - } - }; - - // Clear all - const handleClear = async () => { - setProcessing(true); - try { - const token = localStorage.getItem("authToken"); - await axios.delete(clearCalendarRoute, { - headers: { Authorization: `Token ${token}` }, - }); - setRefreshTrigger((t) => t + 1); - } catch { - setError("Failed to clear calendar"); - } finally { - setProcessing(false); - } - }; - - // Export Excel - const handleExport = async () => { - setProcessing(true); - try { - const token = localStorage.getItem("authToken"); - const res = await axios.get(exportCalendarRoute, { - headers: { Authorization: `Token ${token}` }, - responseType: "blob", - }); - const url = URL.createObjectURL(new Blob([res.data])); - const a = document.createElement("a"); - a.href = url; - a.download = "calendar.xlsx"; - a.click(); - } catch { - setError("Failed to export excel"); - } finally { - setProcessing(false); - } - }; - - // Import Excel - const handleFileUpload = async (f) => { - if (!f) return; - setProcessing(true); - try { - const token = localStorage.getItem("authToken"); - const form = new FormData(); - form.append("file", f); - await axios.post(importCalendarRoute, form, { - headers: { - Authorization: `Token ${token}`, - "Content-Type": "multipart/form-data", - }, - }); - setFile(null); - setRefreshTrigger((t) => t + 1); - } catch { - setError("Failed to import excel"); - } finally { - setProcessing(false); - } - }; - - return ( - - - Academic Calendar Management - - - {/* Initial loader or error */} - {loading ? ( - - - - ) : ( - <> - {error && ( - setError("")} - > - {error} - - )} - - {/* Events table */} - - - - - - - - - - - {Array.isArray(events) && events.length ? ( - events.map((ev) => ( - - - - - - - )) - ) : ( - - - - )} - -
DescriptionStart DateEnd DateActions
{ev.description}{formatDate(ev.from_date)}{formatDate(ev.to_date)} - - - - -
- No events found -
- - {/* Action buttons */} - - - - - { - setFile(f); - handleFileUpload(f); - }} - icon={} - disabled={processing} - /> - - - )} - - {/* Edit Modal */} - setEditingEvent(null)} - title="Edit Event" - size="lg" - > - - setEditingEvent({ - ...editingEvent, - description: e.target.value, - }) - } - mb="md" - required - disabled={processing} - /> - - setEditingEvent({ - ...editingEvent, - from_date: e.target.value ? new Date(e.target.value) : null, - }) - } - mb="md" - required - disabled={processing} - /> - - setEditingEvent({ - ...editingEvent, - to_date: e.target.value ? new Date(e.target.value) : null, - }) - } - mb="md" - required - disabled={processing} - /> - - - - - - {/* Add Modal */} - setAddModalOpen(false)} - title="Add New Event" - size="lg" - overlayOpacity={0.3} - > - - setNewEvent({ ...newEvent, description: e.target.value }) - } - mb="md" - required - disabled={processing} - /> - - setNewEvent({ - ...newEvent, - from_date: e.target.value ? new Date(e.target.value) : null, - }) - } - mb="md" - required - disabled={processing} - /> - - setNewEvent({ - ...newEvent, - to_date: e.target.value ? new Date(e.target.value) : null, - }) - } - mb="md" - required - disabled={processing} - /> - - - - -
- ); -} - -export default AcademicCalendar; diff --git a/src/Modules/Academic/AdminAddDashboard.jsx b/src/Modules/Academic/AdminAddDashboard.jsx deleted file mode 100644 index 86d247038..000000000 --- a/src/Modules/Academic/AdminAddDashboard.jsx +++ /dev/null @@ -1,569 +0,0 @@ -import React, { useEffect, useState, useCallback, useMemo } from 'react'; -import { - Title, Select, Group, Button, - Table, Text, Loader, Alert, - Card, Stack, Checkbox, Badge, Tabs, Modal, ActionIcon -} from '@mantine/core'; -import { showNotification } from '@mantine/notifications'; -import { IconTrash } from '@tabler/icons-react'; -import axios from 'axios'; - -import { - adminListAddRequestsRoute, - approveAddRequestsRoute, - deleteAddRequestsRoute, -} from '../../routes/academicRoutes'; - -const SEMESTER_CHOICES = [ - { value: 'Odd Semester', label: 'Odd Semester' }, - { value: 'Even Semester', label: 'Even Semester' }, - { value: 'Summer Semester', label: 'Summer Semester' }, -]; - -const generateAcademicYears = () => { - const currentYear = new Date().getFullYear(); - const yearsToShow = 5; - return Array.from({ length: yearsToShow }, (_, i) => { - const year = currentYear - i; - const value = `${year}-${(year + 1).toString().slice(-2)}`; - return { value, label: value }; - }); -}; - -export default function AdminAddDashboard() { - const [year, setYear] = useState(''); - const [semester, setSemester] = useState(''); - const [requests, setRequests] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [selectedIds, setSelectedIds] = useState(new Set()); - const [processing, setProcessing] = useState(false); - const [statusFilter, setStatusFilter] = useState(''); - const [deleteModal, setDeleteModal] = useState({ open: false, ids: [] }); - const [deleting, setDeleting] = useState(false); - - const academicYears = useMemo(() => generateAcademicYears(), []); - - const fetchRequests = useCallback(async () => { - if (!year || !semester) return; - - const token = localStorage.getItem('authToken'); - if (!token) { - setError('Authentication required'); - return; - } - - setLoading(true); - setError(null); - setSelectedIds(new Set()); - - try { - const { data } = await axios.get(adminListAddRequestsRoute, { - params: { academic_year: year, semester_type: semester }, - headers: { Authorization: `Token ${token}` }, - }); - setRequests(Array.isArray(data) ? data : []); - } catch (err) { - const errorMsg = err.response?.data?.error || err.response?.data?.detail || err.message; - setError(errorMsg || 'Failed to load requests'); - } finally { - setLoading(false); - } - }, [year, semester]); - - useEffect(() => { - fetchRequests(); - }, [fetchRequests]); - - const toggleSelection = useCallback((id) => { - setSelectedIds(prev => { - const newSet = new Set(prev); - newSet.has(id) ? newSet.delete(id) : newSet.add(id); - return newSet; - }); - }, []); - - const pendingRequests = useMemo( - () => requests.filter(r => r.status === 'Pending'), - [requests] - ); - - const processedRequests = useMemo( - () => requests.filter(r => r.status !== 'Pending'), - [requests] - ); - - const filteredProcessedRequests = useMemo( - () => statusFilter ? processedRequests.filter(r => r.status === statusFilter) : processedRequests, - [processedRequests, statusFilter] - ); - - const toggleSelectAll = useCallback(() => { - if (selectedIds.size === pendingRequests.length && pendingRequests.length > 0) { - setSelectedIds(new Set()); - } else { - setSelectedIds(new Set(pendingRequests.map(r => r.id))); - } - }, [selectedIds.size, pendingRequests]); - - const handleAction = useCallback(async (action) => { - if (selectedIds.size === 0) { - showNotification({ - title: 'No Selection', - message: `Please select at least one request to ${action}.`, - color: 'yellow', - }); - return; - } - - const token = localStorage.getItem('authToken'); - if (!token) { - showNotification({ - title: 'Authentication Error', - message: 'Please login again', - color: 'red' - }); - return; - } - - setProcessing(true); - try { - const response = await axios.post( - approveAddRequestsRoute, - { - request_ids: Array.from(selectedIds), - action - }, - { headers: { Authorization: `Token ${token}` } } - ); - - const summary = response.data?.summary; - const results = response.data?.results || []; - - // Check for errors in results - const errors = results.filter(r => r.status === 'error' || r.status === 'already_processed'); - - if (summary && summary.success > 0) { - showNotification({ - title: 'Success', - message: `Processed ${summary.success} request(s) successfully${errors.length > 0 ? `. ${errors.length} failed.` : ''}`, - color: 'green' - }); - } else if (errors.length > 0) { - const errorMessages = errors.map(e => `ID ${e.id}: ${e.detail || e.current_status || e.status}`).join(', '); - showNotification({ - title: 'Processing Failed', - message: errorMessages.length > 100 ? 'Some requests failed. Check console for details.' : errorMessages, - color: 'red', - autoClose: 8000 - }); - } else { - showNotification({ - title: 'Warning', - message: 'No requests were processed successfully', - color: 'yellow' - }); - } - - setSelectedIds(new Set()); - await fetchRequests(); - } catch (err) { - const errorMsg = err.response?.data?.error || err.message; - showNotification({ - title: `${action === 'approve' ? 'Approval' : 'Rejection'} Error`, - message: errorMsg || `Failed to ${action} requests`, - color: 'red', - }); - } finally { - setProcessing(false); - } - }, [selectedIds, fetchRequests]); - - const handleApprove = useCallback(() => handleAction('approve'), [handleAction]); - const handleReject = useCallback(() => handleAction('reject'), [handleAction]); - - const openDeleteModal = useCallback((ids) => { - setDeleteModal({ open: true, ids: Array.isArray(ids) ? ids : [ids] }); - }, []); - - const closeDeleteModal = useCallback(() => { - setDeleteModal({ open: false, ids: [] }); - }, []); - - const handleDelete = useCallback(async () => { - const { ids } = deleteModal; - if (ids.length === 0) return; - - const token = localStorage.getItem('authToken'); - if (!token) { - showNotification({ - title: 'Authentication Error', - message: 'Please login again', - color: 'red' - }); - return; - } - - setDeleting(true); - try { - const response = await axios.post( - deleteAddRequestsRoute, - { request_ids: ids }, - { headers: { Authorization: `Token ${token}` } } - ); - - showNotification({ - title: 'Success', - message: `Deleted ${response.data.deleted} request(s)`, - color: 'green' - }); - - setSelectedIds(prev => { - const newSet = new Set(prev); - ids.forEach(id => newSet.delete(id)); - return newSet; - }); - - closeDeleteModal(); - await fetchRequests(); - } catch (err) { - showNotification({ - title: 'Delete Failed', - message: err.response?.data?.error || err.message, - color: 'red', - }); - } finally { - setDeleting(false); - } - }, [deleteModal, fetchRequests, closeDeleteModal]); - - const handleBulkDelete = useCallback(() => { - if (selectedIds.size === 0) { - showNotification({ - title: 'No Selection', - message: 'Please select at least one request to delete', - color: 'yellow', - }); - return; - } - openDeleteModal(Array.from(selectedIds)); - }, [selectedIds, openDeleteModal]); - - const exportToExcel = useCallback((data, filename) => { - if (data.length === 0) { - showNotification({ - title: 'No Data', - message: 'No data available to export', - color: 'yellow', - }); - return; - } - - const headers = ['Student ID', 'Student Name', 'Slot', 'Course Code', 'Course Name', 'Status', 'Requested At', 'Processed At']; - const csvRows = [headers.join(',')]; - - data.forEach(r => { - const row = [ - r.student || '', - r.student_name || '', - r.slot || '', - r.course || '', - r.course_name || '', - r.status || '', - r.created_at ? new Date(r.created_at).toLocaleString() : '', - r.processed_at ? new Date(r.processed_at).toLocaleString() : '' - ]; - csvRows.push(row.map(val => `"${val}"`).join(',')); - }); - - const csvContent = csvRows.join('\n'); - const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); - const link = document.createElement('a'); - const url = URL.createObjectURL(blob); - - link.setAttribute('href', url); - link.setAttribute('download', `${filename}_${year}_${semester}.csv`); - link.style.visibility = 'hidden'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - showNotification({ - title: 'Success', - message: 'Data exported successfully', - color: 'green', - }); - }, [year, semester]); - - return ( - <> - - - - - - - - - - - - - - - {loading ? ( - - - - ) : error ? ( - {error} - ) : (!year || !semester) ? ( - - Select academic year and semester to view add course requests. - - ) : ( - - - - Pending Requests ({pendingRequests.length}) - - - Processed Requests ({processedRequests.length}) - - - - - {pendingRequests.length > 0 ? ( - - - Pending Requests - - - - - - - - - - - - - - - - - - - {pendingRequests.map(r => ( - - - - - - - - - - ))} - -
- 0} - onChange={toggleSelectAll} - indeterminate={selectedIds.size > 0 && selectedIds.size < pendingRequests.length} - /> - StudentSlotCourseStatusRequested AtActions
- toggleSelection(r.id)} - /> - - {r.student} - {r.student_name && ( - {r.student_name} - )} - {r.slot} - {r.course} - {r.course_name && ( - {r.course_name} - )} - - {r.status} - {new Date(r.created_at).toLocaleString()} - openDeleteModal(r.id)} - title="Delete request" - > - - -
-
- ) : ( - - No pending requests found for the selected period. - - )} -
- - - {processedRequests.length > 0 ? ( - - - Processed Requests - - - - - - - - - - - - - - - {filteredProcessedRequests.map(r => ( - - - - - - - - - ))} - -
StudentSlotCourse - - Status - Requested AtProcessed At
- {r.student} - {r.student_name && ( - {r.student_name} - )} - {r.slot} - {r.course} - {r.course_name && ( - {r.course_name} - )} - - - {r.status} - - {new Date(r.created_at).toLocaleString()} - {r.processed_at - ? new Date(r.processed_at).toLocaleString() - : '—'} -
-
- ) : ( - - No processed requests found for the selected period. - - )} -
-
- )} - - - - Are you sure you want to permanently delete {deleteModal.ids.length} course add request{deleteModal.ids.length > 1 ? 's' : ''}? - - - This action cannot be undone. - - - - - - - - ); -} diff --git a/src/Modules/Academic/AdminBatchChange.jsx b/src/Modules/Academic/AdminBatchChange.jsx deleted file mode 100644 index d0e453c03..000000000 --- a/src/Modules/Academic/AdminBatchChange.jsx +++ /dev/null @@ -1,238 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import axios from "axios"; -import { - Card, - Text, - Select, - Loader, - Center, - Alert, - Table, - Button, - Modal, - Group, - TextInput, -} from "@mantine/core"; -import { - listBatchesRoute, - listStudentsRoute, - applyBatchRoute, -} from "../../routes/academicRoutes"; - -export default function AdminBatchChange() { - const [batches, setBatches] = useState([]); - const [sourceBatch, setSourceBatch] = useState(""); - const [students, setStudents] = useState([]); - const [loadingBatches, setLoadingBatches] = useState(false); - const [loadingStudents, setLoadingStudents] = useState(false); - const [error, setError] = useState(""); - const [changes, setChanges] = useState({}); - const [modalOpen, setModalOpen] = useState(false); - - useEffect(() => { - setLoadingBatches(true); - const token = localStorage.getItem("authToken"); - axios - .get(listBatchesRoute, { headers: { Authorization: `Token ${token}` } }) - .then((res) => setBatches(res.data)) - .catch(() => setError("Failed to load batches.")) - .finally(() => setLoadingBatches(false)); - }, []); - - const fetchStudents = useCallback(() => { - if (!sourceBatch) return; - setLoadingStudents(true); - const token = localStorage.getItem("authToken"); - axios - .get(listStudentsRoute, { - params: { batch_id: sourceBatch }, - headers: { Authorization: `Token ${token}` }, - }) - .then((res) => { - setStudents(res.data); - const init = {}; - res.data.forEach((st) => { - init[st.id] = { - new_batch_id: st.current_batch_id, - new_batch_year: st.current_batch_year, - }; - }); - setChanges(init); - }) - .catch(() => setError("Failed to load students.")) - .finally(() => setLoadingStudents(false)); - }, [sourceBatch]); - - const handleBatchSelect = (studentId, newBatchId) => { - setChanges((prev) => ({ - ...prev, - [studentId]: { ...prev[studentId], new_batch_id: newBatchId }, - })); - }; - - const handleYearChange = (studentId, newYear) => { - setChanges((prev) => ({ - ...prev, - [studentId]: { ...prev[studentId], new_batch_year: newYear }, - })); - }; - - const toConfirm = students.filter((st) => { - const change = changes[st.id]; - return ( - change.new_batch_id.toString() !== st.current_batch_id.toString() || - change.new_batch_year.toString() !== st.current_batch_year.toString() - ); - }); - - const submitChanges = () => setModalOpen(true); - - const confirmApply = () => { - const payload = toConfirm.map((st) => ({ - student_id: st.id, - new_batch_id: changes[st.id].new_batch_id, - new_batch_year: changes[st.id].new_batch_year, - })); - const token = localStorage.getItem("authToken"); - axios - .post(applyBatchRoute, payload, { - headers: { Authorization: `Token ${token}` }, - }) - .then(() => { - setModalOpen(false); - fetchStudents(); - }) - .catch(() => setError("Failed to apply batch changes.")); - }; - - return ( - - - handleBatchSelect(st.id, e.target.value) - } - > - - {batches.map((b) => ( - - ))} - - - - - handleYearChange(st.id, e.currentTarget.value) - } - /> - - - ))} - - - - - - - - )} - - setModalOpen(false)} - title="Confirm Batch Change" - > - You are changing the batch for these students: - - - - - - - - - - {toConfirm.map((st) => { - const change = changes[st.id]; - const newBObj = batches.find( - (b) => String(b.id) === String(change.new_batch_id) - ); - const newLabel = newBObj - ? `${newBObj.label} / ${change.new_batch_year}` - : "-"; - return ( - - - - - - ); - })} - -
UsernameOld BatchNew Batch / Year
{st.username}{st.current_batch}{newLabel}
- - - - -
-
- ); -} diff --git a/src/Modules/Academic/AdminDropDashboard.jsx b/src/Modules/Academic/AdminDropDashboard.jsx deleted file mode 100644 index f9e3882ff..000000000 --- a/src/Modules/Academic/AdminDropDashboard.jsx +++ /dev/null @@ -1,550 +0,0 @@ -import React, { useEffect, useState, useCallback, useMemo } from 'react'; -import { - Title, Select, Group, Button, - Table, Text, Loader, Alert, - Card, Stack, Checkbox, Badge, Tabs, Modal, ActionIcon -} from '@mantine/core'; -import { showNotification } from '@mantine/notifications'; -import { IconTrash } from '@tabler/icons-react'; -import axios from 'axios'; - -import { - adminListDropRequestsRoute, - approveDropRequestsRoute, - deleteDropRequestsRoute, -} from '../../routes/academicRoutes'; - -const SEMESTER_CHOICES = [ - { value: 'Odd Semester', label: 'Odd Semester' }, - { value: 'Even Semester', label: 'Even Semester' }, - { value: 'Summer Semester', label: 'Summer Semester' }, -]; - -const generateAcademicYears = () => { - const currentYear = new Date().getFullYear(); - const yearsToShow = 5; - return Array.from({ length: yearsToShow }, (_, i) => { - const year = currentYear - i; - const value = `${year}-${(year + 1).toString().slice(-2)}`; - return { value, label: value }; - }); -}; - -export default function AdminDropDashboard() { - const [year, setYear] = useState(''); - const [semester, setSemester] = useState(''); - const [requests, setRequests] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [selectedIds, setSelectedIds] = useState(new Set()); - const [processing, setProcessing] = useState(false); - const [statusFilter, setStatusFilter] = useState(''); - const [deleteModal, setDeleteModal] = useState({ open: false, ids: [] }); - const [deleting, setDeleting] = useState(false); - - const academicYears = useMemo(() => generateAcademicYears(), []); - - const fetchRequests = useCallback(async () => { - if (!year || !semester) return; - - const token = localStorage.getItem('authToken'); - if (!token) { - setError('Authentication required'); - return; - } - - setLoading(true); - setError(null); - setSelectedIds(new Set()); - - try { - const { data } = await axios.get(adminListDropRequestsRoute, { - params: { academic_year: year, semester_type: semester }, - headers: { Authorization: `Token ${token}` }, - }); - setRequests(Array.isArray(data) ? data : []); - } catch (err) { - const errorMsg = err.response?.data?.error || err.response?.data?.detail || err.message; - setError(errorMsg || 'Failed to load requests'); - } finally { - setLoading(false); - } - }, [year, semester]); - - useEffect(() => { - fetchRequests(); - }, [fetchRequests]); - - const toggleSelection = useCallback((id) => { - setSelectedIds(prev => { - const newSet = new Set(prev); - newSet.has(id) ? newSet.delete(id) : newSet.add(id); - return newSet; - }); - }, []); - - const pendingRequests = useMemo( - () => requests.filter(r => r.status === 'Pending'), - [requests] - ); - - const processedRequests = useMemo( - () => requests.filter(r => r.status !== 'Pending'), - [requests] - ); - - const filteredProcessedRequests = useMemo( - () => statusFilter ? processedRequests.filter(r => r.status === statusFilter) : processedRequests, - [processedRequests, statusFilter] - ); - - const toggleSelectAll = useCallback(() => { - if (selectedIds.size === pendingRequests.length && pendingRequests.length > 0) { - setSelectedIds(new Set()); - } else { - setSelectedIds(new Set(pendingRequests.map(r => r.id))); - } - }, [selectedIds.size, pendingRequests]); - - const handleAction = useCallback(async (action) => { - if (selectedIds.size === 0) { - showNotification({ - title: 'No Selection', - message: `Please select at least one request to ${action}.`, - color: 'yellow', - }); - return; - } - - const token = localStorage.getItem('authToken'); - if (!token) { - showNotification({ - title: 'Authentication Error', - message: 'Please login again', - color: 'red' - }); - return; - } - - setProcessing(true); - try { - const response = await axios.post( - approveDropRequestsRoute, - { - request_ids: Array.from(selectedIds), - action - }, - { headers: { Authorization: `Token ${token}` } } - ); - - const summary = response.data?.summary; - showNotification({ - title: 'Success', - message: summary - ? `Processed ${summary.success} request(s) successfully` - : `${action === 'approve' ? 'Approved' : 'Rejected'} ${selectedIds.size} request(s)`, - color: 'green' - }); - - setSelectedIds(new Set()); - await fetchRequests(); - } catch (err) { - const errorMsg = err.response?.data?.error || err.message; - showNotification({ - title: `${action === 'approve' ? 'Approval' : 'Rejection'} Error`, - message: errorMsg || `Failed to ${action} requests`, - color: 'red', - }); - } finally { - setProcessing(false); - } - }, [selectedIds, fetchRequests]); - - const handleApprove = useCallback(() => handleAction('approve'), [handleAction]); - const handleReject = useCallback(() => handleAction('reject'), [handleAction]); - - const openDeleteModal = useCallback((ids) => { - setDeleteModal({ open: true, ids: Array.isArray(ids) ? ids : [ids] }); - }, []); - - const closeDeleteModal = useCallback(() => { - setDeleteModal({ open: false, ids: [] }); - }, []); - - const handleDelete = useCallback(async () => { - const { ids } = deleteModal; - if (ids.length === 0) return; - - const token = localStorage.getItem('authToken'); - if (!token) { - showNotification({ - title: 'Authentication Error', - message: 'Please login again', - color: 'red' - }); - return; - } - - setDeleting(true); - try { - const response = await axios.post( - deleteDropRequestsRoute, - { request_ids: ids }, - { headers: { Authorization: `Token ${token}` } } - ); - - showNotification({ - title: 'Success', - message: `Deleted ${response.data.deleted} request(s)`, - color: 'green' - }); - - setSelectedIds(prev => { - const newSet = new Set(prev); - ids.forEach(id => newSet.delete(id)); - return newSet; - }); - - closeDeleteModal(); - await fetchRequests(); - } catch (err) { - showNotification({ - title: 'Delete Failed', - message: err.response?.data?.error || err.message, - color: 'red', - }); - } finally { - setDeleting(false); - } - }, [deleteModal, fetchRequests, closeDeleteModal]); - - const handleBulkDelete = useCallback(() => { - if (selectedIds.size === 0) { - showNotification({ - title: 'No Selection', - message: 'Please select at least one request to delete', - color: 'yellow', - }); - return; - } - openDeleteModal(Array.from(selectedIds)); - }, [selectedIds, openDeleteModal]); - - const exportToExcel = useCallback((data, filename) => { - if (data.length === 0) { - showNotification({ - title: 'No Data', - message: 'No data available to export', - color: 'yellow', - }); - return; - } - - const headers = ['Student ID', 'Student Name', 'Slot', 'Course Code', 'Course Name', 'Status', 'Requested At', 'Processed At']; - const csvRows = [headers.join(',')]; - - data.forEach(r => { - const row = [ - r.student || '', - r.student_name || '', - r.slot || '', - r.course || '', - r.course_name || '', - r.status || '', - r.created_at ? new Date(r.created_at).toLocaleString() : '', - r.processed_at ? new Date(r.processed_at).toLocaleString() : '' - ]; - csvRows.push(row.map(val => `"${val}"`).join(',')); - }); - - const csvContent = csvRows.join('\n'); - const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); - const link = document.createElement('a'); - const url = URL.createObjectURL(blob); - - link.setAttribute('href', url); - link.setAttribute('download', `${filename}_${year}_${semester}.csv`); - link.style.visibility = 'hidden'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - showNotification({ - title: 'Success', - message: 'Data exported successfully', - color: 'green', - }); - }, [year, semester]); - - return ( - <> - - - - - - - - - - - - - - - {loading ? ( - - - - ) : error ? ( - {error} - ) : (!year || !semester) ? ( - - Select academic year and semester to view drop requests. - - ) : ( - - - - Pending Requests ({pendingRequests.length}) - - - Processed Requests ({processedRequests.length}) - - - - - {pendingRequests.length > 0 ? ( - - - Pending Requests - - - - - - - - - - - - - - - - - - - {pendingRequests.map(r => ( - - - - - - - - - - ))} - -
- 0} - onChange={toggleSelectAll} - indeterminate={selectedIds.size > 0 && selectedIds.size < pendingRequests.length} - /> - StudentSlotCourseStatusRequested AtActions
- toggleSelection(r.id)} - /> - - {r.student} - {r.student_name && ( - {r.student_name} - )} - {r.slot} - {r.course} - {r.course_name && ( - {r.course_name} - )} - - {r.status} - {new Date(r.created_at).toLocaleString()} - openDeleteModal(r.id)} - title="Delete request" - > - - -
-
- ) : ( - - No pending requests found for the selected period. - - )} -
- - - {processedRequests.length > 0 ? ( - - - Processed Requests - - - - - - - - - - - - - - - {filteredProcessedRequests.map(r => ( - - - - - - - - - ))} - -
StudentSlotCourse - - Status - Requested AtProcessed At
- {r.student} - {r.student_name && ( - {r.student_name} - )} - {r.slot} - {r.course} - {r.course_name && ( - {r.course_name} - )} - - - {r.status} - - {new Date(r.created_at).toLocaleString()} - {r.processed_at - ? new Date(r.processed_at).toLocaleString() - : '—'} -
-
- ) : ( - - No processed requests found for the selected period. - - )} -
-
- )} - - - - Are you sure you want to permanently delete {deleteModal.ids.length} course drop request{deleteModal.ids.length > 1 ? 's' : ''}? - - - This action cannot be undone. - - - - - - - - ); -} diff --git a/src/Modules/Academic/AdminPromoteSemester.jsx b/src/Modules/Academic/AdminPromoteSemester.jsx deleted file mode 100644 index aaa9dc004..000000000 --- a/src/Modules/Academic/AdminPromoteSemester.jsx +++ /dev/null @@ -1,239 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import axios from "axios"; -import { - Card, - Text, - Select, - Loader, - Center, - Alert, - ScrollArea, - Table, - Button, - Modal, - Group, - Checkbox, - LoadingOverlay, -} from "@mantine/core"; -import { - listBatchesRoute, - listStudentsPromoteRoute, - applyPromoteRoute, -} from "../../routes/academicRoutes"; - -export default function AdminPromoteSemester() { - const [batches, setBatches] = useState([]); - const [sourceBatch, setSourceBatch] = useState(""); - const [students, setStudents] = useState([]); - const [loadingBatches, setLoadingBatches] = useState(false); - const [loadingStudents, setLoadingStudents] = useState(false); - const [loadingApply, setLoadingApply] = useState(false); - const [error, setError] = useState(""); - const [successMessage, setSuccessMessage] = useState(""); - const [selected, setSelected] = useState({}); - const [modalOpen, setModalOpen] = useState(false); - - // Load batch list - useEffect(() => { - setLoadingBatches(true); - const token = localStorage.getItem("authToken"); - axios - .get(listBatchesRoute, { headers: { Authorization: `Token ${token}` } }) - .then((res) => setBatches(res.data)) - .catch(() => setError("Failed to load batches.")) - .finally(() => setLoadingBatches(false)); - }, []); - - // Fetch students for promotion - const fetchStudents = useCallback( - (batchId) => { - setLoadingStudents(true); - const token = localStorage.getItem("authToken"); - axios - .get(listStudentsPromoteRoute, { - params: { batch_id: batchId }, - headers: { Authorization: `Token ${token}` }, - }) - .then((res) => { - setStudents(res.data); - const init = {}; - res.data.forEach((st) => { - init[st.id] = false; - }); - setSelected(init); - }) - .catch(() => setError("Failed to load students.")) - .finally(() => setLoadingStudents(false)); - }, - [setStudents, setSelected] - ); - - // Reload students when batch changes - useEffect(() => { - setError(""); - setSuccessMessage(""); - if (!sourceBatch) { - setStudents([]); - setSelected({}); - return; - } - fetchStudents(sourceBatch); - }, [sourceBatch, fetchStudents]); - - const toggleSelect = (sid) => { - setSelected((prev) => ({ ...prev, [sid]: !prev[sid] })); - }; - - const selectAll = () => { - const all = {}; - students.forEach((st) => { - all[st.id] = true; - }); - setSelected(all); - }; - - const deselectAll = () => { - const none = {}; - students.forEach((st) => { - none[st.id] = false; - }); - setSelected(none); - }; - - const toConfirm = students.filter((st) => selected[st.id]); - - const submitChanges = () => setModalOpen(true); - - const confirmApply = () => { - setLoadingApply(true); - const payload = toConfirm.map((st) => st.id); - const token = localStorage.getItem("authToken"); - axios - .post(applyPromoteRoute, payload, { - headers: { Authorization: `Token ${token}` }, - }) - .then(() => { - setModalOpen(false); - setSuccessMessage("Selected students have been successfully promoted."); - setError(""); - fetchStudents(sourceBatch); - }) - .catch(() => setError("Failed to promote students.")) - .finally(() => setLoadingApply(false)); - }; - - const batchOptions = batches.map((b) => ({ - value: String(b.id), - label: b.label, - })); - - return ( - - - - - )} - - - Requested At - {data.some(r => r.processed_at) && Processed At} - {statusFilter === 'Rejected' && Actions} - - - - {data.map((r, index) => ( - - {statusFilter === 'Rejected' && ( - - toggleProcessedSelection(r.id)} - /> - - )} - {index + 1} - - {r.student} - {r.student_name} - - {r.slot} - - {r.old_course} - {r.old_course_name} - - - {r.new_course} - {r.new_course_name} - - - - {r.status} - - - {new Date(r.created_at).toLocaleString()} - {data.some(req => req.processed_at) && ( - {r.processed_at ? new Date(r.processed_at).toLocaleString() : '-'} - )} - {statusFilter === 'Rejected' && ( - - openRevertModal(r.id)} - title="Revert to Pending" - loading={reverting} - > - - - - )} - - ))} - - - ); - - return ( - <> - - - - - - - - - - - - - - {loading ? ( - - - - ) : error ? ( - {error} - ) : (!year || !semester) ? ( - - Select academic year and semester to view replacement requests. - - ) : ( - - - - Pending Requests ({pendingRequests.length}) - - - Processed Requests ({processedRequests.length}) - - - - - {pendingRequests.length > 0 ? ( - - - Pending Requests - - - - - - {renderPendingTable(pendingRequests)} - - ) : ( - No pending replacement requests. - )} - - - - {processedRequests.length > 0 ? ( - - - Processed Requests - - {statusFilter === 'Rejected' && ( - - )} - - - - {renderProcessedTable(filteredProcessedRequests, true)} - - ) : ( - No processed replacement requests. - )} - - - )} - - - - Are you sure you want to permanently delete {deleteModal.ids.length} replacement request{deleteModal.ids.length > 1 ? 's' : ''}? - - - This action cannot be undone. - - - - - - - - - - Are you sure you want to revert {revertModal.ids.length} rejected request{revertModal.ids.length > 1 ? 's' : ''} back to Pending status? - - - The request{revertModal.ids.length > 1 ? 's' : ''} will be moved back to the Pending Requests tab. - - - - - - - - ); -} diff --git a/src/Modules/Academic/AdminStudentDashboard.jsx b/src/Modules/Academic/AdminStudentDashboard.jsx deleted file mode 100644 index 1952684bb..000000000 --- a/src/Modules/Academic/AdminStudentDashboard.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React,{useState} from 'react' -import {Card,TextInput,Button,Loader,Notification,Table,Space,Text} from '@mantine/core' -import axios from 'axios' -import { StudentSearchRoute } from '../../routes/academicRoutes' - -export default function AdminStudentDashboard(){ - const [rollNo,setRollNo]=useState('') - const [info,setInfo]=useState(null) - const [loading,setLoading]=useState(false) - const [error,setError]=useState('') - - const search=async()=>{ - setLoading(true) - setError('') - setInfo(null) - try{ - const token=localStorage.getItem('authToken') - const {data}=await axios.post( - StudentSearchRoute, - {rollno:rollNo}, - {headers:{Authorization:`Token ${token}`}} - ) - setInfo(data) - }catch(err){ - setError(err.response?.data?.error||'Student not found') - }finally{ - setLoading(false) - } - } - - return ( - - setRollNo(e.currentTarget.value.toUpperCase())} - mb="sm" - /> - - {error&&<> - - {error} - } - {info&&<> - - Student Details - - - {Object.entries(info).map(([k,v])=>( - - - - - ))} - -
- {k.replace(/_/g,' ')} - {v??'—'}
- } -
- ) -} diff --git a/src/Modules/Academic/AllocateCourses.jsx b/src/Modules/Academic/AllocateCourses.jsx deleted file mode 100644 index c2771182c..000000000 --- a/src/Modules/Academic/AllocateCourses.jsx +++ /dev/null @@ -1,198 +0,0 @@ -import React, { useState } from "react"; -import { - Card, - Text, - Button, - TextInput, - Alert, - NumberInput, - Loader, - Select, -} from "@mantine/core"; -import axios from "axios"; -import { - checkAllocationRoute, - startAllocationRoute, -} from "../../routes/academicRoutes"; - -function AllocateCourses() { - const [batch, setBatch] = useState(""); - const [semester, setSemester] = useState(""); - const [year, setYear] = useState(""); - const [programmeType, setProgrammeType] = useState("UG"); - const [error, setError] = useState(""); - const [success, setSuccess] = useState(""); - const [loading, setLoading] = useState(false); - const [showStartButton, setShowStartButton] = useState(false); - - const handleCheckAllocation = async () => { - setError(""); - setSuccess(""); - setLoading(true); - setShowStartButton(false); - - const token = localStorage.getItem("authToken"); - if (!token) { - setError("No token found"); - setLoading(false); - return; - } - - try { - const response = await axios.post( - checkAllocationRoute, - { batch, sem: semester, year, programme_type: programmeType }, - { - headers: { - Authorization: `Token ${token}`, - "Content-Type": "application/json", - }, - }, - ); - - const result = response.data; - - if (result.status === 2) { - setSuccess("Courses are successfully allocated."); - } else if (result.status === 1) { - setError("Courses not yet allocated. Start allocation."); - setShowStartButton(true); - } else if (result.status === -1) { - setError("Registration is under process."); - } else if (result.status === -2) { - setError("Registration didn't start."); - } else { - setError(result.message); - } - } catch (err) { - const errorMessage = - err.response?.data?.message || "Error checking allocation."; - setError(errorMessage); - } - - setLoading(false); - }; - - const handleStartAllocation = async () => { - setLoading(true); - setSuccess(""); - setError(""); - - const token = localStorage.getItem("authToken"); - if (!token) { - setError("No token found"); - setLoading(false); - return; - } - - try { - const response = await axios.post( - startAllocationRoute, - { batch, semester, year, programme_type: programmeType }, - { - headers: { - Authorization: `Token ${token}`, - "Content-Type": "application/json", - }, - }, - ); - - if (response.data.status === 1) { - setSuccess("Course allocation successful!"); - setShowStartButton(false); - } else { - setError(response.data.message || "Allocation failed"); - } - } catch (err) { - setError(err.response?.data?.message || "Error starting allocation."); - } - setLoading(false); - }; - - return ( - - - Allocate Courses - - - - - - - {(props) => ( - - )} - - - - ); -} \ No newline at end of file diff --git a/src/Modules/Academic/AvailableCourses.jsx b/src/Modules/Academic/AvailableCourses.jsx deleted file mode 100644 index 8cf4b2e0f..000000000 --- a/src/Modules/Academic/AvailableCourses.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { Card, Text, Loader, Group } from "@mantine/core"; -import axios from "axios"; -import FusionTable from "../../components/FusionTable"; -import { nextSemCoursesRoute } from "../../routes/academicRoutes"; - -function AvailableCourses() { - const [courses, setCourses] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchCourses = async () => { - try { - const token = localStorage.getItem("authToken"); // Get token from local storage - if (!token) { - throw new Error("No token found"); // Handle the case where the token is not available - } - const response = await axios.get(nextSemCoursesRoute, { - headers: { - Authorization: `Token ${token}`, - }, - }); - setCourses(response.data.courses_list); // Set courses from API response - } catch (error) { - console.error("Error fetching courses:", error); - setCourses([]); // Or set a default message in the table - } finally { - setTimeout(() => setLoading(false)); - } - }; - - fetchCourses(); - }, []); - - const columnNames = [ - "Slot Name", - "Slot Type", - "Semester", - "Credits", - "Course", - ]; - - const mappedCourses = courses.map((course) => ({ - "Slot Name": {course.name}, - "Slot Type": course.type, - Semester: course.semester.semester_no, - Credits: course.courses[0].credit, - Course: ( - - {course.courses.map((cour) => cour.name).join("\n")} - - ), - })); - - return ( - - - Available Courses Next Semester - - {loading ? ( - - - - ) : ( -
- -
- )} -
- ); -} - -export default AvailableCourses; diff --git a/src/Modules/Academic/DeletePreRegistration.jsx b/src/Modules/Academic/DeletePreRegistration.jsx deleted file mode 100644 index f62b3bb13..000000000 --- a/src/Modules/Academic/DeletePreRegistration.jsx +++ /dev/null @@ -1,285 +0,0 @@ -import { useState } from "react"; -import { - Card, - Text, - Button, - TextInput, - Alert, - Group, - Modal, - Loader, -} from "@mantine/core"; -import axios from "axios"; -import FusionTable from "../../components/FusionTable"; -import { - deletePreRegistrationRoute, - searchPreRegistrationRoute, -} from "../../routes/academicRoutes"; - -function RegistrationSearch() { - const [rollNo, setRollNo] = useState(""); - const [semester, setSemester] = useState(""); - const [searchResults, setSearchResults] = useState(null); - const [error, setError] = useState(""); - const [deleteModalOpen, setDeleteModalOpen] = useState(false); - const [loading, setLoading] = useState(false); // Add loading state - - const handleSearch = async () => { - setError(""); - setSearchResults(null); - setLoading(true); // Start loading - if (!rollNo || !semester) { - setError("Please fill both Roll No and Semester fields"); - setLoading(false); // Stop loading - return; - } - - const token = localStorage.getItem("authToken"); // Get token from local storage - if (!token) { - setLoading(false); // Stop loading - throw new Error("No token found"); // Handle the case where the token is not available - } - - try { - const response = await axios.post( - searchPreRegistrationRoute, - { - roll_no: rollNo, - sem_no: semester, - }, - { - headers: { - Authorization: `Token ${token}`, - }, - }, - ); - - if (response.data.student_registration_check) { - setSearchResults(response.data); - } else { - setError("No Registration Found!"); - } - } catch (err) { - setError(err); - } finally { - setLoading(false); // Stop loading - } - }; - - const handleDelete = async () => { - const token = localStorage.getItem("authToken"); // Get token from local storage - if (!token) { - throw new Error("No token found"); // Handle the case where the token is not available - } - - setLoading(true); // Start loading - try { - const response = await axios.post( - deletePreRegistrationRoute, - { - roll_no: searchResults.student_registration_check.student_id, - sem_no: - searchResults.initial_registration[0]?.semester_id?.semester_no, - }, - { - headers: { - Authorization: `Token ${token}`, - }, - }, - ); - setDeleteModalOpen(false); - alert(response.data.message); - setSearchResults(null); - } catch (er) { - setError(er); - } finally { - setLoading(false); // Stop loading - } - }; - - const columnNames = ["Course", "Semester", "Course Slot", "Type", "Priority"]; - - const mappedResults = searchResults - ? searchResults.initial_registration.map((result) => ({ - Course: result.course_id?.name || "N/A", - Semester: result.semester_id?.semester_no || "N/A", - "Course Slot": result.course_slot_id?.name || "N/A", - Type: result.registration_type || "N/A", - Priority: result.priority || "N/A", - })) - : []; - - return ( - - - Search and Manage Registrations - - -
- setRollNo(e.target.value)} - /> - - setSemester(e.target.value)} - /> - - -
- - {error && ( - - {error} - - )} - - {searchResults && ( -
- - Search Results - - - - Initial Registrations: - - -
- -
- - - Student Registration Check: - - - - - Student Roll No: - - {searchResults.student_registration_check.student_id} - - - - Pre-Registration Flag: - - {searchResults.student_registration_check.pre_registration_flag - ? "True" - : "False"} - - - - Final Registration Flag: - - {searchResults.student_registration_check - .final_registration_flag - ? "True" - : "False"} - - - - - - -
- )} - - {loading && ( -
- -
- )} - - setDeleteModalOpen(false)} - title="Confirm Deletion" - > - - Are you sure you want to delete pre-registration details? - - - This action cannot be undone! - - - - - - -
- ); -} - -export default RegistrationSearch; diff --git a/src/Modules/Academic/Faculty_TA_Dashboard.jsx b/src/Modules/Academic/Faculty_TA_Dashboard.jsx deleted file mode 100644 index 3fcba59f6..000000000 --- a/src/Modules/Academic/Faculty_TA_Dashboard.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useState, useEffect } from 'react'; -import axios from 'axios'; -import { Card, Title, Tabs, Table, Textarea, Button } from '@mantine/core'; -import * as URLS from "../../routes/academicRoutes"; - -export function Faculty_TA_Dashboard() { - const [assignments, setAssignments] = useState([]); - const [pending, setPending] = useState([]); - const [approved, setApproved] = useState([]); - const [remarks, setRemarks] = useState({}); - - useEffect(() => loadAll(), []); - - function loadAll() { - const token = localStorage.getItem('authToken'); - axios.get(URLS.FAC_ASSIGNMENTS_URL, { headers: { Authorization: `Token ${token}` } }) - .then(r => setAssignments(r.data.assignments)); - axios.get(URLS.FAC_PENDING_URL, { headers: { Authorization: `Token ${token}` } }) - .then(r => setPending(r.data.stipends)); - axios.get(URLS.FAC_APPROVED_URL, { headers: { Authorization: `Token ${token}` } }) - .then(r => setApproved(r.data.stipends)); - } - - async function handleApprove(id) { - const token = localStorage.getItem('authToken'); - await axios.post(URLS.FAC_APPROVE_URL(id), { - role: 'faculty', - remark: remarks[id] || '', - }, { headers: { Authorization: `Token ${token}` } }); - loadAll(); - } - - return ( - - Faculty Dashboard - - - - Assignments - Pending - Approved - - - - - - - - - {assignments.map(a => ( - - - - - ))} - -
TAPeriod
{a.ta_username} - {a.start_month}/{a.start_year} - {a.end_month}/{a.end_year} -
-
- - - - - - - - {pending.map(s => ( - - - -
TAMM/YYYYRemarkAction
{s.ta}{s.month}/{s.year} -