farwew commited on
Commit
0f7c7f3
·
verified ·
1 Parent(s): dda2e5b

Update static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +632 -590
static/index.html CHANGED
@@ -1,591 +1,633 @@
1
- <!DOCTYPE html>
2
- <html lang="id">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Student Management 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: white;
25
- border-radius: 15px;
26
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
27
- overflow: hidden;
28
- }
29
-
30
- .header {
31
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
32
- color: white;
33
- padding: 30px;
34
- text-align: center;
35
- }
36
-
37
- .header h1 {
38
- font-size: 2.5rem;
39
- margin-bottom: 10px;
40
- }
41
-
42
- .header p {
43
- font-size: 1.1rem;
44
- opacity: 0.9;
45
- }
46
-
47
- .content {
48
- padding: 30px;
49
- }
50
-
51
- .form-section {
52
- background: #f8f9fa;
53
- border-radius: 10px;
54
- padding: 25px;
55
- margin-bottom: 30px;
56
- border-left: 4px solid #4facfe;
57
- }
58
-
59
- .form-section h2 {
60
- color: #333;
61
- margin-bottom: 20px;
62
- font-size: 1.5rem;
63
- }
64
-
65
- .form-grid {
66
- display: grid;
67
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
68
- gap: 20px;
69
- margin-bottom: 20px;
70
- }
71
-
72
- .form-group {
73
- display: flex;
74
- flex-direction: column;
75
- }
76
-
77
- .form-group label {
78
- font-weight: 600;
79
- margin-bottom: 8px;
80
- color: #555;
81
- }
82
-
83
- .form-group input,
84
- .form-group select,
85
- .form-group textarea {
86
- padding: 12px;
87
- border: 2px solid #e1e5e9;
88
- border-radius: 8px;
89
- font-size: 14px;
90
- transition: all 0.3s ease;
91
- }
92
-
93
- .form-group input:focus,
94
- .form-group select:focus,
95
- .form-group textarea:focus {
96
- outline: none;
97
- border-color: #4facfe;
98
- box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
99
- }
100
-
101
- .form-group textarea {
102
- resize: vertical;
103
- min-height: 80px;
104
- }
105
-
106
- .btn {
107
- padding: 12px 25px;
108
- border: none;
109
- border-radius: 8px;
110
- font-size: 14px;
111
- font-weight: 600;
112
- cursor: pointer;
113
- transition: all 0.3s ease;
114
- text-transform: uppercase;
115
- letter-spacing: 0.5px;
116
- }
117
-
118
- .btn-primary {
119
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
120
- color: white;
121
- }
122
-
123
- .btn-primary:hover {
124
- transform: translateY(-2px);
125
- box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);
126
- }
127
-
128
- .btn-secondary {
129
- background: #6c757d;
130
- color: white;
131
- }
132
-
133
- .btn-secondary:hover {
134
- background: #5a6268;
135
- transform: translateY(-2px);
136
- }
137
-
138
- .btn-danger {
139
- background: #dc3545;
140
- color: white;
141
- }
142
-
143
- .btn-danger:hover {
144
- background: #c82333;
145
- transform: translateY(-2px);
146
- }
147
-
148
- .btn-warning {
149
- background: #ffc107;
150
- color: #212529;
151
- }
152
-
153
- .btn-warning:hover {
154
- background: #e0a800;
155
- transform: translateY(-2px);
156
- }
157
-
158
- .students-section {
159
- margin-top: 30px;
160
- }
161
-
162
- .students-header {
163
- display: flex;
164
- justify-content: space-between;
165
- align-items: center;
166
- margin-bottom: 20px;
167
- }
168
-
169
- .students-grid {
170
- display: grid;
171
- grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
172
- gap: 20px;
173
- }
174
-
175
- .student-card {
176
- background: white;
177
- border-radius: 12px;
178
- padding: 20px;
179
- box-shadow: 0 4px 15px rgba(0,0,0,0.1);
180
- border: 1px solid #e1e5e9;
181
- transition: all 0.3s ease;
182
- }
183
-
184
- .student-card:hover {
185
- transform: translateY(-5px);
186
- box-shadow: 0 8px 25px rgba(0,0,0,0.15);
187
- }
188
-
189
- .student-photo {
190
- width: 80px;
191
- height: 80px;
192
- border-radius: 50%;
193
- object-fit: cover;
194
- margin-bottom: 15px;
195
- border: 3px solid #4facfe;
196
- }
197
-
198
- .student-info h3 {
199
- color: #333;
200
- margin-bottom: 10px;
201
- font-size: 1.2rem;
202
- }
203
-
204
- .student-info p {
205
- color: #666;
206
- margin-bottom: 5px;
207
- font-size: 0.9rem;
208
- }
209
-
210
- .student-actions {
211
- margin-top: 15px;
212
- display: flex;
213
- gap: 10px;
214
- }
215
-
216
- .student-actions .btn {
217
- flex: 1;
218
- padding: 8px 12px;
219
- font-size: 12px;
220
- }
221
-
222
- .modal {
223
- display: none;
224
- position: fixed;
225
- z-index: 1000;
226
- left: 0;
227
- top: 0;
228
- width: 100%;
229
- height: 100%;
230
- background-color: rgba(0,0,0,0.5);
231
- backdrop-filter: blur(5px);
232
- }
233
-
234
- .modal-content {
235
- background-color: white;
236
- margin: 5% auto;
237
- padding: 30px;
238
- border-radius: 15px;
239
- width: 90%;
240
- max-width: 600px;
241
- max-height: 80vh;
242
- overflow-y: auto;
243
- animation: modalSlideIn 0.3s ease;
244
- }
245
-
246
- @keyframes modalSlideIn {
247
- from {
248
- opacity: 0;
249
- transform: translateY(-50px);
250
- }
251
- to {
252
- opacity: 1;
253
- transform: translateY(0);
254
- }
255
- }
256
-
257
- .close {
258
- color: #aaa;
259
- float: right;
260
- font-size: 28px;
261
- font-weight: bold;
262
- cursor: pointer;
263
- }
264
-
265
- .close:hover {
266
- color: #333;
267
- }
268
-
269
- .alert {
270
- padding: 15px;
271
- margin-bottom: 20px;
272
- border-radius: 8px;
273
- font-weight: 500;
274
- }
275
-
276
- .alert-success {
277
- background-color: #d4edda;
278
- color: #155724;
279
- border: 1px solid #c3e6cb;
280
- }
281
-
282
- .alert-error {
283
- background-color: #f8d7da;
284
- color: #721c24;
285
- border: 1px solid #f5c6cb;
286
- }
287
-
288
- .loading {
289
- display: none;
290
- text-align: center;
291
- padding: 20px;
292
- }
293
-
294
- .spinner {
295
- border: 4px solid #f3f3f3;
296
- border-top: 4px solid #4facfe;
297
- border-radius: 50%;
298
- width: 40px;
299
- height: 40px;
300
- animation: spin 1s linear infinite;
301
- margin: 0 auto 10px;
302
- }
303
-
304
- @keyframes spin {
305
- 0% { transform: rotate(0deg); }
306
- 100% { transform: rotate(360deg); }
307
- }
308
-
309
- @media (max-width: 768px) {
310
- .header h1 {
311
- font-size: 2rem;
312
- }
313
-
314
- .form-grid {
315
- grid-template-columns: 1fr;
316
- }
317
-
318
- .students-grid {
319
- grid-template-columns: 1fr;
320
- }
321
-
322
- .students-header {
323
- flex-direction: column;
324
- gap: 15px;
325
- }
326
- }
327
- </style>
328
- </head>
329
- <body>
330
- <div class="container">
331
- <div class="header">
332
- <h1>Student Management System</h1>
333
- <p>Sistem Manajemen Data Mahasiswa</p>
334
- </div>
335
-
336
- <div class="content">
337
- <!-- Form Section -->
338
- <div class="form-section">
339
- <h2 id="form-title">Tambah Data Mahasiswa</h2>
340
-
341
- <div id="alert-container"></div>
342
-
343
- <form id="student-form" enctype="multipart/form-data">
344
- <input type="hidden" id="student-id" name="student_id">
345
-
346
- <div class="form-grid">
347
- <div class="form-group">
348
- <label for="nrp">NRP *</label>
349
- <input type="text" id="nrp" name="nrp" required>
350
- </div>
351
-
352
- <div class="form-group">
353
- <label for="nama">Nama Lengkap *</label>
354
- <input type="text" id="nama" name="nama" required>
355
- </div>
356
-
357
- <div class="form-group">
358
- <label for="jenis_kelamin">Jenis Kelamin *</label>
359
- <select id="jenis_kelamin" name="jenis_kelamin" required>
360
- <option value="">Pilih Jenis Kelamin</option>
361
- <option value="Laki-laki">Laki-laki</option>
362
- <option value="Perempuan">Perempuan</option>
363
- </select>
364
- </div>
365
-
366
- <div class="form-group">
367
- <label for="telpon">Nomor Telepon *</label>
368
- <input type="tel" id="telpon" name="telpon" required>
369
- </div>
370
-
371
- <div class="form-group">
372
- <label for="photo">Foto</label>
373
- <input type="file" id="photo" name="photo" accept="image/*">
374
- </div>
375
- </div>
376
-
377
- <div class="form-group">
378
- <label for="alamat">Alamat *</label>
379
- <textarea id="alamat" name="alamat" required placeholder="Masukkan alamat lengkap..."></textarea>
380
- </div>
381
-
382
- <div style="display: flex; gap: 15px; margin-top: 25px;">
383
- <button type="submit" class="btn btn-primary" id="submit-btn">
384
- <span id="submit-text">Simpan Data</span>
385
- </button>
386
- <button type="button" class="btn btn-secondary" id="cancel-btn" onclick="resetForm()" style="display: none;">
387
- Batal
388
- </button>
389
- </div>
390
- </form>
391
- </div>
392
-
393
- <!-- Students List Section -->
394
- <div class="students-section">
395
- <div class="students-header">
396
- <h2>Daftar Mahasiswa</h2>
397
- <button class="btn btn-primary" onclick="loadStudents()">Refresh Data</button>
398
- </div>
399
-
400
- <div class="loading" id="loading">
401
- <div class="spinner"></div>
402
- <p>Memuat data...</p>
403
- </div>
404
-
405
- <div class="students-grid" id="students-container">
406
- <!-- Students will be loaded here -->
407
- </div>
408
- </div>
409
- </div>
410
- </div>
411
-
412
- <script>
413
- let isEditMode = false;
414
- let currentEditId = null;
415
-
416
- // Load students when page loads
417
- document.addEventListener('DOMContentLoaded', function() {
418
- loadStudents();
419
- });
420
-
421
- // Form submission
422
- document.getElementById('student-form').addEventListener('submit', async function(e) {
423
- e.preventDefault();
424
-
425
- const submitBtn = document.getElementById('submit-btn');
426
- const submitText = document.getElementById('submit-text');
427
- const originalText = submitText.textContent;
428
-
429
- // Show loading state
430
- submitBtn.disabled = true;
431
- submitText.textContent = 'Menyimpan...';
432
-
433
- const formData = new FormData(this);
434
-
435
- try {
436
- let response;
437
- if (isEditMode && currentEditId) {
438
- response = await fetch(`/api/students/${currentEditId}`, {
439
- method: 'PUT',
440
- body: formData
441
- });
442
- } else {
443
- response = await fetch('/api/students', {
444
- method: 'POST',
445
- body: formData
446
- });
447
- }
448
-
449
- const result = await response.json();
450
-
451
- if (response.ok) {
452
- showAlert('Data berhasil disimpan!', 'success');
453
- resetForm();
454
- loadStudents();
455
- } else {
456
- showAlert(result.detail || 'Terjadi kesalahan!', 'error');
457
- }
458
- } catch (error) {
459
- showAlert('Terjadi kesalahan koneksi!', 'error');
460
- } finally {
461
- submitBtn.disabled = false;
462
- submitText.textContent = originalText;
463
- }
464
- });
465
-
466
- // Load students
467
- async function loadStudents() {
468
- const container = document.getElementById('students-container');
469
- const loading = document.getElementById('loading');
470
-
471
- loading.style.display = 'block';
472
- container.innerHTML = '';
473
-
474
- try {
475
- const response = await fetch('/api/students');
476
- const students = await response.json();
477
-
478
- if (students.length === 0) {
479
- container.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1/-1;">Belum ada data mahasiswa.</p>';
480
- } else {
481
- container.innerHTML = students.map(student => `
482
- <div class="student-card">
483
- ${student.photo ? `<img src="/uploads/${student.photo}" alt="${student.nama}" class="student-photo">` : '<div class="student-photo" style="background: #f0f0f0; display: flex; align-items: center; justify-content: center; color: #999;">No Photo</div>'}
484
- <div class="student-info">
485
- <h3>${student.nama}</h3>
486
- <p><strong>NRP:</strong> ${student.nrp}</p>
487
- <p><strong>Jenis Kelamin:</strong> ${student.jenis_kelamin}</p>
488
- <p><strong>Telepon:</strong> ${student.telpon}</p>
489
- <p><strong>Alamat:</strong> ${student.alamat}</p>
490
- <p><strong>Tanggal:</strong> ${new Date(student.created_at).toLocaleDateString('id-ID')}</p>
491
- </div>
492
- <div class="student-actions">
493
- <button class="btn btn-warning" onclick="editStudent(${student.id})">Edit</button>
494
- <button class="btn btn-danger" onclick="deleteStudent(${student.id})">Hapus</button>
495
- </div>
496
- </div>
497
- `).join('');
498
- }
499
- } catch (error) {
500
- container.innerHTML = '<p style="text-align: center; color: #dc3545; grid-column: 1/-1;">Gagal memuat data!</p>';
501
- } finally {
502
- loading.style.display = 'none';
503
- }
504
- }
505
-
506
- // Edit student
507
- async function editStudent(id) {
508
- try {
509
- const response = await fetch(`/api/students/${id}`);
510
- const student = await response.json();
511
-
512
- if (response.ok) {
513
- // Fill form with student data
514
- document.getElementById('student-id').value = student.id;
515
- document.getElementById('nrp').value = student.nrp;
516
- document.getElementById('nama').value = student.nama;
517
- document.getElementById('jenis_kelamin').value = student.jenis_kelamin;
518
- document.getElementById('telpon').value = student.telpon;
519
- document.getElementById('alamat').value = student.alamat;
520
-
521
- // Update form state
522
- isEditMode = true;
523
- currentEditId = id;
524
- document.getElementById('form-title').textContent = 'Edit Data Mahasiswa';
525
- document.getElementById('submit-text').textContent = 'Update Data';
526
- document.getElementById('cancel-btn').style.display = 'inline-block';
527
-
528
- // Scroll to form
529
- document.querySelector('.form-section').scrollIntoView({ behavior: 'smooth' });
530
- } else {
531
- showAlert('Gagal memuat data mahasiswa!', 'error');
532
- }
533
- } catch (error) {
534
- showAlert('Terjadi kesalahan koneksi!', 'error');
535
- }
536
- }
537
-
538
- // Delete student
539
- async function deleteStudent(id) {
540
- if (confirm('Apakah Anda yakin ingin menghapus data ini?')) {
541
- try {
542
- const response = await fetch(`/api/students/${id}`, {
543
- method: 'DELETE'
544
- });
545
-
546
- const result = await response.json();
547
-
548
- if (response.ok) {
549
- showAlert('Data berhasil dihapus!', 'success');
550
- loadStudents();
551
- } else {
552
- showAlert(result.detail || 'Gagal menghapus data!', 'error');
553
- }
554
- } catch (error) {
555
- showAlert('Terjadi kesalahan koneksi!', 'error');
556
- }
557
- }
558
- }
559
-
560
- // Reset form
561
- function resetForm() {
562
- document.getElementById('student-form').reset();
563
- document.getElementById('student-id').value = '';
564
- isEditMode = false;
565
- currentEditId = null;
566
- document.getElementById('form-title').textContent = 'Tambah Data Mahasiswa';
567
- document.getElementById('submit-text').textContent = 'Simpan Data';
568
- document.getElementById('cancel-btn').style.display = 'none';
569
- clearAlert();
570
- }
571
-
572
- // Show alert
573
- function showAlert(message, type) {
574
- const container = document.getElementById('alert-container');
575
- container.innerHTML = `
576
- <div class="alert alert-${type}">
577
- ${message}
578
- </div>
579
- `;
580
-
581
- // Auto hide after 5 seconds
582
- setTimeout(clearAlert, 5000);
583
- }
584
-
585
- // Clear alert
586
- function clearAlert() {
587
- document.getElementById('alert-container').innerHTML = '';
588
- }
589
- </script>
590
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Student Management 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: white;
25
+ border-radius: 15px;
26
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
27
+ overflow: hidden;
28
+ }
29
+
30
+ .header {
31
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
32
+ color: white;
33
+ padding: 30px;
34
+ text-align: center;
35
+ }
36
+
37
+ .header h1 {
38
+ font-size: 2.5rem;
39
+ margin-bottom: 10px;
40
+ }
41
+
42
+ .header p {
43
+ font-size: 1.1rem;
44
+ opacity: 0.9;
45
+ }
46
+
47
+ .content {
48
+ padding: 30px;
49
+ }
50
+
51
+ .form-section {
52
+ background: #f8f9fa;
53
+ border-radius: 10px;
54
+ padding: 25px;
55
+ margin-bottom: 30px;
56
+ border-left: 4px solid #4facfe;
57
+ }
58
+
59
+ .form-section h2 {
60
+ color: #333;
61
+ margin-bottom: 20px;
62
+ font-size: 1.5rem;
63
+ }
64
+
65
+ .form-grid {
66
+ display: grid;
67
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
68
+ gap: 20px;
69
+ margin-bottom: 20px;
70
+ }
71
+
72
+ .form-group {
73
+ display: flex;
74
+ flex-direction: column;
75
+ }
76
+
77
+ .form-group label {
78
+ font-weight: 600;
79
+ margin-bottom: 8px;
80
+ color: #555;
81
+ }
82
+
83
+ .form-group input,
84
+ .form-group select,
85
+ .form-group textarea {
86
+ padding: 12px;
87
+ border: 2px solid #e1e5e9;
88
+ border-radius: 8px;
89
+ font-size: 14px;
90
+ transition: all 0.3s ease;
91
+ }
92
+
93
+ .form-group input:focus,
94
+ .form-group select:focus,
95
+ .form-group textarea:focus {
96
+ outline: none;
97
+ border-color: #4facfe;
98
+ box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
99
+ }
100
+
101
+ .form-group textarea {
102
+ resize: vertical;
103
+ min-height: 80px;
104
+ }
105
+
106
+ .btn {
107
+ padding: 12px 25px;
108
+ border: none;
109
+ border-radius: 8px;
110
+ font-size: 14px;
111
+ font-weight: 600;
112
+ cursor: pointer;
113
+ transition: all 0.3s ease;
114
+ text-transform: uppercase;
115
+ letter-spacing: 0.5px;
116
+ }
117
+
118
+ .btn-primary {
119
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
120
+ color: white;
121
+ }
122
+
123
+ .btn-primary:hover {
124
+ transform: translateY(-2px);
125
+ box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);
126
+ }
127
+
128
+ .btn-secondary {
129
+ background: #6c757d;
130
+ color: white;
131
+ }
132
+
133
+ .btn-secondary:hover {
134
+ background: #5a6268;
135
+ transform: translateY(-2px);
136
+ }
137
+
138
+ .btn-danger {
139
+ background: #dc3545;
140
+ color: white;
141
+ }
142
+
143
+ .btn-danger:hover {
144
+ background: #c82333;
145
+ transform: translateY(-2px);
146
+ }
147
+
148
+ .btn-warning {
149
+ background: #ffc107;
150
+ color: #212529;
151
+ }
152
+
153
+ .btn-warning:hover {
154
+ background: #e0a800;
155
+ transform: translateY(-2px);
156
+ }
157
+
158
+ .students-section {
159
+ margin-top: 30px;
160
+ }
161
+
162
+ .students-header {
163
+ display: flex;
164
+ justify-content: space-between;
165
+ align-items: center;
166
+ margin-bottom: 20px;
167
+ }
168
+
169
+ .students-grid {
170
+ display: grid;
171
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
172
+ gap: 20px;
173
+ }
174
+
175
+ .student-card {
176
+ background: white;
177
+ border-radius: 12px;
178
+ padding: 20px;
179
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
180
+ border: 1px solid #e1e5e9;
181
+ transition: all 0.3s ease;
182
+ }
183
+
184
+ .student-card:hover {
185
+ transform: translateY(-5px);
186
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
187
+ }
188
+
189
+ .student-photo {
190
+ width: 80px;
191
+ height: 80px;
192
+ border-radius: 50%;
193
+ object-fit: cover;
194
+ margin-bottom: 15px;
195
+ border: 3px solid #4facfe;
196
+ }
197
+
198
+ .student-info h3 {
199
+ color: #333;
200
+ margin-bottom: 10px;
201
+ font-size: 1.2rem;
202
+ }
203
+
204
+ .student-info p {
205
+ color: #666;
206
+ margin-bottom: 5px;
207
+ font-size: 0.9rem;
208
+ }
209
+
210
+ .student-actions {
211
+ margin-top: 15px;
212
+ display: flex;
213
+ gap: 10px;
214
+ }
215
+
216
+ .student-actions .btn {
217
+ flex: 1;
218
+ padding: 8px 12px;
219
+ font-size: 12px;
220
+ }
221
+
222
+ .modal {
223
+ display: none;
224
+ position: fixed;
225
+ z-index: 1000;
226
+ left: 0;
227
+ top: 0;
228
+ width: 100%;
229
+ height: 100%;
230
+ background-color: rgba(0,0,0,0.5);
231
+ backdrop-filter: blur(5px);
232
+ }
233
+
234
+ .modal-content {
235
+ background-color: white;
236
+ margin: 5% auto;
237
+ padding: 30px;
238
+ border-radius: 15px;
239
+ width: 90%;
240
+ max-width: 600px;
241
+ max-height: 80vh;
242
+ overflow-y: auto;
243
+ animation: modalSlideIn 0.3s ease;
244
+ }
245
+
246
+ @keyframes modalSlideIn {
247
+ from {
248
+ opacity: 0;
249
+ transform: translateY(-50px);
250
+ }
251
+ to {
252
+ opacity: 1;
253
+ transform: translateY(0);
254
+ }
255
+ }
256
+
257
+ .close {
258
+ color: #aaa;
259
+ float: right;
260
+ font-size: 28px;
261
+ font-weight: bold;
262
+ cursor: pointer;
263
+ }
264
+
265
+ .close:hover {
266
+ color: #333;
267
+ }
268
+
269
+ .alert {
270
+ padding: 15px;
271
+ margin-bottom: 20px;
272
+ border-radius: 8px;
273
+ font-weight: 500;
274
+ }
275
+
276
+ .alert-success {
277
+ background-color: #d4edda;
278
+ color: #155724;
279
+ border: 1px solid #c3e6cb;
280
+ }
281
+
282
+ .alert-error {
283
+ background-color: #f8d7da;
284
+ color: #721c24;
285
+ border: 1px solid #f5c6cb;
286
+ }
287
+
288
+ .loading {
289
+ display: none;
290
+ text-align: center;
291
+ padding: 20px;
292
+ }
293
+
294
+ .spinner {
295
+ border: 4px solid #f3f3f3;
296
+ border-top: 4px solid #4facfe;
297
+ border-radius: 50%;
298
+ width: 40px;
299
+ height: 40px;
300
+ animation: spin 1s linear infinite;
301
+ margin: 0 auto 10px;
302
+ }
303
+
304
+ @keyframes spin {
305
+ 0% { transform: rotate(0deg); }
306
+ 100% { transform: rotate(360deg); }
307
+ }
308
+
309
+ @media (max-width: 768px) {
310
+ .header h1 {
311
+ font-size: 2rem;
312
+ }
313
+
314
+ .form-grid {
315
+ grid-template-columns: 1fr;
316
+ }
317
+
318
+ .students-grid {
319
+ grid-template-columns: 1fr;
320
+ }
321
+
322
+ .students-header {
323
+ flex-direction: column;
324
+ gap: 15px;
325
+ }
326
+ }
327
+ </style>
328
+ </head>
329
+ <body>
330
+ <div class="container">
331
+ <div class="header">
332
+ <h1>Student Management System</h1>
333
+ <p>Sistem Manajemen Data Mahasiswa</p>
334
+ </div>
335
+
336
+ <div class="content">
337
+ <!-- Form Section -->
338
+ <div class="form-section">
339
+ <h2 id="form-title">Tambah Data Mahasiswa</h2>
340
+
341
+ <div id="alert-container"></div>
342
+
343
+ <form id="student-form" enctype="multipart/form-data">
344
+ <input type="hidden" id="student-id" name="student_id">
345
+
346
+ <div class="form-grid">
347
+ <div class="form-group">
348
+ <label for="nrp">NRP *</label>
349
+ <input type="text" id="nrp" name="nrp" required>
350
+ </div>
351
+
352
+ <div class="form-group">
353
+ <label for="nama">Nama Lengkap *</label>
354
+ <input type="text" id="nama" name="nama" required>
355
+ </div>
356
+
357
+ <div class="form-group">
358
+ <label for="jenis_kelamin">Jenis Kelamin *</label>
359
+ <select id="jenis_kelamin" name="jenis_kelamin" required>
360
+ <option value="">Pilih Jenis Kelamin</option>
361
+ <option value="Laki-laki">Laki-laki</option>
362
+ <option value="Perempuan">Perempuan</option>
363
+ </select>
364
+ </div>
365
+
366
+ <div class="form-group">
367
+ <label for="telpon">Nomor Telepon *</label>
368
+ <input type="tel" id="telpon" name="telpon" required>
369
+ </div>
370
+
371
+ <div class="form-group">
372
+ <label for="photo">Foto</label>
373
+ <input type="file" id="photo" name="photo" accept="image/*">
374
+ </div>
375
+ </div>
376
+
377
+ <div class="form-group">
378
+ <label for="alamat">Alamat *</label>
379
+ <textarea id="alamat" name="alamat" required placeholder="Masukkan alamat lengkap..."></textarea>
380
+ </div>
381
+
382
+ <div style="display: flex; gap: 15px; margin-top: 25px;">
383
+ <button type="submit" class="btn btn-primary" id="submit-btn">
384
+ <span id="submit-text">Simpan Data</span>
385
+ </button>
386
+ <button type="button" class="btn btn-secondary" id="cancel-btn" onclick="resetForm()" style="display: none;">
387
+ Batal
388
+ </button>
389
+ </div>
390
+ </form>
391
+ </div>
392
+
393
+ <!-- Students List Section -->
394
+ <div class="students-section">
395
+ <div class="students-header">
396
+ <h2>Daftar Mahasiswa</h2>
397
+ <button class="btn btn-primary" onclick="loadStudents()">Refresh Data</button>
398
+ </div>
399
+
400
+ <div class="loading" id="loading">
401
+ <div class="spinner"></div>
402
+ <p>Memuat data...</p>
403
+ </div>
404
+
405
+ <div class="students-grid" id="students-container">
406
+ <!-- Students will be loaded here -->
407
+ </div>
408
+ </div>
409
+ </div>
410
+ </div>
411
+
412
+ <!-- Add Photo Zoom Modal -->
413
+ <div id="photo-modal" class="modal">
414
+ <div class="modal-content" style="max-width: 800px; text-align: center;">
415
+ <span class="close" onclick="closePhotoModal()">&times;</span>
416
+ <h2 id="modal-student-name" style="margin-bottom: 15px;"></h2>
417
+ <img id="modal-photo" src="" alt="Student Photo" style="max-width: 100%; max-height: 70vh; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.2);">
418
+ </div>
419
+ </div>
420
+
421
+ <script>
422
+ let isEditMode = false;
423
+ let currentEditId = null;
424
+
425
+ // Load students when page loads
426
+ document.addEventListener('DOMContentLoaded', function() {
427
+ loadStudents();
428
+ });
429
+
430
+ // Form submission
431
+ document.getElementById('student-form').addEventListener('submit', async function(e) {
432
+ e.preventDefault();
433
+
434
+ const submitBtn = document.getElementById('submit-btn');
435
+ const submitText = document.getElementById('submit-text');
436
+ const originalText = submitText.textContent;
437
+
438
+ // Show loading state
439
+ submitBtn.disabled = true;
440
+ submitText.textContent = 'Menyimpan...';
441
+
442
+ const formData = new FormData(this);
443
+
444
+ try {
445
+ let response;
446
+ if (isEditMode && currentEditId) {
447
+ response = await fetch(`/api/students/${currentEditId}`, {
448
+ method: 'PUT',
449
+ body: formData
450
+ });
451
+ } else {
452
+ response = await fetch('/api/students', {
453
+ method: 'POST',
454
+ body: formData
455
+ });
456
+ }
457
+
458
+ const result = await response.json();
459
+
460
+ if (response.ok) {
461
+ showAlert('Data berhasil disimpan!', 'success');
462
+ resetForm();
463
+ loadStudents();
464
+ } else {
465
+ showAlert(result.detail || 'Terjadi kesalahan!', 'error');
466
+ }
467
+ } catch (error) {
468
+ showAlert('Terjadi kesalahan koneksi!', 'error');
469
+ } finally {
470
+ submitBtn.disabled = false;
471
+ submitText.textContent = originalText;
472
+ }
473
+ });
474
+
475
+ // Load students
476
+ async function loadStudents() {
477
+ const container = document.getElementById('students-container');
478
+ const loading = document.getElementById('loading');
479
+
480
+ loading.style.display = 'block';
481
+ container.innerHTML = '';
482
+
483
+ try {
484
+ const response = await fetch('/api/students');
485
+ const students = await response.json();
486
+
487
+ if (students.length === 0) {
488
+ container.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1/-1;">Belum ada data mahasiswa.</p>';
489
+ } else {
490
+ container.innerHTML = students.map(student => `
491
+ <div class="student-card">
492
+ ${student.photo ?
493
+ `<img src="/uploads/${student.photo}" alt="${student.nama}" class="student-photo"
494
+ onclick="openPhotoModal('/uploads/${student.photo}', '${student.nama}')"
495
+ style="cursor: pointer;" title="Klik untuk memperbesar">` :
496
+ '<div class="student-photo" style="background: #f0f0f0; display: flex; align-items: center; justify-content: center; color: #999;">No Photo</div>'}
497
+ <div class="student-info">
498
+ <h3>${student.nama}</h3>
499
+ <p><strong>NRP:</strong> ${student.nrp}</p>
500
+ <p><strong>Jenis Kelamin:</strong> ${student.jenis_kelamin}</p>
501
+ <p><strong>Telepon:</strong> ${student.telpon}</p>
502
+ <p><strong>Alamat:</strong> ${student.alamat}</p>
503
+ <p><strong>Tanggal:</strong> ${new Date(student.created_at).toLocaleDateString('id-ID')}</p>
504
+ </div>
505
+ <div class="student-actions">
506
+ <button class="btn btn-warning" onclick="editStudent(${student.id})">Edit</button>
507
+ <button class="btn btn-danger" onclick="deleteStudent(${student.id})">Hapus</button>
508
+ </div>
509
+ </div>
510
+ `).join('');
511
+ }
512
+ } catch (error) {
513
+ container.innerHTML = '<p style="text-align: center; color: #dc3545; grid-column: 1/-1;">Gagal memuat data!</p>';
514
+ } finally {
515
+ loading.style.display = 'none';
516
+ }
517
+ }
518
+
519
+ // Edit student
520
+ async function editStudent(id) {
521
+ try {
522
+ const response = await fetch(`/api/students/${id}`);
523
+ const student = await response.json();
524
+
525
+ if (response.ok) {
526
+ // Fill form with student data
527
+ document.getElementById('student-id').value = student.id;
528
+ document.getElementById('nrp').value = student.nrp;
529
+ document.getElementById('nama').value = student.nama;
530
+ document.getElementById('jenis_kelamin').value = student.jenis_kelamin;
531
+ document.getElementById('telpon').value = student.telpon;
532
+ document.getElementById('alamat').value = student.alamat;
533
+
534
+ // Update form state
535
+ isEditMode = true;
536
+ currentEditId = id;
537
+ document.getElementById('form-title').textContent = 'Edit Data Mahasiswa';
538
+ document.getElementById('submit-text').textContent = 'Update Data';
539
+ document.getElementById('cancel-btn').style.display = 'inline-block';
540
+
541
+ // Scroll to form
542
+ document.querySelector('.form-section').scrollIntoView({ behavior: 'smooth' });
543
+ } else {
544
+ showAlert('Gagal memuat data mahasiswa!', 'error');
545
+ }
546
+ } catch (error) {
547
+ showAlert('Terjadi kesalahan koneksi!', 'error');
548
+ }
549
+ }
550
+
551
+ // Delete student
552
+ async function deleteStudent(id) {
553
+ if (confirm('Apakah Anda yakin ingin menghapus data ini?')) {
554
+ try {
555
+ const response = await fetch(`/api/students/${id}`, {
556
+ method: 'DELETE'
557
+ });
558
+
559
+ const result = await response.json();
560
+
561
+ if (response.ok) {
562
+ showAlert('Data berhasil dihapus!', 'success');
563
+ loadStudents();
564
+ } else {
565
+ showAlert(result.detail || 'Gagal menghapus data!', 'error');
566
+ }
567
+ } catch (error) {
568
+ showAlert('Terjadi kesalahan koneksi!', 'error');
569
+ }
570
+ }
571
+ }
572
+
573
+ // Reset form
574
+ function resetForm() {
575
+ document.getElementById('student-form').reset();
576
+ document.getElementById('student-id').value = '';
577
+ isEditMode = false;
578
+ currentEditId = null;
579
+ document.getElementById('form-title').textContent = 'Tambah Data Mahasiswa';
580
+ document.getElementById('submit-text').textContent = 'Simpan Data';
581
+ document.getElementById('cancel-btn').style.display = 'none';
582
+ clearAlert();
583
+ }
584
+
585
+ // Show alert
586
+ function showAlert(message, type) {
587
+ const container = document.getElementById('alert-container');
588
+ container.innerHTML = `
589
+ <div class="alert alert-${type}">
590
+ ${message}
591
+ </div>
592
+ `;
593
+
594
+ // Auto hide after 5 seconds
595
+ setTimeout(clearAlert, 5000);
596
+ }
597
+
598
+ // Clear alert
599
+ function clearAlert() {
600
+ document.getElementById('alert-container').innerHTML = '';
601
+ }
602
+
603
+ // Photo modal functions
604
+ function openPhotoModal(photoUrl, studentName) {
605
+ const modal = document.getElementById('photo-modal');
606
+ const modalPhoto = document.getElementById('modal-photo');
607
+ const modalStudentName = document.getElementById('modal-student-name');
608
+
609
+ modalPhoto.src = photoUrl;
610
+ modalStudentName.textContent = studentName;
611
+ modal.style.display = 'block';
612
+
613
+ // Close when clicking outside the image
614
+ modal.onclick = function(event) {
615
+ if (event.target === modal) {
616
+ closePhotoModal();
617
+ }
618
+ };
619
+
620
+ // Close with Escape key
621
+ document.addEventListener('keydown', function(event) {
622
+ if (event.key === 'Escape') {
623
+ closePhotoModal();
624
+ }
625
+ });
626
+ }
627
+
628
+ function closePhotoModal() {
629
+ document.getElementById('photo-modal').style.display = 'none';
630
+ }
631
+ </script>
632
+ </body>
633
  </html>