Subh775 commited on
Commit
0e736c9
Β·
verified Β·
1 Parent(s): adbf28d

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +636 -0
templates/index.html ADDED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Smart Attendance System</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1200px;
23
+ margin: 0 auto;
24
+ background: rgba(255, 255, 255, 0.95);
25
+ border-radius: 20px;
26
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
27
+ overflow: hidden;
28
+ backdrop-filter: blur(10px);
29
+ }
30
+
31
+ .header {
32
+ background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
33
+ color: white;
34
+ padding: 30px;
35
+ text-align: center;
36
+ }
37
+
38
+ .header h1 {
39
+ font-size: 2.5rem;
40
+ margin-bottom: 10px;
41
+ font-weight: 600;
42
+ }
43
+
44
+ .header p {
45
+ font-size: 1.1rem;
46
+ opacity: 0.9;
47
+ }
48
+
49
+ .stats-grid {
50
+ display: grid;
51
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
52
+ gap: 20px;
53
+ padding: 30px;
54
+ background: #f8fafc;
55
+ }
56
+
57
+ .stat-card {
58
+ background: white;
59
+ padding: 25px;
60
+ border-radius: 15px;
61
+ text-align: center;
62
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
63
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
64
+ }
65
+
66
+ .stat-card:hover {
67
+ transform: translateY(-5px);
68
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
69
+ }
70
+
71
+ .stat-number {
72
+ font-size: 2.5rem;
73
+ font-weight: bold;
74
+ margin-bottom: 10px;
75
+ }
76
+
77
+ .stat-label {
78
+ color: #64748b;
79
+ font-size: 1rem;
80
+ text-transform: uppercase;
81
+ letter-spacing: 0.5px;
82
+ }
83
+
84
+ .total { color: #3b82f6; }
85
+ .present { color: #10b981; }
86
+ .absent { color: #ef4444; }
87
+
88
+ .main-content {
89
+ display: grid;
90
+ grid-template-columns: 1fr 1fr;
91
+ gap: 30px;
92
+ padding: 30px;
93
+ }
94
+
95
+ .section {
96
+ background: white;
97
+ border-radius: 15px;
98
+ padding: 30px;
99
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
100
+ }
101
+
102
+ .section h2 {
103
+ color: #1e293b;
104
+ margin-bottom: 20px;
105
+ font-size: 1.5rem;
106
+ font-weight: 600;
107
+ }
108
+
109
+ .camera-container {
110
+ position: relative;
111
+ margin-bottom: 20px;
112
+ }
113
+
114
+ #video {
115
+ width: 100%;
116
+ max-width: 400px;
117
+ border-radius: 10px;
118
+ border: 3px solid #e2e8f0;
119
+ }
120
+
121
+ .camera-overlay {
122
+ position: absolute;
123
+ top: 50%;
124
+ left: 50%;
125
+ transform: translate(-50%, -50%);
126
+ width: 200px;
127
+ height: 200px;
128
+ border: 2px dashed #4f46e5;
129
+ border-radius: 50%;
130
+ pointer-events: none;
131
+ }
132
+
133
+ .controls {
134
+ display: flex;
135
+ gap: 10px;
136
+ margin-bottom: 20px;
137
+ flex-wrap: wrap;
138
+ }
139
+
140
+ .btn {
141
+ padding: 12px 24px;
142
+ border: none;
143
+ border-radius: 8px;
144
+ font-size: 1rem;
145
+ font-weight: 600;
146
+ cursor: pointer;
147
+ transition: all 0.3s ease;
148
+ text-transform: uppercase;
149
+ letter-spacing: 0.5px;
150
+ }
151
+
152
+ .btn-primary {
153
+ background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
154
+ color: white;
155
+ }
156
+
157
+ .btn-success {
158
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
159
+ color: white;
160
+ }
161
+
162
+ .btn-danger {
163
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
164
+ color: white;
165
+ }
166
+
167
+ .btn:hover {
168
+ transform: translateY(-2px);
169
+ box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
170
+ }
171
+
172
+ .btn:disabled {
173
+ opacity: 0.6;
174
+ cursor: not-allowed;
175
+ transform: none;
176
+ }
177
+
178
+ .form-group {
179
+ margin-bottom: 20px;
180
+ }
181
+
182
+ .form-group label {
183
+ display: block;
184
+ margin-bottom: 8px;
185
+ color: #374151;
186
+ font-weight: 600;
187
+ }
188
+
189
+ .form-group input {
190
+ width: 100%;
191
+ padding: 12px;
192
+ border: 2px solid #e2e8f0;
193
+ border-radius: 8px;
194
+ font-size: 1rem;
195
+ transition: border-color 0.3s ease;
196
+ }
197
+
198
+ .form-group input:focus {
199
+ outline: none;
200
+ border-color: #4f46e5;
201
+ }
202
+
203
+ .status-message {
204
+ padding: 15px;
205
+ border-radius: 8px;
206
+ margin-bottom: 20px;
207
+ font-weight: 600;
208
+ text-align: center;
209
+ }
210
+
211
+ .status-success {
212
+ background: #dcfce7;
213
+ color: #166534;
214
+ border: 1px solid #bbf7d0;
215
+ }
216
+
217
+ .status-error {
218
+ background: #fef2f2;
219
+ color: #991b1b;
220
+ border: 1px solid #fecaca;
221
+ }
222
+
223
+ .attendance-list {
224
+ max-height: 400px;
225
+ overflow-y: auto;
226
+ }
227
+
228
+ .attendance-item {
229
+ display: flex;
230
+ justify-content: space-between;
231
+ align-items: center;
232
+ padding: 15px;
233
+ border-bottom: 1px solid #e2e8f0;
234
+ transition: background-color 0.3s ease;
235
+ }
236
+
237
+ .attendance-item:hover {
238
+ background: #f8fafc;
239
+ }
240
+
241
+ .attendance-item:last-child {
242
+ border-bottom: none;
243
+ }
244
+
245
+ .attendance-name {
246
+ font-weight: 600;
247
+ color: #1e293b;
248
+ }
249
+
250
+ .attendance-time {
251
+ color: #64748b;
252
+ font-size: 0.9rem;
253
+ }
254
+
255
+ .time-display {
256
+ background: #f1f5f9;
257
+ padding: 10px;
258
+ border-radius: 8px;
259
+ text-align: center;
260
+ margin-bottom: 20px;
261
+ font-family: 'Courier New', monospace;
262
+ font-size: 1.1rem;
263
+ font-weight: bold;
264
+ color: #1e293b;
265
+ }
266
+
267
+ .loading {
268
+ text-align: center;
269
+ padding: 20px;
270
+ color: #64748b;
271
+ }
272
+
273
+ .spinner {
274
+ display: inline-block;
275
+ width: 20px;
276
+ height: 20px;
277
+ border: 3px solid #f3f3f3;
278
+ border-top: 3px solid #4f46e5;
279
+ border-radius: 50%;
280
+ animation: spin 1s linear infinite;
281
+ margin-right: 10px;
282
+ }
283
+
284
+ @keyframes spin {
285
+ 0% { transform: rotate(0deg); }
286
+ 100% { transform: rotate(360deg); }
287
+ }
288
+
289
+ @media (max-width: 768px) {
290
+ .main-content {
291
+ grid-template-columns: 1fr;
292
+ gap: 20px;
293
+ padding: 20px;
294
+ }
295
+
296
+ .stats-grid {
297
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
298
+ gap: 15px;
299
+ padding: 20px;
300
+ }
301
+
302
+ .header h1 {
303
+ font-size: 2rem;
304
+ }
305
+
306
+ .controls {
307
+ flex-direction: column;
308
+ }
309
+
310
+ .btn {
311
+ width: 100%;
312
+ }
313
+ }
314
+ </style>
315
+ </head>
316
+ <body>
317
+ <div class="container">
318
+ <div class="header">
319
+ <h1>🎯 Smart Attendance System</h1>
320
+ <p>Advanced Face Recognition Attendance Management</p>
321
+ </div>
322
+
323
+ <div class="stats-grid">
324
+ <div class="stat-card">
325
+ <div class="stat-number total" id="totalUsers">{{ stats.total_users }}</div>
326
+ <div class="stat-label">Total Users</div>
327
+ </div>
328
+ <div class="stat-card">
329
+ <div class="stat-number present" id="presentToday">{{ stats.present_today }}</div>
330
+ <div class="stat-label">Present Today</div>
331
+ </div>
332
+ <div class="stat-card">
333
+ <div class="stat-number absent" id="absentToday">{{ stats.absent_today }}</div>
334
+ <div class="stat-label">Absent Today</div>
335
+ </div>
336
+ </div>
337
+
338
+ <div class="main-content">
339
+ <div class="section">
340
+ <h2>πŸ“· Face Recognition</h2>
341
+ <div class="time-display" id="currentTime"></div>
342
+
343
+ <div class="camera-container">
344
+ <video id="video" autoplay muted></video>
345
+ <div class="camera-overlay"></div>
346
+ </div>
347
+
348
+ <div class="controls">
349
+ <button class="btn btn-primary" id="startCamera">Start Camera</button>
350
+ <button class="btn btn-success" id="checkIn">Check In</button>
351
+ <button class="btn btn-danger" id="checkOut">Check Out</button>
352
+ </div>
353
+
354
+ <div id="recognitionStatus"></div>
355
+ </div>
356
+
357
+ <div class="section">
358
+ <h2>πŸ‘€ Add New User</h2>
359
+ <form id="addUserForm" enctype="multipart/form-data">
360
+ <div class="form-group">
361
+ <label for="userName">Full Name</label>
362
+ <input type="text" id="userName" name="name" required placeholder="Enter full name">
363
+ </div>
364
+ <div class="form-group">
365
+ <label for="faceImage">Face Photo</label>
366
+ <input type="file" id="faceImage" name="face_image" accept="image/*" required>
367
+ </div>
368
+ <button type="submit" class="btn btn-primary" style="width: 100%;">Add User</button>
369
+ </form>
370
+
371
+ <div id="addUserStatus"></div>
372
+
373
+ <h3 style="margin-top: 30px; margin-bottom: 15px; color: #1e293b;">πŸ“‹ Today's Attendance</h3>
374
+ <div class="attendance-list" id="attendanceList">
375
+ <div class="loading">
376
+ <div class="spinner"></div>
377
+ Loading attendance...
378
+ </div>
379
+ </div>
380
+ </div>
381
+ </div>
382
+ </div>
383
+
384
+ <script>
385
+ let video;
386
+ let isRecognizing = false;
387
+
388
+ // Initialize the application
389
+ document.addEventListener('DOMContentLoaded', function() {
390
+ updateCurrentTime();
391
+ setInterval(updateCurrentTime, 1000);
392
+ loadAttendanceData();
393
+ setInterval(loadAttendanceData, 30000); // Refresh every 30 seconds
394
+
395
+ setupEventListeners();
396
+ });
397
+
398
+ function updateCurrentTime() {
399
+ const now = new Date();
400
+ const options = {
401
+ timeZone: 'Asia/Kolkata',
402
+ weekday: 'long',
403
+ year: 'numeric',
404
+ month: 'long',
405
+ day: 'numeric',
406
+ hour: '2-digit',
407
+ minute: '2-digit',
408
+ second: '2-digit',
409
+ hour12: true
410
+ };
411
+
412
+ document.getElementById('currentTime').textContent = now.toLocaleString('en-IN', options);
413
+ }
414
+
415
+ function setupEventListeners() {
416
+ document.getElementById('startCamera').addEventListener('click', startCamera);
417
+ document.getElementById('checkIn').addEventListener('click', () => recognizeAndMark('check_in'));
418
+ document.getElementById('checkOut').addEventListener('click', () => recognizeAndMark('check_out'));
419
+ document.getElementById('addUserForm').addEventListener('submit', addUser);
420
+ }
421
+
422
+ async function startCamera() {
423
+ try {
424
+ const stream = await navigator.mediaDevices.getUserMedia({
425
+ video: {
426
+ width: { ideal: 640 },
427
+ height: { ideal: 480 },
428
+ facingMode: 'user'
429
+ }
430
+ });
431
+
432
+ video = document.getElementById('video');
433
+ video.srcObject = stream;
434
+
435
+ document.getElementById('startCamera').textContent = 'Camera Active';
436
+ document.getElementById('startCamera').disabled = true;
437
+
438
+ showStatus('Camera started successfully!', 'success');
439
+ } catch (error) {
440
+ console.error('Error accessing camera:', error);
441
+ showStatus('Error accessing camera. Please check permissions.', 'error');
442
+ }
443
+ }
444
+
445
+ async function recognizeAndMark(type) {
446
+ if (!video || !video.srcObject || isRecognizing) {
447
+ showStatus('Please start the camera first or wait for current recognition to complete.', 'error');
448
+ return;
449
+ }
450
+
451
+ isRecognizing = true;
452
+ const actionText = type === 'check_in' ? 'Checking In' : 'Checking Out';
453
+ showStatus(`${actionText}... Please look at the camera.`, 'info');
454
+
455
+ try {
456
+ // Capture frame from video
457
+ const canvas = document.createElement('canvas');
458
+ canvas.width = video.videoWidth;
459
+ canvas.height = video.videoHeight;
460
+ const ctx = canvas.getContext('2d');
461
+ ctx.drawImage(video, 0, 0);
462
+
463
+ const imageData = canvas.toDataURL('image/jpeg', 0.8);
464
+
465
+ const response = await fetch('/api/recognize', {
466
+ method: 'POST',
467
+ headers: {
468
+ 'Content-Type': 'application/json',
469
+ },
470
+ body: JSON.stringify({
471
+ image: imageData,
472
+ type: type
473
+ })
474
+ });
475
+
476
+ const result = await response.json();
477
+
478
+ if (result.success) {
479
+ const actionPast = type === 'check_in' ? 'checked in' : 'checked out';
480
+ showStatus(`βœ… ${result.name} successfully ${actionPast} at ${result.time}! (Confidence: ${result.confidence}%)`, 'success');
481
+ loadAttendanceData(); // Refresh attendance list
482
+ loadStats(); // Refresh statistics
483
+ } else {
484
+ showStatus(`❌ ${result.message}`, 'error');
485
+ }
486
+
487
+ } catch (error) {
488
+ console.error('Recognition error:', error);
489
+ showStatus('Recognition failed. Please try again.', 'error');
490
+ } finally {
491
+ isRecognizing = false;
492
+ }
493
+ }
494
+
495
+ async function addUser(event) {
496
+ event.preventDefault();
497
+
498
+ const formData = new FormData();
499
+ const nameInput = document.getElementById('userName');
500
+ const imageInput = document.getElementById('faceImage');
501
+
502
+ if (!nameInput.value.trim()) {
503
+ showUserStatus('Please enter a name.', 'error');
504
+ return;
505
+ }
506
+
507
+ if (!imageInput.files[0]) {
508
+ showUserStatus('Please select a face image.', 'error');
509
+ return;
510
+ }
511
+
512
+ formData.append('name', nameInput.value.trim());
513
+ formData.append('face_image', imageInput.files[0]);
514
+
515
+ showUserStatus('Adding user... Please wait.', 'info');
516
+
517
+ try {
518
+ const response = await fetch('/api/add_user', {
519
+ method: 'POST',
520
+ body: formData
521
+ });
522
+
523
+ const result = await response.json();
524
+
525
+ if (result.success) {
526
+ showUserStatus(`βœ… ${result.message}`, 'success');
527
+ document.getElementById('addUserForm').reset();
528
+ loadStats(); // Refresh statistics
529
+ } else {
530
+ showUserStatus(`❌ ${result.message}`, 'error');
531
+ }
532
+
533
+ } catch (error) {
534
+ console.error('Add user error:', error);
535
+ showUserStatus('Failed to add user. Please try again.', 'error');
536
+ }
537
+ }
538
+
539
+ async function loadStats() {
540
+ try {
541
+ const response = await fetch('/api/stats');
542
+ const stats = await response.json();
543
+
544
+ document.getElementById('totalUsers').textContent = stats.total_users;
545
+ document.getElementById('presentToday').textContent = stats.present_today;
546
+ document.getElementById('absentToday').textContent = stats.absent_today;
547
+
548
+ } catch (error) {
549
+ console.error('Error loading stats:', error);
550
+ }
551
+ }
552
+
553
+ async function loadAttendanceData() {
554
+ try {
555
+ const response = await fetch('/api/stats');
556
+ const data = await response.json();
557
+
558
+ const attendanceList = document.getElementById('attendanceList');
559
+
560
+ if (data.today_attendance && data.today_attendance.length > 0) {
561
+ attendanceList.innerHTML = data.today_attendance.map(record => `
562
+ <div class="attendance-item">
563
+ <div>
564
+ <div class="attendance-name">${record.name}</div>
565
+ <div class="attendance-time">
566
+ ${record.check_in ? `In: ${record.check_in}` : 'Not checked in'}
567
+ ${record.check_out ? ` | Out: ${record.check_out}` : ''}
568
+ </div>
569
+ </div>
570
+ <div style="color: ${record.check_out ? '#10b981' : '#f59e0b'};">
571
+ ${record.check_out ? 'βœ… Complete' : 'πŸ• Active'}
572
+ </div>
573
+ </div>
574
+ `).join('');
575
+ } else {
576
+ attendanceList.innerHTML = `
577
+ <div class="attendance-item" style="justify-content: center; color: #64748b;">
578
+ πŸ“ No attendance records for today
579
+ </div>
580
+ `;
581
+ }
582
+
583
+ // Update stats
584
+ document.getElementById('totalUsers').textContent = data.total_users;
585
+ document.getElementById('presentToday').textContent = data.present_today;
586
+ document.getElementById('absentToday').textContent = data.absent_today;
587
+
588
+ } catch (error) {
589
+ console.error('Error loading attendance:', error);
590
+ document.getElementById('attendanceList').innerHTML = `
591
+ <div class="attendance-item" style="justify-content: center; color: #ef4444;">
592
+ ❌ Error loading attendance data
593
+ </div>
594
+ `;
595
+ }
596
+ }
597
+
598
+ function showStatus(message, type) {
599
+ const statusDiv = document.getElementById('recognitionStatus');
600
+ statusDiv.innerHTML = `<div class="status-message status-${type === 'success' ? 'success' : 'error'}">${message}</div>`;
601
+
602
+ if (type === 'success') {
603
+ setTimeout(() => {
604
+ statusDiv.innerHTML = '';
605
+ }, 5000);
606
+ }
607
+ }
608
+
609
+ function showUserStatus(message, type) {
610
+ const statusDiv = document.getElementById('addUserStatus');
611
+ statusDiv.innerHTML = `<div class="status-message status-${type === 'success' ? 'success' : 'error'}">${message}</div>`;
612
+
613
+ if (type === 'success') {
614
+ setTimeout(() => {
615
+ statusDiv.innerHTML = '';
616
+ }, 5000);
617
+ }
618
+ }
619
+
620
+ // Add visual feedback for camera status
621
+ document.getElementById('video').addEventListener('loadedmetadata', function() {
622
+ this.style.border = '3px solid #10b981';
623
+ });
624
+
625
+ // Handle page visibility changes to manage camera
626
+ document.addEventListener('visibilitychange', function() {
627
+ if (document.hidden && video && video.srcObject) {
628
+ // Optionally pause video when page is hidden
629
+ video.pause();
630
+ } else if (!document.hidden && video && video.srcObject) {
631
+ video.play();
632
+ }
633
+ });
634
+ </script>
635
+ </body>
636
+ </html>