adding/removing students functionality

- version : 1.5
- description : adding the functionality to add/remove points for any
  student .
//############################################//
	modified:   app.py
	modified:   templates/index.html
	modified:   templates/modify_info.html
	modified:   templates/points.html
	modified:   templates/template.csv
//############################################//
هذا الالتزام موجود في:
2025-07-10 13:00:19 +03:00
الأصل bf2b6647ce
التزام 55c5c4d6a8
5 ملفات معدلة مع 303 إضافات و115 حذوفات

168
app.py
عرض الملف

@@ -3,7 +3,7 @@ import csv
import io
import re
from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory
import datetime # Import datetime for current date
import datetime
# --- App Setup ---
app = Flask(__name__)
@@ -32,8 +32,9 @@ def init_db():
school_name TEXT NOT NULL,
address TEXT NOT NULL,
memorizing TEXT NOT NULL,
notes TEXT, -- New: Optional notes field
registration_date TEXT NOT NULL -- New: Registration date
notes TEXT,
registration_date TEXT NOT NULL,
points INTEGER DEFAULT 0 NOT NULL -- New: Points field, defaults to 0
)
''')
# Add indexes for faster search
@@ -50,6 +51,7 @@ def init_db_command():
def validate_phone(phone):
return bool(app.config['PHONE_REGEX'].match(phone)) if phone else True
# MODIFIED: Removed 'points' from validation
def validate_student_data(form_data, is_csv=False):
errors = []
required_fields = ['student_name', 'age', 'parent_name',
@@ -84,7 +86,6 @@ def validate_student_data(form_data, is_csv=False):
reg_date_str = form_data.get('registration_date')
if reg_date_str:
try:
# Attempt to parse the date. Format 'YYYY-MM-DD' is common for HTML date input
datetime.datetime.strptime(reg_date_str, '%Y-%m-%d')
except ValueError:
errors.append("تاريخ التسجيل غير صالح. يجب أن يكون بالصيغة YYYY-MM-DD")
@@ -96,8 +97,12 @@ def validate_student_data(form_data, is_csv=False):
def index():
try:
with get_db_connection() as conn:
# MODIFIED: Added points to the SELECT statement
students = conn.execute('''
SELECT * FROM students
SELECT id, student_name, age, parent_name, parent_phone_1, parent_phone_2,
student_phone, grade, school_name, address, memorizing, notes,
registration_date, points
FROM students
ORDER BY student_name ASC
''').fetchall()
return render_template('index.html', students=students)
@@ -115,10 +120,9 @@ def add_student():
flash(error, 'danger')
return redirect(url_for('index'))
# Get current date if registration_date is not provided
registration_date = form_data.get('registration_date')
if not registration_date:
registration_date = datetime.date.today().isoformat() # YYYY-MM-DD format
registration_date = datetime.date.today().isoformat()
try:
student_data = (
@@ -132,11 +136,15 @@ def add_student():
form_data['school_name'],
form_data['address'],
form_data['memorizing'],
form_data.get('notes') or None, # New: notes
registration_date # New: registration_date
form_data.get('notes') or None,
registration_date,
# MODIFIED: No points input from form, it will use the DEFAULT 0 from schema
# You don't need to explicitly pass 0 here, as the database will handle it.
# If you did pass it, it would be `0` here.
)
with get_db_connection() as conn:
# MODIFIED: Removed 'points' from INSERT statement
conn.execute('''
INSERT INTO students (
student_name, age, parent_name,
@@ -160,10 +168,14 @@ def add_student():
@app.route('/modify_student/<int:student_id>', methods=['GET', 'POST'])
def modify_student(student_id):
if request.method == 'GET':
# Display the modification form
try:
with get_db_connection() as conn:
student = conn.execute('SELECT * FROM students WHERE id = ?', (student_id,)).fetchone()
# MODIFIED: Added points to the SELECT statement
student = conn.execute('''
SELECT id, student_name, age, parent_name, parent_phone_1, parent_phone_2,
student_phone, grade, school_name, address, memorizing, notes,
registration_date, points
FROM students WHERE id = ?''', (student_id,)).fetchone()
if student is None:
flash('الطالب غير موجود.', 'danger')
@@ -175,27 +187,39 @@ def modify_student(student_id):
return redirect(url_for('index'))
elif request.method == 'POST':
# Process the modification form submission
form_data = request.form
validation_errors = validate_student_data(form_data)
if validation_errors:
for error in validation_errors:
flash(error, 'danger')
# Fetch student again to re-render form with current data and errors
try:
with get_db_connection() as conn:
student = conn.execute('SELECT * FROM students WHERE id = ?', (student_id,)).fetchone()
# MODIFIED: Added points to the SELECT statement
student = conn.execute('''
SELECT id, student_name, age, parent_name, parent_phone_1, parent_phone_2,
student_phone, grade, school_name, address, memorizing, notes,
registration_date, points
FROM students WHERE id = ?''', (student_id,)).fetchone()
return render_template('modify_info.html', student=student)
except sqlite3.Error as e:
flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
return redirect(url_for('index'))
# Get current date if registration_date is not provided during modification
registration_date = form_data.get('registration_date')
if not registration_date:
registration_date = datetime.date.today().isoformat() # YYYY-MM-DD format
# MODIFIED: Preserve original registration_date if not provided on update
# Fetch original date from DB if not provided in form.
# This requires fetching the student first if registration_date isn't present in form_data
try:
with get_db_connection() as conn:
original_student = conn.execute('SELECT registration_date FROM students WHERE id = ?', (student_id,)).fetchone()
if original_student:
registration_date = original_student['registration_date']
else:
registration_date = datetime.date.today().isoformat() # Fallback
except sqlite3.Error:
registration_date = datetime.date.today().isoformat() # Fallback on DB error
try:
student_data = (
@@ -209,12 +233,15 @@ def modify_student(student_id):
form_data['school_name'],
form_data['address'],
form_data['memorizing'],
form_data.get('notes') or None, # New: notes
registration_date, # New: registration_date
student_id # The ID is the last parameter for the WHERE clause
form_data.get('notes') or None,
registration_date,
# MODIFIED: No points input from form, ensure points are *not* updated here
student_id
)
with get_db_connection() as conn:
# MODIFIED: Removed 'points' from UPDATE statement in modify_student
# The points should only be modifiable via the points page.
conn.execute('''
UPDATE students SET
student_name = ?,
@@ -227,8 +254,8 @@ def modify_student(student_id):
school_name = ?,
address = ?,
memorizing = ?,
notes = ?, -- New
registration_date = ? -- New
notes = ?,
registration_date = ?
WHERE id = ?
''', student_data)
conn.commit()
@@ -265,7 +292,7 @@ def import_csv():
valid_rows = []
row_errors = []
# Assuming CSV now has 12 columns: existing 10 + notes + registration_date
# MODIFIED: CSV now has 12 columns: existing 10 + notes + registration_date (points are DEFAULT)
expected_columns = 12
for i, row in enumerate(csv_reader, 1):
@@ -285,8 +312,9 @@ def import_csv():
'school_name': row[7].strip(),
'address': row[8].strip(),
'memorizing': row[9].strip(),
'notes': row[10].strip(), # New: notes
'registration_date': row[11].strip() # New: registration_date
'notes': row[10].strip(),
'registration_date': row[11].strip()
# MODIFIED: No 'points' expected from CSV input, it will default to 0
}
# If registration_date is empty in CSV, set to current date
@@ -299,7 +327,7 @@ def import_csv():
row_errors.append(f'السطر {i}: {"; ".join(errors)}')
continue
# Prepare for insertion
# Prepare for insertion (points will default to 0)
valid_rows.append((
student_data['student_name'],
int(student_data['age']),
@@ -311,20 +339,21 @@ def import_csv():
student_data['school_name'],
student_data['address'],
student_data['memorizing'],
student_data['notes'] or None, # New: notes
student_data['registration_date'] # New: registration_date
student_data['notes'] or None,
student_data['registration_date']
# No points value here, it relies on DEFAULT 0
))
# Process validation results
if row_errors:
flash(f'تم العثور على أخطاء في {len(row_errors)} سطراً', 'warning')
for error in row_errors[:5]: # Show first 5 errors
for error in row_errors[:5]:
flash(error, 'danger')
if len(row_errors) > 5:
flash(f'...و {len(row_errors)-5} أخطاء إضافية', 'danger')
if valid_rows:
with get_db_connection() as conn:
# MODIFIED: Removed 'points' from INSERT statement for CSV import
conn.executemany('''
INSERT INTO students (
student_name, age, parent_name,
@@ -349,7 +378,6 @@ def import_csv():
@app.route('/download_csv_template')
def download_csv_template():
# The directory where the template.csv is located (your templates folder)
# The second argument is the filename to be sent
return send_from_directory(app.template_folder, 'template.csv', as_attachment=True)
# Route for the "تسجيل حضور أو حفظ" page
@@ -357,16 +385,86 @@ def download_csv_template():
def record():
return render_template('record.html')
# Route for the "النقاط" page
@app.route('/points')
# MODIFIED: Points route is already updated from previous turn, just ensure the logic
# prevents negative points (which it already does).
@app.route('/points', methods=['GET', 'POST'])
def points():
return render_template('points.html')
if request.method == 'POST':
student_id = request.form.get('student_id')
point_amount_str = request.form.get('point_amount')
operation = request.form.get('operation') # 'add' or 'remove'
if not student_id or not point_amount_str or not operation:
flash('الرجاء تعبئة جميع الحقول المطلوبة.', 'danger')
return redirect(url_for('points'))
try:
student_id = int(student_id)
point_amount = int(point_amount_str)
if point_amount < 0:
flash('قيمة النقاط لا يمكن أن تكون سالبة.', 'danger')
return redirect(url_for('points'))
if point_amount == 0:
flash('الرجاء إدخال قيمة نقاط أكبر من صفر.', 'warning')
return redirect(url_for('points'))
with get_db_connection() as conn:
student = conn.execute('SELECT points, student_name FROM students WHERE id = ?', (student_id,)).fetchone()
if student is None:
flash('الطالب غير موجود.', 'danger')
return redirect(url_for('points'))
current_points = student['points']
student_name = student['student_name']
new_points = current_points
if operation == 'add':
new_points += point_amount
flash_message = f'تمت إضافة {point_amount} نقطة للطالب {student_name}.'
flash_category = 'success'
elif operation == 'remove':
# Prevent negative points: cap at 0 if removal amount exceeds current points
if current_points < point_amount:
flash_message = (f'لا يمكن خصم {point_amount} نقطة من {student_name} حيث يمتلك {current_points} نقطة فقط. '
f'تم خصم {current_points} نقطة وتم تعيين النقاط إلى 0.')
new_points = 0 # Ensure it doesn't go below zero
flash_category = 'warning'
else:
new_points -= point_amount
flash_message = f'تم خصم {point_amount} نقطة من الطالب {student_name}.'
flash_category = 'success'
else:
flash('عملية غير صالحة.', 'danger')
return redirect(url_for('points'))
conn.execute('UPDATE students SET points = ? WHERE id = ?', (new_points, student_id))
conn.commit()
flash(f'{flash_message} النقاط الجديدة لـ {student_name}: {new_points}', flash_category)
except ValueError:
flash('النقاط وقيمة الطالب يجب أن تكون أرقاماً صحيحة.', 'danger')
except sqlite3.Error as e:
flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
except Exception as e:
flash(f'خطأ غير متوقع: {str(e)}', 'danger')
return redirect(url_for('points'))
else: # GET request
try:
with get_db_connection() as conn:
students = conn.execute('SELECT id, student_name, points FROM students ORDER BY student_name ASC').fetchall()
return render_template('points.html', students=students)
except sqlite3.Error as e:
flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
return render_template('points.html', students=[])
@app.route('/delete_student/<int:student_id>', methods=['POST'])
def delete_student(student_id):
try:
with get_db_connection() as conn:
# First, check if the student exists
student = conn.execute('SELECT id FROM students WHERE id = ?', (student_id,)).fetchone()
if student is None:
flash('الطالب غير موجود.', 'danger')

عرض الملف

@@ -1,24 +1,20 @@
{# templates/index.html #}
{% extends 'template.html' %}
{% block title %}الصفحة الرئيسية{% endblock %} {# Specific title for this page #}
{% block title %}الصفحة الرئيسية{% endblock %}
{% block content %}
<header class="text-center"> {# Removed mb-8 from header, now handled by h1 and nav #}
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">نظام إدارة الطلاب</h1> {# Added mb-6 for spacing below title #}
{# Navigation Bar - More Eye-Attractive Styling #}
<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"> {# Added padding, background, rounded corners, shadow, and inline-flex for better alignment #}
<header class="text-center">
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">نظام إدارة الطلاب</h1>
<nav class="mb-8">
<ul class="flex justify-center space-x-4 space-x-reverse bg-white p-2 rounded-full shadow-lg inline-flex">
<li>
{# Active link styling #}
<a href="{{ url_for('index') }}" 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>
{# Inactive link styling #}
<a href="{{ url_for('record') }}" 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>
{# 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>
</li>
</ul>
@@ -95,8 +91,7 @@
<p class="mt-2" style="direction: ltr; text-align: right;">
<code class="text-xs font-mono">اسم الطالب, العمر, اسم ولي الأمر, هاتف ولي الأمر 1, هاتف ولي الأمر 2, هاتف الطالب, الصف, اسم المدرسة, العنوان, المحفوظات, الملاحظات, تاريخ التسجيل</code>
</p>
<p>إذا كانت الحقول الاختيارية (هاتف ولي الأمر 2، هاتف الطالب، الملاحظات، تاريخ التسجيل) فارغة، اتركها كذلك. تاريخ التسجيل سيُعيّن تلقائياً لليوم الحالي إذا تُرِك فارغاً.</p>
{# Added link to download CSV template #}
<p>إذا كانت الحقول الاختيارية (هاتف ولي الأمر 2، هاتف الطالب، الملاحظات، تاريخ التسجيل) فارغة، اتركها كذلك. تاريخ التسجيل سيُعيّن تلقائياً لليوم الحالي إذا تُرِك فارغاً. **النقاط تبدأ من صفر ولا تُدرج في الملف CSV.**</p>
<p class="mt-3">
<a href="{{ url_for('download_csv_template') }}" class="text-blue-600 hover:underline font-semibold" download>
تنزيل قالب CSV
@@ -128,7 +123,6 @@
</button>
</div>
{# Search input box for fuzzy finding #}
<div class="mb-6">
<label for="search-input" class="sr-only">البحث عن طالب</label>
<input type="text" id="search-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">
@@ -148,8 +142,9 @@
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">المدرسة</th>
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">العنوان</th>
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">المحفوظات</th>
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">تاريخ التسجيل</th> {# New Header #}
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">ملاحظات</th> {# New Header #}
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">تاريخ التسجيل</th>
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">ملاحظات</th>
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">النقاط</th> {# ADDED: Points header for table #}
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="students-table-body">
@@ -179,17 +174,17 @@
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ student['school_name'] }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ student['address'] }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ student['memorizing'] }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ student['registration_date'] }}</td> {# Display registration_date #}
<td class="px-6 py-4 text-sm text-gray-600 max-w-xs overflow-hidden text-ellipsis">{{ student['notes'] or 'لا يوجد' }}</td> {# Display notes #}
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ student['registration_date'] }}</td>
<td class="px-6 py-4 text-sm text-gray-600 max-w-xs overflow-hidden text-ellipsis">{{ student['notes'] or 'لا يوجد' }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-blue-700 font-semibold">{{ student['points'] }}</td> {# ADDED: Display points #}
</tr>
{% else %}
<tr id="no-students-row">
<td colspan="12" class="text-center py-6 text-gray-500">لم تتم إضافة أي طالب بعد.</td> {# Adjusted colspan #}
<td colspan="13" class="text-center py-6 text-gray-500">لم تتم إضافة أي طالب بعد.</td> {# Adjusted colspan #}
</tr>
{% endfor %}
</tbody>
</table>
{# Message to display when no search results are found #}
<div id="no-search-results" class="text-center py-6 text-gray-500 hidden">
لا توجد نتائج بحث مطابقة.
</div>
@@ -197,7 +192,6 @@
</div>
<script>
// Get references to all necessary elements
const csvFileInput = document.getElementById('csv-file-input');
const chooseFileButton = document.getElementById('choose-file-button');
const fileNameDisplay = document.getElementById('file-name-display');
@@ -207,7 +201,6 @@
const addStudentForm = document.getElementById('add-student-form');
const submitAddButton = document.getElementById('submit-add-button');
// New elements for search functionality
const searchInput = document.getElementById('search-input');
const studentsTableBody = document.getElementById('students-table-body');
const noStudentsRow = document.getElementById('no-students-row');
@@ -229,8 +222,6 @@
});
});
// Function to perform a fuzzy match
// Checks if all characters of the pattern exist sequentially in the text, allowing for other characters in between.
function fuzzyMatch(pattern, text) {
pattern = pattern.toLowerCase();
text = text.toLowerCase();
@@ -246,32 +237,24 @@
return patternIdx === pattern.length;
}
// Function to highlight occurrences of a term within text
function highlightText(text, term) {
if (!term) return text;
// Escape special characters in the term to be used in regex
const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Use a regular expression with 'gi' for global and case-insensitive matching
const regex = new RegExp(escapedTerm, 'gi');
// Replace all occurrences of the term with the highlighted version
return text.replace(regex, match => `<span class="highlight">${match}</span>`);
}
// Function to filter students based on search input
function filterStudents() {
const searchTerm = searchInput.value.trim();
let foundResults = 0;
// Hide the "no students" row if it exists
if (noStudentsRow) {
noStudentsRow.classList.add('hidden');
}
if (searchTerm === '') {
// If search term is empty, show all rows and restore their original content
allStudentData.forEach(data => {
data.element.classList.remove('hidden');
// Restore original HTML for highlighted cells
if (data.element.children[2]) {
data.element.children[2].innerHTML = data.originalStudentNameHTML;
}
@@ -279,37 +262,31 @@
data.element.children[4].innerHTML = data.originalParentNameHTML;
}
});
noSearchResultsDiv.classList.add('hidden'); // Hide no results message
// If there were no students initially (allStudentData is empty), show the "no students" row again
noSearchResultsDiv.classList.add('hidden');
if (noStudentsRow && allStudentData.length === 0) {
noStudentsRow.classList.remove('hidden');
}
return;
}
// Iterate through the stored student data
allStudentData.forEach(data => {
const studentName = data.student_name;
const parentName = data.parent_name;
// Perform fuzzy search ONLY on student name or parent name
const isMatch = fuzzyMatch(searchTerm, studentName) || fuzzyMatch(searchTerm, parentName);
if (isMatch) {
data.element.classList.remove('hidden'); // Show the row
data.element.classList.remove('hidden');
foundResults++;
// Apply highlighting to the student name cell
if (data.element.children[2]) {
data.element.children[2].innerHTML = highlightText(studentName, searchTerm);
}
// Apply highlighting to the parent name cell
if (data.element.children[4]) {
data.element.children[4].innerHTML = highlightText(parentName, searchTerm);
}
} else {
data.element.classList.add('hidden'); // Hide the row
// Restore original content if the row is hidden
data.element.classList.add('hidden');
if (data.element.children[2]) {
data.element.children[2].innerHTML = data.originalStudentNameHTML;
}
@@ -319,7 +296,6 @@
}
});
// Show/hide the "no search results" message based on found results
if (foundResults > 0) {
noSearchResultsDiv.classList.add('hidden');
} else {
@@ -327,57 +303,42 @@
}
}
// Event listener for the search input
searchInput.addEventListener('input', filterStudents);
// When the "Choose File" button is clicked, it programmatically clicks the hidden file input.
chooseFileButton.addEventListener('click', () => {
csvFileInput.click();
});
// When a file is selected using the file input...
csvFileInput.addEventListener('change', () => {
if (csvFileInput.files.length > 0) {
const fileName = csvFileInput.files[0].name;
fileNameDisplay.textContent = `الملف المختار: ${fileName}`;
// Show the file name display and the submit button
fileNameDisplay.classList.remove('hidden');
submitImportButton.classList.remove('hidden');
// Hide the original "Choose File" button to avoid confusion
chooseFileButton.classList.add('hidden');
} else {
// If no file is selected (e.g., user cancels file dialog)
fileNameDisplay.classList.add('hidden');
submitImportButton.classList.add('hidden');
chooseFileButton.classList.remove('hidden');
}
});
// Toggle the visibility of the "Add New Student" form
addManuallyButton.addEventListener('click', () => {
addStudentSection.classList.toggle('hidden');
});
// --- Prevent Double Submissions ---
// For the CSV Import Form
importForm.addEventListener('submit', () => {
submitImportButton.disabled = true;
submitImportButton.textContent = 'جاري الرفع...'; // "Uploading..."
submitImportButton.textContent = 'جاري الرفع...';
});
// For the Manual Add Student Form
addStudentForm.addEventListener('submit', () => {
submitAddButton.disabled = true;
submitAddButton.textContent = 'جاري الحفظ...'; // "Saving..."
submitAddButton.textContent = 'جاري الحفظ...';
});
// --- New JavaScript for Delete Confirmation ---
function confirmDelete(studentId, studentName) {
if (confirm(`هل أنت متأكد أنك تريد حذف الطالب "${studentName}"؟ هذا الإجراء لا يمكن التراجع عنه.`)) {
// If confirmed, submit the hidden form for deletion
document.getElementById(`delete-form-${studentId}`).submit();
}
}

عرض الملف

@@ -1,3 +1,4 @@
{# templates/modify_info.html #}
{% extends 'template.html' %}
{% block title %}تعديل معلومات الطالب{% endblock %}
@@ -6,7 +7,6 @@
<header class="text-center">
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">تعديل معلومات الطالب</h1>
{# Navigation Bar #}
<nav class="mb-8">
<ul class="flex justify-center space-x-4 space-x-reverse bg-white p-2 rounded-full shadow-lg inline-flex">
<li>
@@ -87,14 +87,13 @@
</div>
<script>
// Prevent double submission for the modify form
const modifyStudentForm = document.getElementById('modify-student-form');
const submitModifyButton = document.getElementById('submit-modify-button');
if (modifyStudentForm && submitModifyButton) {
modifyStudentForm.addEventListener('submit', () => {
submitModifyButton.disabled = true;
submitModifyButton.textContent = 'جاري الحفظ...'; // "Saving..."
submitModifyButton.textContent = 'جاري الحفظ...';
});
}
</script>

عرض الملف

@@ -3,21 +3,17 @@
{% block title %}النقاط{% endblock %}
{% block content %}
<header class="text-center"> {# Removed mb-8 from header, now handled by h1 and nav #}
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">نظام نقاط الطلاب</h1> {# Added mb-6 for spacing below title #}
{# Navigation Bar - More Eye-Attractive Styling #}
<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"> {# Added padding, background, rounded corners, shadow, and inline-flex for better alignment #}
<header class="text-center">
<h1 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">نظام نقاط الطلاب</h1>
<nav class="mb-8">
<ul class="flex justify-center space-x-4 space-x-reverse bg-white p-2 rounded-full shadow-lg inline-flex">
<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>
</li>
<li>
{# Inactive link styling #}
<a href="{{ url_for('record') }}" 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>
{# Active link styling #}
<a href="{{ url_for('points') }}" 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>
</ul>
@@ -25,7 +21,141 @@
</header>
<div class="bg-white p-8 rounded-xl shadow-lg mb-8">
<p class="text-gray-600 text-center">هذه صفحة لعرض وإدارة نقاط الطلاب.</p>
{# Content for student points/rewards will go here #}
<h2 class="text-2xl font-semibold mb-6 text-gray-800 border-b pb-4">إدارة نقاط الطلاب</h2>
<form id="points-form" action="{{ url_for('points') }}" method="POST">
<div class="mb-6">
<label for="student_id" class="block text-sm font-medium text-gray-700 mb-1">اسم الطالب</label>
<select name="student_id" id="student_id" required 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">
<option value="">اختر طالباً...</option>
{% for student in students %}
<option value="{{ student.id }}">{{ student.student_name }} (النقاط الحالية: {{ student.points }})</option>
{% endfor %}
</select>
</div>
<div class="mb-6 flex space-x-4 space-x-reverse">
<button type="button" id="add-points-btn" data-operation="add" class="flex-1 px-6 py-3 bg-green-600 text-white font-semibold rounded-lg shadow-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all">
إضافة نقاط
</button>
<button type="button" id="remove-points-btn" data-operation="remove" class="flex-1 px-6 py-3 bg-red-600 text-white font-semibold rounded-lg shadow-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-all">
خصم نقاط
</button>
</div>
<div class="mb-6">
<label for="point_amount" class="block text-sm font-medium text-gray-700 mb-1">المبلغ</label>
<div class="flex space-x-2 space-x-reverse">
<button type="button" class="quick-add-btn px-4 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-400" data-points="5">5 نقاط</button>
<button type="button" class="quick-add-btn px-4 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-400" data-points="10">10 نقاط</button>
<button type="button" class="quick-add-btn px-4 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-400" data-points="15">15 نقطة</button>
<button type="button" class="quick-add-btn px-4 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-400" data-points="20">20 نقطة</button>
<input type="number" name="point_amount" id="point_amount" placeholder="أو أدخل يدوياً" min="1" required class="flex-1 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 text-right">
</div>
</div>
<input type="hidden" name="operation" id="operation-input">
<div class="mt-8 text-left">
<button type="submit" id="submit-points-btn" class="px-8 py-3 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all" disabled>
تطبيق النقاط
</button>
</div>
</form>
</div>
<div class="bg-white p-6 rounded-xl shadow-lg">
<h2 class="text-2xl font-semibold mb-4 text-gray-800 border-b pb-4">قائمة الطلاب والنقاط</h2>
<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-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">#</th>
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">اسم الطالب</th>
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">النقاط</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for student in students %}
<tr class="hover:bg-gray-50 transition-colors duration-200">
<td class="px-6 py-4 whitespace-nowrap text-sm font-bold text-gray-700">{{ loop.index }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ student.student_name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-lg font-semibold text-blue-700">{{ student.points }}</td>
</tr>
{% else %}
<tr>
<td colspan="3" class="text-center py-6 text-gray-500">لم تتم إضافة أي طالب بعد.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<script>
const addPointsBtn = document.getElementById('add-points-btn');
const removePointsBtn = document.getElementById('remove-points-btn');
const pointAmountInput = document.getElementById('point_amount');
const operationInput = document.getElementById('operation-input');
const submitPointsBtn = document.getElementById('submit-points-btn');
const quickAddButtons = document.querySelectorAll('.quick-add-btn');
// Function to update the submit button state (disabled/enabled)
function updateSubmitButtonState() {
const studentSelected = document.getElementById('student_id').value !== '';
const amountEntered = pointAmountInput.value.trim() !== '' && parseInt(pointAmountInput.value) > 0;
const operationSelected = operationInput.value !== '';
if (studentSelected && amountEntered && operationSelected) {
submitPointsBtn.disabled = false;
submitPointsBtn.classList.remove('bg-gray-400', 'hover:bg-gray-500');
submitPointsBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
} else {
submitPointsBtn.disabled = true;
submitPointsBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
submitPointsBtn.classList.add('bg-gray-400', 'hover:bg-gray-500');
}
}
// Event listeners for Add/Remove buttons
addPointsBtn.addEventListener('click', () => {
operationInput.value = 'add';
addPointsBtn.classList.add('bg-green-700'); // Darker green when active
removePointsBtn.classList.remove('bg-red-700'); // Remove darker red if it was active
updateSubmitButtonState();
});
removePointsBtn.addEventListener('click', () => {
operationInput.value = 'remove';
removePointsBtn.classList.add('bg-red-700'); // Darker red when active
addPointsBtn.classList.remove('bg-green-700'); // Remove darker green if it was active
updateSubmitButtonState();
});
// Event listener for quick add buttons
quickAddButtons.forEach(button => {
button.addEventListener('click', () => {
pointAmountInput.value = button.dataset.points;
updateSubmitButtonState();
});
});
// Event listener for manual input change
pointAmountInput.addEventListener('input', updateSubmitButtonState);
// Event listener for student selection change
document.getElementById('student_id').addEventListener('change', updateSubmitButtonState);
// Initial state check when the page loads
updateSubmitButtonState();
// Prevent double submission
const pointsForm = document.getElementById('points-form');
if (pointsForm) {
pointsForm.addEventListener('submit', () => {
submitPointsBtn.disabled = true;
submitPointsBtn.textContent = 'جاري المعالجة...'; // "Processing..."
});
}
</script>
{% endblock %}

عرض الملف

@@ -1,5 +1,5 @@
أحمد العلي,14,محمد العلي,0912345678,0911223344,0955667788,الصف التاسع,مدرسة النور,شارع الحرية، دمشق,5 أجزاء من القرآن
فاطمة الزهراء,12,علي الزهراء,0998765432,,,"الصف السابع",مدرسة الأمل,حي الميدان، حلب,جزء عم
يوسف خالد,16,خالد المصري,0933445566,,0944556677,"الصف الحادي عشر",ثانوية المتفوقين,شارع بغداد، حمص,سورة البقرة
سارة إبراهيم,15,إبراهيم الحسن,0988112233,0977445566,,الصف العاشر,مدرسة المعرفة,اللاذقية,الأربعون النووية
عمر عبد الله,13,عبد الله فتحي,0966998877,,,,الصف الثامن,مدرسة الإيمان,طرطوس,جزء تبارك
أحمد العلي,14,محمد العلي,0912345678,0911223344,0955667788,الصف التاسع,مدرسة النور,شارع الحرية، دمشق,5 أجزاء من القرآن,ملاحظات عن أحمد,2024-01-15
فاطمة الزهراء,12,علي الزهراء,0998765432,,,"الصف السابع",مدرسة الأمل,حي الميدان، حلب,جزء عم,,2024-02-20
يوسف خالد,16,خالد المصري,0933445566,,0944556677,"الصف الحادي عشر",ثانوية المتفوقين,شارع بغداد، حمص,سورة البقرة,نشيط جداً,2023-11-01
سارة إبراهيم,15,إبراهيم الحسن,0988112233,0977445566,,الصف العاشر,مدرسة المعرفة,اللاذقية,الأربعون النووية,,
عمر عبد الله,13,عبد الله فتحي,0966998877,,,,الصف الثامن,مدرسة الإيمان,طرطوس,جزء تبارك,,
لا يمكن عرض هذا الملف لأنه يحتوي على عدد خاطئ من الحقول في السطر 5.