updated UI and logics
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
Award
|
||||
} from 'lucide-react';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useDialog } from '../../context/DialogContext';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
interface Event {
|
||||
@@ -52,6 +53,7 @@ interface Registration {
|
||||
|
||||
export const StudentRegistrationsView: React.FC = () => {
|
||||
const { user } = useAuth();
|
||||
const { showAlert, showConfirm } = useDialog();
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
||||
const [registrations, setRegistrations] = useState<Registration[]>([]);
|
||||
@@ -74,10 +76,25 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
|
||||
const daysList = ["Day 1", "Day 2", "Day 3"]; // Standard slots
|
||||
|
||||
const [batches, setBatches] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchEvents();
|
||||
fetchBatches();
|
||||
}, []);
|
||||
|
||||
const fetchBatches = async () => {
|
||||
try {
|
||||
const res = await fetch(API_BASE_URL + '/api/batches');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setBatches(data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEvent) {
|
||||
fetchRegistrations(selectedEvent.id);
|
||||
@@ -161,6 +178,33 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Dismiss Student from event
|
||||
const handleDismissStudent = async (regId: string, studentName: string) => {
|
||||
showConfirm(
|
||||
"Dismiss Student",
|
||||
`Are you sure you want to dismiss ${studentName} from this event? This will completely remove their registration.`,
|
||||
async () => {
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/api/registrations/${regId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (res.ok) {
|
||||
showAlert("Success", `${studentName} has been successfully dismissed.`, "success");
|
||||
await fetchRegistrations(selectedEvent!.id);
|
||||
} else {
|
||||
showAlert("Error", "Failed to dismiss student.", "error");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showAlert("Error", "An error occurred while dismissing the student.", "error");
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Toggle Attendance Cell local state
|
||||
const handleToggleAttendance = (regId: string, day: string) => {
|
||||
setAttendanceGrid(prev => ({
|
||||
@@ -197,10 +241,10 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
alert("Attendance records synchronized successfully!");
|
||||
showAlert("Success", "Attendance records synchronized successfully!", "success");
|
||||
await fetchRegistrations(selectedEvent!.id);
|
||||
} else {
|
||||
alert("Failed to save attendance.");
|
||||
showAlert("Error", "Failed to save attendance.", "error");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -223,7 +267,10 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
|
||||
// Upload OD File to all registered students
|
||||
const handleUploadODFile = async () => {
|
||||
if (!odFileBase64) return alert("Please select an On-Duty file first.");
|
||||
if (!odFileBase64) {
|
||||
showAlert("Error", "Please select an On-Duty file first.", "error");
|
||||
return;
|
||||
}
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
// Loop over and update all registrations for this event
|
||||
@@ -234,7 +281,7 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
body: JSON.stringify({ odUrl: odFileBase64 })
|
||||
});
|
||||
}
|
||||
alert("OD sheet broadcasted to all registered students successfully!");
|
||||
showAlert("Success", "OD sheet broadcasted to all registered students successfully!", "success");
|
||||
setOdFileBase64(null);
|
||||
await fetchRegistrations(selectedEvent!.id);
|
||||
} catch (err) {
|
||||
@@ -256,7 +303,7 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
|
||||
if (res.ok) {
|
||||
setInspectCertReg(null);
|
||||
alert(status === 'APPROVED' ? "Certificate approved!" : "Certificate rejected.");
|
||||
showAlert("Certificate Audited", status === 'APPROVED' ? "Certificate approved!" : "Certificate rejected.", status === 'APPROVED' ? "success" : "info");
|
||||
await fetchRegistrations(selectedEvent!.id);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -271,6 +318,12 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
r.regNo.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const getStudentBatchName = (reg: any) => {
|
||||
const classStr = `${reg.year} - ${reg.section}`;
|
||||
const match = batches.find(b => b.department === reg.dept && b.classes?.includes(classStr));
|
||||
return match ? match.name : '';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8 animate-in fade-in duration-300">
|
||||
{/* Header Info */}
|
||||
@@ -377,12 +430,13 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
<th className="py-4">College</th>
|
||||
<th className="py-4">Alliance/Team</th>
|
||||
<th className="py-4 text-center">Payment Status</th>
|
||||
<th className="py-4 text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-50">
|
||||
{filteredRoster.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="py-8 text-center text-slate-300 italic font-bold">No registered students matched the search.</td>
|
||||
<td colSpan={6} className="py-8 text-center text-slate-300 italic font-bold">No registered students matched the search.</td>
|
||||
</tr>
|
||||
) : (
|
||||
filteredRoster.map((reg) => (
|
||||
@@ -391,8 +445,13 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
<p className="text-sm font-black text-brand-navy">{reg.userName}</p>
|
||||
<p className="text-[10px] text-slate-400 mt-0.5">{reg.regNo} | {reg.phone}</p>
|
||||
</td>
|
||||
<td className="py-4 text-slate-600">
|
||||
<td className="py-4 text-slate-600">
|
||||
{reg.year} Year / SEC {reg.section}
|
||||
{getStudentBatchName(reg) && (
|
||||
<span className="ml-1.5 px-1.5 py-0.5 bg-brand-indigo/10 text-brand-indigo text-[8px] font-black rounded">
|
||||
{getStudentBatchName(reg)}
|
||||
</span>
|
||||
)}
|
||||
<p className="text-[9px] text-brand-indigo mt-0.5">{reg.dept}</p>
|
||||
</td>
|
||||
<td className="py-4 text-slate-600 truncate max-w-[150px]">{reg.college}</td>
|
||||
@@ -420,6 +479,15 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
{reg.paymentStatus}
|
||||
</button>
|
||||
</td>
|
||||
<td className="py-4 text-center">
|
||||
<button
|
||||
onClick={() => handleDismissStudent(reg.id, reg.userName)}
|
||||
disabled={isProcessing}
|
||||
className="px-3 py-1.5 bg-rose-50 border border-rose-100 text-rose-600 rounded-lg text-[9px] font-black tracking-widest uppercase hover:bg-rose-100 hover:text-rose-700 transition-all"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
@@ -450,17 +518,53 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
<th className="py-4">Student</th>
|
||||
<th className="py-4">Register No</th>
|
||||
{daysList.map(day => <th key={day} className="py-4 text-center">{day}</th>)}
|
||||
<th className="py-4 text-center">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<span className="text-[10px]">Mark All</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={
|
||||
registrations.length > 0 &&
|
||||
registrations.every(reg =>
|
||||
daysList.every(day => attendanceGrid[reg.id]?.[day] || false)
|
||||
)
|
||||
}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
setAttendanceGrid(prev => {
|
||||
const next = { ...prev };
|
||||
registrations.forEach(reg => {
|
||||
next[reg.id] = next[reg.id] || {};
|
||||
daysList.forEach(day => {
|
||||
next[reg.id][day] = checked;
|
||||
});
|
||||
});
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
className="w-4 h-4 rounded border-slate-300 text-brand-indigo focus:ring-brand-indigo cursor-pointer"
|
||||
title="Toggle all days for all students"
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-50">
|
||||
{registrations.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="py-8 text-center text-slate-300 italic font-bold">No students registered yet.</td>
|
||||
<td colSpan={6} className="py-8 text-center text-slate-300 italic font-bold">No students registered yet.</td>
|
||||
</tr>
|
||||
) : (
|
||||
registrations.map((reg) => (
|
||||
<tr key={reg.id} className="hover:bg-slate-50/50">
|
||||
<td className="py-4 font-black text-brand-navy">{reg.userName}</td>
|
||||
<tr key={reg.id} className="hover:bg-slate-50/50">
|
||||
<td className="py-4 font-black text-brand-navy">
|
||||
{reg.userName}
|
||||
{getStudentBatchName(reg) && (
|
||||
<span className="ml-1.5 px-1 py-0.5 bg-brand-indigo/10 text-brand-indigo text-[7px] font-black rounded block w-max mt-0.5">
|
||||
{getStudentBatchName(reg)}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-4 text-slate-400">{reg.regNo}</td>
|
||||
{daysList.map(day => (
|
||||
<td key={day} className="py-4 text-center">
|
||||
@@ -472,6 +576,24 @@ export const StudentRegistrationsView: React.FC = () => {
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
<td className="py-4 text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={daysList.every(day => attendanceGrid[reg.id]?.[day] || false)}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
setAttendanceGrid(prev => ({
|
||||
...prev,
|
||||
[reg.id]: daysList.reduce((acc, day) => {
|
||||
acc[day] = checked;
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>)
|
||||
}));
|
||||
}}
|
||||
className="w-4.5 h-4.5 rounded border-slate-300 text-brand-indigo focus:ring-brand-indigo cursor-pointer"
|
||||
title="Toggle all days for this student"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user