making the UI and the logic for the record route .
- version : 2.4 //############################################// modified: app.py modified: templates/record.html //############################################//
هذا الالتزام موجود في:
249
app.py
249
app.py
@@ -16,11 +16,14 @@ app.config['PHONE_REGEX'] = re.compile(r'^09\d{8}$') # Syrian phone format
|
|||||||
DATABASE_FOLDER = os.path.join(app.root_path, 'databases')
|
DATABASE_FOLDER = os.path.join(app.root_path, 'databases')
|
||||||
DATABASE_FILE = os.path.join(DATABASE_FOLDER, 'students.db')
|
DATABASE_FILE = os.path.join(DATABASE_FOLDER, 'students.db')
|
||||||
|
|
||||||
# --- Database Functions ---
|
# --- Improved Database Functions ---
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
os.makedirs(DATABASE_FOLDER, exist_ok=True)
|
os.makedirs(DATABASE_FOLDER, exist_ok=True)
|
||||||
conn = sqlite3.connect(DATABASE_FILE)
|
conn = sqlite3.connect(DATABASE_FILE, timeout=30) # Increase timeout to 30 seconds
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
|
# Enable WAL mode for better concurrency
|
||||||
|
conn.execute('PRAGMA journal_mode=WAL')
|
||||||
|
conn.execute('PRAGMA busy_timeout=30000') # 30 second timeout
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
@@ -47,7 +50,46 @@ def init_db():
|
|||||||
''')
|
''')
|
||||||
conn.execute('CREATE INDEX idx_student_name ON students(student_name)')
|
conn.execute('CREATE INDEX idx_student_name ON students(student_name)')
|
||||||
conn.execute('CREATE INDEX idx_parent_name ON students(parent_name)')
|
conn.execute('CREATE INDEX idx_parent_name ON students(parent_name)')
|
||||||
print("Database initialized with schema constraints and indexes")
|
|
||||||
|
# NEW: Create lessons table
|
||||||
|
conn.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS lessons (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
lesson_date TEXT NOT NULL,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# NEW: Create attendance table
|
||||||
|
conn.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS attendance (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
student_id INTEGER NOT NULL,
|
||||||
|
lesson_id INTEGER NOT NULL,
|
||||||
|
pages_completed INTEGER DEFAULT 0,
|
||||||
|
attended BOOLEAN DEFAULT 1,
|
||||||
|
FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (lesson_id) REFERENCES lessons (id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(student_id, lesson_id)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# NEW: Create settings table for global variables
|
||||||
|
conn.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
key_name TEXT UNIQUE NOT NULL,
|
||||||
|
key_value TEXT NOT NULL
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# NEW: Initialize total lessons count if not exists
|
||||||
|
conn.execute('''
|
||||||
|
INSERT OR IGNORE INTO settings (key_name, key_value)
|
||||||
|
VALUES ('total_lessons', '0')
|
||||||
|
''')
|
||||||
|
|
||||||
|
print("Database initialized with attendance system")
|
||||||
|
|
||||||
@app.cli.command('init-db')
|
@app.cli.command('init-db')
|
||||||
def init_db_command():
|
def init_db_command():
|
||||||
@@ -359,10 +401,6 @@ def import_csv():
|
|||||||
def download_csv_template():
|
def download_csv_template():
|
||||||
return send_from_directory('templates', 'template.csv', as_attachment=True)
|
return send_from_directory('templates', 'template.csv', as_attachment=True)
|
||||||
|
|
||||||
@app.route('/record')
|
|
||||||
def record():
|
|
||||||
return render_template('record.html')
|
|
||||||
|
|
||||||
@app.route('/points', methods=['GET', 'POST'])
|
@app.route('/points', methods=['GET', 'POST'])
|
||||||
def points():
|
def points():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@@ -466,5 +504,202 @@ def delete_student(student_id):
|
|||||||
|
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
@app.route('/record', methods=['GET', 'POST'])
|
||||||
|
def record():
|
||||||
|
today = datetime.date.today().isoformat()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Handle form submission for attendance and pages
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
lesson_date = request.form.get('lesson_date')
|
||||||
|
if not lesson_date:
|
||||||
|
lesson_date = today
|
||||||
|
|
||||||
|
conn = get_db_connection()
|
||||||
|
|
||||||
|
# Start a transaction explicitly
|
||||||
|
conn.execute('BEGIN IMMEDIATE TRANSACTION')
|
||||||
|
|
||||||
|
# Create new lesson
|
||||||
|
lesson_result = conn.execute(
|
||||||
|
'INSERT INTO lessons (lesson_date) VALUES (?) RETURNING id',
|
||||||
|
(lesson_date,)
|
||||||
|
).fetchone()
|
||||||
|
lesson_id = lesson_result['id']
|
||||||
|
|
||||||
|
# Process attendance for each student
|
||||||
|
student_ids = request.form.getlist('student_id')
|
||||||
|
attended_students = request.form.getlist('attended')
|
||||||
|
pages_data = request.form.getlist('pages_completed')
|
||||||
|
|
||||||
|
# Use executemany for better performance
|
||||||
|
attendance_data = []
|
||||||
|
for i, student_id in enumerate(student_ids):
|
||||||
|
attended = 1 if student_id in attended_students else 0
|
||||||
|
pages = pages_data[i] if i < len(pages_data) else '0'
|
||||||
|
|
||||||
|
try:
|
||||||
|
pages_int = int(pages) if pages.strip() else 0
|
||||||
|
except ValueError:
|
||||||
|
pages_int = 0
|
||||||
|
|
||||||
|
attendance_data.append((student_id, lesson_id, pages_int, attended))
|
||||||
|
|
||||||
|
if attendance_data:
|
||||||
|
conn.executemany('''
|
||||||
|
INSERT OR REPLACE INTO attendance (student_id, lesson_id, pages_completed, attended)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
''', attendance_data)
|
||||||
|
|
||||||
|
# Update total lessons count
|
||||||
|
current_total = get_total_lessons(conn)
|
||||||
|
update_total_lessons(conn, current_total + 1)
|
||||||
|
|
||||||
|
# Commit the transaction
|
||||||
|
conn.commit()
|
||||||
|
flash('تم حفظ بيانات الحضور والإنجاز بنجاح!', 'success')
|
||||||
|
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
if conn:
|
||||||
|
conn.rollback()
|
||||||
|
if 'locked' in str(e):
|
||||||
|
flash('قاعدة البيانات مشغولة حالياً. الرجاء المحاولة مرة أخرى بعد بضع ثوانٍ.', 'danger')
|
||||||
|
else:
|
||||||
|
flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
|
||||||
|
except Exception as e:
|
||||||
|
if conn:
|
||||||
|
conn.rollback()
|
||||||
|
flash(f'خطأ في حفظ البيانات: {str(e)}', 'danger')
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return redirect(url_for('record'))
|
||||||
|
|
||||||
|
else: # GET request
|
||||||
|
students_data = []
|
||||||
|
conn = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = get_db_connection()
|
||||||
|
students_data = get_students_with_attendance(conn)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
flash(f'خطأ في تحميل البيانات: {str(e)}', 'danger')
|
||||||
|
students_data = []
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render_template('record.html', students=students_data, today=today)
|
||||||
|
|
||||||
|
# Helper functions that accept connection as parameter
|
||||||
|
def get_total_lessons(conn=None):
|
||||||
|
close_conn = False
|
||||||
|
if conn is None:
|
||||||
|
conn = get_db_connection()
|
||||||
|
close_conn = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = conn.execute("SELECT key_value FROM settings WHERE key_name = 'total_lessons'").fetchone()
|
||||||
|
return int(result['key_value']) if result else 0
|
||||||
|
finally:
|
||||||
|
if close_conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def update_total_lessons(conn, new_total):
|
||||||
|
conn.execute("UPDATE settings SET key_value = ? WHERE key_name = 'total_lessons'", (str(new_total),))
|
||||||
|
|
||||||
|
def get_students_with_attendance(conn):
|
||||||
|
"""Get students with their attendance statistics"""
|
||||||
|
students = conn.execute('''
|
||||||
|
SELECT s.id, s.student_name, s.points,
|
||||||
|
COUNT(a.id) as lessons_attended,
|
||||||
|
COALESCE(SUM(a.pages_completed), 0) as total_pages,
|
||||||
|
(SELECT key_value FROM settings WHERE key_name = 'total_lessons') as total_lessons
|
||||||
|
FROM students s
|
||||||
|
LEFT JOIN attendance a ON s.id = a.student_id AND a.attended = 1
|
||||||
|
GROUP BY s.id, s.student_name, s.points
|
||||||
|
ORDER BY s.student_name ASC
|
||||||
|
''').fetchall()
|
||||||
|
|
||||||
|
students_data = []
|
||||||
|
for student in students:
|
||||||
|
total_lessons = int(student['total_lessons']) if student['total_lessons'] else 0
|
||||||
|
attendance_percentage = (student['lessons_attended'] / total_lessons * 100) if total_lessons > 0 else 0
|
||||||
|
|
||||||
|
students_data.append({
|
||||||
|
'id': student['id'],
|
||||||
|
'student_name': student['student_name'],
|
||||||
|
'points': student['points'],
|
||||||
|
'lessons_attended': student['lessons_attended'],
|
||||||
|
'total_pages': student['total_pages'],
|
||||||
|
'attendance_percentage': round(attendance_percentage, 1),
|
||||||
|
'total_lessons': total_lessons
|
||||||
|
})
|
||||||
|
|
||||||
|
return students_data
|
||||||
|
|
||||||
|
# NEW: Route to add a lesson manually
|
||||||
|
@app.route('/add_lesson', methods=['POST'])
|
||||||
|
def add_lesson():
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
lesson_date = request.form.get('lesson_date', datetime.date.today().isoformat())
|
||||||
|
|
||||||
|
conn = get_db_connection()
|
||||||
|
conn.execute('BEGIN IMMEDIATE TRANSACTION')
|
||||||
|
|
||||||
|
# Create new lesson
|
||||||
|
conn.execute('INSERT INTO lessons (lesson_date) VALUES (?)', (lesson_date,))
|
||||||
|
|
||||||
|
# Update total lessons
|
||||||
|
current_total = get_total_lessons(conn)
|
||||||
|
update_total_lessons(conn, current_total + 1)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
flash('تم إضافة درس جديد بنجاح!', 'success')
|
||||||
|
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
if conn:
|
||||||
|
conn.rollback()
|
||||||
|
if 'locked' in str(e):
|
||||||
|
flash('قاعدة البيانات مشغولة حالياً. الرجاء المحاولة مرة أخرى بعد بضع ثوانٍ.', 'danger')
|
||||||
|
else:
|
||||||
|
flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
|
||||||
|
except Exception as e:
|
||||||
|
if conn:
|
||||||
|
conn.rollback()
|
||||||
|
flash(f'خطأ في إضافة الدرس: {str(e)}', 'danger')
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return redirect(url_for('record'))
|
||||||
|
|
||||||
|
# NEW: Route to get attendance history for a student
|
||||||
|
@app.route('/student_attendance/<int:student_id>')
|
||||||
|
def student_attendance(student_id):
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
attendance_history = conn.execute('''
|
||||||
|
SELECT l.lesson_date, a.pages_completed, a.attended
|
||||||
|
FROM attendance a
|
||||||
|
JOIN lessons l ON a.lesson_id = l.id
|
||||||
|
WHERE a.student_id = ?
|
||||||
|
ORDER BY l.lesson_date DESC
|
||||||
|
LIMIT 20
|
||||||
|
''', (student_id,)).fetchall()
|
||||||
|
|
||||||
|
student = conn.execute('SELECT student_name FROM students WHERE id = ?', (student_id,)).fetchone()
|
||||||
|
|
||||||
|
return render_template('student_attendance.html',
|
||||||
|
attendance_history=attendance_history,
|
||||||
|
student=student)
|
||||||
|
except Exception as e:
|
||||||
|
flash(f'خطأ في تحميل سجل الحضور: {str(e)}', 'danger')
|
||||||
|
return redirect(url_for('record'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|||||||
@@ -1,31 +1,195 @@
|
|||||||
{% extends 'template.html' %}
|
{% extends 'template.html' %}
|
||||||
|
|
||||||
{% block title %}تسجيل حضور أو حفظ{% endblock %}
|
{% block title %}تسجيل حضور وحفظ المحفوظات{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<header class="text-center"> {# Removed mb-8 from header, now handled by h1 and nav #}
|
<header class="text-center">
|
||||||
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">تسجيل حضور الطلاب وحفظ المحفوظات</h1> {# Added mb-6 for spacing below title #}
|
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">نظام متابعة الحضور والإنجاز</h1>
|
||||||
{# Navigation Bar - More Eye-Attractive Styling #}
|
<nav class="mb-8">
|
||||||
<nav class="mb-8"> {# Kept mb-8 for space below nav #}
|
<ul class="flex justify-center space-x-4 space-x-reverse bg-white p-2 rounded-full shadow-lg inline-flex">
|
||||||
<ul class="flex justify-center space-x-4 space-x-reverse bg-white p-2 rounded-full shadow-lg inline-flex"> {# Added padding, background, rounded corners, shadow, and inline-flex for better alignment #}
|
|
||||||
<li>
|
<li>
|
||||||
{# Inactive link styling #}
|
|
||||||
<a href="{{ url_for('index') }}" class="text-gray-700 hover:text-blue-700 hover:bg-blue-50 font-medium px-6 py-3 rounded-full transition-all duration-300 ease-in-out hover:shadow-sm transform hover:scale-105">الصفحة الرئيسية</a>
|
<a href="{{ url_for('index') }}" class="text-gray-700 hover:text-blue-700 hover:bg-blue-50 font-medium px-6 py-3 rounded-full transition-all duration-300 ease-in-out hover:shadow-sm transform hover:scale-105">الصفحة الرئيسية</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{# Active link styling #}
|
|
||||||
<a href="{{ url_for('record') }}" class="text-white bg-blue-600 hover:bg-blue-700 font-semibold px-6 py-3 rounded-full transition-all duration-300 ease-in-out shadow-md">تسجيل حضور أو حفظ</a>
|
<a href="{{ url_for('record') }}" class="text-white bg-blue-600 hover:bg-blue-700 font-semibold px-6 py-3 rounded-full transition-all duration-300 ease-in-out shadow-md">تسجيل حضور أو حفظ</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{# Inactive link styling #}
|
|
||||||
<a href="{{ url_for('points') }}" class="text-gray-700 hover:text-blue-700 hover:bg-blue-50 font-medium px-6 py-3 rounded-full transition-all duration-300 ease-in-out hover:shadow-sm transform hover:scale-105">النقاط</a>
|
<a href="{{ url_for('points') }}" class="text-gray-700 hover:text-blue-700 hover:bg-blue-50 font-medium px-6 py-3 rounded-full transition-all duration-300 ease-in-out hover:shadow-sm transform hover:scale-105">النقاط</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- Attendance Recording Section -->
|
||||||
<div class="bg-white p-8 rounded-xl shadow-lg mb-8">
|
<div class="bg-white p-8 rounded-xl shadow-lg mb-8">
|
||||||
<p class="text-gray-600 text-center">هذه صفحة لتسجيل حضور الطلاب وتحديث تقدمهم في حفظ المحفوظات.</p>
|
<h2 class="text-2xl font-semibold mb-6 text-gray-800 border-b pb-4">تسجيل حضور وإنجاز اليوم</h2>
|
||||||
{# Content for attendance/memorization recording will go here #}
|
|
||||||
|
<form id="attendance-form" action="{{ url_for('record') }}" method="POST">
|
||||||
|
<input type="hidden" name="lesson_date" value="{{ today }}">
|
||||||
|
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="search_student_input" class="block text-sm font-medium text-gray-700 mb-2">ابحث عن طالب</label>
|
||||||
|
<input type="text" id="search_student_input" placeholder="اكتب اسم الطالب للبحث..."
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 text-right" dir="rtl">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200 text-right">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
<input type="checkbox" id="select_all_attendance" class="h-4 w-4 text-blue-600">
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">اسم الطالب</th>
|
||||||
|
<th class="px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">نسبة الحضور</th>
|
||||||
|
<th class="px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">الصفحات المنجزة</th>
|
||||||
|
<th class="px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">صفحات اليوم</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-200" id="attendance-table-body">
|
||||||
|
{% for student in students %}
|
||||||
|
<tr class="hover:bg-gray-50 transition-colors duration-200 student-row" data-student-name="{{ student.student_name }}">
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<input type="checkbox" name="attended" value="{{ student.id }}"
|
||||||
|
class="attendance-checkbox h-4 w-4 text-blue-600" checked>
|
||||||
|
<input type="hidden" name="student_id" value="{{ student.id }}">
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-sm font-medium text-gray-900">{{ student.student_name }}</span>
|
||||||
|
<span class="ml-2 px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded-full">{{ student.points }} نقاط</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
||||||
|
<div class="bg-green-600 h-2.5 rounded-full"
|
||||||
|
style="width: {{ student.attendance_percentage }}%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-gray-600 mr-2">{{ student.attendance_percentage }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">
|
||||||
|
{{ student.lessons_attended }} من {{ student.total_lessons }} درس
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-600">
|
||||||
|
<span class="font-semibold text-green-600">{{ student.total_pages }}</span> صفحة
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<input type="number" name="pages_completed" value="0" min="0" max="20"
|
||||||
|
class="pages-input w-20 px-2 py-1 border border-gray-300 rounded text-center"
|
||||||
|
data-student-id="{{ student.id }}">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center py-6 text-gray-500">
|
||||||
|
لم تتم إضافة أي طالب بعد.
|
||||||
|
<a href="{{ url_for('index') }}" class="text-blue-600 hover:underline">أضف طلاباً أولاً</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8 text-center">
|
||||||
|
<button type="submit" id="submit-attendance-btn"
|
||||||
|
class="px-8 py-3 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 transition-all"
|
||||||
|
{% if not students %}disabled{% endif %}>
|
||||||
|
<i class="fas fa-save ml-2"></i>حفظ الحضور والإنجاز
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Today's date for the lesson date input
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
document.querySelector('input[name="lesson_date"]').value = today;
|
||||||
|
|
||||||
|
// Search functionality
|
||||||
|
const searchInput = document.getElementById('search_student_input');
|
||||||
|
const studentRows = document.querySelectorAll('.student-row');
|
||||||
|
|
||||||
|
searchInput.addEventListener('input', function() {
|
||||||
|
const searchTerm = this.value.trim().toLowerCase();
|
||||||
|
|
||||||
|
studentRows.forEach(row => {
|
||||||
|
const studentName = row.getAttribute('data-student-name').toLowerCase();
|
||||||
|
if (studentName.includes(searchTerm)) {
|
||||||
|
row.style.display = '';
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select all functionality
|
||||||
|
const selectAllCheckbox = document.getElementById('select_all_attendance');
|
||||||
|
const attendanceCheckboxes = document.querySelectorAll('.attendance-checkbox');
|
||||||
|
|
||||||
|
selectAllCheckbox.addEventListener('change', function() {
|
||||||
|
attendanceCheckboxes.forEach(checkbox => {
|
||||||
|
if (checkbox.closest('tr').style.display !== 'none') {
|
||||||
|
checkbox.checked = this.checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quick select all button
|
||||||
|
document.getElementById('quick-select-all').addEventListener('click', function() {
|
||||||
|
attendanceCheckboxes.forEach(checkbox => {
|
||||||
|
if (checkbox.closest('tr').style.display !== 'none') {
|
||||||
|
checkbox.checked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
selectAllCheckbox.checked = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quick set pages button
|
||||||
|
document.getElementById('quick-set-pages').addEventListener('click', function() {
|
||||||
|
document.querySelectorAll('.pages-input').forEach(input => {
|
||||||
|
if (input.closest('tr').style.display !== 'none') {
|
||||||
|
input.value = '1';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update select all checkbox when individual checkboxes change
|
||||||
|
attendanceCheckboxes.forEach(checkbox => {
|
||||||
|
checkbox.addEventListener('change', function() {
|
||||||
|
const visibleCheckboxes = Array.from(attendanceCheckboxes).filter(cb =>
|
||||||
|
cb.closest('tr').style.display !== 'none'
|
||||||
|
);
|
||||||
|
const allChecked = visibleCheckboxes.every(cb => cb.checked);
|
||||||
|
selectAllCheckbox.checked = allChecked;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form submission handling
|
||||||
|
document.getElementById('attendance-form').addEventListener('submit', function() {
|
||||||
|
const submitBtn = document.getElementById('submit-attendance-btn');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin ml-2"></i>جاري الحفظ...';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pages-input:focus {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
ring: 2px;
|
||||||
|
ring-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attendance-checkbox:checked {
|
||||||
|
background-color: #10b981;
|
||||||
|
border-color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-row:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم