// Admin Panel JavaScript
// Handles admin functionalities like creating events and managing admins
// DOM Elements
const sidebarMenuItems = document.querySelectorAll(".sidebar-menu li");
const dashboardSections = document.querySelectorAll(".dashboard-section");
const createEventForm = document.getElementById("create-event-form");
const createEventStatus = document.getElementById("create-event-status");
const eventsTableBody = document.getElementById("events-table-body");
const addAdminForm = document.getElementById("add-admin-form");
const adminStatus = document.getElementById("admin-status");
const adminsTableBody = document.getElementById("admins-table-body");
const confirmModal = document.getElementById("confirm-modal");
const confirmMessage = document.getElementById("confirm-message");
const confirmYesBtn = document.getElementById("confirm-yes");
const confirmNoBtn = document.getElementById("confirm-no");
// Stats elements
const totalEventsEl = document.getElementById("total-events");
const registeredUsersEl = document.getElementById("registered-users");
const totalCandidatesEl = document.getElementById("total-candidates");
const totalVotesEl = document.getElementById("total-votes");
const eventsChartCanvas = document.getElementById("events-chart");
const participationChartCanvas = document.getElementById("participation-chart");
// Variables
let currentAction = null;
let currentActionData = null;
let eventsChart = null;
let participationChart = null;
let knownAdmins = [];
// Event Listeners
document.addEventListener("DOMContentLoaded", init);
// Initialize sidebar menu
sidebarMenuItems.forEach(item => {
item.addEventListener("click", () => {
// Update active menu item
sidebarMenuItems.forEach(menuItem => {
menuItem.classList.remove("active");
});
item.classList.add("active");
// Show corresponding section
const targetSection = item.getAttribute("data-target");
dashboardSections.forEach(section => {
section.style.display = "none";
});
document.getElementById(targetSection).style.display = "block";
// Load data for the selected section
if (targetSection === "manage-events") {
loadEvents();
} else if (targetSection === "manage-admins") {
loadAdmins();
} else if (targetSection === "system-stats") {
loadStats();
}
});
});
// Initialize create event form
if (createEventForm) {
createEventForm.addEventListener("submit", createEvent);
}
// Initialize add admin form
if (addAdminForm) {
addAdminForm.addEventListener("submit", addAdmin);
}
// Initialize confirm modal buttons
if (confirmYesBtn) {
confirmYesBtn.addEventListener("click", confirmAction);
}
if (confirmNoBtn) {
confirmNoBtn.addEventListener("click", cancelAction);
}
async function init() {
console.log("Initializing admin panel...");
// Check if contract is available
if (typeof window.contract === "undefined" || !window.contract) {
console.log("Contract not initialized yet, waiting...");
setTimeout(init, 500);
return;
}
// Check if user is admin
if (!window.isAdmin) {
showAdminAccessDenied();
return;
}
// Set minimum date for event creation
const now = new Date();
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
const eventStartInput = document.getElementById("event-start");
const eventEndInput = document.getElementById("event-end");
if (eventStartInput) {
const formattedDate = formatDateForInput(tomorrow);
eventStartInput.min = formattedDate;
// Set default value to tomorrow
eventStartInput.value = formattedDate;
}
if (eventEndInput) {
const nextWeek = new Date(now);
nextWeek.setDate(nextWeek.getDate() + 7);
// Set default value to next week
eventEndInput.value = formatDateForInput(nextWeek);
}
}
// CREATE EVENT
async function createEvent(event) {
event.preventDefault();
// Get form values
const name = document.getElementById("event-name").value;
const description = document.getElementById("event-description").value;
const imageInput = document.getElementById("event-image");
const startTime = new Date(document.getElementById("event-
start").value).getTime() / 1000;
const endTime = new Date(document.getElementById("event-
end").value).getTime() / 1000;
// Validate
if (!name || !description || !startTime || !endTime) {
showStatusMessage(createEventStatus, "Please fill all required fields",
"error");
return;
}
if (endTime <= startTime) {
showStatusMessage(createEventStatus, "End time must be after start time",
"error");
return;
}
// Show loading status
showStatusMessage(createEventStatus, "Creating event... Please wait", "info");
try {
// Upload image to IPFS if provided
let imageHash = "";
if (imageInput.files && imageInput.files[0]) {
showStatusMessage(createEventStatus, "Uploading image to IPFS...",
"info");
try {
imageHash = await uploadToIPFS(imageInput.files[0]);
} catch (error) {
console.error("Error uploading to IPFS:", error);
showStatusMessage(createEventStatus, "Error uploading image: " +
error.message, "error");
return;
}
}
// Create event on blockchain
showStatusMessage(createEventStatus, "Submitting transaction to
blockchain...", "info");
const tx = await contract.createEvent(name, description, imageHash,
startTime, endTime);
showStatusMessage(createEventStatus, "Transaction submitted! Waiting for
confirmation...", "info");
// Wait for confirmation
await tx.wait();
// Show success message
showStatusMessage(createEventStatus, "Event created successfully!",
"success");
// Reset form
createEventForm.reset();
} catch (error) {
console.error("Error creating event:", error);
showStatusMessage(createEventStatus, "Error creating event: " +
error.message, "error");
}
}
// MANAGE EVENTS
async function loadEvents() {
if (!eventsTableBody) return;
// Show loading indicator
eventsTableBody.innerHTML = '<tr><td colspan="7" class="loading-
indicator">Loading events...</td></tr>';
try {
// Get event count
const eventCount = await contract.eventCount();
if (eventCount.toNumber() === 0) {
eventsTableBody.innerHTML = '<tr><td colspan="7" class="text-center">No
events created yet</td></tr>';
return;
}
// Clear table
eventsTableBody.innerHTML = '';
// Load all events
for (let i = eventCount.toNumber() - 1; i >= 0; i--) {
await loadEventRow(i);
}
} catch (error) {
console.error("Error loading events:", error);
eventsTableBody.innerHTML = '<tr><td colspan="7" class="text-center">Error
loading events</td></tr>';
}
}
async function loadEventRow(eventId) {
try {
// Get event details
const eventDetails = await contract.getEventDetails(eventId);
// Create table row
const row = document.createElement("tr");
// Current time for status calculation
const now = new Date();
const startTime = new Date(eventDetails[3].toNumber() * 1000);
const endTime = new Date(eventDetails[4].toNumber() * 1000);
// Determine event status
let statusClass = "";
let statusText = "";
if (!eventDetails[5]) { // Not active
statusClass = "ended-status";
statusText = "Ended";
} else if (now < startTime) {
statusClass = "upcoming-status";
statusText = "Upcoming";
} else if (now > endTime) {
statusClass = "ended-status";
statusText = "Expired";
} else {
statusClass = "active-status";
statusText = "Active";
}
// Format row content
row.innerHTML = `
<td>${eventId}</td>
<td>${eventDetails[0]}</td>
<td><span class="event-status ${statusClass}">${statusText}</span></td>
<td>${formatDate(startTime)}</td>
<td>${formatDate(endTime)}</td>
<td>${eventDetails[6].toNumber()}</td>
<td>
<div class="table-actions">
<a href="./event-details.html?id=${eventId}" class="btn action-
btn outline-btn">View</a>
${eventDetails[5] && now < endTime ? `<button class="btn
action-btn danger-btn" onclick="showEndEventConfirmation(${eventId})">End</button>`
: ''}
</div>
</td>
`;
// Add to table
eventsTableBody.appendChild(row);
} catch (error) {
console.error(`Error loading event ${eventId}:`, error);
}
}
function showEndEventConfirmation(eventId) {
currentAction = "endEvent";
currentActionData = eventId;
confirmMessage.textContent = `Are you sure you want to end Event #${eventId}?
This action cannot be undone and will prevent any further voting.`;
// Show modal
confirmModal.classList.add("active");
}
async function endEvent(eventId) {
try {
// Call contract to end event
const tx = await contract.endEvent(eventId);
// Wait for confirmation
await tx.wait();
// Reload events
await loadEvents();
// Show success message
alert("Event ended successfully");
} catch (error) {
console.error("Error ending event:", error);
alert("Error ending event: " + error.message);
}
}
// MANAGE ADMINS
async function loadAdmins() {
if (!adminsTableBody) return;
// Show loading indicator
adminsTableBody.innerHTML = '<tr><td colspan="3" class="loading-
indicator">Loading admins...</td></tr>';
try {
// Get contract owner
const owner = await contract.owner();
// Initialize admins array with owner
knownAdmins = [owner];
// We don't have a way to get all admins from the contract
// So we'll just display the known ones (owner + any added in this session)
// Clear table
adminsTableBody.innerHTML = '';
// Add owner first
addAdminRow(owner, true);
// Add other known admins
for (let i = 0; i < knownAdmins.length; i++) {
if (knownAdmins[i] !== owner) {
const isAdmin = await contract.isAdmin(knownAdmins[i]);
if (isAdmin) {
addAdminRow(knownAdmins[i], false);
}
}
}
} catch (error) {
console.error("Error loading admins:", error);
adminsTableBody.innerHTML = '<tr><td colspan="3" class="text-center">Error
loading admins</td></tr>';
}
}
function addAdminRow(address, isOwner) {
const row = document.createElement("tr");
// Generate a fake date for demo purposes
const addedDate = new Date();
addedDate.setDate(addedDate.getDate() - Math.floor(Math.random() * 30));
row.innerHTML = `
<td>${address}</td>
<td>${formatDate(addedDate)}</td>
<td>
<div class="table-actions">
${isOwner ? '<span>Owner</span>' : `<button class="btn action-btn
danger-btn" onclick="showRemoveAdminConfirmation('${address}')">Remove</button>`}
</div>
</td>
`;
adminsTableBody.appendChild(row);
}
async function addAdmin(event) {
event.preventDefault();
// Get admin address
const adminAddress = document.getElementById("admin-address").value.trim();
// Validate
if (!adminAddress) {
showStatusMessage(adminStatus, "Please enter an admin address", "error");
return;
}
// Validate address format
if (!ethers.utils.isAddress(adminAddress)) {
showStatusMessage(adminStatus, "Invalid Ethereum address format", "error");
return;
}
// Show loading status
showStatusMessage(adminStatus, "Adding admin... Please wait", "info");
try {
// Check if already admin
const isAdmin = await contract.isAdmin(adminAddress);
if (isAdmin) {
showStatusMessage(adminStatus, "Address is already an admin", "error");
return;
}
// Call contract to add admin
const tx = await contract.addAdmin(adminAddress);
// Wait for confirmation
await tx.wait();
// Add to known admins
if (!knownAdmins.includes(adminAddress)) {
knownAdmins.push(adminAddress);
}
// Reload admins
await loadAdmins();
// Show success message
showStatusMessage(adminStatus, "Admin added successfully!", "success");
// Reset form
document.getElementById("admin-address").value = "";
} catch (error) {
console.error("Error adding admin:", error);
showStatusMessage(adminStatus, "Error adding admin: " + error.message,
"error");
}
}
function showRemoveAdminConfirmation(address) {
currentAction = "removeAdmin";
currentActionData = address;
confirmMessage.textContent = `Are you sure you want to remove ${address} as
admin?`;
// Show modal
confirmModal.classList.add("active");
}
async function removeAdmin(address) {
try {
// Call contract to remove admin
const tx = await contract.removeAdmin(address);
// Wait for confirmation
await tx.wait();
// Remove from known admins
knownAdmins = knownAdmins.filter(admin => admin !== address);
// Reload admins
await loadAdmins();
// Show success message
alert("Admin removed successfully");
} catch (error) {
console.error("Error removing admin:", error);
alert("Error removing admin: " + error.message);
}
}
// SYSTEM STATS
async function loadStats() {
if (!totalEventsEl) return;
try {
// Get event count
const eventCount = await contract.eventCount();
totalEventsEl.textContent = eventCount.toString();
// For demo purposes, we'll use some placeholder data
// In a real application, you would calculate these from contract events
registeredUsersEl.textContent = "52"; // Placeholder
totalCandidatesEl.textContent = "18"; // Placeholder
totalVotesEl.textContent = "127"; // Placeholder
// Create charts
createEventsChart();
createParticipationChart();
} catch (error) {
console.error("Error loading stats:", error);
}
}
function createEventsChart() {
if (!eventsChartCanvas) return;
// Destroy existing chart if it exists
if (eventsChart) {
eventsChart.destroy();
}
// Sample data for demo purposes
const ctx = eventsChartCanvas.getContext('2d');
eventsChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Events Created',
data: [2, 3, 1, 4, 2, 3],
backgroundColor: '#3b82f6'
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
function createParticipationChart() {
if (!participationChartCanvas) return;
// Destroy existing chart if it exists
if (participationChart) {
participationChart.destroy();
}
// Sample data for demo purposes
const ctx = participationChartCanvas.getContext('2d');
participationChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Votes Cast',
data: [15, 28, 12, 32, 18, 22],
borderColor: '#10b981',
tension: 0.1,
fill: false
}, {
label: 'Candidates',
data: [2, 4, 3, 5, 2, 2],
borderColor: '#8b5cf6',
tension: 0.1,
fill: false
}]
},
options: {
responsive: true
}
});
}
// HELPER FUNCTIONS
// Show confirmation modal action
async function confirmAction() {
// Hide modal
confirmModal.classList.remove("active");
// Execute action
if (currentAction === "endEvent") {
await endEvent(currentActionData);
} else if (currentAction === "removeAdmin") {
await removeAdmin(currentActionData);
}
// Reset current action
currentAction = null;
currentActionData = null;
}
// Cancel confirmation modal action
function cancelAction() {
// Hide modal
confirmModal.classList.remove("active");
// Reset current action
currentAction = null;
currentActionData = null;
}
// Show admin access denied page
function showAdminAccessDenied() {
// Replace the entire dashboard content
const mainContent = document.querySelector("main");
if (mainContent) {
mainContent.innerHTML = `
<div class="empty-state">
<i class="fas fa-lock"></i>
<h2>Access Denied</h2>
<p>You do not have admin privileges to access this page.</p>
<a href="./events.html" class="btn primary-btn">Go Back to
Events</a>
</div>
`;
}
}
// Show status message
function showStatusMessage(element, message, type) {
if (!element) return;
element.textContent = message;
element.className = "status-message";
if (type === "success") {
element.classList.add("success-message");
} else if (type === "error") {
element.classList.add("error-message");
} else {
element.classList.add("info-message");
}
// Auto-hide success messages after 5 seconds
if (type === "success") {
setTimeout(() => {
element.textContent = "";
element.className = "status-message";
}, 5000);
}
}
// Format date for display
function formatDate(date) {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(date);
}
// Format date for datetime-local input
function formatDateForInput(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}`;
}