updated UI and logics

This commit is contained in:
2026-06-22 12:16:30 +05:30
parent ef93f4bf48
commit 947c89167d
22 changed files with 2243 additions and 360 deletions

View File

@@ -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>
))
)}