-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadmin.html
More file actions
323 lines (301 loc) · 11.6 KB
/
admin.html
File metadata and controls
323 lines (301 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard | Voting Portal</title>
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
:root {
--primary-color: #7c4dff;
--secondary-color: #536dfe;
--background-color: #f4f5f7;
--card-bg-color: #ffffff;
--text-color: #333;
--shadow-color: rgba(0, 0, 0, 0.1);
}
body {
font-family: 'Roboto', sans-serif;
background-color: var(--background-color);
margin: 0;
padding: 24px;
color: var(--text-color);
}
.container {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 24px;
}
.card {
background-color: var(--card-bg-color);
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 12px var(--shadow-color);
transition: transform 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px var(--shadow-color);
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
border-bottom: 1px solid #eee;
padding-bottom: 16px;
margin-bottom: 16px;
color: var(--primary-color);
}
.card-header .material-icons {
font-size: 32px;
}
.card-title {
font-size: 20px;
font-weight: 500;
margin: 0;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
transition: border-color 0.3s, box-shadow 0.3s;
}
.form-group input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(124, 77, 255, 0.2);
}
.btn {
display: inline-block;
padding: 12px 24px;
border: none;
border-radius: 8px;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
color: white;
font-size: 16px;
font-weight: 500;
cursor: pointer;
text-align: center;
transition: transform 0.2s, box-shadow 0.3s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(124, 77, 255, 0.3);
}
.list {
list-style: none;
padding: 0;
margin: 0;
max-height: 400px;
overflow-y: auto;
}
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 8px;
border-bottom: 1px solid #f0f0f0;
}
.list-item:last-child {
border-bottom: none;
}
.list-item .name {
font-weight: 500;
}
.list-item .detail {
color: #666;
font-size: 14px;
}
.vote-count {
font-size: 18px;
font-weight: 700;
color: var(--secondary-color);
}
.status {
padding: 4px 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
.status.voted {
background-color: #e8f5e9; /* Green */
color: #388e3c;
}
.status.not-voted {
background-color: #fff3e0; /* Orange */
color: #f57c00;
}
</style>
</head>
<body>
<div class="container">
<!-- Live Vote Count Card -->
<div class="card">
<div class="card-header">
<span class="material-icons">how_to_vote</span>
<h2 class="card-title">Live Vote Count</h2>
</div>
<ul class="list" id="vote-count-list">
<!-- Candidate vote counts will be dynamically inserted here -->
<li class="list-item">
<span class="name">Loading candidates...</span>
</li>
</ul>
</div>
<!-- Add Candidate Card -->
<div class="card">
<div class="card-header">
<span class="material-icons">person_add</span>
<h2 class="card-title">Add New Candidate</h2>
</div>
<form id="add-candidate-form">
<div class="form-group">
<label for="candidate-name">Candidate Name</label>
<input type="text" id="candidate-name" placeholder="e.g., John Doe" required>
</div>
<button type="submit" class="btn">Add Candidate</button>
</form>
</div>
<!-- Registered Students Card -->
<div class="card">
<div class="card-header">
<span class="material-icons">groups</span>
<h2 class="card-title">Registered Students</h2>
</div>
<ul class="list" id="student-list">
<!-- Student list will be dynamically inserted here -->
<li class="list-item">
<span class="name">Loading students...</span>
</li>
</ul>
</div>
</div>
<!-- Socket.IO client library -->
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script>
// Establish WebSocket connection with the server
const socket = io('http://localhost:3000');
// DOM Elements
const addCandidateForm = document.getElementById('add-candidate-form');
const candidateNameInput = document.getElementById('candidate-name');
const voteCountList = document.getElementById('vote-count-list');
const studentList = document.getElementById('student-list');
// --- Event Listeners ---
// Handle the "Add Candidate" form submission
addCandidateForm.addEventListener('submit', async (event) => {
event.preventDefault();
const candidateName = candidateNameInput.value.trim();
if (!candidateName) {
alert('Please enter a candidate name.');
return;
}
try {
const response = await fetch('http://localhost:3000/api/candidates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: candidateName })
});
if (response.ok) {
alert('Candidate added successfully!');
candidateNameInput.value = ''; // Clear the input
// No need to manually refresh; the WebSocket will update the list
} else {
const result = await response.json();
alert('Error: ' + (result.message || 'Could not add candidate.'));
}
} catch (error) {
console.error('Error adding candidate:', error);
alert('A network error occurred.');
}
});
// --- Functions to Update UI ---
// Function to render the list of candidates and their votes
function updateVoteCounts(candidates) {
voteCountList.innerHTML = ''; // Clear the list
if (candidates.length === 0) {
voteCountList.innerHTML = '<li class="list-item"><span class="name">No candidates yet.</span></li>';
return;
}
candidates.forEach(candidate => {
const listItem = document.createElement('li');
listItem.className = 'list-item';
listItem.innerHTML = `
<span class="name">${candidate.name}</span>
<span class="vote-count">${candidate.votes} Votes</span>
`;
voteCountList.appendChild(listItem);
});
}
// Function to render the list of registered students
function updateStudentList(students) {
studentList.innerHTML = ''; // Clear the list
if (students.length === 0) {
studentList.innerHTML = '<li class="list-item"><span class="name">No students have logged in yet.</span></li>';
return;
}
students.forEach(student => {
const listItem = document.createElement('li');
listItem.className = 'list-item';
const hasVotedStatus = student.hasVoted
? '<span class="status voted">Voted</span>'
: '<span class="status not-voted">Not Voted</span>';
listItem.innerHTML = `
<div>
<div class="name">${student.fullName}</div>
<div class="detail">Class ${student.className} | Roll No: ${student.rollNumber}</div>
</div>
${hasVotedStatus}
`;
studentList.appendChild(listItem);
});
}
// --- Initial Data Fetch ---
// Fetch initial data when the page loads
async function fetchInitialData() {
try {
// Fetch candidates
const candidatesRes = await fetch('http://localhost:3000/api/candidates');
const candidates = await candidatesRes.json();
updateVoteCounts(candidates);
// Fetch students
const studentsRes = await fetch('http://localhost:3000/api/students');
const students = await studentsRes.json();
updateStudentList(students);
} catch (error) {
console.error('Error fetching initial data:', error);
voteCountList.innerHTML = '<li class="list-item"><span class="name">Could not load data.</span></li>';
studentList.innerHTML = '<li class="list-item"><span class="name">Could not load data.</span></li>';
}
}
// --- WebSocket Listeners ---
// Listen for 'connect' event
socket.on('connect', () => {
console.log('Connected to server via WebSocket!');
});
// Listen for 'update' event from the server
socket.on('update', (data) => {
console.log('Received update from server:', data);
// When an update is received, re-render both lists
updateVoteCounts(data.candidates);
updateStudentList(data.students);
});
// Load all data when the page is ready
document.addEventListener('DOMContentLoaded', fetchInitialData);
</script>
</body>
</html>