update center page

This commit is contained in:
Flatlogic Bot 2026-04-16 14:19:43 +00:00
parent e5366bdd58
commit 371b07bffa
21 changed files with 1150 additions and 130 deletions

View File

@ -127,7 +127,7 @@ render_flash($flash);
<div class="col-md-6"><div class="detail-item"><span>رقم الهاتف</span><strong><?= e((string) $application['phone']) ?></strong></div></div>
<div class="col-md-6"><div class="detail-item"><span>البريد الإلكتروني</span><strong><?= e((string) $application['email']) ?></strong></div></div>
<div class="col-md-6"><div class="detail-item"><span>السعة المتوقعة</span><strong><?= e((string) $application['expected_students']) ?> طالب</strong></div></div>
<div class="col-md-6"><div class="detail-item"><span>فترة البرنامج</span><strong><?= e((string) $application['start_date']) ?> — <?= e((string) $application['end_date']) ?></strong></div></div>
<div class="col-md-6"><div class="detail-item"><span>الدورة / الفترة</span><strong><?= e((string) ($application['cycle_name'] ?? 'دورة مخصصة')) ?> (<?= e((string) $application['start_date']) ?> — <?= e((string) $application['end_date']) ?>)</strong></div></div>
</div>

View File

@ -20,15 +20,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Read list
$search = clean_text($_GET['search'] ?? '', 255);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$query = 'SELECT * FROM center_applications';
$countQuery = 'SELECT COUNT(*) FROM center_applications';
$params = [];
if ($search !== '') {
$query .= ' WHERE center_name LIKE ? OR city LIKE ? OR director_name LIKE ?';
$where = ' WHERE center_name LIKE ? OR city LIKE ? OR director_name LIKE ?';
$query .= $where;
$countQuery .= $where;
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$query .= ' ORDER BY id DESC';
$stmtCount = db()->prepare($countQuery);
$stmtCount->execute($params);
$totalItems = (int)$stmtCount->fetchColumn();
$query .= ' ORDER BY id DESC LIMIT ' . $limit . ' OFFSET ' . $offset;
$stmt = db()->prepare($query);
$stmt->execute($params);
$applications = $stmt->fetchAll();
@ -56,15 +68,7 @@ render_flash($flash);
</div>
<!-- Search Bar -->
<form method="GET" action="applications.php" class="mb-4">
<div class="input-group">
<input type="text" name="search" class="form-control" placeholder="ابحث باسم المركز، المدينة، أو المسؤول..." value="<?= e($search) ?>">
<button class="btn btn-outline-secondary" type="submit">بحث</button>
<?php if ($search !== ''): ?>
<a href="applications.php" class="btn btn-outline-danger">إلغاء</a>
<?php endif; ?>
</div>
</form>
<?php render_search_bar($search, "ابحث باسم المركز، المدينة، أو المسؤول...", "applications.php", $_GET); ?>
<div class="table-responsive">
<table class="table app-table align-middle">
@ -139,6 +143,7 @@ render_flash($flash);
</tbody>
</table>
</div>
<?php render_pagination($totalItems, $limit, $page, $_GET); ?>
</div>
</div>
</div>

View File

@ -65,7 +65,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $isApproved) {
$cycleCreation = create_school_cycle((int) $application['id'], $cycleValues, $cycleRollover);
$newCycleId = (int) ($cycleCreation['cycle_id'] ?? 0);
$rolloverSummary = (array) ($cycleCreation['rollover'] ?? []);
$flashMessage = 'تم إنشاء الدورة الموسمية الجديدة بنجاح. يمكنك الآن العمل داخل ' . format_school_cycle_name($cycleValues['season'], (int) $cycleValues['year']) . '.';
$flashMessage = 'تم إنشاء الدورة الموسمية الجديدة بنجاح. يمكنك الآن العمل داخل ' . e((string)($cycleValues['cycle_name'] ?? 'الدورة الجديدة')) . '.';
$rolloverParts = [];
if (!empty($rolloverSummary['teachers'])) {
$rolloverParts[] = 'ترحيل ' . (int) $rolloverSummary['teachers'] . ' من أعضاء الفريق';
@ -134,6 +134,7 @@ $teachersUrl = school_page_url('teachers.php', (int) $application['id'], $select
$assessmentsUrl = school_page_url('assessments.php', (int) $application['id'], $selectedCycleId);
$attendanceUrl = school_page_url('attendance.php', (int) $application['id'], $selectedCycleId);
$approvedSchoolUrl = school_page_url('approved_school.php', (int) $application['id'], $selectedCycleId);
$centerSubjectsUrl = school_page_url('center_subjects.php', (int) $application['id'], $selectedCycleId);
$studentCycleMetrics = $isApproved && $selectedCycleId > 0 ? school_student_metrics_by_cycle((int) $application['id'], $selectedCycleId) : ['total' => 0, 'active' => 0];
$teacherCycleMetrics = $isApproved && $selectedCycleId > 0 ? school_teacher_metrics_by_cycle((int) $application['id'], $selectedCycleId) : ['total' => 0, 'active' => 0];
$assessmentCycleMetrics = $isApproved && $selectedCycleId > 0 ? school_assessment_metrics_by_cycle((int) $application['id'], $selectedCycleId) : ['total' => 0, 'active' => 0, 'active_weight' => 0];
@ -149,6 +150,11 @@ render_flash($flash);
?>
<section class="py-4 py-lg-5">
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
<?php if (!$isApproved): ?>
<div class="page-banner approved-hero mb-4 mb-lg-5">
<div class="row g-4 align-items-center">
@ -186,6 +192,7 @@ render_flash($flash);
<a class="btn btn-outline-secondary" href="center_profile.php?id=<?= e((string) $application['id']) ?>">إعدادات المركز</a>
<a class="btn btn-outline-secondary" href="<?= e($studentsUrl) ?>">تسجيل الطلاب</a>
<a class="btn btn-outline-secondary" href="<?= e($teachersUrl) ?>">فريق المعلمين</a>
<a class="btn btn-outline-secondary" href="<?= e($centerSubjectsUrl) ?>">المواد الدراسية</a>
<a class="btn btn-outline-secondary" href="<?= e($assessmentsUrl) ?>">التقييمات والأوزان</a>
<a class="btn btn-outline-secondary" href="<?= e($attendanceUrl) ?>">غياب الطلاب</a>
<a class="btn btn-outline-secondary" href="applications.php?status=approved">كل المراكز المعتمدة</a>
@ -270,30 +277,14 @@ render_flash($flash);
<form method="post" class="vstack gap-3" novalidate>
<input type="hidden" name="cycle_action" value="create_cycle">
<div>
<label class="form-label" for="season">الموسم</label>
<select class="form-select <?= isset($cycleErrors['season']) ? 'is-invalid' : '' ?>" id="season" name="season">
<?php foreach (school_cycle_season_options() as $seasonKey => $seasonMeta): ?>
<option value="<?= e($seasonKey) ?>" <?= $cycleValues['season'] === $seasonKey ? 'selected' : '' ?>><?= e((string) $seasonMeta['label']) ?></option>
<label class="form-label" for="global_cycle_id">اختر الدورة</label>
<select class="form-select <?= isset($cycleErrors['global_cycle_id']) ? 'is-invalid' : '' ?>" id="global_cycle_id" name="global_cycle_id">
<option value="">اختر دورة مسجلة مسبقاً</option>
<?php foreach (get_active_global_cycles() as $cycle): ?>
<option value="<?= e((string)$cycle['id']) ?>"><?= e((string)$cycle['cycle_name']) ?> (<?= e((string)$cycle['start_date']) ?> - <?= e((string)$cycle['end_date']) ?>)</option>
<?php endforeach; ?>
</select>
<?php if (isset($cycleErrors['season'])): ?><div class="invalid-feedback"><?= e($cycleErrors['season']) ?></div><?php endif; ?>
</div>
<div>
<label class="form-label" for="year">السنة</label>
<input class="form-control <?= isset($cycleErrors['year']) ? 'is-invalid' : '' ?>" id="year" name="year" value="<?= e($cycleValues['year']) ?>" inputmode="numeric" placeholder="2026">
<?php if (isset($cycleErrors['year'])): ?><div class="invalid-feedback"><?= e($cycleErrors['year']) ?></div><?php endif; ?>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="start_date">تاريخ البداية</label>
<input class="form-control <?= isset($cycleErrors['start_date']) ? 'is-invalid' : '' ?>" id="start_date" name="start_date" type="date" value="<?= e($cycleValues['start_date']) ?>">
<?php if (isset($cycleErrors['start_date'])): ?><div class="invalid-feedback"><?= e($cycleErrors['start_date']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="end_date">تاريخ النهاية</label>
<input class="form-control <?= isset($cycleErrors['end_date']) ? 'is-invalid' : '' ?>" id="end_date" name="end_date" type="date" value="<?= e($cycleValues['end_date']) ?>">
<?php if (isset($cycleErrors['end_date'])): ?><div class="invalid-feedback"><?= e($cycleErrors['end_date']) ?></div><?php endif; ?>
</div>
<?php if (isset($cycleErrors['global_cycle_id'])): ?><div class="invalid-feedback"><?= e($cycleErrors['global_cycle_id']) ?></div><?php endif; ?>
</div>
<div>
<label class="form-label" for="status">حالة البداية</label>
@ -413,6 +404,7 @@ render_flash($flash);
<div class="quick-link-stack">
<a class="quick-link-item" href="<?= e($studentsUrl) ?>"><strong>تسجيل الطلاب</strong><span>فتح صفحة القيد وكشف المدرسة لهذا المركز.</span></a>
<a class="quick-link-item" href="<?= e($teachersUrl) ?>"><strong>فريق المعلمين</strong><span>إدارة المعلمين والمشرفين والكوادر التشغيلية للمركز.</span></a>
<a class="quick-link-item" href="<?= e($centerSubjectsUrl) ?>"><strong>المواد الدراسية</strong><span>تحديد وتحديث المواد الدراسية التي يتم تقديمها في هذا المركز.</span></a>
<a class="quick-link-item" href="<?= e($assessmentsUrl) ?>"><strong>التقييمات والأوزان</strong><span>تعريف أنواع التقييم، المقاييس، والأوزان التشغيلية لهذا المركز.</span></a>
<a class="quick-link-item" href="<?= e($attendanceUrl) ?>"><strong>غياب الطلاب</strong><span>تسجيل الغياب اليومي، الأعذار، وحالات التأخر للطلاب المعتمدين.</span></a>
<a class="quick-link-item" href="application_detail.php?id=<?= e((string) $application['id']) ?>"><strong>ملف الاعتماد</strong><span>العودة إلى سجل القرار والتقييم.</span></a>
@ -428,7 +420,7 @@ render_flash($flash);
</div>
</div>
</div>
</div>
</section>
<?php render_page_end();

View File

@ -46,7 +46,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
}
}
$assessments = $isApprovedSchool && $selectedCycleId > 0 ? list_school_assessments_by_cycle((int) $application['id'], $selectedCycleId) : [];
$search = clean_text($_GET['search'] ?? '', 255);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$assessments = $isApprovedSchool && $selectedCycleId > 0 ? list_school_assessments_by_cycle((int) $application['id'], $selectedCycleId, $search, $limit, $offset) : [];
$totalAssessments = $isApprovedSchool && $selectedCycleId > 0 ? count_school_assessments_by_cycle((int) $application['id'], $selectedCycleId, $search) : 0;
$metrics = $isApprovedSchool && $selectedCycleId > 0 ? school_assessment_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
'total' => 0,
'active' => 0,
@ -96,7 +103,7 @@ render_flash($flash);
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php require __DIR__ . '/includes/sidebar.php'; ?>
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
@ -307,6 +314,7 @@ render_flash($flash);
</div>
<span class="header-chip"><?= e((string) $metrics['percentage']) ?> نسبي / <?= e((string) $metrics['rubric']) ?> Rubric</span>
</div>
<?php render_search_bar($search, 'ابحث باسم التقييم أو الفئة...', school_page_url('assessments.php', (int)$application['id'], $selectedCycleId), $_GET); ?>
<div class="row g-3 mb-3">
<div class="col-md-4"><div class="school-data-item"><strong>إجمالي الأنواع</strong><span><?= e((string) $metrics['total']) ?> نوع</span></div></div>
@ -351,6 +359,7 @@ render_flash($flash);
</tbody>
</table>
</div>
<?php render_pagination($totalAssessments, $limit, $page, $_GET); ?>
<?php endif; ?>
</div>

View File

@ -54,7 +54,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
$students = $isApprovedSchool && $selectedCycleId > 0 ? list_school_students_by_cycle((int) $application['id'], $selectedCycleId) : [];
$studentOptions = $isApprovedSchool && $selectedCycleId > 0 ? school_student_options_by_cycle((int) $application['id'], $selectedCycleId) : [];
$records = $isApprovedSchool && $selectedCycleId > 0 ? list_school_attendance_records_by_cycle((int) $application['id'], $selectedCycleId) : [];
$search = clean_text($_GET['search'] ?? '', 255);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$records = $isApprovedSchool && $selectedCycleId > 0 ? list_school_attendance_records_by_cycle((int) $application['id'], $selectedCycleId, $search, $limit, $offset) : [];
$totalRecords = $isApprovedSchool && $selectedCycleId > 0 ? count_school_attendance_records_by_cycle((int) $application['id'], $selectedCycleId, $search) : 0;
$metrics = $isApprovedSchool && $selectedCycleId > 0 ? school_attendance_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
'total' => 0,
'absent' => 0,
@ -114,7 +121,7 @@ render_flash($flash);
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php require __DIR__ . '/includes/sidebar.php'; ?>
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
@ -309,6 +316,7 @@ render_flash($flash);
</div>
<span class="header-chip"><?= e((string) $metrics['total']) ?> سجل / <?= e((string) $metrics['affected_students']) ?> طلاب</span>
</div>
<?php render_search_bar($search, 'ابحث باسم الطالب أو الكود...', school_page_url('attendance.php', (int)$application['id'], $selectedCycleId), $_GET); ?>
<div class="row g-3 mb-3">
<div class="col-md-3"><div class="school-data-item"><strong>الغياب</strong><span><?= e((string) $metrics['absent']) ?> حالة</span></div></div>
@ -353,6 +361,7 @@ render_flash($flash);
</tbody>
</table>
</div>
<?php render_pagination($totalRecords, $limit, $page, $_GET); ?>
<?php endif; ?>
</div>

View File

@ -141,15 +141,17 @@ render_flash($flash);
<input type="number" min="10" max="2000" class="form-control <?= isset($errors['expected_students']) ? 'is-invalid' : '' ?>" id="expected_students" name="expected_students" value="<?= e($values['expected_students']) ?>" placeholder="150">
<?php if (isset($errors['expected_students'])): ?><div class="invalid-feedback"><?= e($errors['expected_students']) ?></div><?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label" for="start_date">تاريخ البداية</label>
<input type="date" class="form-control <?= isset($errors['start_date']) ? 'is-invalid' : '' ?>" id="start_date" name="start_date" value="<?= e($values['start_date']) ?>">
<?php if (isset($errors['start_date'])): ?><div class="invalid-feedback"><?= e($errors['start_date']) ?></div><?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label" for="end_date">تاريخ النهاية</label>
<input type="date" class="form-control <?= isset($errors['end_date']) ? 'is-invalid' : '' ?>" id="end_date" name="end_date" value="<?= e($values['end_date']) ?>">
<?php if (isset($errors['end_date'])): ?><div class="invalid-feedback"><?= e($errors['end_date']) ?></div><?php endif; ?>
<div class="col-md-8">
<label class="form-label" for="global_cycle_id">الدورة المطلوبة</label>
<select class="form-select <?= isset($errors['global_cycle_id']) ? 'is-invalid' : '' ?>" id="global_cycle_id" name="global_cycle_id">
<option value="">اختر الدورة...</option>
<?php foreach (get_active_global_cycles() as $cycle): ?>
<option value="<?= e((string)$cycle['id']) ?>" <?= ((string)($values['global_cycle_id'] ?? '')) === ((string)$cycle['id']) ? 'selected' : '' ?>>
<?= e((string)$cycle['cycle_name']) ?> (<?= e((string)$cycle['start_date']) ?> - <?= e((string)$cycle['end_date']) ?>)
</option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['global_cycle_id'])): ?><div class="invalid-feedback"><?= e($errors['global_cycle_id']) ?></div><?php endif; ?>
</div>
<div class="col-12">

View File

@ -15,7 +15,7 @@ if (!$application) {
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php require __DIR__ . '/includes/sidebar.php'; ?>
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
<div class="app-card text-center py-5">
@ -111,7 +111,7 @@ render_flash($flash);
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php require __DIR__ . '/includes/sidebar.php'; ?>
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
<div class="page-banner mb-4">

124
center_subjects.php Normal file
View File

@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
$flash = consume_flash();
$applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0;
$requestedCycleId = filter_input(INPUT_GET, 'cycle', FILTER_VALIDATE_INT) ?: 0;
$application = $applicationId > 0 ? get_application($applicationId) : null;
$isApproved = $application && (string) $application['status'] === 'approved';
$selectedCycleId = 0;
if ($isApproved) {
$cycleContext = resolve_school_cycle_context((int) $application['id'], $application, $requestedCycleId);
$selectedCycle = $cycleContext['selected'];
$selectedCycleId = $selectedCycle ? (int) ($selectedCycle['id'] ?? 0) : 0;
}
if (!$application || !$isApproved) {
http_response_code(404);
render_page_start('المركز غير موجود', 'profile');
render_flash($flash);
?>
<section class="py-5">
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
<div class="app-card text-center py-5">
<div class="empty-title mb-2">المركز غير موجود</div>
<a class="btn btn-dark" href="applications.php?status=approved">العودة</a>
</div>
</div>
</div>
</div>
</section>
<?php
render_page_end();
exit;
}
$available_subjects = get_enabled_subjects();
$current_subjects = is_string($application['subjects']) ? json_decode($application['subjects'], true) : [];
if (!is_array($current_subjects)) $current_subjects = [];
$current_subjects_ids = array_map('strval', $current_subjects);
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_subjects') {
$selected_subjects = $_POST['subjects'] ?? [];
if (!is_array($selected_subjects)) {
$selected_subjects = [];
}
// We can allow 0 subjects, but typically at least 1.
// Let's allow saving any selection.
$selected_subjects_ids = array_map('intval', $selected_subjects);
try {
update_application_subjects((int) $application['id'], $selected_subjects_ids);
set_flash('success', 'تم تحديث المواد الدراسية للمركز بنجاح.');
$selectedCycleIdStr = $selectedCycleId > 0 ? '&cycle=' . $selectedCycleId : '';
header('Location: center_subjects.php?id=' . $application['id'] . $selectedCycleIdStr);
exit;
} catch (Throwable $e) {
$errors['form'] = 'تعذر حفظ البيانات. يرجى المحاولة لاحقاً.';
}
}
render_page_start('إدارة المواد الدراسية: ' . (string) $application['center_name'], 'profile', 'تحديد المواد الدراسية التي يتم تدريسها في المركز.');
render_flash($flash);
?>
<section class="py-4 py-lg-5">
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
<div class="page-banner mb-4">
<h1 class="page-title mb-2">المواد الدراسية للمركز</h1>
<p class="page-copy mb-0">تحديد وتحديث المواد الدراسية التي يتم تقديمها في هذا المركز. سيتم توفير هذه المواد لاختيارها في التقييمات والجداول.</p>
</div>
<div class="app-card mb-4">
<form method="post">
<input type="hidden" name="action" value="update_subjects">
<?php if (isset($errors['form'])): ?><div class="alert alert-danger mb-3"><?= e($errors['form']) ?></div><?php endif; ?>
<div class="mb-4">
<label class="form-label d-block fw-bold mb-3">اختر المواد المتاحة في المركز</label>
<?php if (empty($available_subjects)): ?>
<div class="alert alert-info">لا توجد مواد دراسية مفعلة في النظام حالياً. يرجى إضافة مواد من الإدارة المركزية أولاً.</div>
<?php else: ?>
<div class="row g-3">
<?php foreach ($available_subjects as $subject): ?>
<div class="col-md-6 col-lg-4">
<div class="form-check border rounded-3 p-3 h-100 bg-light-subtle">
<input class="form-check-input ms-0 me-2 mt-1 float-end" type="checkbox" name="subjects[]" value="<?= e((string) $subject['id']) ?>" id="subject_<?= e((string) $subject['id']) ?>" <?= in_array((string) $subject['id'], $current_subjects_ids, true) ? 'checked' : '' ?>>
<label class="form-check-label d-block me-4 pe-2" for="subject_<?= e((string) $subject['id']) ?>">
<span class="fw-bold d-block text-dark"><?= e((string) $subject['name']) ?></span>
<?php if (!empty($subject['description'])): ?>
<small class="text-muted d-block mt-1"><?= e((string) $subject['description']) ?></small>
<?php endif; ?>
</label>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="text-start">
<button type="submit" class="btn btn-dark px-4">حفظ المواد الدراسية</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
<?php render_page_end();

View File

@ -0,0 +1,40 @@
-- Safely add the column.
SET @dbname = DATABASE();
SET @tablename = 'school_cycles';
SET @columnname = 'global_cycle_id';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
"SELECT 1",
CONCAT("ALTER TABLE ", @tablename, " ADD ", @columnname, " INT UNSIGNED NULL AFTER center_application_id;")
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Safely add foreign key
SET @fkname = 'fk_school_cycles_global_cycle';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (constraint_name = @fkname)
) > 0,
"SELECT 1",
CONCAT("ALTER TABLE ", @tablename, " ADD CONSTRAINT ", @fkname, " FOREIGN KEY (", @columnname, ") REFERENCES global_cycles(id) ON DELETE SET NULL;")
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- We can make season/year nullable or give defaults, since we will use global cycles instead.
-- We'll just alter them to allow NULL
ALTER TABLE school_cycles MODIFY season VARCHAR(20) NULL;
ALTER TABLE school_cycles MODIFY year SMALLINT UNSIGNED NULL;

View File

@ -0,0 +1,45 @@
CREATE TABLE IF NOT EXISTS global_cycles (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
cycle_name VARCHAR(150) NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- We need to safely add the column.
SET @dbname = DATABASE();
SET @tablename = 'center_applications';
SET @columnname = 'global_cycle_id';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
"SELECT 1",
CONCAT("ALTER TABLE ", @tablename, " ADD ", @columnname, " INT UNSIGNED NULL AFTER expected_students;")
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Safely add foreign key
SET @fkname = 'fk_center_applications_global_cycle';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (constraint_name = @fkname)
) > 0,
"SELECT 1",
CONCAT("ALTER TABLE ", @tablename, " ADD CONSTRAINT ", @fkname, " FOREIGN KEY (", @columnname, ") REFERENCES global_cycles(id) ON DELETE SET NULL;")
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;

49
fix_app.php Normal file
View File

@ -0,0 +1,49 @@
<?php
$file = 'includes/app.php';
$content = file_get_contents($file);
$search1 = "ensure_center_application_schema(\$pdo);";
$replace1 = "ensure_app_settings_schema(\$pdo);\n ensure_center_application_schema(\$pdo);";
$content = str_replace($search1, $replace1, $content);
$search2 = "function ensure_center_application_schema(PDO \$pdo): void";
$replace2 = "function ensure_app_settings_schema(PDO \$pdo): void
{
\$pdo->exec(\"
CREATE TABLE IF NOT EXISTS app_settings (
id INT PRIMARY KEY DEFAULT 1,
app_name VARCHAR(190) NOT NULL DEFAULT 'Central Admin',
app_email VARCHAR(190) DEFAULT NULL,
app_telephone VARCHAR(60) DEFAULT NULL,
app_logo VARCHAR(255) DEFAULT NULL,
app_favicon VARCHAR(255) DEFAULT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
\");
\$pdo->exec(\"INSERT IGNORE INTO app_settings (id, app_name) VALUES (1, 'Central Admin')\");
}
function get_app_settings(): array
{
\$pdo = db_connection();
\$stmt = \$pdo->query('SELECT * FROM app_settings WHERE id = 1');
\$res = \$stmt->fetch(PDO::FETCH_ASSOC);
if (!\$res) {
return [
'app_name' => 'Central Admin',
'app_email' => '',
'app_telephone' => '',
'app_logo' => '',
'app_favicon' => ''
];
}
return \$res;
}
function ensure_center_application_schema(PDO \$pdo): void";
$content = str_replace($search2, $replace2, $content);
file_put_contents($file, $content);
echo "Done\n";

52
fix_app_settings.py Normal file
View File

@ -0,0 +1,52 @@
import re
with open('includes/app.php', 'r') as f:
content = f.read()
# Add ensure_app_settings_schema call
content = content.replace(
'ensure_center_application_schema($pdo);',
'ensure_app_settings_schema($pdo);
ensure_center_application_schema($pdo);'
)
# Add function definitions before ensure_center_application_schema definition
new_funcs = """function ensure_app_settings_schema(PDO $pdo): void
{
$pdo->exec("
CREATE TABLE IF NOT EXISTS app_settings (
id INT PRIMARY KEY DEFAULT 1,
app_name VARCHAR(190) NOT NULL DEFAULT 'Central Admin',
app_email VARCHAR(190) DEFAULT NULL,
app_telephone VARCHAR(60) DEFAULT NULL,
app_logo VARCHAR(255) DEFAULT NULL,
app_favicon VARCHAR(255) DEFAULT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
$pdo->exec("INSERT IGNORE INTO app_settings (id, app_name) VALUES (1, 'Central Admin')");
}
function get_app_settings(): array
{
$pdo = db_connection();
$stmt = $pdo->query('SELECT * FROM app_settings WHERE id = 1');
$res = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$res) {
return [
'app_name' => 'Central Admin',
'app_email' => '',
'app_telephone' => '',
'app_logo' => '',
'app_favicon' => ''
];
}
return $res;
}
function ensure_center_application_schema(PDO $pdo): void"""
content = content.replace('function ensure_center_application_schema(PDO $pdo): void', new_funcs)
with open('includes/app.php', 'w') as f:
f.write(content)

297
global_cycles.php Normal file
View File

@ -0,0 +1,297 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
$flash = consume_flash();
$errors = [];
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'add') {
$cycleName = clean_text($_POST['cycle_name'] ?? '');
$startDate = $_POST['start_date'] ?? '';
$endDate = $_POST['end_date'] ?? '';
if ($cycleName === '') $errors['cycle_name'] = 'اسم الدورة مطلوب';
if ($startDate === '') $errors['start_date'] = 'تاريخ البداية مطلوب';
if ($endDate === '') $errors['end_date'] = 'تاريخ النهاية مطلوب';
if (empty($errors)) {
try {
$stmt = db()->prepare('INSERT INTO global_cycles (cycle_name, start_date, end_date) VALUES (?, ?, ?)');
$stmt->execute([$cycleName, $startDate, $endDate]);
set_flash('success', 'تم إضافة الدورة بنجاح.');
header('Location: global_cycles.php');
exit;
} catch (Throwable $e) {
$errors['form'] = 'حدث خطأ أثناء حفظ الدورة.';
}
}
} elseif ($action === 'toggle') {
$cycleId = filter_input(INPUT_POST, 'cycle_id', FILTER_VALIDATE_INT);
if ($cycleId) {
try {
$stmt = db()->prepare('UPDATE global_cycles SET is_active = NOT is_active WHERE id = ?');
$stmt->execute([$cycleId]);
set_flash('success', 'تم تغيير حالة الدورة.');
} catch (Throwable $e) {
set_flash('error', 'حدث خطأ.');
}
}
header('Location: global_cycles.php');
exit;
} elseif ($action === 'delete') {
$cycleId = filter_input(INPUT_POST, 'cycle_id', FILTER_VALIDATE_INT);
if ($cycleId) {
try {
$stmt = db()->prepare('DELETE FROM global_cycles WHERE id = ?');
$stmt->execute([$cycleId]);
set_flash('success', 'تم حذف الدورة بنجاح.');
} catch (Throwable $e) {
set_flash('error', 'لا يمكن حذف الدورة، ربما تكون مرتبطة بطلبات أخرى.');
}
}
header('Location: global_cycles.php');
exit;
} elseif ($action === 'edit') {
$cycleId = filter_input(INPUT_POST, 'cycle_id', FILTER_VALIDATE_INT);
$cycleName = clean_text($_POST['cycle_name'] ?? '');
$startDate = $_POST['start_date'] ?? '';
$endDate = $_POST['end_date'] ?? '';
if ($cycleId && $cycleName && $startDate && $endDate) {
try {
$stmt = db()->prepare('UPDATE global_cycles SET cycle_name = ?, start_date = ?, end_date = ? WHERE id = ?');
$stmt->execute([$cycleName, $startDate, $endDate, $cycleId]);
set_flash('success', 'تم تعديل الدورة بنجاح.');
} catch (Throwable $e) {
set_flash('error', 'حدث خطأ أثناء التعديل.');
}
}
header('Location: global_cycles.php');
exit;
}
}
// Fetch cycles
$search = clean_text($_GET['search'] ?? '', 255);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$cycles = [];
$totalItems = 0;
try {
$query = 'SELECT * FROM global_cycles';
$countQuery = 'SELECT COUNT(*) FROM global_cycles';
$params = [];
if ($search !== '') {
$where = ' WHERE cycle_name LIKE ?';
$query .= $where;
$countQuery .= $where;
$params[] = "%$search%";
}
$stmtCount = db()->prepare($countQuery);
$stmtCount->execute($params);
$totalItems = (int)$stmtCount->fetchColumn();
$query .= ' ORDER BY start_date DESC LIMIT ' . $limit . ' OFFSET ' . $offset;
$stmt = db()->prepare($query);
$stmt->execute($params);
$cycles = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {}
render_page_start('إدارة الدورات الموسمية', 'cycles', 'إضافة وإدارة الدورات التي تتقدم لها المراكز.');
render_flash($flash);
?>
<section class="py-4 py-lg-5">
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php require __DIR__ . '/includes/sidebar.php'; ?>
</div>
<div class="col-lg-9">
<div class="page-banner mb-4">
<div class="row align-items-center">
<div class="col-md-8">
<h1 class="page-title mb-2">الدورات الموسمية</h1>
<p class="page-copy mb-0">يمكنك هنا تعريف الدورات الجديدة ليتاح للمراكز التقديم عليها.</p>
</div>
<div class="col-md-4 text-md-end mt-3 mt-md-0">
<button type="button" class="btn btn-dark" data-bs-toggle="modal" data-bs-target="#addCycleModal">
إضافة دورة جديدة
</button>
</div>
</div>
</div>
<?php if (!empty($errors['form'])): ?>
<div class="alert alert-danger mb-4"><?= e($errors['form']) ?></div>
<?php endif; ?>
<?php render_search_bar($search, "ابحث باسم الدورة...", "global_cycles.php", $_GET); ?>
<div class="app-card table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>اسم الدورة</th>
<th>الفترة الزمنية</th>
<th>الحالة</th>
<th class="text-end">الإجراءات</th>
</tr>
</thead>
<tbody>
<?php if (empty($cycles)): ?>
<tr>
<td colspan="4" class="text-center py-4 text-muted">لا توجد دورات مسجلة حالياً.</td>
</tr>
<?php else: ?>
<?php foreach ($cycles as $cycle): ?>
<tr>
<td><strong><?= e((string)$cycle['cycle_name']) ?></strong></td>
<td>
<small class="text-muted d-block">من: <?= e((string)$cycle['start_date']) ?></small>
<small class="text-muted d-block">إلى: <?= e((string)$cycle['end_date']) ?></small>
</td>
<td>
<?php if ($cycle['is_active']): ?>
<span class="badge bg-success">متاحة للتقديم</span>
<?php else: ?>
<span class="badge bg-secondary">مغلقة</span>
<?php endif; ?>
</td>
<td class="text-end">
<div class="d-flex justify-content-end gap-2">
<button type="button" class="btn btn-sm btn-outline-primary"
data-bs-toggle="modal"
data-bs-target="#editCycleModal"
data-id="<?= e((string)$cycle['id']) ?>"
data-name="<?= e((string)$cycle['cycle_name']) ?>"
data-start="<?= e((string)$cycle['start_date']) ?>"
data-end="<?= e((string)$cycle['end_date']) ?>">
تعديل
</button>
<form method="post" class="d-inline-block m-0" onsubmit="return confirm('هل أنت متأكد من حذف هذه الدورة؟');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="cycle_id" value="<?= e((string)$cycle['id']) ?>">
<button type="submit" class="btn btn-sm btn-outline-danger">
حذف
</button>
</form>
<form method="post" class="d-inline-block m-0">
<input type="hidden" name="action" value="toggle">
<input type="hidden" name="cycle_id" value="<?= e((string)$cycle['id']) ?>">
<button type="submit" class="btn btn-sm <?= $cycle['is_active'] ? 'btn-outline-secondary' : 'btn-outline-success' ?>">
<?= $cycle['is_active'] ? 'إغلاق' : 'تفعيل' ?>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php render_pagination($totalItems, $limit, $page, $_GET); ?>
</div>
</div>
</div>
</section>
<!-- Add Cycle Modal -->
<div class="modal fade" id="addCycleModal" tabindex="-1" aria-labelledby="addCycleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post">
<input type="hidden" name="action" value="add">
<div class="modal-header">
<h5 class="modal-title" id="addCycleModalLabel">إضافة دورة جديدة</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="إغلاق"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label" for="cycle_name">اسم الدورة</label>
<input type="text" class="form-control" id="cycle_name" name="cycle_name" required placeholder="مثال: الدورة الصيفية لعام 2026">
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="start_date">تاريخ البداية</label>
<input type="date" class="form-control" id="start_date" name="start_date" required>
</div>
<div class="col-md-6">
<label class="form-label" for="end_date">تاريخ النهاية</label>
<input type="date" class="form-control" id="end_date" name="end_date" required>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">إلغاء</button>
<button type="submit" class="btn btn-dark">حفظ الدورة</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Cycle Modal -->
<div class="modal fade" id="editCycleModal" tabindex="-1" aria-labelledby="editCycleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="cycle_id" id="edit_cycle_id">
<div class="modal-header">
<h5 class="modal-title" id="editCycleModalLabel">تعديل الدورة</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="إغلاق"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label" for="edit_cycle_name">اسم الدورة</label>
<input type="text" class="form-control" id="edit_cycle_name" name="cycle_name" required>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="edit_start_date">تاريخ البداية</label>
<input type="date" class="form-control" id="edit_start_date" name="start_date" required>
</div>
<div class="col-md-6">
<label class="form-label" for="edit_end_date">تاريخ النهاية</label>
<input type="date" class="form-control" id="edit_end_date" name="end_date" required>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">إلغاء</button>
<button type="submit" class="btn btn-primary">حفظ التعديلات</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var editModal = document.getElementById('editCycleModal');
if(editModal) {
editModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var id = button.getAttribute('data-id');
var name = button.getAttribute('data-name');
var start = button.getAttribute('data-start');
var end = button.getAttribute('data-end');
editModal.querySelector('#edit_cycle_id').value = id;
editModal.querySelector('#edit_cycle_name').value = name;
editModal.querySelector('#edit_start_date').value = start;
editModal.querySelector('#edit_end_date').value = end;
});
}
});
</script>
<?php render_page_end(); ?>

View File

@ -96,6 +96,7 @@ function db_connection(): PDO
}
if (!$bootstrapped) {
ensure_app_settings_schema($pdo);
ensure_center_application_schema($pdo);
seed_center_application_demo_data($pdo);
ensure_school_module_schema($pdo);
@ -107,6 +108,39 @@ function db_connection(): PDO
return $pdo;
}
function ensure_app_settings_schema(PDO $pdo): void
{
$pdo->exec("
CREATE TABLE IF NOT EXISTS app_settings (
id INT PRIMARY KEY DEFAULT 1,
app_name VARCHAR(190) NOT NULL DEFAULT 'Central Admin',
app_email VARCHAR(190) DEFAULT NULL,
app_telephone VARCHAR(60) DEFAULT NULL,
app_logo VARCHAR(255) DEFAULT NULL,
app_favicon VARCHAR(255) DEFAULT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
$pdo->exec("INSERT IGNORE INTO app_settings (id, app_name) VALUES (1, 'Central Admin')");
}
function get_app_settings(): array
{
$pdo = db_connection();
$stmt = $pdo->query('SELECT * FROM app_settings WHERE id = 1');
$res = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$res) {
return [
'app_name' => 'Central Admin',
'app_email' => '',
'app_telephone' => '',
'app_logo' => '',
'app_favicon' => ''
];
}
return $res;
}
function ensure_center_application_schema(PDO $pdo): void
{
static $done = false;
@ -406,7 +440,7 @@ function application_defaults(): array
'phone' => '',
'email' => '',
'expected_students' => '',
'start_date' => '',
'start_date' => '', 'global_cycle_id' => '',
'end_date' => '',
'notes' => '',
'subjects' => [],
@ -1294,6 +1328,75 @@ function render_page_start(string $pageTitle, string $active = 'home', string $p
<?php
}
function render_pagination(int $totalItems, int $limit, int $page, array $queryParams = []): void {
if ($totalItems <= $limit) {
return;
}
$totalPages = (int) ceil($totalItems / $limit);
$page = max(1, min($page, $totalPages));
echo '<nav aria-label="Page navigation" class="mt-4">';
echo '<ul class="pagination justify-content-center">';
// Prev
$prevClass = $page <= 1 ? 'disabled' : '';
$prevUrl = '?';
$params = $queryParams;
$params['page'] = $page - 1;
$prevUrl .= http_build_query($params);
echo '<li class="page-item ' . $prevClass . '">';
echo '<a class="page-link" href="' . e($prevUrl) . '">السابق</a>';
echo '</li>';
// Pages
for ($i = 1; $i <= $totalPages; $i++) {
$activeClass = $i === $page ? 'active' : '';
$params['page'] = $i;
$url = '?' . http_build_query($params);
echo '<li class="page-item ' . $activeClass . '">';
echo '<a class="page-link" href="' . e($url) . '">' . $i . '</a>';
echo '</li>';
}
// Next
$nextClass = $page >= $totalPages ? 'disabled' : '';
$params['page'] = $page + 1;
$nextUrl = '?' . http_build_query($params);
echo '<li class="page-item ' . $nextClass . '">';
echo '<a class="page-link" href="' . e($nextUrl) . '">التالي</a>';
echo '</li>';
echo '</ul>';
echo '</nav>';
}
function render_search_bar(string $currentSearch, string $placeholder = "بحث...", string $action = "", array $hiddenParams = []): void {
echo '<form method="GET" action="' . e($action) . '" class="mb-4 search-form">';
foreach ($hiddenParams as $k => $v) {
if ($k === 'search' || $k === 'page') continue;
echo '<input type="hidden" name="' . e($k) . '" value="' . e((string)$v) . '">';
}
echo '<div class="input-group">';
echo '<input type="text" name="search" class="form-control" placeholder="' . e($placeholder) . '" value="' . e($currentSearch) . '">';
echo '<button class="btn btn-outline-secondary" type="submit">بحث</button>';
if ($currentSearch !== '') {
$clearParams = $hiddenParams;
unset($clearParams['search']);
unset($clearParams['page']);
$clearUrl = $action;
if (!empty($clearParams)) {
$clearUrl .= '?' . http_build_query($clearParams);
}
echo '<a href="' . e($clearUrl) . '" class="btn btn-outline-danger">إلغاء</a>';
}
echo '</div>';
echo '</form>';
}
function render_flash(?array $flash): void
{
if (!$flash || empty($flash['message'])) {
@ -1368,3 +1471,14 @@ function get_enabled_subjects(): array
return [];
}
}
function get_active_global_cycles(): array {
try {
$stmt = db()->query('SELECT * FROM global_cycles WHERE is_active = 1 ORDER BY start_date DESC');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
return [];
}
}

View File

@ -0,0 +1,73 @@
<?php
$script = basename($_SERVER['SCRIPT_NAME'] ?? '');
$activePage = 'dashboard';
if ($script === 'center_profile.php') $activePage = 'profile';
if ($script === 'students.php') $activePage = 'students';
if ($script === 'teachers.php') $activePage = 'teachers';
if ($script === 'assessments.php') $activePage = 'assessments';
if ($script === 'attendance.php') $activePage = 'attendance';
if ($script === 'center_subjects.php') $activePage = 'subjects';
// We assume $application is available in scope.
if (!isset($application) || !$application) {
// Fallback if somehow included without context
$application = ['id' => 0, 'center_name' => 'المركز غير متوفر'];
}
// Same for selected cycle
$selectedCycleIdStr = isset($selectedCycleId) && $selectedCycleId > 0 ? '&cycle=' . $selectedCycleId : '';
$baseQuery = '?id=' . e((string) $application['id']) . $selectedCycleIdStr;
?>
<aside class="admin-sidebar sticky-top" style="top: 2rem; z-index: 10;">
<div class="sidebar-nav">
<div class="sidebar-label text-truncate" title="<?= e((string) $application['center_name']) ?>">
<?= e((string) $application['center_name']) ?>
</div>
<a href="approved_school.php<?= $baseQuery ?>" class="sidebar-link <?= $activePage === 'dashboard' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4zM3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z"/><path fill-rule="evenodd" d="M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10zm8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3z"/></svg>
لوحة القيادة
</a>
<div class="sidebar-label mt-3">الإدارة الأكاديمية</div>
<a href="students.php<?= $baseQuery ?>" class="sidebar-link <?= $activePage === 'students' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/><path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/><path d="M4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/></svg>
الطلاب
</a>
<a href="teachers.php<?= $baseQuery ?>" class="sidebar-link <?= $activePage === 'teachers' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8zm-7.978-1A.261.261 0 0 1 7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002a.274.274 0 0 1-.014.002H7.022zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0zM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816zM4.92 10A5.493 5.493 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275zM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/></svg>
المعلمون والفريق
</a>
<a href="center_subjects.php<?= $baseQuery ?>" class="sidebar-link <?= $activePage === 'subjects' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.156 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811V2.828zm7.5-.141c.654-.596 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z"/></svg>
المواد الدراسية
</a>
<a href="assessments.php<?= $baseQuery ?>" class="sidebar-link <?= $activePage === 'assessments' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"/><path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/></svg>
التقييمات
</a>
<a href="attendance.php<?= $baseQuery ?>" class="sidebar-link <?= $activePage === 'attendance' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1V2zm1 12h2V2h-2v12zm-3 0V7H7v7h2zm-5 0v-3H2v3h2z"/></svg>
سجلات الغياب
</a>
<div class="sidebar-label mt-3">إعدادات المركز</div>
<a href="center_profile.php<?= $baseQuery ?>" class="sidebar-link <?= $activePage === 'profile' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"/><path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/></svg>
ملف المركز
</a>
<div class="sidebar-label mt-3">العودة</div>
<a href="admin.php" class="sidebar-link">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"/></svg>
الإدارة المركزية
</a>
</div>
</aside>

View File

@ -203,30 +203,26 @@ function validate_school_cycle_input(array $input, ?array $application = null):
{
$defaults = school_cycle_defaults($application);
$data = $defaults;
$data['season'] = clean_text((string) ($input['season'] ?? $defaults['season']), 20);
$data['year'] = clean_text((string) ($input['year'] ?? $defaults['year']), 4);
$data['start_date'] = clean_text((string) ($input['start_date'] ?? $defaults['start_date']), 20);
$data['end_date'] = clean_text((string) ($input['end_date'] ?? $defaults['end_date']), 20);
$data['status'] = clean_text((string) ($input['status'] ?? 'active'), 20);
$data['global_cycle_id'] = filter_var($input['global_cycle_id'] ?? null, FILTER_VALIDATE_INT) ?: null;
$errors = [];
if (!array_key_exists($data['season'], school_cycle_season_options())) {
$errors['season'] = 'يرجى اختيار موسم صحيح.';
}
$year = (int) $data['year'];
if ((string) $year !== $data['year'] || $year < 2020 || $year > 2100) {
$errors['year'] = 'يرجى إدخال سنة صحيحة مثل 2026.';
}
if ($data['start_date'] === '' || strtotime($data['start_date']) === false) {
$errors['start_date'] = 'يرجى إدخال تاريخ بداية صحيح.';
}
if ($data['end_date'] === '' || strtotime($data['end_date']) === false) {
$errors['end_date'] = 'يرجى إدخال تاريخ نهاية صحيح.';
}
if (!isset($errors['start_date'], $errors['end_date']) && strtotime($data['end_date']) < strtotime($data['start_date'])) {
$errors['end_date'] = 'تاريخ النهاية يجب أن يكون بعد البداية أو مساوياً لها.';
if (empty($data['global_cycle_id'])) {
$errors['global_cycle_id'] = 'يرجى اختيار الدورة.';
} else {
try {
$stmt = db_connection()->prepare('SELECT * FROM global_cycles WHERE id = ?');
$stmt->execute([$data['global_cycle_id']]);
if ($cycle = $stmt->fetch(PDO::FETCH_ASSOC)) {
$data['cycle_name'] = $cycle['cycle_name'];
$data['start_date'] = $cycle['start_date'];
$data['end_date'] = $cycle['end_date'];
$data['season'] = null; // No longer used
$data['year'] = null; // No longer used
} else {
$errors['global_cycle_id'] = 'الدورة غير صالحة';
}
} catch (Throwable $e) {}
}
if (!in_array($data['status'], ['active', 'upcoming'], true)) {
@ -260,20 +256,32 @@ function ensure_default_school_cycle_record(PDO $pdo, array $application): int
return (int) $existing['id'];
}
$season = detect_school_cycle_season((string) ($application['start_date'] ?? ''));
$year = (int) date('Y', strtotime((string) ($application['start_date'] ?? 'now')) ?: time());
$season = null;
$year = null;
$startDate = (string) ($application['start_date'] ?? date('Y-m-d'));
$endDate = (string) ($application['end_date'] ?? $startDate);
$cycleName = format_school_cycle_name($season, $year);
$cycleName = 'الدورة الأساسية';
$globalCycleId = $application['global_cycle_id'] ?? null;
if ($globalCycleId) {
$gcStmt = $pdo->prepare('SELECT * FROM global_cycles WHERE id = ?');
$gcStmt->execute([$globalCycleId]);
if ($gc = $gcStmt->fetch()) {
$cycleName = $gc['cycle_name'];
$startDate = $gc['start_date'];
$endDate = $gc['end_date'];
}
}
$status = ((string) ($application['status'] ?? '') === 'approved') ? 'active' : 'upcoming';
$insert = $pdo->prepare(
'INSERT INTO school_cycles (
center_application_id, season, year, cycle_name, start_date, end_date,
status, archived_at, created_at, updated_at
status, archived_at, created_at, updated_at, global_cycle_id
) VALUES (
:center_application_id, :season, :year, :cycle_name, :start_date, :end_date,
:status, NULL, NOW(), NOW()
:status, NULL, NOW(), NOW(), :global_cycle_id
)'
);
$insert->execute([
@ -284,6 +292,7 @@ function ensure_default_school_cycle_record(PDO $pdo, array $application): int
':start_date' => $startDate,
':end_date' => $endDate,
':status' => $status,
':global_cycle_id' => $globalCycleId
]);
return (int) $pdo->lastInsertId();
@ -420,9 +429,10 @@ function copy_school_cycle_rollover(PDO $pdo, int $centerApplicationId, int $sou
function create_school_cycle(int $centerApplicationId, array $data, array $rollover = []): array
{
$pdo = db_connection();
$season = $data['season'];
$year = (int) $data['year'];
$cycleName = format_school_cycle_name($season, $year);
$season = $data['season'] ?? null;
$year = isset($data['year']) ? (int) $data['year'] : null;
$cycleName = $data['cycle_name'] ?? format_school_cycle_name($season ?? 'summer', $year ?? (int)date('Y'));
$globalCycleId = $data['global_cycle_id'] ?? null;
$rollover = array_merge(school_cycle_rollover_defaults(), $rollover);
$sourceCycleId = (int) ($rollover['source_cycle_id'] ?? 0);
@ -455,10 +465,10 @@ function create_school_cycle(int $centerApplicationId, array $data, array $rollo
$insert = $pdo->prepare(
'INSERT INTO school_cycles (
center_application_id, season, year, cycle_name, start_date, end_date,
status, archived_at, created_at, updated_at
status, archived_at, created_at, updated_at, global_cycle_id
) VALUES (
:center_application_id, :season, :year, :cycle_name, :start_date, :end_date,
:status, :archived_at, NOW(), NOW()
:status, :archived_at, NOW(), NOW(), :global_cycle_id
)'
);
$insert->execute([
@ -470,6 +480,7 @@ function create_school_cycle(int $centerApplicationId, array $data, array $rollo
':end_date' => $data['end_date'],
':status' => $data['status'],
':archived_at' => $data['status'] === 'archived' ? date('Y-m-d H:i:s') : null,
':global_cycle_id' => $globalCycleId,
]);
$cycleId = (int) $pdo->lastInsertId();
@ -581,21 +592,54 @@ function create_student_in_cycle(int $centerApplicationId, int $cycleId, array $
return (int) $pdo->lastInsertId();
}
function list_school_students_by_cycle(int $centerApplicationId, int $cycleId): array
function list_school_students_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
$stmt = $pdo->prepare(
'SELECT * FROM school_students
WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id
ORDER BY created_at DESC, id DESC'
);
$stmt->execute([
$query = 'SELECT * FROM school_students WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
$params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
]);
];
if ($search !== '') {
$query .= ' AND (student_code LIKE :search1 OR full_name LIKE :search2 OR guardian_phone LIKE :search3)';
$params[':search1'] = "%$search%";
$params[':search2'] = "%$search%";
$params[':search3'] = "%$search%";
}
$query .= ' ORDER BY created_at DESC, id DESC';
if ($limit > 0) {
$query .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return $stmt->fetchAll();
}
function count_school_students_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
{
$pdo = db_connection();
$query = 'SELECT COUNT(*) FROM school_students WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
$params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
];
if ($search !== '') {
$query .= ' AND (student_code LIKE :search1 OR full_name LIKE :search2 OR guardian_phone LIKE :search3)';
$params[':search1'] = "%$search%";
$params[':search2'] = "%$search%";
$params[':search3'] = "%$search%";
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
}
function school_student_metrics_by_cycle(int $centerApplicationId, int $cycleId): array
{
$pdo = db_connection();
@ -654,21 +698,54 @@ function create_teacher_in_cycle(int $centerApplicationId, int $cycleId, array $
return (int) $pdo->lastInsertId();
}
function list_school_teachers_by_cycle(int $centerApplicationId, int $cycleId): array
function list_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
$stmt = $pdo->prepare(
'SELECT * FROM school_teachers
WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id
ORDER BY created_at DESC, id DESC'
);
$stmt->execute([
$query = 'SELECT * FROM school_teachers WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
$params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
]);
];
if ($search !== '') {
$query .= ' AND (full_name LIKE :search1 OR email LIKE :search2 OR phone LIKE :search3)';
$params[':search1'] = "%$search%";
$params[':search2'] = "%$search%";
$params[':search3'] = "%$search%";
}
$query .= ' ORDER BY created_at DESC, id DESC';
if ($limit > 0) {
$query .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return $stmt->fetchAll();
}
function count_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
{
$pdo = db_connection();
$query = 'SELECT COUNT(*) FROM school_teachers WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
$params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
];
if ($search !== '') {
$query .= ' AND (full_name LIKE :search1 OR email LIKE :search2 OR phone LIKE :search3)';
$params[':search1'] = "%$search%";
$params[':search2'] = "%$search%";
$params[':search3'] = "%$search%";
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
}
function school_teacher_metrics_by_cycle(int $centerApplicationId, int $cycleId): array
{
$pdo = db_connection();
@ -729,21 +806,52 @@ function create_assessment_type_in_cycle(int $centerApplicationId, int $cycleId,
return (int) $pdo->lastInsertId();
}
function list_school_assessments_by_cycle(int $centerApplicationId, int $cycleId): array
function list_school_assessments_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
$stmt = $pdo->prepare(
'SELECT * FROM school_assessment_types
WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id
ORDER BY is_active DESC, created_at DESC, id DESC'
);
$stmt->execute([
$query = 'SELECT * FROM school_assessment_types WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
$params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
]);
];
if ($search !== '') {
$query .= ' AND (assessment_name LIKE :search1 OR assessment_category LIKE :search2)';
$params[':search1'] = "%$search%";
$params[':search2'] = "%$search%";
}
$query .= ' ORDER BY is_active DESC, created_at DESC, id DESC';
if ($limit > 0) {
$query .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return $stmt->fetchAll();
}
function count_school_assessments_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
{
$pdo = db_connection();
$query = 'SELECT COUNT(*) FROM school_assessment_types WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
$params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
];
if ($search !== '') {
$query .= ' AND (assessment_name LIKE :search1 OR assessment_category LIKE :search2)';
$params[':search1'] = "%$search%";
$params[':search2'] = "%$search%";
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
}
function school_assessment_metrics_by_cycle(int $centerApplicationId, int $cycleId): array
{
$pdo = db_connection();
@ -860,23 +968,58 @@ function create_attendance_record_in_cycle(int $centerApplicationId, int $cycleI
return (int) $pdo->lastInsertId();
}
function list_school_attendance_records_by_cycle(int $centerApplicationId, int $cycleId): array
function list_school_attendance_records_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
$stmt = $pdo->prepare(
'SELECT ar.*, s.student_code, s.full_name, s.grade_level, s.guardian_phone
$query = 'SELECT ar.*, s.student_code, s.full_name, s.grade_level, s.guardian_phone
FROM school_attendance_records ar
INNER JOIN school_students s ON s.id = ar.student_id
WHERE ar.center_application_id = :center_application_id AND ar.cycle_id = :cycle_id
ORDER BY ar.attendance_date DESC, ar.created_at DESC, ar.id DESC'
);
$stmt->execute([
WHERE ar.center_application_id = :center_application_id AND ar.cycle_id = :cycle_id';
$params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
]);
];
if ($search !== '') {
$query .= ' AND (s.full_name LIKE :search1 OR s.student_code LIKE :search2)';
$params[':search1'] = "%$search%";
$params[':search2'] = "%$search%";
}
$query .= ' ORDER BY ar.attendance_date DESC, ar.created_at DESC, ar.id DESC';
if ($limit > 0) {
$query .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return $stmt->fetchAll();
}
function count_school_attendance_records_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
{
$pdo = db_connection();
$query = 'SELECT COUNT(*)
FROM school_attendance_records ar
INNER JOIN school_students s ON s.id = ar.student_id
WHERE ar.center_application_id = :center_application_id AND ar.cycle_id = :cycle_id';
$params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
];
if ($search !== '') {
$query .= ' AND (s.full_name LIKE :search1 OR s.student_code LIKE :search2)';
$params[':search1'] = "%$search%";
$params[':search2'] = "%$search%";
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
}
function school_attendance_metrics_by_cycle(int $centerApplicationId, int $cycleId): array
{
$pdo = db_connection();

View File

@ -5,6 +5,7 @@ $statusQuery = $_GET['status'] ?? '';
$activePage = 'admin';
if ($script === 'app_settings.php') $activePage = 'app_settings';
if ($script === 'subjects.php') $activePage = 'subjects';
if ($script === 'global_cycles.php') $activePage = 'global_cycles';
if ($script === 'dashboard.php') $activePage = 'dashboard';
if ($script === 'applications.php') $activePage = ($statusQuery === 'approved') ? 'approved' : 'applications';
if (in_array($script, ['approved_school.php', 'center_profile.php', 'students.php', 'teachers.php', 'assessments.php', 'attendance.php'], true)) $activePage = 'approved';
@ -38,6 +39,10 @@ if (!isset($recentApproved)) {
<div class="sidebar-label mt-3">الإعدادات</div>
<a href="global_cycles.php" class="sidebar-link <?= $activePage === 'global_cycles' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1V2zm1 12h2V2h-2v12zm-3 0V7H7v7h2zm-5 0v-3H2v3h2z"/></svg>
الدورات الموسمية
</a>
<a href="app_settings.php" class="sidebar-link <?= $activePage === 'app_settings' ? 'active' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"/><path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/></svg>
الإعدادات العامة

38
patch_approved_school.py Normal file
View File

@ -0,0 +1,38 @@
import re
with open('approved_school.php', 'r') as f:
content = f.read()
# Replace the layout start
layout_start_replacement = """<section class="py-4 py-lg-5">
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
<?php if (!$isApproved): ?>"""
content = re.sub(r'<section class="py-4 py-lg-5">\s*<div class="container-xxl">\s*<\?php if \(!\$isApproved\): \?>', layout_start_replacement, content)
# Replace the layout end. Wait, currently it is:
# </div>
# </div>
#
# </div>
# </section>
# Let's just do string replacement for the end.
end_str = """ </div>
</div>
</div>
</section>"""
new_end_str = """ </div>
</div>
</div>
</div>
</section>"""
content = content.replace(end_str, new_end_str)
with open('approved_school.php', 'w') as f:
f.write(content)

View File

@ -53,7 +53,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
}
}
$students = $isApprovedSchool && $selectedCycleId > 0 ? list_school_students_by_cycle((int) $application['id'], $selectedCycleId) : [];
$search = clean_text($_GET['search'] ?? '', 255);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$students = $isApprovedSchool && $selectedCycleId > 0 ? list_school_students_by_cycle((int) $application['id'], $selectedCycleId, $search, $limit, $offset) : [];
$totalStudents = $isApprovedSchool && $selectedCycleId > 0 ? count_school_students_by_cycle((int) $application['id'], $selectedCycleId, $search) : 0;
$metrics = $isApprovedSchool && $selectedCycleId > 0 ? school_student_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
'total' => 0,
'boys' => 0,
@ -84,7 +91,7 @@ render_flash($flash);
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php require __DIR__ . '/includes/sidebar.php'; ?>
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
@ -322,6 +329,7 @@ render_flash($flash);
</div>
<span class="header-chip"><?= e((string) $metrics['boys']) ?> طلاب / <?= e((string) $metrics['girls']) ?> طالبات</span>
</div>
<?php render_search_bar($search, 'ابحث باسم الطالب، الكود، أو رقم الهاتف...', school_page_url('students.php', (int)$application['id'], $selectedCycleId), $_GET); ?>
<div class="row g-3 mb-3">
<div class="col-md-4"><div class="school-data-item"><strong>إجمالي القيد</strong><span><?= e((string) $metrics['total']) ?> طالب</span></div></div>
@ -369,6 +377,7 @@ render_flash($flash);
</tbody>
</table>
</div>
<?php render_pagination($totalStudents, $limit, $page, $_GET); ?>
<?php endif; ?>
</div>

View File

@ -53,14 +53,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Read list
$search = clean_text($_GET['search'] ?? '', 255);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$query = 'SELECT * FROM subjects';
$countQuery = 'SELECT COUNT(*) FROM subjects';
$params = [];
if ($search !== '') {
$query .= ' WHERE name LIKE ? OR description LIKE ?';
$where = ' WHERE name LIKE ? OR description LIKE ?';
$query .= $where;
$countQuery .= $where;
$params[] = "%$search%";
$params[] = "%$search%";
}
$query .= ' ORDER BY id DESC';
$stmtCount = db()->prepare($countQuery);
$stmtCount->execute($params);
$totalItems = (int)$stmtCount->fetchColumn();
$query .= ' ORDER BY id DESC LIMIT ' . $limit . ' OFFSET ' . $offset;
$stmt = db()->prepare($query);
$stmt->execute($params);
$subjects = $stmt->fetchAll();
@ -88,15 +100,7 @@ render_flash($flash);
</div>
<!-- Search Bar -->
<form method="GET" action="subjects.php" class="mb-4">
<div class="input-group">
<input type="text" name="search" class="form-control" placeholder="ابحث باسم المادة أو الوصف..." value="<?= e($search) ?>">
<button class="btn btn-outline-secondary" type="submit">بحث</button>
<?php if ($search !== ''): ?>
<a href="subjects.php" class="btn btn-outline-danger">إلغاء</a>
<?php endif; ?>
</div>
</form>
<?php render_search_bar($search, "ابحث باسم المادة أو الوصف...", "subjects.php", $_GET); ?>
<div class="table-responsive">
<table class="table app-table align-middle">
@ -160,6 +164,7 @@ render_flash($flash);
</tbody>
</table>
</div>
<?php render_pagination($totalItems, $limit, $page, $_GET); ?>
</div>
</div>
</div>

View File

@ -46,7 +46,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
}
}
$teachers = $isApprovedSchool && $selectedCycleId > 0 ? list_school_teachers_by_cycle((int) $application['id'], $selectedCycleId) : [];
$search = clean_text($_GET['search'] ?? '', 255);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$teachers = $isApprovedSchool && $selectedCycleId > 0 ? list_school_teachers_by_cycle((int) $application['id'], $selectedCycleId, $search, $limit, $offset) : [];
$totalTeachers = $isApprovedSchool && $selectedCycleId > 0 ? count_school_teachers_by_cycle((int) $application['id'], $selectedCycleId, $search) : 0;
$metrics = $isApprovedSchool && $selectedCycleId > 0 ? school_teacher_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
'total' => 0,
'active' => 0,
@ -83,7 +90,7 @@ render_flash($flash);
<div class="container-xxl">
<div class="row g-4 align-items-start">
<div class="col-lg-3">
<?php require __DIR__ . '/includes/sidebar.php'; ?>
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
</div>
<div class="col-lg-9">
@ -296,6 +303,7 @@ render_flash($flash);
</div>
<span class="header-chip"><?= e((string) $metrics['email_ready']) ?> بريد جاهز / <?= e((string) $metrics['pending']) ?> بانتظار التفعيل</span>
</div>
<?php render_search_bar($search, 'ابحث باسم المعلم، البريد، أو رقم الهاتف...', school_page_url('teachers.php', (int)$application['id'], $selectedCycleId), $_GET); ?>
<div class="row g-3 mb-3">
<div class="col-md-4"><div class="school-data-item"><strong>إجمالي الفريق</strong><span><?= e((string) $metrics['total']) ?> عضو</span></div></div>
@ -341,6 +349,7 @@ render_flash($flash);
</tbody>
</table>
</div>
<?php render_pagination($totalTeachers, $limit, $page, $_GET); ?>
<?php endif; ?>
</div>