farwew commited on
Commit
f2e1f0c
·
verified ·
1 Parent(s): 1e32901

Upload 9 files

Browse files
Files changed (9) hide show
  1. .env +6 -0
  2. Dockerfile +24 -0
  3. app.py +119 -0
  4. create_db.py +43 -0
  5. database.py +136 -0
  6. requirements.txt +5 -0
  7. templates/edit_siswa.html +250 -0
  8. templates/index.html +482 -0
  9. templates/tambah_siswa.html +249 -0
.env ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # MongoDB Atlas configuration
2
+ MONGO_URI=mongodb+srv://Farhan:Banyuwangi2005@peweb-10.zoelhgg.mongodb.net/?retryWrites=true&w=majority&appName=Peweb-10
3
+ MONGO_DB_NAME=student_reports
4
+
5
+ # Port configuration
6
+ PORT=7860
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a lightweight Python base image
2
+ FROM python:3.9-slim-buster
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements file and install dependencies
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+ RUN pip install gunicorn
11
+
12
+ # Copy the rest of your application code
13
+ COPY . .
14
+
15
+ # Set default environment variables (can be overridden at runtime)
16
+ ENV MONGO_URI=mongodb+srv://username:password@cluster.mongodb.net/ \
17
+ MONGO_DB_NAME=student_reports \
18
+ PORT=7860
19
+
20
+ # Expose the port that Gunicorn will run on
21
+ EXPOSE ${PORT}
22
+
23
+ # Command to run the Flask application using Gunicorn
24
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app", "--timeout", "120"]
app.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, redirect, url_for, send_file, Response
2
+ from fpdf import FPDF
3
+ import database
4
+ import io
5
+ from datetime import datetime
6
+ import os # Import os module for environment variables
7
+
8
+ app = Flask(__name__)
9
+
10
+ @app.route('/')
11
+ def index():
12
+ mahasiswa_data = database.get_all_mahasiswa()
13
+ status = request.args.get('status')
14
+ return render_template('index.html', mahasiswa=mahasiswa_data, status=status)
15
+
16
+ @app.route('/tambah_siswa', methods=['GET', 'POST'])
17
+ def tambah_siswa():
18
+ if request.method == 'POST':
19
+ NRP = request.form['NRP']
20
+ nama_lengkap = request.form['nama_lengkap']
21
+ no_hp = request.form['no_hp']
22
+ tanggal_lahir = request.form['tanggal_lahir'] # Format YYYY-MM-DD from HTML date input
23
+
24
+ if database.add_mahasiswa(NRP, nama_lengkap, no_hp, tanggal_lahir):
25
+ return redirect(url_for('index', status='success'))
26
+ else:
27
+ # Handle error, maybe show an error message on the form
28
+ return "Error adding data. Please check server logs.", 500
29
+ return render_template('tambah_siswa.html')
30
+
31
+ @app.route('/cetak_pdf')
32
+ def cetak_pdf():
33
+ try:
34
+ pdf = FPDF('L', 'mm', 'A5')
35
+ pdf.add_page()
36
+
37
+ # School Header
38
+ pdf.set_font('Arial', 'B', 16)
39
+ pdf.cell(190, 7, 'SEKOLAH MENENGAH KEJURUAN NEGERI 5 Cilegon', 0, 1, 'C')
40
+ pdf.set_font('Arial', 'B', 12)
41
+ pdf.cell(190, 7, 'DAFTAR SISWA KELAS IX JURUSAN REKAYASA KECERDASAN ARTIFISIAL', 0, 1, 'C')
42
+ pdf.cell(10, 7, '', 0, 1) # Space
43
+
44
+ # Table Header
45
+ pdf.set_font('Arial', 'B', 10)
46
+ pdf.cell(30, 6, 'NRP', 1, 0, 'C')
47
+ pdf.cell(80, 6, 'NAMA MAHASISWA', 1, 0, 'C')
48
+ pdf.cell(40, 6, 'NO HP', 1, 0, 'C')
49
+ pdf.cell(40, 6, 'TANGGAL LHR', 1, 1, 'C')
50
+
51
+ # Table Data
52
+ pdf.set_font('Arial', '', 10)
53
+ mahasiswa_data = database.get_all_mahasiswa()
54
+ for row in mahasiswa_data:
55
+ # Get NRP/NIM value
56
+ nim_value = str(row.get('NRP', row.get('nim', '')))
57
+ pdf.cell(30, 6, nim_value, 1, 0, 'C')
58
+ pdf.cell(80, 6, row['nama_lengkap'], 1, 0)
59
+ pdf.cell(40, 6, str(row['no_hp']), 1, 0, 'C')
60
+
61
+ # Format date for display - handle both datetime object and string
62
+ tanggal = row['tanggal_lahir']
63
+ if isinstance(tanggal, datetime):
64
+ formatted_date = tanggal.strftime('%d-%m-%Y')
65
+ else:
66
+ # If it's already a string, use it directly
67
+ formatted_date = tanggal
68
+
69
+ pdf.cell(40, 6, formatted_date, 1, 1, 'C')
70
+
71
+ # Create a BytesIO object to hold the PDF data
72
+ pdf_buffer = io.BytesIO()
73
+ pdf_buffer.write(pdf.output(dest='S'))
74
+ pdf_buffer.seek(0)
75
+
76
+ # Return PDF as a response
77
+ return send_file(
78
+ pdf_buffer,
79
+ mimetype='application/pdf',
80
+ as_attachment=True,
81
+ download_name='daftar_siswa.pdf'
82
+ )
83
+
84
+ except Exception as e:
85
+ print(f"Error generating PDF: {str(e)}")
86
+ return f"Error generating PDF: {str(e)}", 500
87
+
88
+ @app.route('/edit_siswa/<id>', methods=['GET', 'POST'])
89
+ def edit_siswa(id):
90
+ if request.method == 'POST':
91
+ nama_lengkap = request.form['nama_lengkap']
92
+ no_hp = request.form['no_hp']
93
+ tanggal_lahir = request.form['tanggal_lahir']
94
+
95
+ if database.update_mahasiswa(id, nama_lengkap, no_hp, tanggal_lahir):
96
+ return redirect(url_for('index', status='updated'))
97
+ else:
98
+ return "Error updating data. Please check server logs.", 500
99
+
100
+ # GET request - show the edit form with current data
101
+ student = database.get_mahasiswa_by_id(id)
102
+ if student:
103
+ return render_template('edit_siswa.html', student=student)
104
+ else:
105
+ return "Student not found", 404
106
+
107
+ @app.route('/hapus_siswa', methods=['POST'])
108
+ def hapus_siswa():
109
+ id = request.form['id']
110
+ if database.delete_mahasiswa(id):
111
+ return redirect(url_for('index', status='deleted'))
112
+ else:
113
+ return "Error deleting data. Please check server logs.", 500
114
+
115
+ if __name__ == '__main__':
116
+ # Use environment variable for port (Hugging Face default is 7860)
117
+ port = int(os.environ.get('PORT', 7860))
118
+ # Run the Flask app
119
+ app.run(host='0.0.0.0', port=port, debug=True) # Set debug=False for production
create_db.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pymongo import MongoClient
3
+ from datetime import datetime
4
+ from dotenv import load_dotenv
5
+
6
+ # Load environment variables
7
+ load_dotenv()
8
+
9
+ # MongoDB Atlas connection
10
+ MONGO_URI = os.environ.get('MONGO_URI')
11
+ DB_NAME = os.environ.get('MONGO_DB_NAME', 'student_reports')
12
+
13
+ def create_database_and_collection():
14
+ try:
15
+ # Connect to MongoDB Atlas
16
+ client = MongoClient(MONGO_URI)
17
+ db = client[DB_NAME]
18
+
19
+ # Check if collection exists, create it if not
20
+ if 'mahasiswa' not in db.list_collection_names():
21
+ db.create_collection('mahasiswa')
22
+ print(f"Collection 'mahasiswa' created successfully.")
23
+
24
+ # Check if collection is empty and insert sample data if it is
25
+ if db.mahasiswa.count_documents({}) == 0:
26
+ sample_data = [
27
+ {'NRP': '5054231012', 'nama_lengkap': 'Budi Santoso', 'no_hp': '089699935552', 'tanggal_lahir': datetime(2017, 9, 2)},
28
+ {'NRP': '5054231013', 'nama_lengkap': 'Dewi Lestari', 'no_hp': '089699935553', 'tanggal_lahir': datetime(2017, 9, 2)},
29
+ {'NRP': '5054231014', 'nama_lengkap': 'Ahmad Suryanto', 'no_hp': '089699935554', 'tanggal_lahir': datetime(2017, 9, 3)},
30
+ {'NRP': '5054231015', 'nama_lengkap': 'Siti Rahayu', 'no_hp': '089699935555', 'tanggal_lahir': datetime(2017, 9, 3)}
31
+ ]
32
+ db.mahasiswa.insert_many(sample_data)
33
+ print("Sample data inserted.")
34
+ else:
35
+ print("Collection already contains data. Skipping sample data insertion.")
36
+
37
+ print(f"Database '{DB_NAME}' and 'mahasiswa' collection ensured successfully.")
38
+
39
+ except Exception as e:
40
+ print(f"Error: {e}")
41
+
42
+ if __name__ == '__main__':
43
+ create_database_and_collection()
database.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pymongo import MongoClient
3
+ from datetime import datetime
4
+ from dotenv import load_dotenv
5
+
6
+ # Load environment variables
7
+ load_dotenv()
8
+
9
+ # MongoDB Atlas connection string
10
+ MONGO_URI = os.environ.get('MONGO_URI')
11
+ DB_NAME = os.environ.get('MONGO_DB_NAME', 'student_reports')
12
+
13
+ def get_db_connection():
14
+ try:
15
+ client = MongoClient(MONGO_URI)
16
+ db = client[DB_NAME]
17
+ return db
18
+ except Exception as e:
19
+ print(f"Error connecting to MongoDB: {e}")
20
+ return None
21
+
22
+ def get_all_mahasiswa():
23
+ db = get_db_connection()
24
+ if db is None:
25
+ return []
26
+
27
+ try:
28
+ # Convert MongoDB documents to dictionaries
29
+ mahasiswa = list(db.mahasiswa.find({}, {'_id': 0}))
30
+
31
+ # Convert string dates to datetime objects for compatibility with the app
32
+ for student in mahasiswa:
33
+ if isinstance(student['tanggal_lahir'], str):
34
+ student['tanggal_lahir'] = datetime.strptime(student['tanggal_lahir'], '%Y-%m-%d')
35
+
36
+ return mahasiswa
37
+ except Exception as e:
38
+ print(f"Error fetching data: {e}")
39
+ return []
40
+
41
+ def add_mahasiswa(NRP, nama_lengkap, no_hp, tanggal_lahir):
42
+ db = get_db_connection()
43
+ if db is None:
44
+ return False
45
+
46
+ try:
47
+ # Create a document
48
+ student = {
49
+ "NRP": NRP,
50
+ "nama_lengkap": nama_lengkap,
51
+ "no_hp": no_hp,
52
+ "tanggal_lahir": datetime.strptime(tanggal_lahir, '%Y-%m-%d')
53
+ }
54
+
55
+ # Insert into collection
56
+ db.mahasiswa.insert_one(student)
57
+ return True
58
+ except Exception as e:
59
+ print(f"Error adding data: {e}")
60
+ return False
61
+
62
+ def get_mahasiswa_by_id(id):
63
+ db = get_db_connection()
64
+ if db is None:
65
+ return None
66
+
67
+ try:
68
+ # Find student by NRP/NIM
69
+ student = db.mahasiswa.find_one({"NRP": id})
70
+
71
+ # If not found with NRP, try with nim
72
+ if student is None:
73
+ student = db.mahasiswa.find_one({"nim": id})
74
+
75
+ if student:
76
+ # Remove MongoDB _id field before returning
77
+ student.pop('_id', None)
78
+
79
+ # Convert string dates to datetime objects if needed
80
+ if isinstance(student['tanggal_lahir'], str):
81
+ student['tanggal_lahir'] = datetime.strptime(student['tanggal_lahir'], '%Y-%m-%d')
82
+
83
+ return student
84
+ except Exception as e:
85
+ print(f"Error fetching student: {e}")
86
+ return None
87
+
88
+ def update_mahasiswa(id, nama_lengkap, no_hp, tanggal_lahir):
89
+ db = get_db_connection()
90
+ if db is None:
91
+ return False
92
+
93
+ try:
94
+ # Find if student exists with NRP or nim
95
+ student = db.mahasiswa.find_one({"NRP": id})
96
+ id_field = "NRP"
97
+
98
+ if student is None:
99
+ student = db.mahasiswa.find_one({"nim": id})
100
+ id_field = "nim"
101
+
102
+ if student is None:
103
+ return False
104
+
105
+ # Update the document
106
+ result = db.mahasiswa.update_one(
107
+ {id_field: id},
108
+ {"$set": {
109
+ "nama_lengkap": nama_lengkap,
110
+ "no_hp": no_hp,
111
+ "tanggal_lahir": datetime.strptime(tanggal_lahir, '%Y-%m-%d')
112
+ }}
113
+ )
114
+
115
+ return result.modified_count > 0
116
+ except Exception as e:
117
+ print(f"Error updating data: {e}")
118
+ return False
119
+
120
+ def delete_mahasiswa(id):
121
+ db = get_db_connection()
122
+ if db is None:
123
+ return False
124
+
125
+ try:
126
+ # Try to delete by NRP
127
+ result = db.mahasiswa.delete_one({"NRP": id})
128
+
129
+ # If nothing was deleted, try with nim
130
+ if result.deleted_count == 0:
131
+ result = db.mahasiswa.delete_one({"nim": id})
132
+
133
+ return result.deleted_count > 0
134
+ except Exception as e:
135
+ print(f"Error deleting data: {e}")
136
+ return False
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask
2
+ fpdf2
3
+ pymongo
4
+ python-dotenv
5
+ gunicorn
templates/edit_siswa.html ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Edit Data Siswa</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
8
+ <style>
9
+ :root {
10
+ --primary-color: #6d28d9; /* Purple */
11
+ --secondary-color: #7c3aed;
12
+ --accent-color: #4f46e5;
13
+ --success-color: #10b981;
14
+ --danger-color: #ef4444;
15
+ --background-color: #f3f4f6;
16
+ --card-color: #ffffff;
17
+ --text-color: #374151;
18
+ --shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
19
+ }
20
+
21
+ * {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
29
+ background-color: var(--background-color);
30
+ color: var(--text-color);
31
+ min-height: 100vh;
32
+ display: flex;
33
+ justify-content: center;
34
+ align-items: center;
35
+ padding: 2rem;
36
+ }
37
+
38
+ .container {
39
+ width: 100%;
40
+ max-width: 600px;
41
+ background-color: var(--card-color);
42
+ border-radius: 1rem;
43
+ box-shadow: var(--shadow);
44
+ overflow: hidden;
45
+ transition: transform 0.3s ease;
46
+ }
47
+
48
+ .container:hover {
49
+ transform: translateY(-5px);
50
+ }
51
+
52
+ .header {
53
+ background-color: var(--primary-color);
54
+ color: white;
55
+ padding: 1.5rem;
56
+ text-align: center;
57
+ position: relative;
58
+ }
59
+
60
+ .header h1 {
61
+ font-size: 1.8rem;
62
+ margin-bottom: 0.5rem;
63
+ }
64
+
65
+ .header p {
66
+ font-size: 0.9rem;
67
+ opacity: 0.9;
68
+ }
69
+
70
+ .form-container {
71
+ padding: 2rem;
72
+ }
73
+
74
+ .form-group {
75
+ margin-bottom: 1.5rem;
76
+ position: relative;
77
+ }
78
+
79
+ .form-group i {
80
+ position: absolute;
81
+ top: 42px;
82
+ left: 12px;
83
+ color: var(--primary-color);
84
+ }
85
+
86
+ label {
87
+ display: block;
88
+ margin-bottom: 0.5rem;
89
+ font-weight: 600;
90
+ color: var(--text-color);
91
+ font-size: 0.95rem;
92
+ }
93
+
94
+ input[type="text"],
95
+ input[type="date"] {
96
+ width: 100%;
97
+ padding: 0.8rem 0.8rem 0.8rem 2.5rem;
98
+ border: 1px solid #e5e7eb;
99
+ border-radius: 0.5rem;
100
+ font-size: 1rem;
101
+ transition: all 0.3s ease;
102
+ background-color: #f9fafb;
103
+ }
104
+
105
+ input[type="text"]:focus,
106
+ input[type="date"]:focus {
107
+ border-color: var(--primary-color);
108
+ box-shadow: 0 0 0 3px rgba(109, 40, 217, 0.2);
109
+ outline: none;
110
+ }
111
+
112
+ input[type="date"] {
113
+ padding-left: 2.5rem;
114
+ }
115
+
116
+ .button-group {
117
+ display: flex;
118
+ justify-content: space-between;
119
+ margin-top: 2rem;
120
+ }
121
+
122
+ .btn {
123
+ padding: 0.8rem 1.5rem;
124
+ border: none;
125
+ border-radius: 0.5rem;
126
+ font-weight: 600;
127
+ cursor: pointer;
128
+ transition: all 0.3s ease;
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ font-size: 1rem;
133
+ }
134
+
135
+ .btn i {
136
+ margin-right: 0.5rem;
137
+ }
138
+
139
+ .btn-primary {
140
+ background-color: var(--primary-color);
141
+ color: white;
142
+ flex: 1;
143
+ margin-right: 1rem;
144
+ }
145
+
146
+ .btn-primary:hover {
147
+ background-color: var(--secondary-color);
148
+ transform: translateY(-2px);
149
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
150
+ }
151
+
152
+ .btn-secondary {
153
+ background-color: #e5e7eb;
154
+ color: #4b5563;
155
+ }
156
+
157
+ .btn-secondary:hover {
158
+ background-color: #d1d5db;
159
+ transform: translateY(-2px);
160
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
161
+ }
162
+
163
+ /* Animation for form input focus */
164
+ @keyframes pulse {
165
+ 0% { transform: scale(1); }
166
+ 50% { transform: scale(1.02); }
167
+ 100% { transform: scale(1); }
168
+ }
169
+
170
+ .form-group:focus-within {
171
+ animation: pulse 0.5s ease;
172
+ }
173
+
174
+ /* Responsive */
175
+ @media (max-width: 640px) {
176
+ .container {
177
+ border-radius: 0;
178
+ }
179
+ .button-group {
180
+ flex-direction: column;
181
+ }
182
+ .btn-primary {
183
+ margin-right: 0;
184
+ margin-bottom: 1rem;
185
+ }
186
+ }
187
+ </style>
188
+ </head>
189
+ <body>
190
+ <div class="container">
191
+ <div class="header">
192
+ <h1>Edit Data Siswa</h1>
193
+ <p>Perbarui informasi siswa</p>
194
+ </div>
195
+
196
+ <div class="form-container">
197
+ <form action="{{ url_for('edit_siswa', id=student.NRP if student.NRP is defined else student.nim) }}" method="POST">
198
+ <div class="form-group">
199
+ <label for="NRP"><i class="fas fa-id-card"></i> NRP/NIM:</label>
200
+ <input type="text" id="NRP" name="NRP" value="{{ student.NRP if student.NRP is defined else student.nim }}" readonly>
201
+ </div>
202
+
203
+ <div class="form-group">
204
+ <label for="nama_lengkap"><i class="fas fa-user"></i> Nama Lengkap:</label>
205
+ <input type="text" id="nama_lengkap" name="nama_lengkap" value="{{ student.nama_lengkap }}" required>
206
+ </div>
207
+
208
+ <div class="form-group">
209
+ <label for="no_hp"><i class="fas fa-phone"></i> Nomor HP:</label>
210
+ <input type="text" id="no_hp" name="no_hp" value="{{ student.no_hp }}" required>
211
+ </div>
212
+
213
+ <div class="form-group">
214
+ <label for="tanggal_lahir"><i class="fas fa-calendar-alt"></i> Tanggal Lahir:</label>
215
+ <input type="date" id="tanggal_lahir" name="tanggal_lahir"
216
+ value="{{ student.tanggal_lahir.strftime('%Y-%m-%d') if student.tanggal_lahir is not string else student.tanggal_lahir }}" required>
217
+ </div>
218
+
219
+ <div class="button-group">
220
+ <button type="submit" class="btn btn-primary">
221
+ <i class="fas fa-save"></i> Simpan Perubahan
222
+ </button>
223
+ <a href="{{ url_for('index') }}" class="btn btn-secondary">
224
+ <i class="fas fa-arrow-left"></i> Batal
225
+ </a>
226
+ </div>
227
+ </form>
228
+ </div>
229
+ </div>
230
+
231
+ <script>
232
+ // Animasi sederhana untuk input saat form loading
233
+ document.addEventListener('DOMContentLoaded', function() {
234
+ const inputs = document.querySelectorAll('input');
235
+ inputs.forEach((input, index) => {
236
+ setTimeout(() => {
237
+ input.style.opacity = '0';
238
+ input.style.transform = 'translateY(20px)';
239
+ input.style.transition = 'all 0.5s ease';
240
+
241
+ setTimeout(() => {
242
+ input.style.opacity = '1';
243
+ input.style.transform = 'translateY(0)';
244
+ }, 100);
245
+ }, index * 100);
246
+ });
247
+ });
248
+ </script>
249
+ </body>
250
+ </html>
templates/index.html ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Sistem Laporan Siswa</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
8
+ <style>
9
+ :root {
10
+ --primary-color: #6d28d9; /* Ungu - matching with tambah_siswa.html */
11
+ --secondary-color: #7c3aed;
12
+ --success-color: #10b981; /* Green */
13
+ --background-color: #f3f4f6;
14
+ --card-color: #ffffff;
15
+ --text-color: #374151;
16
+ --border-color: #e5e7eb;
17
+ --shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
18
+ }
19
+
20
+ * {
21
+ margin: 0;
22
+ padding: 0;
23
+ box-sizing: border-box;
24
+ }
25
+
26
+ body {
27
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
28
+ margin: 0;
29
+ padding: 20px;
30
+ background-color: var(--background-color);
31
+ line-height: 1.6;
32
+ color: var(--text-color);
33
+ }
34
+
35
+ .container {
36
+ max-width: 1100px;
37
+ margin: 0 auto;
38
+ background-color: var(--card-color);
39
+ padding: 30px;
40
+ border-radius: 1rem;
41
+ box-shadow: var(--shadow);
42
+ animation: fadeIn 0.5s ease;
43
+ }
44
+
45
+ @keyframes fadeIn {
46
+ from { opacity: 0; transform: translateY(20px); }
47
+ to { opacity: 1; transform: translateY(0); }
48
+ }
49
+
50
+ .header {
51
+ text-align: center;
52
+ margin-bottom: 30px;
53
+ padding-bottom: 20px;
54
+ border-bottom: 2px solid var(--border-color);
55
+ position: relative;
56
+ }
57
+
58
+ .header::after {
59
+ content: '';
60
+ position: absolute;
61
+ bottom: -2px;
62
+ left: 50%;
63
+ transform: translateX(-50%);
64
+ width: 100px;
65
+ height: 4px;
66
+ background-color: var(--primary-color);
67
+ border-radius: 2px;
68
+ }
69
+
70
+ h1 {
71
+ color: var(--primary-color);
72
+ margin: 0;
73
+ font-size: 28px;
74
+ font-weight: 700;
75
+ letter-spacing: -0.5px;
76
+ }
77
+
78
+ h2 {
79
+ color: #555;
80
+ font-size: 22px;
81
+ margin: 15px 0;
82
+ font-weight: 600;
83
+ }
84
+
85
+ h3 {
86
+ color: var(--text-color);
87
+ margin: 25px 0 15px 0;
88
+ font-size: 18px;
89
+ font-weight: 600;
90
+ position: relative;
91
+ padding-left: 15px;
92
+ }
93
+
94
+ h3::before {
95
+ content: '';
96
+ position: absolute;
97
+ left: 0;
98
+ top: 50%;
99
+ transform: translateY(-50%);
100
+ width: 5px;
101
+ height: 20px;
102
+ background-color: var(--primary-color);
103
+ border-radius: 3px;
104
+ }
105
+
106
+ .action-buttons {
107
+ display: flex;
108
+ justify-content: center;
109
+ gap: 20px;
110
+ margin: 25px 0;
111
+ }
112
+
113
+ .action-btn {
114
+ padding: 12px 25px;
115
+ border-radius: 8px;
116
+ color: white;
117
+ text-decoration: none;
118
+ font-weight: 600;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ transition: all 0.3s ease;
123
+ }
124
+
125
+ .action-btn i {
126
+ margin-right: 8px;
127
+ font-size: 16px;
128
+ }
129
+
130
+ .action-btn:hover {
131
+ transform: translateY(-3px);
132
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
133
+ }
134
+
135
+ .btn-print {
136
+ background-color: var(--success-color);
137
+ }
138
+
139
+ .btn-print:hover {
140
+ background-color: #0da76f;
141
+ }
142
+
143
+ .btn-add {
144
+ background-color: var(--primary-color);
145
+ }
146
+
147
+ .btn-add:hover {
148
+ background-color: var(--secondary-color);
149
+ }
150
+
151
+ .table-container {
152
+ overflow-x: auto;
153
+ border-radius: 8px;
154
+ box-shadow: var(--shadow);
155
+ }
156
+
157
+ table {
158
+ width: 100%;
159
+ border-collapse: collapse;
160
+ border-radius: 8px;
161
+ overflow: hidden;
162
+ }
163
+
164
+ th, td {
165
+ padding: 15px 20px;
166
+ text-align: left;
167
+ }
168
+
169
+ th {
170
+ background-color: var(--primary-color);
171
+ color: white;
172
+ font-weight: 600;
173
+ text-transform: uppercase;
174
+ font-size: 14px;
175
+ letter-spacing: 0.5px;
176
+ }
177
+
178
+ tr:nth-child(even) {
179
+ background-color: #f9fafb;
180
+ }
181
+
182
+ tr:not(thead tr) {
183
+ border-bottom: 1px solid var(--border-color);
184
+ }
185
+
186
+ tr:last-child {
187
+ border-bottom: none;
188
+ }
189
+
190
+ tr:hover:not(thead tr) {
191
+ background-color: #f3f4f6;
192
+ }
193
+
194
+ td {
195
+ transition: all 0.2s ease;
196
+ }
197
+
198
+ .alert {
199
+ padding: 15px 20px;
200
+ background-color: #ecfdf5;
201
+ border-left: 4px solid var(--success-color);
202
+ color: #065f46;
203
+ border-radius: 8px;
204
+ margin-bottom: 20px;
205
+ display: flex;
206
+ align-items: center;
207
+ opacity: 1;
208
+ transition: opacity 0.5s ease, transform 0.5s ease;
209
+ transform: translateY(0);
210
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
211
+ {% if status != 'success' %}display: none;{% endif %}
212
+ }
213
+
214
+ .alert i {
215
+ margin-right: 10px;
216
+ font-size: 20px;
217
+ color: var(--success-color);
218
+ }
219
+
220
+ .alert.fade-out {
221
+ opacity: 0;
222
+ transform: translateY(-20px);
223
+ }
224
+
225
+ .empty-state {
226
+ text-align: center;
227
+ padding: 40px 20px;
228
+ color: #6b7280;
229
+ }
230
+
231
+ .empty-state i {
232
+ font-size: 48px;
233
+ color: #d1d5db;
234
+ margin-bottom: 15px;
235
+ }
236
+
237
+ .empty-state p {
238
+ font-size: 16px;
239
+ margin-bottom: 20px;
240
+ }
241
+
242
+ /* Action icons styling */
243
+ .actions {
244
+ width: 100px;
245
+ text-align: center;
246
+ }
247
+
248
+ .action-icon {
249
+ display: inline-flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ width: 36px;
253
+ height: 36px;
254
+ border-radius: 50%;
255
+ color: white;
256
+ margin: 0 3px;
257
+ transition: all 0.2s ease;
258
+ }
259
+
260
+ .edit-icon {
261
+ background-color: #3b82f6;
262
+ }
263
+
264
+ .edit-icon:hover {
265
+ background-color: #2563eb;
266
+ transform: translateY(-2px);
267
+ }
268
+
269
+ .delete-icon {
270
+ background-color: #ef4444;
271
+ }
272
+
273
+ .delete-icon:hover {
274
+ background-color: #dc2626;
275
+ transform: translateY(-2px);
276
+ }
277
+
278
+ /* Modal styles */
279
+ .modal {
280
+ display: none;
281
+ position: fixed;
282
+ top: 0;
283
+ left: 0;
284
+ width: 100%;
285
+ height: 100%;
286
+ background-color: rgba(0, 0, 0, 0.5);
287
+ z-index: 1000;
288
+ align-items: center;
289
+ justify-content: center;
290
+ }
291
+
292
+ .modal-content {
293
+ background-color: white;
294
+ padding: 2rem;
295
+ border-radius: 1rem;
296
+ width: 90%;
297
+ max-width: 500px;
298
+ box-shadow: var(--shadow);
299
+ text-align: center;
300
+ animation: modalFadeIn 0.3s ease;
301
+ }
302
+
303
+ @keyframes modalFadeIn {
304
+ from { opacity: 0; transform: translateY(-50px); }
305
+ to { opacity: 1; transform: translateY(0); }
306
+ }
307
+
308
+ .modal h3 {
309
+ color: #dc2626;
310
+ margin-bottom: 1rem;
311
+ padding: 0;
312
+ }
313
+
314
+ .modal h3::before {
315
+ display: none;
316
+ }
317
+
318
+ .modal p {
319
+ margin-bottom: 1.5rem;
320
+ }
321
+
322
+ .modal-buttons {
323
+ display: flex;
324
+ justify-content: center;
325
+ gap: 1rem;
326
+ }
327
+
328
+ .modal-btn {
329
+ padding: 0.75rem 1.5rem;
330
+ border-radius: 0.5rem;
331
+ font-weight: 600;
332
+ cursor: pointer;
333
+ }
334
+
335
+ .btn-confirm {
336
+ background-color: #ef4444;
337
+ color: white;
338
+ }
339
+
340
+ .btn-cancel {
341
+ background-color: #e5e7eb;
342
+ color: #4b5563;
343
+ }
344
+
345
+ /* Responsive Design */
346
+ @media (max-width: 768px) {
347
+ .action-buttons {
348
+ flex-direction: column;
349
+ gap: 10px;
350
+ }
351
+
352
+ .action-btn {
353
+ width: 100%;
354
+ }
355
+
356
+ th, td {
357
+ padding: 12px 15px;
358
+ }
359
+
360
+ h1 {
361
+ font-size: 24px;
362
+ }
363
+
364
+ h2 {
365
+ font-size: 18px;
366
+ }
367
+ }
368
+ </style>
369
+ </head>
370
+ <body>
371
+ <div class="container">
372
+ <div class="header">
373
+ <h1>SEKOLAH MENENGAH KEJURUAN NEGERI 5 CILEGON</h1>
374
+ <h2>Sistem Laporan Siswa</h2>
375
+ </div>
376
+
377
+ <div class="alert">
378
+ <i class="fas fa-check-circle"></i>
379
+ <span>Data berhasil disimpan!</span>
380
+ </div>
381
+
382
+ <div class="action-buttons">
383
+ <a href="{{ url_for('cetak_pdf') }}" class="action-btn btn-print" target="_blank">
384
+ <i class="fas fa-file-pdf"></i> Cetak Laporan PDF
385
+ </a>
386
+ <a href="{{ url_for('tambah_siswa') }}" class="action-btn btn-add">
387
+ <i class="fas fa-user-plus"></i> Tambah Data Siswa
388
+ </a>
389
+ </div>
390
+
391
+ <h3>Daftar Siswa Kelas IX Jurusan Rekayasa Kecerdasan Artifisial</h3>
392
+
393
+ <div class="table-container">
394
+ {% if mahasiswa and mahasiswa|length > 0 %}
395
+ <table>
396
+ <thead>
397
+ <tr>
398
+ <th>NRP/NIM</th>
399
+ <th>Nama Lengkap</th>
400
+ <th>No HP</th>
401
+ <th>Tanggal Lahir</th>
402
+ <th>Aksi</th>
403
+ </tr>
404
+ </thead>
405
+ <tbody>
406
+ {% for student in mahasiswa %}
407
+ <tr>
408
+ <td>{{ student.NRP if student.NRP is defined else student.nim }}</td>
409
+ <td>{{ student.nama_lengkap }}</td>
410
+ <td>{{ student.no_hp }}</td>
411
+ <td>{{ student.tanggal_lahir.strftime('%d-%m-%Y') if student.tanggal_lahir is not string else student.tanggal_lahir }}</td>
412
+ <td class="actions">
413
+ <a href="{{ url_for('edit_siswa', id=student.NRP if student.NRP is defined else student.nim) }}" class="action-icon edit-icon" title="Edit">
414
+ <i class="fas fa-edit"></i>
415
+ </a>
416
+ <a href="#" onclick="confirmDelete('{{ student.NRP if student.NRP is defined else student.nim }}')" class="action-icon delete-icon" title="Hapus">
417
+ <i class="fas fa-trash-alt"></i>
418
+ </a>
419
+ </td>
420
+ </tr>
421
+ {% endfor %}
422
+ </tbody>
423
+ </table>
424
+ {% else %}
425
+ <div class="empty-state">
426
+ <i class="fas fa-user-graduate"></i>></i>
427
+ <p>Tidak ada data siswa ditemukan.</p>ak ada data siswa ditemukan.</p>
428
+ </div>
429
+ {% endif %} endif %}
430
+ </div>div>
431
+ </div> </div>
432
+
433
+ <!-- Modal for delete confirmation -->
434
+ <div id="deleteModal" class="modal">
435
+ <div class="modal-content">3>
436
+ <h3><i class="fas fa-exclamation-triangle"></i> Konfirmasi Hapus</h3>
437
+ <p>Apakah Anda yakin ingin menghapus data siswa ini?</p>}}" method="POST"> <p>Apakah Anda yakin ingin menghapus data siswa ini?</p>
438
+ <form id="deleteForm" action="{{ url_for('hapus_siswa') }}" method="POST"> action="{{ url_for('hapus_siswa') }}" method="POST">
439
+ <input type="hidden" id="deleteId" name="id">" name="id">
440
+ <div class="modal-buttons">lass="modal-btn btn-cancel" onclick="closeModal()">Batal</button>
441
+ <button type="button" class="modal-btn btn-cancel" onclick="closeModal()">Batal</button>e="button" class="modal-btn btn-cancel" onclick="closeModal()">Batal</button>
442
+ <button type="submit" class="modal-btn btn-confirm">Hapus</button>Hapus</button>
443
+ </div>
444
+ </form>
445
+ </div>
446
+ </div>
447
+ <script>
448
+ <script>tion confirmDelete(id) {
449
+ // Add this JavaScript to the existing script = id;/ Add this JavaScript to the existing script
450
+ function confirmDelete(id) {'deleteModal').style.display = 'flex';ion confirmDelete(id) {
451
+ document.getElementById('deleteId').value = id;d;
452
+ document.getElementById('deleteModal').style.display = 'flex';;
453
+ }unction closeModal() { }
454
+
455
+ function closeModal() {
456
+ document.getElementById('deleteModal').style.display = 'none';
457
+ }
458
+
459
+ // Close modal when clicking outside of it
460
+ window.onclick = function(event) {
461
+ const modal = document.getElementById('deleteModal');
462
+ if (event.target == modal) {
463
+ closeModal();
464
+ }
465
+ }
466
+ </script>
467
+ </body>
468
+ </html></body> </script> } } closeModal(); if (event.target == modal) { const modal = document.getElementById('deleteModal'); window.onclick = function(event) { // Close modal when clicking outside of it } document.getElementById('deleteModal').style.display = 'none';
469
+ function closeModal() {
470
+ document.getElementById('deleteModal').style.display = 'none';
471
+ }
472
+
473
+ // Close modal when clicking outside of it
474
+ window.onclick = function(event) {
475
+ const modal = document.getElementById('deleteModal');
476
+ if (event.target == modal) {
477
+ closeModal();
478
+ }
479
+ }
480
+ </script>
481
+ </body>
482
+ </html>
templates/tambah_siswa.html ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Tambah Data Siswa</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
8
+ <style>
9
+ :root {
10
+ --primary-color: #6d28d9; /* Purple */
11
+ --secondary-color: #7c3aed;
12
+ --accent-color: #4f46e5;
13
+ --success-color: #10b981;
14
+ --danger-color: #ef4444;
15
+ --background-color: #f3f4f6;
16
+ --card-color: #ffffff;
17
+ --text-color: #374151;
18
+ --shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
19
+ }
20
+
21
+ * {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
29
+ background-color: var(--background-color);
30
+ color: var(--text-color);
31
+ min-height: 100vh;
32
+ display: flex;
33
+ justify-content: center;
34
+ align-items: center;
35
+ padding: 2rem;
36
+ }
37
+
38
+ .container {
39
+ width: 100%;
40
+ max-width: 600px;
41
+ background-color: var(--card-color);
42
+ border-radius: 1rem;
43
+ box-shadow: var(--shadow);
44
+ overflow: hidden;
45
+ transition: transform 0.3s ease;
46
+ }
47
+
48
+ .container:hover {
49
+ transform: translateY(-5px);
50
+ }
51
+
52
+ .header {
53
+ background-color: var(--primary-color);
54
+ color: white;
55
+ padding: 1.5rem;
56
+ text-align: center;
57
+ position: relative;
58
+ }
59
+
60
+ .header h1 {
61
+ font-size: 1.8rem;
62
+ margin-bottom: 0.5rem;
63
+ }
64
+
65
+ .header p {
66
+ font-size: 0.9rem;
67
+ opacity: 0.9;
68
+ }
69
+
70
+ .form-container {
71
+ padding: 2rem;
72
+ }
73
+
74
+ .form-group {
75
+ margin-bottom: 1.5rem;
76
+ position: relative;
77
+ }
78
+
79
+ .form-group i {
80
+ position: absolute;
81
+ top: 42px;
82
+ left: 12px;
83
+ color: var(--primary-color);
84
+ }
85
+
86
+ label {
87
+ display: block;
88
+ margin-bottom: 0.5rem;
89
+ font-weight: 600;
90
+ color: var(--text-color);
91
+ font-size: 0.95rem;
92
+ }
93
+
94
+ input[type="text"],
95
+ input[type="date"] {
96
+ width: 100%;
97
+ padding: 0.8rem 0.8rem 0.8rem 2.5rem;
98
+ border: 1px solid #e5e7eb;
99
+ border-radius: 0.5rem;
100
+ font-size: 1rem;
101
+ transition: all 0.3s ease;
102
+ background-color: #f9fafb;
103
+ }
104
+
105
+ input[type="text"]:focus,
106
+ input[type="date"]:focus {
107
+ border-color: var(--primary-color);
108
+ box-shadow: 0 0 0 3px rgba(109, 40, 217, 0.2);
109
+ outline: none;
110
+ }
111
+
112
+ input[type="date"] {
113
+ padding-left: 2.5rem;
114
+ }
115
+
116
+ .button-group {
117
+ display: flex;
118
+ justify-content: space-between;
119
+ margin-top: 2rem;
120
+ }
121
+
122
+ .btn {
123
+ padding: 0.8rem 1.5rem;
124
+ border: none;
125
+ border-radius: 0.5rem;
126
+ font-weight: 600;
127
+ cursor: pointer;
128
+ transition: all 0.3s ease;
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ font-size: 1rem;
133
+ }
134
+
135
+ .btn i {
136
+ margin-right: 0.5rem;
137
+ }
138
+
139
+ .btn-primary {
140
+ background-color: var(--primary-color);
141
+ color: white;
142
+ flex: 1;
143
+ margin-right: 1rem;
144
+ }
145
+
146
+ .btn-primary:hover {
147
+ background-color: var(--secondary-color);
148
+ transform: translateY(-2px);
149
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
150
+ }
151
+
152
+ .btn-secondary {
153
+ background-color: #e5e7eb;
154
+ color: #4b5563;
155
+ }
156
+
157
+ .btn-secondary:hover {
158
+ background-color: #d1d5db;
159
+ transform: translateY(-2px);
160
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
161
+ }
162
+
163
+ /* Animation for form input focus */
164
+ @keyframes pulse {
165
+ 0% { transform: scale(1); }
166
+ 50% { transform: scale(1.02); }
167
+ 100% { transform: scale(1); }
168
+ }
169
+
170
+ .form-group:focus-within {
171
+ animation: pulse 0.5s ease;
172
+ }
173
+
174
+ /* Responsive */
175
+ @media (max-width: 640px) {
176
+ .container {
177
+ border-radius: 0;
178
+ }
179
+ .button-group {
180
+ flex-direction: column;
181
+ }
182
+ .btn-primary {
183
+ margin-right: 0;
184
+ margin-bottom: 1rem;
185
+ }
186
+ }
187
+ </style>
188
+ </head>
189
+ <body>
190
+ <div class="container">
191
+ <div class="header">
192
+ <h1>Tambah Data Siswa</h1>
193
+ <p>Silakan isi formulir di bawah dengan lengkap</p>
194
+ </div>
195
+
196
+ <div class="form-container">
197
+ <form action="{{ url_for('tambah_siswa') }}" method="POST">
198
+ <div class="form-group">
199
+ <label for="NRP"><i class="fas fa-id-card"></i> NRP/NIM:</label>
200
+ <input type="text" id="NRP" name="NRP" placeholder="Masukkan NRP siswa" required>
201
+ </div>
202
+
203
+ <div class="form-group">
204
+ <label for="nama_lengkap"><i class="fas fa-user"></i> Nama Lengkap:</label>
205
+ <input type="text" id="nama_lengkap" name="nama_lengkap" placeholder="Masukkan nama lengkap siswa" required>
206
+ </div>
207
+
208
+ <div class="form-group">
209
+ <label for="no_hp"><i class="fas fa-phone"></i> Nomor HP:</label>
210
+ <input type="text" id="no_hp" name="no_hp" placeholder="Masukkan nomor HP" required>
211
+ </div>
212
+
213
+ <div class="form-group">
214
+ <label for="tanggal_lahir"><i class="fas fa-calendar-alt"></i> Tanggal Lahir:</label>
215
+ <input type="date" id="tanggal_lahir" name="tanggal_lahir" required>
216
+ </div>
217
+
218
+ <div class="button-group">
219
+ <button type="submit" class="btn btn-primary">
220
+ <i class="fas fa-save"></i> Simpan Data
221
+ </button>
222
+ <a href="{{ url_for('index') }}" class="btn btn-secondary">
223
+ <i class="fas fa-arrow-left"></i> Kembali
224
+ </a>
225
+ </div>
226
+ </form>
227
+ </div>
228
+ </div>
229
+
230
+ <script>
231
+ // Animasi sederhana untuk input saat form loading
232
+ document.addEventListener('DOMContentLoaded', function() {
233
+ const inputs = document.querySelectorAll('input');
234
+ inputs.forEach((input, index) => {
235
+ setTimeout(() => {
236
+ input.style.opacity = '0';
237
+ input.style.transform = 'translateY(20px)';
238
+ input.style.transition = 'all 0.5s ease';
239
+
240
+ setTimeout(() => {
241
+ input.style.opacity = '1';
242
+ input.style.transform = 'translateY(0)';
243
+ }, 100);
244
+ }, index * 100);
245
+ });
246
+ });
247
+ </script>
248
+ </body>
249
+ </html>