update center page
This commit is contained in:
parent
e5366bdd58
commit
371b07bffa
@ -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>
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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
124
center_subjects.php
Normal 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();
|
||||
40
db/migrations/20260416_alter_school_cycles.sql
Normal file
40
db/migrations/20260416_alter_school_cycles.sql
Normal 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;
|
||||
45
db/migrations/20260416_global_cycles.sql
Normal file
45
db/migrations/20260416_global_cycles.sql
Normal 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
49
fix_app.php
Normal 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
52
fix_app_settings.py
Normal 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
297
global_cycles.php
Normal 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(); ?>
|
||||
116
includes/app.php
116
includes/app.php
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
73
includes/center_sidebar.php
Normal file
73
includes/center_sidebar.php
Normal 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>
|
||||
@ -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();
|
||||
|
||||
@ -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
38
patch_approved_school.py
Normal 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)
|
||||
13
students.php
13
students.php
@ -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>
|
||||
|
||||
|
||||
27
subjects.php
27
subjects.php
@ -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>
|
||||
|
||||
13
teachers.php
13
teachers.php
@ -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>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user