diff --git a/app_settings.php b/app_settings.php
new file mode 100644
index 0000000..4a66fd3
--- /dev/null
+++ b/app_settings.php
@@ -0,0 +1,139 @@
+ $settings['app_name'] ?? '',
+ 'app_email' => $settings['app_email'] ?? '',
+ 'app_telephone' => $settings['app_telephone'] ?? '',
+];
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $values['app_name'] = clean_text($_POST['app_name'] ?? '', 190);
+ $values['app_email'] = clean_text($_POST['app_email'] ?? '', 190);
+ $values['app_telephone'] = clean_text($_POST['app_telephone'] ?? '', 60);
+
+ if ($values['app_name'] === '') $errors['app_name'] = 'مطلوب';
+
+ $logoPath = $settings['app_logo'] ?? '';
+ $faviconPath = $settings['app_favicon'] ?? '';
+
+ // Handle Uploads
+ $uploadDir = __DIR__ . '/assets/images/uploads/';
+ if (!is_dir($uploadDir)) {
+ mkdir($uploadDir, 0755, true);
+ }
+
+ if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
+ $logoExt = strtolower(pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION));
+ if (in_array($logoExt, ['png', 'jpg', 'jpeg', 'svg', 'gif'])) {
+ $logoName = 'app_logo_' . time() . '.' . $logoExt;
+ if (move_uploaded_file($_FILES['logo']['tmp_name'], $uploadDir . $logoName)) {
+ $logoPath = 'assets/images/uploads/' . $logoName;
+ }
+ } else {
+ $errors['logo'] = 'صيغة غير مدعومة';
+ }
+ }
+
+ if (isset($_FILES['favicon']) && $_FILES['favicon']['error'] === UPLOAD_ERR_OK) {
+ $faviconExt = strtolower(pathinfo($_FILES['favicon']['name'], PATHINFO_EXTENSION));
+ if (in_array($faviconExt, ['png', 'ico', 'svg'])) {
+ $faviconName = 'app_favicon_' . time() . '.' . $faviconExt;
+ if (move_uploaded_file($_FILES['favicon']['tmp_name'], $uploadDir . $faviconName)) {
+ $faviconPath = 'assets/images/uploads/' . $faviconName;
+ }
+ } else {
+ $errors['favicon'] = 'صيغة غير مدعومة';
+ }
+ }
+
+ if (empty($errors)) {
+ try {
+ $stmt = db()->prepare('UPDATE app_settings SET app_name = ?, app_email = ?, app_telephone = ?, app_logo = ?, app_favicon = ?, updated_at = NOW() WHERE id = 1');
+ $stmt->execute([
+ $values['app_name'],
+ $values['app_email'],
+ $values['app_telephone'],
+ $logoPath,
+ $faviconPath
+ ]);
+ set_flash('success', 'تم تحديث الإعدادات العامة بنجاح.');
+ header('Location: app_settings.php');
+ exit;
+ } catch (Throwable $e) {
+ $errors['form'] = 'تعذر الحفظ.';
+ }
+ }
+}
+
+render_page_start('إعدادات النظام', 'app_settings', 'إعدادات النظام العامة');
+render_flash($flash);
+?>
+
+
+
+
+
+
+
+
+
الإعدادات العامة للنظام
+
تعديل اسم النظام، الشعار (Logo)، الأيقونة (Favicon)، وبيانات التواصل.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/application_detail.php b/application_detail.php
index 65024b2..68ac527 100644
--- a/application_detail.php
+++ b/application_detail.php
@@ -27,6 +27,19 @@ if (!$application) {
$reviewErrors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+
+ if (isset($_POST['action']) && $_POST['action'] === 'update_subjects') {
+ $selected_subjects = isset($_POST['subjects']) && is_array($_POST['subjects']) ? $_POST['subjects'] : [];
+ $valid_subjects = [];
+ foreach ($selected_subjects as $sid) {
+ $sid = filter_var($sid, FILTER_VALIDATE_INT);
+ if ($sid) $valid_subjects[] = $sid;
+ }
+ update_application_subjects($applicationId, $valid_subjects);
+ set_flash('success', 'تم تحديث المواد الدراسية بنجاح.');
+ header('Location: application_detail.php?id=' . urlencode((string) $applicationId));
+ exit;
+ }
$status = clean_text((string) ($_POST['status'] ?? 'submitted'), 30);
$adminNotes = clean_text((string) ($_POST['admin_notes'] ?? ''), 1000);
$evaluationScore = filter_var($_POST['evaluation_score'] ?? null, FILTER_VALIDATE_INT, [
@@ -63,6 +76,12 @@ render_flash($flash);
?>
+
+
+
+
+
+
@@ -111,6 +130,31 @@ render_flash($flash);
فترة البرنامج = e((string) $application['start_date']) ?> — = e((string) $application['end_date']) ?>
+
+
+
+
المواد الدراسية المطلوبة
+
تعديل المواد
+
+
+
+ لم يتم اختيار أي مواد.
+
+
+ = e($subject_map[$sub_id] ?? 'مادة غير معروفة') ?>
+
+
+
+
+
ملخص البرنامج والاحتياجات
= nl2br(e((string) ($application['notes'] ?: 'لا توجد ملاحظات إضافية.'))) ?>
@@ -187,6 +231,44 @@ render_flash($flash);
+
+
+
+
+
+
+
diff --git a/applications.php b/applications.php
index 9d45b80..25ed7d9 100644
--- a/applications.php
+++ b/applications.php
@@ -2,131 +2,146 @@
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
-$flash = consume_flash();
-$statusFilter = clean_text((string) ($_GET['status'] ?? 'all'), 50);
-$applications = list_applications($statusFilter);
-$stats = dashboard_metrics();
-$statusTabs = ['all' => 'الكل'] + array_map(fn ($meta) => $meta['label'], status_map());
-$currentCount = count($applications);
-$reviewBacklog = $stats['submitted'] + $stats['under_review'];
-$approvalRate = $stats['all'] > 0 ? (int) round(($stats['approved'] / $stats['all']) * 100) : 0;
+// Handle POST actions (e.g. Delete)
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $action = $_POST['action'] ?? '';
+
+ if ($action === 'delete') {
+ $id = (int)($_POST['id'] ?? 0);
+ if ($id > 0) {
+ $stmt = db()->prepare('DELETE FROM center_applications WHERE id = ?');
+ $stmt->execute([$id]);
+ set_flash('success', 'تم حذف الطلب بنجاح.');
+ }
+ header('Location: applications.php');
+ exit;
+ }
+}
-render_page_start('لوحة الطلبات', 'applications', 'قائمة موحدة لطلبات فتح المراكز الصيفية مع فلترة حسب حالة المراجعة.');
+// Read list
+$search = clean_text($_GET['search'] ?? '', 255);
+$query = 'SELECT * FROM center_applications';
+$params = [];
+if ($search !== '') {
+ $query .= ' WHERE center_name LIKE ? OR city LIKE ? OR director_name LIKE ?';
+ $params[] = "%$search%";
+ $params[] = "%$search%";
+ $params[] = "%$search%";
+}
+$query .= ' ORDER BY id DESC';
+$stmt = db()->prepare($query);
+$stmt->execute($params);
+$applications = $stmt->fetchAll();
+
+$flash = consume_flash();
+render_page_start('إدارة الطلبات', 'applications', 'قائمة بطلبات فتح المراكز');
render_flash($flash);
?>
-
-
-
-
صفحة مستقلة للمراجعة
-
لوحة طلبات فتح المراكز
-
تم تخصيص هذه الصفحة بالكامل لمتابعة الطلبات: مؤشرات سريعة، فلاتر واضحة، وجدول مراجعة عملي يقود مباشرة إلى صفحة التفاصيل واتخاذ القرار.
-
-
-
-
الحمل التشغيلي الحالي
-
= e((string) $reviewBacklog) ?>
-
طلبات تحتاج عملاً من المشرف العام الآن، مع نسبة اعتماد إجمالية تبلغ = e((string) $approvalRate) ?>٪.
+
-
-
إجمالي الطلبات
= e((string) $stats['all']) ?>
جميع الطلبات في قاعدة البيانات.
-
بانتظار الاستلام
= e((string) $stats['submitted']) ?>
طلبات جديدة لم يبدأ تقييمها بعد.
-
تحت المراجعة
= e((string) $stats['under_review']) ?>
ملفات قيد التحقق والملاحظات.
-
معتمد
= e((string) $stats['approved']) ?>
مراكز جاهزة للانتقال لمرحلة التشغيل.
-
+
+
-
-
-
-
أدوات الفرز والمتابعة
-
اختر حالة محددة للتركيز على شريحة واحدة من الطلبات، أو ارجع إلى الكل لمراجعة الصورة العامة.
-
-
-
-
-
-
-
-
-
-
-
لا توجد طلبات ضمن هذا التصنيف.
-
يمكنك إنشاء طلب جديد أو العودة إلى جميع الطلبات لاستكمال المراجعة.
-
-
-
-
-
-
-
- المرجع
- المركز
- المدينة
- المسؤول
- الفترة
- الحالة
- التقييم
- إجراء
-
-
-
-
+
+
+
- #= e((string) $application['id']) ?>
-
- = e((string) $application['center_name']) ?>
- سعة متوقعة: = e((string) $application['expected_students']) ?> طالب
-
- = e((string) $application['city']) ?>
-
- = e((string) $application['director_name']) ?>
- = e((string) $application['phone']) ?>
-
-
- = e((string) $application['start_date']) ?>
- حتى = e((string) $application['end_date']) ?>
-
- = status_badge((string) $application['status']) ?>
- = $application['evaluation_score'] !== null ? e((string) $application['evaluation_score']) . '%' : '—' ?>
-
-
- صفحة المركز
-
- فتح الملف
-
-
+ المرجع
+ المركز
+ المدينة
+ المسؤول
+ الفترة
+ الحالة
+ الإجراءات
-
-
-
+
+
+
+
+ لا توجد طلبات مسجلة أو لم يتم العثور على نتائج.
+
+
+
+
+ #= e((string) $application['id']) ?>
+
+ = e((string) $application['center_name']) ?>
+ سعة متوقعة: = e((string) $application['expected_students']) ?> طالب
+
+ = e((string) $application['city']) ?>
+
+ = e((string) $application['director_name']) ?>
+ = e((string) $application['phone']) ?>
+
+
+ = e((string) $application['start_date']) ?>
+ حتى = e((string) $application['end_date']) ?>
+
+ = status_badge((string) $application['status']) ?>
+
+
+
+
+
+
+
+
+
-
+
-
+
\ No newline at end of file
diff --git a/approved_school.php b/approved_school.php
index d16e90d..60f592a 100644
--- a/approved_school.php
+++ b/approved_school.php
@@ -17,6 +17,12 @@ if (!$application) {
?>
+
+
+
+
+
+
المركز غير موجود
تحقق من رقم الطلب أو ارجع إلى قائمة المراكز المعتمدة.
@@ -138,7 +144,7 @@ $pageDescription = $isApproved
? 'صفحة تشغيلية للمركز المعتمد تعرض الجاهزية، الدورات الموسمية، والخطوات التالية بعد الموافقة.'
: 'هذه الصفحة تصبح متاحة بعد اعتماد طلب المركز من المشرف العام.';
-render_page_start($pageTitle, 'approved', $pageDescription);
+render_page_start($pageTitle, 'approved', $pageDescription, (string) ($application['favicon'] ?? ''));
render_flash($flash);
?>
@@ -177,6 +183,7 @@ render_flash($flash);
+
+
+
+
+
+
+
+
+
+
المدرسة غير موجودة
@@ -366,6 +372,10 @@ render_flash($flash);
+
+
+
+
+
+
+
+
+
+
المدرسة غير موجودة
@@ -368,6 +374,10 @@ render_flash($flash);
+
+
+
+
إرسال الطلب
العودة إلى لوحة الطلبات
diff --git a/center_profile.php b/center_profile.php
new file mode 100644
index 0000000..3d224e9
--- /dev/null
+++ b/center_profile.php
@@ -0,0 +1,173 @@
+ 0 ? get_application($applicationId) : null;
+
+if (!$application) {
+ http_response_code(404);
+ render_page_start('ملف المركز غير موجود', 'approved', 'تعذر العثور على المركز.');
+ render_flash($flash);
+ ?>
+
+
+
+
+
+
+
+
+
المركز غير موجود
+
العودة
+
+
+
+
+
+ $application['center_name'],
+ 'email' => $application['email'],
+ 'phone' => $application['phone'],
+];
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $values['center_name'] = clean_text($_POST['center_name'] ?? '', 190);
+ $values['email'] = clean_text($_POST['email'] ?? '', 190);
+ $values['phone'] = clean_text($_POST['phone'] ?? '', 60);
+
+ if ($values['center_name'] === '') $errors['center_name'] = 'مطلوب';
+ if ($values['email'] === '') $errors['email'] = 'مطلوب';
+ if ($values['phone'] === '') $errors['phone'] = 'مطلوب';
+
+ $logoPath = $application['logo'];
+ $faviconPath = $application['favicon'];
+
+ // Handle Uploads
+ $uploadDir = __DIR__ . '/assets/images/uploads/';
+
+ if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
+ $logoExt = strtolower(pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION));
+ if (in_array($logoExt, ['png', 'jpg', 'jpeg', 'svg', 'gif'])) {
+ $logoName = 'logo_' . $applicationId . '_' . time() . '.' . $logoExt;
+ if (move_uploaded_file($_FILES['logo']['tmp_name'], $uploadDir . $logoName)) {
+ $logoPath = 'assets/images/uploads/' . $logoName;
+ }
+ } else {
+ $errors['logo'] = 'صيغة غير مدعومة';
+ }
+ }
+
+ if (isset($_FILES['favicon']) && $_FILES['favicon']['error'] === UPLOAD_ERR_OK) {
+ $faviconExt = strtolower(pathinfo($_FILES['favicon']['name'], PATHINFO_EXTENSION));
+ if (in_array($faviconExt, ['png', 'ico', 'svg'])) {
+ $faviconName = 'favicon_' . $applicationId . '_' . time() . '.' . $faviconExt;
+ if (move_uploaded_file($_FILES['favicon']['tmp_name'], $uploadDir . $faviconName)) {
+ $faviconPath = 'assets/images/uploads/' . $faviconName;
+ }
+ } else {
+ $errors['favicon'] = 'صيغة غير مدعومة';
+ }
+ }
+
+ if (empty($errors)) {
+ try {
+ $stmt = db()->prepare('UPDATE center_applications SET center_name = ?, email = ?, phone = ?, logo = ?, favicon = ?, updated_at = NOW() WHERE id = ?');
+ $stmt->execute([
+ $values['center_name'],
+ $values['email'],
+ $values['phone'],
+ $logoPath,
+ $faviconPath,
+ $applicationId
+ ]);
+ set_flash('success', 'تم تحديث بيانات المركز بنجاح.');
+ header('Location: center_profile.php?id=' . $applicationId);
+ exit;
+ } catch (Throwable $e) {
+ $errors['form'] = 'تعذر الحفظ.';
+ }
+ }
+}
+
+render_page_start('إعدادات المركز', 'approved', 'تعديل بيانات وهوية المركز.', (string) ($application['favicon'] ?? ''));
+render_flash($flash);
+?>
+
+
+
+
+
+
+
+
+
إعدادات وهوية المركز
+
تعديل اسم المركز، الشعار (Logo)، الأيقونة (Favicon)، وبيانات التواصل.
+
+
+
+
+
+
+
+
+
diff --git a/dashboard.php b/dashboard.php
index 0ce7d33..fe6d009 100644
--- a/dashboard.php
+++ b/dashboard.php
@@ -11,6 +11,12 @@ render_flash($flash);
?>
+
+
+
+
+
+
@@ -123,6 +129,9 @@ render_flash($flash);
+
+
+
diff --git a/db/migrations/20260416_alter_center_applications_subjects.sql b/db/migrations/20260416_alter_center_applications_subjects.sql
new file mode 100644
index 0000000..95cb024
--- /dev/null
+++ b/db/migrations/20260416_alter_center_applications_subjects.sql
@@ -0,0 +1 @@
+ALTER TABLE center_applications ADD COLUMN subjects JSON NULL AFTER notes;
diff --git a/db/migrations/20260416_app_settings.sql b/db/migrations/20260416_app_settings.sql
new file mode 100644
index 0000000..ca7f11c
--- /dev/null
+++ b/db/migrations/20260416_app_settings.sql
@@ -0,0 +1,10 @@
+CREATE TABLE IF NOT EXISTS app_settings (
+ id INT PRIMARY KEY DEFAULT 1,
+ app_name VARCHAR(255),
+ app_email VARCHAR(255),
+ app_telephone VARCHAR(255),
+ app_logo VARCHAR(255),
+ app_favicon VARCHAR(255),
+ updated_at DATETIME
+);
+INSERT IGNORE INTO app_settings (id, app_name) VALUES (1, 'Central Admin');
diff --git a/db/migrations/20260416_subjects.sql b/db/migrations/20260416_subjects.sql
new file mode 100644
index 0000000..57a9586
--- /dev/null
+++ b/db/migrations/20260416_subjects.sql
@@ -0,0 +1,8 @@
+CREATE TABLE IF NOT EXISTS subjects (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ description TEXT,
+ status ENUM('enabled', 'disabled') DEFAULT 'enabled',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/fix_sidebar.py b/fix_sidebar.py
new file mode 100644
index 0000000..18fe051
--- /dev/null
+++ b/fix_sidebar.py
@@ -0,0 +1,7 @@
+with open('includes/sidebar.php', 'r', encoding='utf-8') as f:
+ content = f.read()
+
+content = content.replace("href=\"approved_school.php', 'center_profile.php?id=", "href=\"approved_school.php?id=")
+
+with open('includes/sidebar.php', 'w', encoding='utf-8') as f:
+ f.write(content)
diff --git a/includes/app.php b/includes/app.php
index 54992f7..a57e7f0 100644
--- a/includes/app.php
+++ b/includes/app.php
@@ -409,6 +409,7 @@ function application_defaults(): array
'start_date' => '',
'end_date' => '',
'notes' => '',
+ 'subjects' => [],
];
}
@@ -416,6 +417,10 @@ function validate_application_input(array $input): array
{
$data = application_defaults();
foreach ($data as $key => $_value) {
+ if ($key === 'subjects') {
+ $data[$key] = is_array($input[$key] ?? null) ? $input[$key] : [];
+ continue;
+ }
$data[$key] = clean_text((string) ($input[$key] ?? ''), $key === 'notes' ? 1000 : 190);
}
@@ -467,6 +472,12 @@ function validate_application_input(array $input): array
$errors['end_date'] = 'يجب أن يكون تاريخ النهاية بعد البداية.';
}
+ if (empty($data['subjects'])) {
+ $errors['subjects'] = 'يرجى اختيار مادة واحدة على الأقل.';
+ } else {
+ $data['subjects'] = array_map('intval', $data['subjects']);
+ }
+
return [$data, $errors];
}
@@ -476,10 +487,10 @@ function create_application(array $data): int
$stmt = $pdo->prepare(
'INSERT INTO center_applications (
center_name, city, center_type, gender_scope, director_name, phone, email,
- expected_students, start_date, end_date, notes, status, submitted_at, updated_at
+ expected_students, start_date, end_date, notes, subjects, status, submitted_at, updated_at
) VALUES (
:center_name, :city, :center_type, :gender_scope, :director_name, :phone, :email,
- :expected_students, :start_date, :end_date, :notes, :status, NOW(), NOW()
+ :expected_students, :start_date, :end_date, :notes, :subjects, :status, NOW(), NOW()
)'
);
@@ -495,6 +506,7 @@ function create_application(array $data): int
':start_date' => $data['start_date'],
':end_date' => $data['end_date'],
':notes' => $data['notes'],
+ ':subjects' => json_encode($data['subjects']),
':status' => 'submitted',
]);
@@ -1334,3 +1346,25 @@ function render_page_end(): void