Files
Event-Management-System/RIT-EMS-main/frontend/src/components/dashboard/StudentRegistrationsView.tsx
2026-06-22 12:16:30 +05:30

746 lines
33 KiB
TypeScript

import { API_BASE_URL } from '../../lib/config';
import React, { useState, useEffect } from 'react';
import {
Users,
CheckSquare,
Upload,
FileCheck,
XCircle,
CheckCircle,
FileText,
Search,
ChevronRight,
Eye,
Award
} from 'lucide-react';
import { useAuth } from '../../context/AuthContext';
import { useDialog } from '../../context/DialogContext';
import { cn } from '../../lib/utils';
interface Event {
id: number;
title: string;
startDate: string;
endDate: string;
type: string;
location: string;
category: string;
department: string;
status: string;
}
interface Registration {
id: string;
userId: string;
eventId: number;
userName: string;
userEmail: string;
regNo: string;
phone: string;
gender: string;
dept: string;
section: string;
year: string;
college: string;
paymentStatus: 'PENDING' | 'COMPLETED';
teamCode: string | null;
teamName: string | null;
isTeamLeader: boolean;
certificationUrl: string | null;
certificationStatus: 'NOT_SUBMITTED' | 'PENDING_APPROVAL' | 'APPROVED';
odUrl: string | null;
}
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[]>([]);
const [attendanceLogs, setAttendanceLogs] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
// Search and view states
const [searchQuery, setSearchQuery] = useState('');
const [activeSubTab, setActiveSubTab] = useState<'roster' | 'attendance' | 'od' | 'certificates'>('roster');
// Audit modals
const [inspectCertReg, setInspectCertReg] = useState<Registration | null>(null);
const [isProcessing, setIsProcessing] = useState(false);
// OD Upload states
const [odFileBase64, setOdFileBase64] = useState<string | null>(null);
// Attendance checking state (maps registrationId -> dayLabel -> isPresent)
const [attendanceGrid, setAttendanceGrid] = useState<Record<string, Record<string, boolean>>>({});
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);
}
}, [selectedEvent]);
const fetchEvents = async () => {
setIsLoading(true);
try {
const res = await fetch(API_BASE_URL + '/api/events');
if (res.ok) {
const data = await res.json();
// Faculty can manage their proposed events, Admins/HODs can manage all
let filtered = data.filter((e: Event) => e.status === 'APPROVED' || e.status === 'COMPLETED' || e.status === 'Event Ongoing');
if (user?.role === 'FACULTY') {
const userDepts = user.department ? user.department.split(',').map(d => d.trim().toLowerCase()) : [];
filtered = filtered.filter((e: Event) => userDepts.includes(e.department?.trim().toLowerCase()));
}
setEvents(filtered);
if (filtered.length > 0) {
setSelectedEvent(filtered[0]);
}
}
} catch (err) {
console.error(err);
} finally {
setIsLoading(false);
}
};
const fetchRegistrations = async (eventId: number) => {
setIsLoading(true);
try {
const [regsRes, attRes] = await Promise.all([
fetch(API_BASE_URL + `/api/registrations?eventId=${eventId}`),
fetch(API_BASE_URL + `/api/attendance?eventId=${eventId}`)
]);
if (regsRes.ok && attRes.ok) {
const regsData = await regsRes.json();
const attData = await attRes.json();
setRegistrations(regsData);
setAttendanceLogs(attData);
// Pre-populate attendance grid from attendanceLogs
const grid: Record<string, Record<string, boolean>> = {};
regsData.forEach((r: Registration) => {
grid[r.id] = {};
daysList.forEach(day => {
const match = attData.find((a: any) => String(a.registrationId || a.registration_id) === String(r.id) && a.dayLabel === day);
grid[r.id][day] = match ? !!match.isPresent : false;
});
});
setAttendanceGrid(grid);
}
} catch (err) {
console.error(err);
} finally {
setIsLoading(false);
}
};
// Toggle Payment Status Manually
const handleTogglePayment = async (reg: Registration) => {
setIsProcessing(true);
const newStatus = reg.paymentStatus === 'COMPLETED' ? 'PENDING' : 'COMPLETED';
try {
const res = await fetch(`${API_BASE_URL}/api/registrations/${reg.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentStatus: newStatus })
});
if (res.ok) {
await fetchRegistrations(selectedEvent!.id);
}
} catch (err) {
console.error(err);
} finally {
setIsProcessing(false);
}
};
// 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 => ({
...prev,
[regId]: {
...prev[regId],
[day]: !prev[regId][day]
}
}));
};
// Save Attendance Grid to Firestore
const handleSaveAttendance = async () => {
setIsProcessing(true);
try {
const payload: any[] = [];
Object.keys(attendanceGrid).forEach(regId => {
daysList.forEach(day => {
payload.push({
registrationId: regId,
eventId: selectedEvent!.id,
dayLabel: day,
batchLabel: "Slot 1",
isPresent: attendanceGrid[regId][day],
date: new Date().toLocaleDateString()
});
});
});
const res = await fetch(API_BASE_URL + '/api/attendance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.ok) {
showAlert("Success", "Attendance records synchronized successfully!", "success");
await fetchRegistrations(selectedEvent!.id);
} else {
showAlert("Error", "Failed to save attendance.", "error");
}
} catch (err) {
console.error(err);
} finally {
setIsProcessing(false);
}
};
// Handle OD PDF File selection
const handleODFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onloadend = () => {
setOdFileBase64(reader.result as string);
};
reader.readAsDataURL(file);
};
// Upload OD File to all registered students
const handleUploadODFile = async () => {
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
for (const reg of registrations) {
await fetch(`${API_BASE_URL}/api/registrations/${reg.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ odUrl: odFileBase64 })
});
}
showAlert("Success", "OD sheet broadcasted to all registered students successfully!", "success");
setOdFileBase64(null);
await fetchRegistrations(selectedEvent!.id);
} catch (err) {
console.error(err);
} finally {
setIsProcessing(false);
}
};
// Approve or Reject screenshot proof
const handleAuditCertificate = async (regId: string, status: 'APPROVED' | 'NOT_SUBMITTED') => {
setIsProcessing(true);
try {
const res = await fetch(`${API_BASE_URL}/api/registrations/${regId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ certificationStatus: status })
});
if (res.ok) {
setInspectCertReg(null);
showAlert("Certificate Audited", status === 'APPROVED' ? "Certificate approved!" : "Certificate rejected.", status === 'APPROVED' ? "success" : "info");
await fetchRegistrations(selectedEvent!.id);
}
} catch (err) {
console.error(err);
} finally {
setIsProcessing(false);
}
};
const filteredRoster = registrations.filter(r =>
r.userName.toLowerCase().includes(searchQuery.toLowerCase()) ||
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 */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-3xl font-black text-text-dark tracking-tight">Student Engagement Hub</h2>
<p className="text-text-muted font-medium text-sm">Review registration rosters, log session attendance checklists, release OD files, and audit certificate proofs.</p>
</div>
{/* Event Selector Dropdown */}
<div className="flex items-center gap-3">
<label className="text-xs font-black text-slate-400 uppercase tracking-widest">Selected Event:</label>
<select
value={selectedEvent?.id || ''}
onChange={(e) => {
const ev = events.find(ev => ev.id === Number(e.target.value));
if (ev) setSelectedEvent(ev);
}}
className="bg-white border border-slate-200 rounded-2xl py-3.5 px-5 text-xs font-black focus:outline-none focus:ring-2 focus:ring-brand-indigo/10 appearance-none cursor-pointer pr-10 shadow-sm"
>
{events.map(e => <option key={e.id} value={e.id}>{e.title}</option>)}
</select>
</div>
</div>
{selectedEvent ? (
<div className="space-y-8">
{/* Quick Metrics Panels */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<div className="bg-white p-6 rounded-[2rem] border border-slate-100 shadow-sm">
<span className="block text-[8px] font-black text-slate-400 uppercase tracking-widest mb-1">Passes Secured</span>
<p className="text-2xl font-black text-slate-900">{registrations.length}</p>
</div>
<div className="bg-white p-6 rounded-[2rem] border border-slate-100 shadow-sm">
<span className="block text-[8px] font-black text-slate-400 uppercase tracking-widest mb-1">Teamed Up</span>
<p className="text-2xl font-black text-slate-900">{registrations.filter(r => r.teamCode).length}</p>
</div>
<div className="bg-white p-6 rounded-[2rem] border border-slate-100 shadow-sm">
<span className="block text-[8px] font-black text-slate-400 uppercase tracking-widest mb-1">In Review Proofs</span>
<p className="text-2xl font-black text-amber-500">{registrations.filter(r => r.certificationStatus === 'PENDING_APPROVAL').length}</p>
</div>
<div className="bg-white p-6 rounded-[2rem] border border-slate-100 shadow-sm">
<span className="block text-[8px] font-black text-slate-400 uppercase tracking-widest mb-1">Verified Certificates</span>
<p className="text-2xl font-black text-emerald-500">{registrations.filter(r => r.certificationStatus === 'APPROVED').length}</p>
</div>
</div>
{/* Sub Navigation Tabs */}
<div className="flex bg-slate-100 p-1 rounded-2xl gap-1 max-w-md">
<button
onClick={() => setActiveSubTab('roster')}
className={cn("flex-1 py-3 rounded-xl text-[9px] font-black uppercase tracking-widest transition-all", activeSubTab === 'roster' ? "bg-white text-brand-indigo shadow-sm" : "text-text-muted hover:text-text-dark")}
>
Roster & Fee
</button>
<button
onClick={() => setActiveSubTab('attendance')}
className={cn("flex-1 py-3 rounded-xl text-[9px] font-black uppercase tracking-widest transition-all", activeSubTab === 'attendance' ? "bg-white text-brand-indigo shadow-sm" : "text-text-muted hover:text-text-dark")}
>
Attendance
</button>
<button
onClick={() => setActiveSubTab('od')}
className={cn("flex-1 py-3 rounded-xl text-[9px] font-black uppercase tracking-widest transition-all", activeSubTab === 'od' ? "bg-white text-brand-indigo shadow-sm" : "text-text-muted hover:text-text-dark")}
>
On-Duty Release
</button>
<button
onClick={() => setActiveSubTab('certificates')}
className={cn("flex-1 py-3 rounded-xl text-[9px] font-black uppercase tracking-widest transition-all", activeSubTab === 'certificates' ? "bg-white text-brand-indigo shadow-sm" : "text-text-muted hover:text-text-dark")}
>
Audits
</button>
</div>
{/* Sub Tab Contents */}
<div className="bg-white rounded-[2.5rem] border border-slate-100 shadow-sm p-8">
{/* SUB TAB 1: ROSTER & MANUAL FEE */}
{activeSubTab === 'roster' && (
<div className="space-y-6">
<div className="flex items-center justify-between gap-6">
<h3 className="text-lg font-black text-slate-800 uppercase tracking-tight">Registration Roster</h3>
{/* Search Roster */}
<div className="relative max-w-xs w-full">
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
placeholder="Search name or regNo..."
className="w-full bg-slate-50 border border-slate-100 rounded-xl py-2.5 pl-10 pr-4 text-xs font-semibold focus:outline-none focus:bg-white focus:border-brand-indigo/20 transition-all"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left text-xs font-bold text-slate-500 uppercase tracking-widest">
<thead>
<tr className="border-b border-slate-100 text-slate-400">
<th className="py-4">Student Details</th>
<th className="py-4">Class</th>
<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={6} className="py-8 text-center text-slate-300 italic font-bold">No registered students matched the search.</td>
</tr>
) : (
filteredRoster.map((reg) => (
<tr key={reg.id} className="hover:bg-slate-50/50">
<td className="py-4">
<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">
{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>
<td className="py-4">
{reg.teamCode ? (
<div>
<p className="text-slate-800 font-black text-[11px]">{reg.teamName}</p>
<p className="text-[9px] text-slate-400">Code: {reg.teamCode}</p>
</div>
) : (
<span className="text-slate-300 font-normal">Individual</span>
)}
</td>
<td className="py-4 text-center">
<button
onClick={() => handleTogglePayment(reg)}
disabled={isProcessing}
className={cn(
"px-3 py-1.5 rounded-lg text-[9px] font-black tracking-widest border transition-all",
reg.paymentStatus === 'COMPLETED'
? "bg-emerald-50 border-emerald-100 text-emerald-600 hover:bg-emerald-100"
: "bg-red-50 border-red-100 text-red-600 hover:bg-red-100 animate-pulse"
)}
>
{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>
))
)}
</tbody>
</table>
</div>
</div>
)}
{/* SUB TAB 2: SESSION ATTENDANCE GRID */}
{activeSubTab === 'attendance' && (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h3 className="text-lg font-black text-slate-800 uppercase tracking-tight">Mark Slot Presence</h3>
<button
onClick={handleSaveAttendance}
disabled={isProcessing}
className="px-6 py-2.5 bg-brand-navy text-white rounded-xl font-black text-[10px] uppercase tracking-widest hover:scale-[1.02] transition-all shadow-md"
>
{isProcessing ? 'Saving...' : 'Save Attendance'}
</button>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left text-xs font-bold text-slate-500 uppercase tracking-widest">
<thead>
<tr className="border-b border-slate-100 text-slate-400">
<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={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}
{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">
<input
type="checkbox"
checked={attendanceGrid[reg.id]?.[day] || false}
onChange={() => handleToggleAttendance(reg.id, day)}
className="w-4.5 h-4.5 rounded border-slate-300 text-brand-indigo focus:ring-brand-indigo cursor-pointer"
/>
</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>
))
)}
</tbody>
</table>
</div>
</div>
)}
{/* SUB TAB 3: ON-DUTY UPLOADER */}
{activeSubTab === 'od' && (
<div className="space-y-6 max-w-xl">
<div>
<h3 className="text-lg font-black text-slate-800 uppercase tracking-tight">On-Duty Official Release</h3>
<p className="text-xs text-slate-400 font-semibold mt-1">Upload the unified signed On-Duty letter for this event. It will be released to all registered students automatically.</p>
</div>
<div className="p-8 border-2 border-dashed border-slate-200 rounded-[2rem] text-center space-y-4">
<Upload className="w-10 h-10 text-slate-300 mx-auto mb-2" />
<div className="relative">
<input
type="file"
accept="application/pdf,image/*"
onChange={handleODFileChange}
className="hidden"
id="od-sheet-uploader"
/>
<label
htmlFor="od-sheet-uploader"
className="px-6 py-3 bg-slate-50 border border-slate-200 text-slate-600 rounded-xl font-black text-[10px] uppercase tracking-widest cursor-pointer hover:bg-slate-100 transition-all inline-block"
>
{odFileBase64 ? 'OD Document Loaded' : 'Choose OD Letter'}
</label>
</div>
{odFileBase64 && (
<p className="text-[10px] font-black text-emerald-500 uppercase tracking-wider">File captured ready to broadcast</p>
)}
</div>
<button
onClick={handleUploadODFile}
disabled={isProcessing || !odFileBase64}
className="w-full py-4 bg-brand-indigo text-white rounded-xl font-black text-[10px] uppercase tracking-widest shadow-md shadow-brand-indigo/10 disabled:opacity-50"
>
{isProcessing ? 'Releasing ODs...' : 'Broadcast OD to All Students'}
</button>
</div>
)}
{/* SUB TAB 4: CERTIFICATE AUDITOR */}
{activeSubTab === 'certificates' && (
<div className="space-y-6">
<div>
<h3 className="text-lg font-black text-slate-800 uppercase tracking-tight">Audit Student Proofs</h3>
<p className="text-xs text-slate-400 font-semibold mt-1">Inspect uploaded completion screenshot proofs and approve them to unlock certificate downloads.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{registrations.filter(r => r.certificationStatus === 'PENDING_APPROVAL').length === 0 ? (
<div className="col-span-2 py-16 bg-slate-50 rounded-3xl border border-dashed border-slate-200 text-center text-slate-400 font-bold uppercase tracking-wider text-xs">
<FileCheck className="w-12 h-12 text-slate-300 mx-auto mb-4 opacity-50" />
All certificate proofs have been successfully audited!
</div>
) : (
registrations.filter(r => r.certificationStatus === 'PENDING_APPROVAL').map((reg) => (
<div
key={reg.id}
className="bg-slate-50 p-6 rounded-3xl border border-slate-100 flex items-center justify-between hover:border-slate-200 transition-all"
>
<div>
<p className="text-sm font-black text-slate-800 uppercase">{reg.userName}</p>
<p className="text-[9px] text-slate-400 font-bold uppercase tracking-wider mt-0.5">{reg.regNo} | {reg.dept}</p>
</div>
<button
onClick={() => setInspectCertReg(reg)}
className="px-4 py-2.5 bg-brand-glow text-brand-indigo rounded-xl font-black text-[10px] uppercase tracking-widest flex items-center gap-1.5 shadow-sm border border-brand-indigo/5"
>
<Eye className="w-4 h-4" /> Inspect Proof
</button>
</div>
))
)}
</div>
</div>
)}
</div>
</div>
) : (
<div className="bg-white rounded-[2.5rem] p-20 text-center border border-slate-100 shadow-sm">
<Users className="w-16 h-16 text-slate-300 mx-auto mb-6 opacity-20" />
<h3 className="text-xl font-black text-slate-800 mb-2">No Live Events Available</h3>
<p className="text-slate-400 font-medium text-sm">Create/approve events in your main dashboard first to access student hub audits.</p>
</div>
)}
{/* INSPECT SCREENSHOT MODAL */}
{inspectCertReg && (
<div className="fixed inset-0 z-[1000] flex items-center justify-center p-6 bg-slate-900/60 backdrop-blur-sm">
<div className="bg-white rounded-[3rem] w-full max-w-md overflow-hidden shadow-2xl relative border border-slate-100 flex flex-col justify-between max-h-[85vh]">
<div className="p-6 bg-slate-900 text-white flex justify-between items-center">
<div>
<h3 className="text-lg font-black uppercase leading-none">Inspect Screenshot</h3>
<p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mt-1">{inspectCertReg.userName} | {inspectCertReg.regNo}</p>
</div>
<button
onClick={() => setInspectCertReg(null)}
className="p-1.5 hover:bg-white/10 rounded-lg text-white"
>
<XCircle className="w-5 h-5" />
</button>
</div>
{/* Proof Body */}
<div className="p-8 overflow-y-auto max-h-[50vh] flex flex-col items-center">
{inspectCertReg.certificationUrl ? (
<img
src={inspectCertReg.certificationUrl}
alt="Completion proof"
className="w-full object-contain rounded-2xl border border-slate-200 shadow-sm"
/>
) : (
<p className="text-xs text-slate-400 font-bold uppercase tracking-widest">No proof uploaded</p>
)}
</div>
{/* Actions Bar */}
<div className="p-6 bg-slate-50 border-t border-slate-100 flex gap-4">
<button
onClick={() => handleAuditCertificate(inspectCertReg.id, 'NOT_SUBMITTED')}
disabled={isProcessing}
className="flex-1 py-3.5 bg-red-50 text-red-600 rounded-xl font-black text-[10px] uppercase tracking-widest border border-red-100 hover:bg-red-100"
>
Reject Proof
</button>
<button
onClick={() => handleAuditCertificate(inspectCertReg.id, 'APPROVED')}
disabled={isProcessing}
className="flex-1 py-3.5 bg-emerald-500 text-white rounded-xl font-black text-[10px] uppercase tracking-widest shadow-md shadow-emerald-100 hover:bg-emerald-600"
>
Approve & Unlock Cert
</button>
</div>
</div>
</div>
)}
</div>
);
};