Autosave: 20260416-163907

This commit is contained in:
Flatlogic Bot 2026-04-16 16:39:04 +00:00
parent 371b07bffa
commit a55b4a5394
15 changed files with 751 additions and 328 deletions

View File

@ -158,7 +158,7 @@ render_flash($flash);
<?php if (!$isApproved): ?>
<div class="page-banner approved-hero mb-4 mb-lg-5">
<div class="row g-4 align-items-center">
<div class="col-lg-8">
<div class="<?= is_super_admin() ? 'col-lg-8' : 'col-lg-12' ?>">
<span class="approved-kicker mb-3">هذه الصفحة تُفتح بعد الاعتماد فقط</span>
<h1 class="page-title mb-3"><?= e((string) $application['center_name']) ?></h1>
<p class="page-copy mb-3">تم تجهيز صفحة الهبوط لهذا المركز، لكنها ستصبح الصفحة التشغيلية الرسمية فقط بعد تغيير الحالة إلى <strong>معتمد</strong>.</p>
@ -177,7 +177,7 @@ render_flash($flash);
<?php else: ?>
<div class="page-banner approved-hero mb-4 mb-lg-5">
<div class="row g-4 align-items-center">
<div class="col-lg-8">
<div class="<?= is_super_admin() ? 'col-lg-8' : 'col-lg-12' ?>">
<span class="approved-kicker mb-3">مركز معتمد وجاهز للانطلاق</span>
<h1 class="page-title mb-3"><?= e((string) $application['center_name']) ?></h1>
<p class="page-copy mb-3">هذه صفحة الهبوط الخاصة بالمركز بعد الاعتماد. يمكن استخدامها كنقطة دخول منظمة لبدء التشغيل، تجهيز التسجيل، ومشاركة المعلومات الأساسية مع فريق المدرسة.</p>
@ -225,7 +225,7 @@ render_flash($flash);
<?php if ($selectedCycle): ?>
<div class="row g-4 mb-4" id="cycles">
<div class="col-lg-8">
<div class="<?= is_super_admin() ? 'col-lg-8' : 'col-lg-12' ?>">
<div class="app-card h-100">
<div class="section-head mb-3">
<div>
@ -249,11 +249,11 @@ render_flash($flash);
<a class="btn btn-outline-secondary" href="<?= e($assessmentsUrl) ?>">تقييمات هذه الدورة</a>
<a class="btn btn-outline-secondary" href="<?= e($attendanceUrl) ?>">غياب هذه الدورة</a>
<?php if (!$isCycleReadOnly): ?>
<form method="post" class="d-inline">
<?php if (is_super_admin()): ?><form method="post" class="d-inline">
<input type="hidden" name="cycle_action" value="archive_cycle">
<input type="hidden" name="cycle_id" value="<?= e((string) $selectedCycleId) ?>">
<button class="btn btn-outline-danger" type="submit">أرشفة هذه الدورة</button>
</form>
</form><?php endif; ?>
<?php endif; ?>
</div>
<?php if ($isCycleReadOnly): ?>
@ -261,7 +261,8 @@ render_flash($flash);
<?php endif; ?>
</div>
</div>
<div class="col-lg-4">
<?php if (is_super_admin()): ?>
<div class="col-lg-4">
<div class="app-card sidebar-card mb-4">
<div class="section-title mb-3">كل دورات المركز</div>
<div class="quick-link-stack">
@ -270,6 +271,7 @@ render_flash($flash);
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<div class="app-card sidebar-card">
<div class="section-title mb-3">فتح دورة جديدة</div>
<p class="section-subtle mb-3">استخدم هذا النموذج عند نهاية الصيف أو الشتاء لبدء دورة جديدة باسم تلقائي مثل Summer 2026 أو Winter 2026.</p>
@ -331,7 +333,7 @@ render_flash($flash);
<?php endif; ?>
<div class="row g-4 align-items-start">
<div class="col-lg-8">
<div class="<?= is_super_admin() ? 'col-lg-8' : 'col-lg-12' ?>">
<div class="app-card mb-4">
<div class="section-head mb-3">
<div>

View File

@ -175,7 +175,7 @@ render_flash($flash);
<?php if ($selectedCycle): ?>
<?php $cycleStatusMap = school_cycle_status_map(); ?>
<div class="row g-4 mb-4 align-items-start">
<div class="col-lg-7">
<div class="col-lg-<?= is_super_admin() ? '7' : '12' ?>">
<div class="app-card h-100">
<div class="section-head mb-3">
<div>
@ -193,14 +193,15 @@ render_flash($flash);
<div class="col-md-4"><div class="school-data-item"><strong>الأوزان المفعلة</strong><span><?= e(number_format($activeWeight, 2, '.', '')) ?>%</span></div></div>
</div>
<div class="cta-stack mt-3">
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>#cycles">إدارة الدورات الموسمية</a>
<?php if (is_super_admin()): ?><a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>#cycles">إدارة الدورات الموسمية</a><?php endif; ?>
</div>
<?php if ($isCycleReadOnly): ?>
<div class="alert alert-warning mt-3 mb-0">هذه الدورة مؤرشفة، لذلك تبقى خطة التقييم للقراءة فقط حالياً.</div>
<?php endif; ?>
</div>
</div>
<div class="col-lg-5">
<?php if (is_super_admin()): ?>
<div class="col-lg-5">
<div class="app-card sidebar-card h-100">
<div class="section-title mb-3">التبديل بين الدورات</div>
<p class="section-subtle mb-3">افتح نفس صفحة التقييمات لأي موسم سابق أو حالي حتى تراجع الأوزان والخطط التاريخية بسرعة.</p>
@ -221,6 +222,7 @@ render_flash($flash);
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>

View File

@ -190,7 +190,7 @@ render_flash($flash);
<?php if ($selectedCycle): ?>
<?php $cycleStatusMap = school_cycle_status_map(); ?>
<div class="row g-4 mb-4 align-items-start">
<div class="col-lg-7">
<div class="col-lg-<?= is_super_admin() ? '7' : '12' ?>">
<div class="app-card h-100">
<div class="section-head mb-3">
<div>
@ -205,14 +205,15 @@ render_flash($flash);
<div class="col-md-4"><div class="school-data-item"><strong>عدد الدورات</strong><span><?= e((string) count($cycleContext['cycles'])) ?> دورة للمركز</span></div></div>
</div>
<div class="cta-stack mt-3">
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>#cycles">إدارة الدورات الموسمية</a>
<?php if (is_super_admin()): ?><a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>#cycles">إدارة الدورات الموسمية</a><?php endif; ?>
</div>
<?php if ($isCycleReadOnly): ?>
<div class="alert alert-warning mt-3 mb-0">هذه الدورة مؤرشفة، لذلك تبقى صفحة الغياب للقراءة فقط حالياً.</div>
<?php endif; ?>
</div>
</div>
<div class="col-lg-5">
<?php if (is_super_admin()): ?>
<div class="col-lg-5">
<div class="app-card sidebar-card h-100">
<div class="section-title mb-3">التبديل بين الدورات</div>
<p class="section-subtle mb-3">افتح سجل الغياب لنفس المركز في أي موسم سابق أو حالي مباشرة من هذه الصفحة.</p>
@ -233,6 +234,7 @@ render_flash($flash);
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>

View File

@ -66,6 +66,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
} catch (Throwable $e) {
$errors['form'] = 'تعذر حفظ البيانات. يرجى المحاولة لاحقاً.';
}
}
}
render_page_start('إدارة المواد الدراسية: ' . (string) $application['center_name'], 'profile', 'تحديد المواد الدراسية التي يتم تدريسها في المركز.');
@ -97,7 +98,7 @@ render_flash($flash);
<?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' : '' ?>>
<input class="form-check-input ms-0 me-2 mt-1 float-end" type="checkbox" name="subjects[]" <?= is_super_admin() ? "" : "disabled" ?> 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'])): ?>
@ -112,7 +113,11 @@ render_flash($flash);
</div>
<div class="text-start">
<?php if (is_super_admin()): ?>
<button type="submit" class="btn btn-dark px-4">حفظ المواد الدراسية</button>
<?php else: ?>
<div class="alert alert-warning mb-0">يمكن للمشرف العام فقط تعديل هذه القائمة.</div>
<?php endif; ?>
</div>
</form>
</div>

View File

@ -2,6 +2,23 @@
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
if (!is_super_admin()) {
http_response_code(403);
render_page_start('صلاحيات غير كافية', '');
?>
<section class="py-5 text-center">
<div class="container-xxl">
<h1 class="mb-3">عذراً</h1>
<p>هذه الصفحة مخصصة للمشرف العام فقط.</p>
<a href="index.php" class="btn btn-dark mt-3">العودة للرئيسية</a>
</div>
</section>
<?php
render_page_end();
exit;
}
$flash = consume_flash();
$errors = [];

View File

@ -8,6 +8,14 @@ if (session_status() === PHP_SESSION_NONE) {
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/cycles.php';
function is_super_admin(): bool {
if (isset($_GET['role'])) {
$_SESSION['role'] = $_GET['role'];
}
return ($_SESSION['role'] ?? 'super_admin') === 'super_admin';
}
function env_value(string $key, string $default = ''): string
{
$serverValue = $_SERVER[$key] ?? null;
@ -1316,7 +1324,18 @@ function render_page_start(string $pageTitle, string $active = 'home', string $p
<li class="nav-item"><a class="nav-link <?= $active === 'modules' ? 'active' : '' ?>" href="modules.php">هيكل النظام</a></li>
</ul>
<div class="d-flex align-items-center gap-2 header-actions">
<span class="header-chip">صلاحية: المشرف العام</span>
<?php
$isSuperAdmin = is_super_admin();
$roleName = $isSuperAdmin ? 'المشرف العام' : 'مدير المركز';
$nextRole = $isSuperAdmin ? 'center_admin' : 'super_admin';
$currentUrl = $_SERVER['REQUEST_URI'];
$roleSwitchUrl = strpos($currentUrl, '?') !== false
? preg_replace('/([?&])role=[^&]*(&|$)/', '$1', $currentUrl)
: $currentUrl;
$roleSwitchUrl = rtrim($roleSwitchUrl, '?&');
$roleSwitchUrl .= (strpos($roleSwitchUrl, '?') !== false ? '&' : '?') . 'role=' . $nextRole;
?>
<a href="<?= e($roleSwitchUrl) ?>" class="header-chip text-decoration-none bg-primary text-white" style="cursor:pointer;" title="اضغط للتبديل">صلاحية: <?= e($roleName) ?> ⟳</a>
<a class="btn btn-dark btn-sm px-3" href="applications.php?status=submitted">مراجعة سريعة</a>
</div>
</div>

View File

@ -298,6 +298,32 @@ function ensure_default_school_cycle_record(PDO $pdo, array $application): int
return (int) $pdo->lastInsertId();
}
function update_teacher_in_cycle(int $id, int $centerApplicationId, int $cycleId, array $data): void
{
$pdo = db_connection();
$stmt = $pdo->prepare(
'UPDATE school_teachers SET
full_name = :full_name, role_title = :role_title, specialization = :specialization,
phone = :phone, email = :email, employment_status = :employment_status, notes = :notes,
updated_at = NOW()
WHERE id = :id AND center_application_id = :center_application_id AND cycle_id = :cycle_id'
);
$stmt->execute([
':id' => $id,
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
':full_name' => $data['full_name'],
':role_title' => $data['role_title'],
':specialization' => $data['specialization'],
':phone' => $data['phone'],
':email' => $data['email'],
':employment_status' => $data['employment_status'],
':notes' => $data['notes'],
]);
}
function list_school_cycles(int $centerApplicationId): array
{
$pdo = db_connection();
@ -592,7 +618,7 @@ function create_student_in_cycle(int $centerApplicationId, int $cycleId, array $
return (int) $pdo->lastInsertId();
}
function list_school_students_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
function list_school_students_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0, array $filters = []): array
{
$pdo = db_connection();
$query = 'SELECT * FROM school_students WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
@ -608,6 +634,7 @@ function list_school_students_by_cycle(int $centerApplicationId, int $cycleId, s
$params[':search3'] = "%$search%";
}
if (!empty($filters["gender"])) { $query .= " AND gender = :gender"; $params[":gender"] = $filters["gender"]; } if (!empty($filters["grade_level"])) { $query .= " AND grade_level = :grade_level"; $params[":grade_level"] = $filters["grade_level"]; } if (!empty($filters["enrollment_status"])) { $query .= " AND enrollment_status = :enrollment_status"; $params[":enrollment_status"] = $filters["enrollment_status"]; }
$query .= ' ORDER BY created_at DESC, id DESC';
if ($limit > 0) {
@ -619,7 +646,7 @@ function list_school_students_by_cycle(int $centerApplicationId, int $cycleId, s
return $stmt->fetchAll();
}
function count_school_students_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
function count_school_students_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', array $filters = []): int
{
$pdo = db_connection();
$query = 'SELECT COUNT(*) FROM school_students WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
@ -635,6 +662,7 @@ function count_school_students_by_cycle(int $centerApplicationId, int $cycleId,
$params[':search3'] = "%$search%";
}
if (!empty($filters["gender"])) { $query .= " AND gender = :gender"; $params[":gender"] = $filters["gender"]; } if (!empty($filters["grade_level"])) { $query .= " AND grade_level = :grade_level"; $params[":grade_level"] = $filters["grade_level"]; } if (!empty($filters["enrollment_status"])) { $query .= " AND enrollment_status = :enrollment_status"; $params[":enrollment_status"] = $filters["enrollment_status"]; }
$stmt = $pdo->prepare($query);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
@ -698,7 +726,7 @@ function create_teacher_in_cycle(int $centerApplicationId, int $cycleId, array $
return (int) $pdo->lastInsertId();
}
function list_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
function list_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, array $filters = [], int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
$query = 'SELECT * FROM school_teachers WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
@ -707,12 +735,23 @@ function list_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, s
':cycle_id' => $cycleId,
];
$search = $filters['search'] ?? '';
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%";
}
if (!empty($filters['role_title'])) {
$query .= ' AND role_title = :role';
$params[':role'] = $filters['role_title'];
}
if (!empty($filters['employment_status'])) {
$query .= ' AND employment_status = :status';
$params[':status'] = $filters['employment_status'];
}
$query .= ' ORDER BY created_at DESC, id DESC';
@ -725,7 +764,7 @@ function list_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, s
return $stmt->fetchAll();
}
function count_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
function count_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, array $filters = []): int
{
$pdo = db_connection();
$query = 'SELECT COUNT(*) FROM school_teachers WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
@ -734,12 +773,23 @@ function count_school_teachers_by_cycle(int $centerApplicationId, int $cycleId,
':cycle_id' => $cycleId,
];
$search = $filters['search'] ?? '';
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%";
}
if (!empty($filters['role_title'])) {
$query .= ' AND role_title = :role';
$params[':role'] = $filters['role_title'];
}
if (!empty($filters['employment_status'])) {
$query .= ' AND employment_status = :status';
$params[':status'] = $filters['employment_status'];
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
@ -1051,3 +1101,28 @@ function school_attendance_metrics_by_cycle(int $centerApplicationId, int $cycle
'today_count' => (int) ($row['today_count'] ?? 0),
];
}
function update_student_in_cycle(int $centerApplicationId, int $cycleId, int $studentId, array $data): void
{
$pdo = db_connection();
$stmt = $pdo->prepare(
'UPDATE school_students SET
student_code = :student_code, full_name = :full_name, gender = :gender, grade_level = :grade_level,
guardian_name = :guardian_name, guardian_phone = :guardian_phone, birth_date = :birth_date,
enrollment_status = :enrollment_status, notes = :notes, updated_at = NOW()
WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id AND id = :id'
);
$stmt->bindValue(':center_application_id', $centerApplicationId, PDO::PARAM_INT);
$stmt->bindValue(':cycle_id', $cycleId, PDO::PARAM_INT);
$stmt->bindValue(':id', $studentId, PDO::PARAM_INT);
$stmt->bindValue(':student_code', $data['student_code'], PDO::PARAM_STR);
$stmt->bindValue(':full_name', $data['full_name'], PDO::PARAM_STR);
$stmt->bindValue(':gender', $data['gender'], PDO::PARAM_STR);
$stmt->bindValue(':grade_level', $data['grade_level'], PDO::PARAM_STR);
$stmt->bindValue(':guardian_name', $data['guardian_name'], PDO::PARAM_STR);
$stmt->bindValue(':guardian_phone', $data['guardian_phone'], PDO::PARAM_STR);
$stmt->bindValue(':birth_date', $data['birth_date'] !== '' ? $data['birth_date'] : null, $data['birth_date'] !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
$stmt->bindValue(':enrollment_status', $data['enrollment_status'], PDO::PARAM_STR);
$stmt->bindValue(':notes', $data['notes'] !== '' ? $data['notes'] : null, $data['notes'] !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
$stmt->execute();
}

32
patch_admin_pages.py Normal file
View File

@ -0,0 +1,32 @@
import re
files = ['subjects.php', 'global_cycles.php']
for file in files:
with open(file, 'r', encoding='utf-8') as f:
content = f.read()
access_check = r"""
if (!is_super_admin()) {
http_response_code(403);
render_page_start('صلاحيات غير كافية', '');
?>
<section class="py-5 text-center">
<div class="container-xxl">
<h1 class="mb-3">عذراً</h1>
<p>هذه الصفحة مخصصة للمشرف العام فقط.</p>
<a href="index.php" class="btn btn-dark mt-3">العودة للرئيسية</a>
</div>
</section>
<?php
render_page_end();
exit;
}
"""
if 'if (!is_super_admin())' not in content:
content = content.replace("require_once __DIR__ . '/includes/app.php';", "require_once __DIR__ . '/includes/app.php';\n" + access_check)
with open(file, 'w', encoding='utf-8') as f:
f.write(content)

36
patch_app_role.py Normal file
View File

@ -0,0 +1,36 @@
import re
with open('includes/app.php', 'r', encoding='utf-8') as f:
content = f.read()
# 1. Add is_super_admin()
role_func = """
function is_super_admin(): bool {
if (isset($_GET['role'])) {
$_SESSION['role'] = $_GET['role'];
}
return ($_SESSION['role'] ?? 'super_admin') === 'super_admin';
}
"""
if 'function is_super_admin' not in content:
content = content.replace("function env_value", role_func + "\nfunction env_value", 1)
# 2. Update the header
header_old = '<span class="header-chip">صلاحية: المشرف العام</span>'
header_new = """<?php
$isSuperAdmin = is_super_admin();
$roleName = $isSuperAdmin ? 'المشرف العام' : 'مدير المركز';
$nextRole = $isSuperAdmin ? 'center_admin' : 'super_admin';
$currentUrl = $_SERVER['REQUEST_URI'];
$roleSwitchUrl = strpos($currentUrl, '?') !== false
? preg_replace('/([?&])role=[^&]*(&|$)/', '$1', $currentUrl)
: $currentUrl;
$roleSwitchUrl = rtrim($roleSwitchUrl, '?&');
$roleSwitchUrl .= (strpos($roleSwitchUrl, '?') !== false ? '&' : '?') . 'role=' . $nextRole;
?>
<a href=\"<?= e($roleSwitchUrl) ?>\" class=\"header-chip text-decoration-none bg-primary text-white\" style=\"cursor:pointer;\" title=\"اضغط للتبديل\">صلاحية: <?= e($roleName) ?> ⟳</a>"""
content = content.replace(header_old, header_new)
with open('includes/app.php', 'w', encoding='utf-8') as f:
f.write(content)

View File

@ -0,0 +1,28 @@
import re
with open('approved_school.php', 'r', encoding='utf-8') as f:
content = f.read()
# Hide "archive cycle" form in approved_school.php
archive_form = r"""(<form method="post" class="d-inline">\s*<input type="hidden" name="cycle_action" value="archive_cycle">.*?<\/form>)"""
content = re.sub(archive_form, r'<?php if (is_super_admin()): ?>\1<?php endif; ?>', content, flags=re.DOTALL)
# Hide col-lg-4 containing cycles switcher and start new cycle form
col_lg_4_cycles = r"""(<div class="col-lg-4">\s*<div class="app-card sidebar-card mb-4">\s*<div class="section-title mb-3">كل دورات المركز.*?</div>\s*</div>)"""
content = re.sub(col_lg_4_cycles, r'<?php if (is_super_admin()): ?>\n\1\n<?php endif; ?>', content, flags=re.DOTALL)
# Adjust col-lg-8 to be conditionally 12
content = re.sub(r'<div class="col-lg-8">', r'<div class="<?= is_super_admin() ? \'col-lg-8\' : \'col-lg-12\' ?>">', content)
# Check POST handler for cycles in approved_school.php
post_handler = r"if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['cycle_action'])) {"
post_handler_new = r"""if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['cycle_action'])) {
if (!is_super_admin()) {
set_flash('error', 'عذراً، الإدارة الكاملة للدورات متاحة للمشرف العام فقط.');
header('Location: approved_school.php?id=' . $application['id']);
exit;
}"""
content = content.replace(post_handler, post_handler_new)
with open('approved_school.php', 'w', encoding='utf-8') as f:
f.write(content)

42
patch_center_subjects.py Normal file
View File

@ -0,0 +1,42 @@
import re
with open('center_subjects.php', 'r', encoding='utf-8') as f:
content = f.read()
# Protect POST update action
post_handler = r"if \(\$_SERVER\['REQUEST_METHOD'\] === 'POST' && isset\(\$_POST\['action'\]\) && \$_POST\['action'\] === 'update_subjects'\) \{"
post_handler_new = r"""if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_subjects') {
if (!is_super_admin()) {
$errors['form'] = 'عذراً، تعديل المواد متاح للمشرف العام فقط.';
} else {"""
content = content.replace(post_handler, post_handler_new)
# Find the try/catch block to close the else
try_block = r""" update_application_subjects\(\(int\) \$application\['id'\], \$selected_subjects_ids\);.*?\} catch \(Throwable \$e\) \{.*?\}"""
try_block_new = r""" 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'] = 'تعذر حفظ البيانات. يرجى المحاولة لاحقاً.';
}
}"""
content = re.sub(r" update_application_subjects\(\(int\) \$application\['id'\], \$selected_subjects_ids\);.*?\} catch \(Throwable \$e\) \{.*?\}", try_block_new, content, flags=re.DOTALL)
# Disable checkboxes and hide button if not super admin
checkbox = r'<input class="form-check-input ms-0 me-2 mt-1 float-end" type="checkbox" name="subjects\[\]"'
checkbox_new = r'<input class="form-check-input ms-0 me-2 mt-1 float-end" type="checkbox" name="subjects[]" <?= is_super_admin() ? "" : "disabled" ?>'
content = re.sub(checkbox, checkbox_new, content)
btn = r'<button type="submit" class="btn btn-dark px-4">حفظ المواد الدراسية</button>'
btn_new = r"""<?php if (is_super_admin()): ?>
<button type="submit" class="btn btn-dark px-4">حفظ المواد الدراسية</button>
<?php else: ?>
<div class="alert alert-warning mb-0">يمكن للمشرف العام فقط تعديل هذه القائمة.</div>
<?php endif; ?>"""
content = content.replace(btn, btn_new)
with open('center_subjects.php', 'w', encoding='utf-8') as f:
f.write(content)

40
patch_cycles_switcher.py Normal file
View File

@ -0,0 +1,40 @@
import re
import glob
files = ['students.php', 'teachers.php', 'assessments.php', 'attendance.php']
for file in files:
with open(file, 'r', encoding='utf-8') as f:
content = f.read()
# Change <div class="col-lg-7"> to conditionally be 12
# But wait, is it col-lg-7?
content = re.sub(r'<div class="col-lg-7">', r'<div class="col-lg-<?= is_super_admin() ? \'7\' : \'12\' ?>', content)
# Wrap col-lg-5 in is_super_admin
# We will find the start of col-lg-5 that contains "التبديل بين الدورات"
# and ends at the closing div
# Let's find:
# <div class="col-lg-5">
# <div class="app-card sidebar-card h-100">
# <div class="section-title mb-3">التبديل بين الدورات</div>
# ...
# </div>
# </div>
pattern = r'(<div class="col-lg-5">\s*<div class="app-card sidebar-card h-100">\s*<div class="section-title mb-3">التبديل بين الدورات</div>.*?</div>\s*</div>)'
def replacer(match):
return f"<?php if (is_super_admin()): ?>\n{match.group(1)}\n<?php endif; ?>"
content = re.sub(pattern, replacer, content, flags=re.DOTALL)
# Also restrict the button "إدارة الدورات الموسمية"
# <a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>#cycles">إدارة الدورات الموسمية</a>
# We should hide this button for center_admin
btn_pattern = r'(<a class="btn btn-outline-secondary" href="<\?= e\(\$approvedSchoolUrl\) \?>#cycles">إدارة الدورات الموسمية</a>)'
content = re.sub(btn_pattern, r'<?php if (is_super_admin()): ?>\1<?php endif; ?>', content)
with open(file, 'w', encoding='utf-8') as f:
f.write(content)

View File

@ -24,6 +24,8 @@ if ($application && $isApprovedSchool) {
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
$action = $_POST['action'] ?? 'add';
$studentId = filter_input(INPUT_POST, 'student_id', FILTER_VALIDATE_INT) ?: 0;
[$values, $errors] = validate_student_input($_POST);
if (!$isApprovedSchool) {
@ -31,19 +33,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
} elseif ($selectedCycleId <= 0) {
$errors['form'] = 'يرجى إنشاء دورة موسمية أولاً من صفحة المركز.';
} elseif ($isCycleReadOnly) {
$errors['form'] = 'هذه الدورة مؤرشفة للقراءة فقط. افتح دورة جديدة أو اختر دورة نشطة لإضافة طلاب جدد.';
$errors['form'] = 'هذه الدورة مؤرشفة للقراءة فقط. افتح دورة جديدة أو اختر دورة نشطة لإضافة/تعديل طلاب.';
}
if ($errors === []) {
try {
create_student_in_cycle((int) $application['id'], $selectedCycleId, $values);
set_flash('success', 'تم تسجيل الطالب بنجاح داخل الدورة الموسمية المحددة.');
if ($action === 'edit' && $studentId > 0) {
update_student_in_cycle((int) $application['id'], $selectedCycleId, $studentId, $values);
set_flash('success', 'تم تحديث بيانات الطالب بنجاح.');
} else {
create_student_in_cycle((int) $application['id'], $selectedCycleId, $values);
set_flash('success', 'تم تسجيل الطالب بنجاح داخل الدورة الموسمية المحددة.');
}
header('Location: ' . school_page_url('students.php', (int) $application['id'], $selectedCycleId));
exit;
} catch (PDOException $exception) {
$duplicateCode = isset($exception->errorInfo[1]) && (int) $exception->errorInfo[1] === 1062;
if ($duplicateCode) {
$errors['student_code'] = 'هذا الكود مستخدم مسبقاً داخل نفس الدورة الموسمية.';
$errors['form'] = 'تعذر الحفظ لوجود تعارض في الكود.';
} else {
$errors['form'] = 'تعذر حفظ بيانات الطالب حالياً. يرجى المحاولة مرة أخرى.';
}
@ -54,12 +62,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
}
$search = clean_text($_GET['search'] ?? '', 255);
$filters = [
'gender' => clean_text($_GET['gender'] ?? '', 50),
'grade_level' => clean_text($_GET['grade_level'] ?? '', 50),
'enrollment_status' => clean_text($_GET['enrollment_status'] ?? '', 50),
];
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$limit = 15;
$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;
$students = $isApprovedSchool && $selectedCycleId > 0 ? list_school_students_by_cycle((int) $application['id'], $selectedCycleId, $search, $limit, $offset, $filters) : [];
$totalStudents = $isApprovedSchool && $selectedCycleId > 0 ? count_school_students_by_cycle((int) $application['id'], $selectedCycleId, $search, $filters) : 0;
$metrics = $isApprovedSchool && $selectedCycleId > 0 ? school_student_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
'total' => 0,
@ -89,7 +103,7 @@ render_flash($flash);
?>
<section class="py-4 py-lg-5">
<div class="container-xxl">
<div class="row g-4 align-items-start">
<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>
@ -125,19 +139,15 @@ render_flash($flash);
<div class="col-lg-8">
<span class="approved-kicker mb-3">صفحة مستقلة لتسجيل الطلاب</span>
<h1 class="page-title mb-3">سجل الطلاب <?= e((string) $application['center_name']) ?></h1>
<p class="page-copy mb-3">هذا هو أول موديول تشغيلي بعد اعتماد المدرسة. هنا يتم إدخال بيانات الطلاب في صفحة منفصلة وواضحة، مع كشف جاهز للمراجعة دون خلطه مع بقية وظائف المدرسة.</p>
<p class="page-copy mb-3">هذا هو أول موديول تشغيلي بعد اعتماد المدرسة. يتم عرض الطلاب مع إمكانية التصفية، بالإضافة إلى إدارة البيانات عن طريق نموذج مدمج.</p>
<div class="hero-meta">
<span><?= e((string) $application['city']) ?></span>
<span>السعة المعتمدة <?= e((string) $expectedCapacity) ?> طالب</span>
<span>المقاعد المتبقية <?= e((string) $remainingSeats) ?></span>
</div>
<div class="cta-stack mt-4">
<a class="btn btn-dark" href="admin.php">لوحة الإدارة</a>
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>">العودة لصفحة المركز</a>
<a class="btn btn-outline-secondary" href="<?= e($teachersUrl) ?>">فريق المعلمين</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="<?= e($applicationDetailUrl) ?>">ملف الاعتماد</a>
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>">صفحة المركز</a>
<a class="btn btn-outline-secondary" href="<?= e($teachersUrl) ?>">المعلمين</a>
</div>
</div>
<div class="col-lg-4">
@ -148,8 +158,6 @@ render_flash($flash);
<div class="launch-metric"><strong><?= e((string) $metrics['active']) ?></strong><span>طلاب مؤكدون</span></div>
<div class="launch-metric"><strong><?= e((string) $metrics['waiting']) ?></strong><span>قائمة الانتظار</span></div>
</div>
<div class="score-bar mt-3 mb-2"><span style="width: <?= e((string) ($expectedCapacity > 0 ? min(100, (int) round(($metrics['total'] / max(1, $expectedCapacity)) * 100)) : 0)) ?>%"></span></div>
<p class="capacity-note mb-0">تم شغل <?= e((string) $metrics['total']) ?> من أصل <?= e((string) $expectedCapacity) ?> مقعد متوقع حتى الآن.</p>
</div>
</div>
</div>
@ -158,12 +166,12 @@ render_flash($flash);
<?php if ($selectedCycle): ?>
<?php $cycleStatusMap = school_cycle_status_map(); ?>
<div class="row g-4 mb-4 align-items-start">
<div class="col-lg-7">
<div class="col-lg-<?= is_super_admin() ? '7' : '12' ?>">
<div class="app-card h-100">
<div class="section-head mb-3">
<div>
<div class="section-title">الدورة الموسمية الحالية</div>
<div class="section-copy">كل بيانات هذه الصفحة مرتبطة الآن بالدورة <strong><?= e($cycleLabel) ?></strong>. عند انتهاء الموسم يمكنك أرشفتها من صفحة المركز والبدء بدورة جديدة بدون فقدان السجلات القديمة.</div>
<div class="section-copy">كل بيانات هذه الصفحة مرتبطة الآن بالدورة <strong><?= e($cycleLabel) ?></strong>.</div>
</div>
<?= school_cycle_status_badge((string) $selectedCycle['status']) ?>
</div>
@ -172,29 +180,23 @@ render_flash($flash);
<div class="col-md-4"><div class="school-data-item"><strong>الفترة</strong><span><?= e((string) $selectedCycle['start_date']) ?> → <?= e((string) $selectedCycle['end_date']) ?></span></div></div>
<div class="col-md-4"><div class="school-data-item"><strong>عدد الدورات</strong><span><?= e((string) count($cycleContext['cycles'])) ?> دورة للمركز</span></div></div>
</div>
<div class="cta-stack mt-3">
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>#cycles">إدارة الدورات الموسمية</a>
</div>
<?php if ($isCycleReadOnly): ?>
<div class="alert alert-warning mt-3 mb-0">هذه الدورة مؤرشفة حالياً، لذلك تبقى السجلات قابلة للمراجعة فقط بدون إضافة طلاب جدد.</div>
<?php endif; ?>
</div>
</div>
<?php if (is_super_admin()): ?>
<div class="col-lg-5">
<div class="app-card sidebar-card h-100">
<div class="section-title mb-3">التبديل بين الدورات</div>
<p class="section-subtle mb-3">يمكنك فتح نفس صفحة الطلاب لأي موسم سابق أو حالي مباشرة من هنا بدون الرجوع إلى صفحة المركز.</p>
<div class="quick-link-stack">
<?php foreach ($cycleContext['cycles'] as $cycle): ?>
<?php
$isCurrentCycleLink = (int) $cycle['id'] === $selectedCycleId;
$isActiveCycleLink = (int) $cycle['id'] === (int) (($cycleContext['active']['id'] ?? 0));
$cycleStatusLabel = (string) ($cycleStatusMap[$cycle['status']]['label'] ?? 'غير معروف');
$cycleMetaLine = (string) $cycle['start_date'] . ' → ' . (string) $cycle['end_date'] . ' — ' . $cycleStatusLabel . ($isActiveCycleLink ? ' — النشطة حالياً' : '');
$cycleMetaLine = (string) $cycle['start_date'] . ' → ' . (string) $cycle['end_date'] . ' — ' . $cycleStatusLabel;
?>
<a class="quick-link-item <?= $isCurrentCycleLink ? 'is-current' : '' ?>" href="<?= e(school_page_url('students.php', (int) $application['id'], (int) $cycle['id'])) ?>">
<div>
<strong><?= e((string) $cycle['cycle_name']) ?><?= $isCurrentCycleLink ? ' — المعروضة الآن' : '' ?></strong>
<strong><?= e((string) $cycle['cycle_name']) ?><?= $isCurrentCycleLink ? ' — المعروضة' : '' ?></strong>
<span><?= e($cycleMetaLine) ?></span>
</div>
</a>
@ -202,149 +204,78 @@ render_flash($flash);
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="row g-4 align-items-start">
<div class="col-lg-4">
<div class="app-card sidebar-card mb-4">
<div class="section-head mb-3">
<div>
<div class="section-title">إضافة طالب جديد</div>
<div class="section-copy">نموذج مخصص للتسجيل فقط، مفصول عن صفحة المدرسة وعن بقية الوحدات.</div>
</div>
</div>
<div class="col-lg-12">
<?php if (!empty($errors['form'])): ?>
<div class="alert alert-danger mb-4"><?= e($errors['form']) ?></div>
<?php endif; ?>
<?php if (!empty($errors['form'])): ?>
<div class="alert alert-danger mb-3"><?= e($errors['form']) ?></div>
<?php endif; ?>
<?php if ($isCycleReadOnly): ?>
<div class="alert alert-warning mb-0">هذه الدورة مؤرشفة. يمكنك مراجعة كشف الطلاب فقط، أو فتح دورة جديدة من صفحة المركز.</div>
<?php else: ?>
<form method="post" novalidate>
<div class="form-section-block mb-3">
<div class="form-section-heading">
<div>
<h2 class="form-section-title">الهوية التعليمية</h2>
<p class="form-section-copy">البيانات الأساسية التي تدخل مباشرة إلى كشف المدرسة.</p>
</div>
</div>
<div class="row g-3">
<div class="col-12">
<label class="form-label" for="student_code">الرقم / الكود</label>
<input class="form-control <?= isset($errors['student_code']) ? 'is-invalid' : '' ?>" id="student_code" name="student_code" value="<?= e($values['student_code']) ?>" placeholder="مثال: ST-401">
<?php if (isset($errors['student_code'])): ?><div class="invalid-feedback"><?= e($errors['student_code']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label class="form-label" for="full_name">اسم الطالب / الطالبة</label>
<input class="form-control <?= isset($errors['full_name']) ? 'is-invalid' : '' ?>" id="full_name" name="full_name" value="<?= e($values['full_name']) ?>" placeholder="الاسم الثلاثي">
<?php if (isset($errors['full_name'])): ?><div class="invalid-feedback"><?= e($errors['full_name']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="gender">النوع</label>
<select class="form-select <?= isset($errors['gender']) ? 'is-invalid' : '' ?>" id="gender" name="gender">
<option value="">اختر</option>
<?php foreach (student_gender_options() as $option): ?>
<option value="<?= e($option) ?>" <?= $values['gender'] === $option ? 'selected' : '' ?>><?= e($option) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['gender'])): ?><div class="invalid-feedback"><?= e($errors['gender']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="grade_level">الصف الدراسي</label>
<select class="form-select <?= isset($errors['grade_level']) ? 'is-invalid' : '' ?>" id="grade_level" name="grade_level">
<option value="">اختر</option>
<?php foreach (student_grade_options() as $option): ?>
<option value="<?= e($option) ?>" <?= $values['grade_level'] === $option ? 'selected' : '' ?>><?= e($option) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['grade_level'])): ?><div class="invalid-feedback"><?= e($errors['grade_level']) ?></div><?php endif; ?>
</div>
</div>
</div>
<div class="form-section-block mb-3">
<div class="form-section-heading">
<div>
<h2 class="form-section-title">ولي الأمر وحالة القيد</h2>
<p class="form-section-copy">تفاصيل التواصل والمتابعة قبل بداية الدوام.</p>
</div>
</div>
<div class="row g-3">
<div class="col-12">
<label class="form-label" for="guardian_name">اسم ولي الأمر</label>
<input class="form-control <?= isset($errors['guardian_name']) ? 'is-invalid' : '' ?>" id="guardian_name" name="guardian_name" value="<?= e($values['guardian_name']) ?>" placeholder="الاسم الكامل">
<?php if (isset($errors['guardian_name'])): ?><div class="invalid-feedback"><?= e($errors['guardian_name']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label class="form-label" for="guardian_phone">هاتف ولي الأمر</label>
<input class="form-control <?= isset($errors['guardian_phone']) ? 'is-invalid' : '' ?>" id="guardian_phone" name="guardian_phone" value="<?= e($values['guardian_phone']) ?>" placeholder="0500000000">
<?php if (isset($errors['guardian_phone'])): ?><div class="invalid-feedback"><?= e($errors['guardian_phone']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="birth_date">تاريخ الميلاد</label>
<input type="date" class="form-control <?= isset($errors['birth_date']) ? 'is-invalid' : '' ?>" id="birth_date" name="birth_date" value="<?= e($values['birth_date']) ?>">
<?php if (isset($errors['birth_date'])): ?><div class="invalid-feedback"><?= e($errors['birth_date']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="enrollment_status">حالة التسجيل</label>
<select class="form-select <?= isset($errors['enrollment_status']) ? 'is-invalid' : '' ?>" id="enrollment_status" name="enrollment_status">
<?php foreach (student_enrollment_status_map() as $key => $meta): ?>
<option value="<?= e($key) ?>" <?= $values['enrollment_status'] === $key ? 'selected' : '' ?>><?= e($meta['label']) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['enrollment_status'])): ?><div class="invalid-feedback"><?= e($errors['enrollment_status']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label class="form-label" for="notes">ملاحظات</label>
<textarea class="form-control" id="notes" name="notes" rows="4" placeholder="مثال: احتياج تعليمي، ملاحظة صحية، أو حالة انتظار."><?= e($values['notes']) ?></textarea>
</div>
</div>
</div>
<div class="d-grid">
<button class="btn btn-dark" type="submit">حفظ الطالب</button>
</div>
</form>
<?php endif; ?>
</div>
<div class="app-card sidebar-card">
<div class="section-title mb-3">جاهزية بقية الوحدات</div>
<ul class="module-roadmap-list mb-0">
<li><strong>المعلمين</strong><span class="section-subtle">الصفحة أصبحت جاهزة الآن لإدارة الفريق التعليمي لهذا المركز.</span><div class="mt-2"><a class="btn btn-sm btn-outline-secondary" href="<?= e($teachersUrl) ?>">فتح صفحة المعلمين</a></div></li>
<li><strong>التقييمات والأوزان</strong><span class="section-subtle">الصفحة أصبحت جاهزة الآن لتعريف أنواع التقييم والمقاييس والأوزان.</span><div class="mt-2"><a class="btn btn-sm btn-outline-secondary" href="<?= e($assessmentsUrl) ?>">فتح صفحة التقييمات</a></div></li>
<li><strong>غياب الطلاب</strong><span class="section-subtle">الصفحة أصبحت جاهزة الآن لتسجيل الغياب اليومي، الأعذار، وحالات التأخر.</span><div class="mt-2"><a class="btn btn-sm btn-outline-secondary" href="<?= e($attendanceUrl) ?>">فتح صفحة الغياب</a></div></li>
</ul>
</div>
</div>
<div class="col-lg-8">
<div class="app-card mb-4">
<div class="section-head mb-3">
<div>
<div class="section-title">كشف المدرسة</div>
<div class="section-copy">الطلاب المسجلون حالياً في هذه المدرسة فقط.</div>
</div>
<span class="header-chip"><?= e((string) $metrics['boys']) ?> طلاب / <?= e((string) $metrics['girls']) ?> طالبات</span>
<div class="d-flex gap-2">
<span class="header-chip"><?= e((string) $metrics['boys']) ?> طلاب / <?= e((string) $metrics['girls']) ?> طالبات</span>
<?php if (!$isCycleReadOnly): ?>
<button type="button" class="btn btn-dark btn-sm" data-bs-toggle="modal" data-bs-target="#studentModal" onclick="resetStudentModal()">
+ إضافة طالب
</button>
<?php endif; ?>
</div>
</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>
<div class="col-md-4"><div class="school-data-item"><strong>حالات نشطة</strong><span><?= e((string) $metrics['active']) ?> طالب مؤكد</span></div></div>
<div class="col-md-4"><div class="school-data-item"><strong>مقاعد متاحة</strong><span><?= e((string) $remainingSeats) ?> مقعد متبقٍ</span></div></div>
</div>
<form method="get" class="mb-4 bg-light p-3 rounded">
<input type="hidden" name="id" value="<?= e((string) $application['id']) ?>">
<input type="hidden" name="cycle" value="<?= e((string) $selectedCycleId) ?>">
<div class="row g-2">
<div class="col-md-3">
<input type="text" name="search" class="form-control form-control-sm" placeholder="ابحث باسم الطالب، الكود..." value="<?= e($search) ?>">
</div>
<div class="col-md-2">
<select name="gender" class="form-select form-select-sm">
<option value="">كل الأنواع</option>
<?php foreach (student_gender_options() as $option): ?>
<option value="<?= e($option) ?>" <?= $filters['gender'] === $option ? 'selected' : '' ?>><?= e($option) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<select name="grade_level" class="form-select form-select-sm">
<option value="">كل الصفوف</option>
<?php foreach (student_grade_options() as $option): ?>
<option value="<?= e($option) ?>" <?= $filters['grade_level'] === $option ? 'selected' : '' ?>><?= e($option) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<select name="enrollment_status" class="form-select form-select-sm">
<option value="">كل الحالات</option>
<?php foreach (student_enrollment_status_map() as $key => $meta): ?>
<option value="<?= e($key) ?>" <?= $filters['enrollment_status'] === $key ? 'selected' : '' ?>><?= e($meta['label']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-outline-secondary btn-sm w-100">تصفية</button>
</div>
</div>
</form>
<?php if ($students === []): ?>
<div class="empty-state text-center p-4">
<div class="empty-title mb-2">لا يوجد طلاب مسجلون بعد</div>
<p class="text-muted mb-0">ابدأ من نموذج التسجيل في الجانب الأيمن لإضافة أول طالب إلى كشف المدرسة.</p>
<div class="empty-title mb-2">لا يوجد طلاب مطابقون</div>
<p class="text-muted mb-0">ابدأ من إضافة طالب أو قم بتغيير الفلاتر.</p>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table app-table align-middle">
<table class="table app-table align-middle table-hover">
<thead>
<tr>
<th>الكود</th>
@ -353,7 +284,7 @@ render_flash($flash);
<th>ولي الأمر</th>
<th>الهاتف</th>
<th>الحالة</th>
<th>التسجيل</th>
<?php if (!$isCycleReadOnly): ?><th>إجراءات</th><?php endif; ?>
</tr>
</thead>
<tbody>
@ -362,16 +293,23 @@ render_flash($flash);
<td><strong><?= e((string) $student['student_code']) ?></strong></td>
<td>
<strong><?= e((string) $student['full_name']) ?></strong>
<small><?= e((string) $student['gender']) ?></small>
<div class="text-muted small"><?= e((string) $student['gender']) ?></div>
</td>
<td><?= e((string) $student['grade_level']) ?></td>
<td>
<strong><?= e((string) $student['guardian_name']) ?></strong>
<?php if (!empty($student['notes'])): ?><small><?= e((string) $student['notes']) ?></small><?php endif; ?>
<?php if (!empty($student['notes'])): ?><div class="text-muted small"><?= e((string) $student['notes']) ?></div><?php endif; ?>
</td>
<td><?= e((string) $student['guardian_phone']) ?></td>
<td><a href="tel:<?= e((string) $student['guardian_phone']) ?>" class="text-decoration-none text-dark"><?= e((string) $student['guardian_phone']) ?></a></td>
<td><?= student_enrollment_status_badge((string) $student['enrollment_status']) ?></td>
<td><?= e(substr((string) $student['created_at'], 0, 10)) ?></td>
<?php if (!$isCycleReadOnly): ?>
<td>
<button type="button" class="btn btn-sm btn-outline-secondary"
data-bs-toggle="modal" data-bs-target="#studentModal"
data-student='<?= htmlspecialchars(json_encode($student), ENT_QUOTES, 'UTF-8') ?>'
onclick="editStudentModal(this)">تعديل</button>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
@ -381,15 +319,6 @@ render_flash($flash);
<?php endif; ?>
</div>
<div class="app-card">
<div class="section-title mb-3">سياق المدرسة</div>
<div class="row g-3">
<div class="col-md-6"><div class="school-data-item"><strong>مدير المركز</strong><span><?= e((string) $application['director_name']) ?></span></div></div>
<div class="col-md-6"><div class="school-data-item"><strong>فترة التشغيل</strong><span><?= e((string) $application['start_date']) ?> — <?= e((string) $application['end_date']) ?></span></div></div>
<div class="col-md-6"><div class="school-data-item"><strong>الفئة المستهدفة</strong><span><?= e((string) $application['gender_scope']) ?></span></div></div>
<div class="col-md-6"><div class="school-data-item"><strong>البريد الرسمي</strong><span><?= e((string) $application['email']) ?></span></div></div>
</div>
</div>
</div>
</div>
<?php endif; ?>
@ -398,4 +327,132 @@ render_flash($flash);
</div>
</section>
<?php render_page_end(); ?>
<!-- Student Modal -->
<div class="modal fade" id="studentModal" tabindex="-1" aria-labelledby="studentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form method="post" id="studentForm" novalidate>
<div class="modal-header">
<h5 class="modal-title" id="studentModalLabel">إضافة طالب جديد</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="إغلاق"></button>
</div>
<div class="modal-body">
<input type="hidden" name="action" id="formAction" value="add">
<input type="hidden" name="student_id" id="formStudentId" value="">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="student_code">الرقم / الكود</label>
<input class="form-control <?= isset($errors['student_code']) ? 'is-invalid' : '' ?>" id="student_code" name="student_code" value="<?= e($values['student_code']) ?>" placeholder="مثال: ST-401" required>
<?php if (isset($errors['student_code'])): ?><div class="invalid-feedback"><?= e($errors['student_code']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="full_name">اسم الطالب / الطالبة</label>
<input class="form-control <?= isset($errors['full_name']) ? 'is-invalid' : '' ?>" id="full_name" name="full_name" value="<?= e($values['full_name']) ?>" placeholder="الاسم الثلاثي" required>
<?php if (isset($errors['full_name'])): ?><div class="invalid-feedback"><?= e($errors['full_name']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="gender">النوع</label>
<select class="form-select <?= isset($errors['gender']) ? 'is-invalid' : '' ?>" id="gender" name="gender" required>
<option value="">اختر</option>
<?php foreach (student_gender_options() as $option): ?>
<option value="<?= e($option) ?>" <?= $values['gender'] === $option ? 'selected' : '' ?>><?= e($option) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['gender'])): ?><div class="invalid-feedback"><?= e($errors['gender']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="grade_level">الصف الدراسي</label>
<select class="form-select <?= isset($errors['grade_level']) ? 'is-invalid' : '' ?>" id="grade_level" name="grade_level" required>
<option value="">اختر</option>
<?php foreach (student_grade_options() as $option): ?>
<option value="<?= e($option) ?>" <?= $values['grade_level'] === $option ? 'selected' : '' ?>><?= e($option) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['grade_level'])): ?><div class="invalid-feedback"><?= e($errors['grade_level']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="guardian_name">اسم ولي الأمر</label>
<input class="form-control <?= isset($errors['guardian_name']) ? 'is-invalid' : '' ?>" id="guardian_name" name="guardian_name" value="<?= e($values['guardian_name']) ?>" placeholder="الاسم الكامل" required>
<?php if (isset($errors['guardian_name'])): ?><div class="invalid-feedback"><?= e($errors['guardian_name']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="guardian_phone">هاتف ولي الأمر</label>
<input class="form-control <?= isset($errors['guardian_phone']) ? 'is-invalid' : '' ?>" id="guardian_phone" name="guardian_phone" value="<?= e($values['guardian_phone']) ?>" placeholder="0500000000" required>
<?php if (isset($errors['guardian_phone'])): ?><div class="invalid-feedback"><?= e($errors['guardian_phone']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="birth_date">تاريخ الميلاد</label>
<input type="date" class="form-control <?= isset($errors['birth_date']) ? 'is-invalid' : '' ?>" id="birth_date" name="birth_date" value="<?= e($values['birth_date']) ?>">
<?php if (isset($errors['birth_date'])): ?><div class="invalid-feedback"><?= e($errors['birth_date']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="enrollment_status">حالة التسجيل</label>
<select class="form-select <?= isset($errors['enrollment_status']) ? 'is-invalid' : '' ?>" id="enrollment_status" name="enrollment_status" required>
<?php foreach (student_enrollment_status_map() as $key => $meta): ?>
<option value="<?= e($key) ?>" <?= $values['enrollment_status'] === $key ? 'selected' : '' ?>><?= e($meta['label']) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['enrollment_status'])): ?><div class="invalid-feedback"><?= e($errors['enrollment_status']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label class="form-label" for="notes">ملاحظات</label>
<textarea class="form-control" id="notes" name="notes" rows="2" placeholder="مثال: احتياج تعليمي، ملاحظة صحية، أو حالة انتظار."><?= e($values['notes']) ?></textarea>
</div>
</div>
</div>
<div class="modal-footer d-flex justify-content-between">
<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>
<script>
function resetStudentModal() {
document.getElementById('studentModalLabel').innerText = 'إضافة طالب جديد';
document.getElementById('formAction').value = 'add';
document.getElementById('formStudentId').value = '';
// Clear inputs except defaults
const form = document.getElementById('studentForm');
form.reset();
document.getElementById('enrollment_status').value = 'active';
}
function editStudentModal(btn) {
const student = JSON.parse(btn.getAttribute('data-student'));
document.getElementById('studentModalLabel').innerText = 'تعديل طالب: ' + student.full_name;
document.getElementById('formAction').value = 'edit';
document.getElementById('formStudentId').value = student.id;
document.getElementById('student_code').value = student.student_code || '';
document.getElementById('full_name').value = student.full_name || '';
document.getElementById('gender').value = student.gender || '';
document.getElementById('grade_level').value = student.grade_level || '';
document.getElementById('guardian_name').value = student.guardian_name || '';
document.getElementById('guardian_phone').value = student.guardian_phone || '';
document.getElementById('birth_date').value = student.birth_date || '';
document.getElementById('enrollment_status').value = student.enrollment_status || '';
document.getElementById('notes').value = student.notes || '';
}
// Show modal if there are errors (from POST)
<?php if (!empty($errors) && $_SERVER['REQUEST_METHOD'] === 'POST'): ?>
document.addEventListener('DOMContentLoaded', function() {
var myModal = new bootstrap.Modal(document.getElementById('studentModal'));
myModal.show();
<?php if (isset($_POST['action']) && $_POST['action'] === 'edit'): ?>
document.getElementById('studentModalLabel').innerText = 'تعديل طالب';
document.getElementById('formAction').value = 'edit';
document.getElementById('formStudentId').value = '<?= e($_POST['student_id'] ?? '') ?>';
<?php endif; ?>
});
<?php endif; ?>
</script>
<?php render_page_end(); ?>

View File

@ -2,6 +2,23 @@
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
if (!is_super_admin()) {
http_response_code(403);
render_page_start('صلاحيات غير كافية', '');
?>
<section class="py-5 text-center">
<div class="container-xxl">
<h1 class="mb-3">عذراً</h1>
<p>هذه الصفحة مخصصة للمشرف العام فقط.</p>
<a href="index.php" class="btn btn-dark mt-3">العودة للرئيسية</a>
</div>
</section>
<?php
render_page_end();
exit;
}
// Handle POST actions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';

View File

@ -24,6 +24,8 @@ if ($application && $isApprovedSchool) {
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
$action = $_POST['action'] ?? 'add';
$teacherId = filter_input(INPUT_POST, 'teacher_id', FILTER_VALIDATE_INT) ?: 0;
[$values, $errors] = validate_teacher_input($_POST);
if (!$isApprovedSchool) {
@ -31,13 +33,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
} elseif ($selectedCycleId <= 0) {
$errors['form'] = 'يرجى إنشاء دورة موسمية أولاً من صفحة المركز.';
} elseif ($isCycleReadOnly) {
$errors['form'] = 'هذه الدورة مؤرشفة للقراءة فقط. افتح دورة جديدة أو اختر دورة نشطة لإضافة أعضاء جدد.';
$errors['form'] = 'هذه الدورة مؤرشفة للقراءة فقط. افتح دورة جديدة أو اختر دورة نشطة لإضافة/تعديل أعضاء.';
}
if ($errors === []) {
try {
create_teacher_in_cycle((int) $application['id'], $selectedCycleId, $values);
set_flash('success', 'تمت إضافة عضو الفريق داخل الدورة الموسمية المحددة بنجاح.');
if ($action === 'edit' && $teacherId > 0) {
update_teacher_in_cycle((int) $application['id'], $selectedCycleId, $teacherId, $values);
set_flash('success', 'تم تحديث بيانات عضو الفريق بنجاح.');
} else {
create_teacher_in_cycle((int) $application['id'], $selectedCycleId, $values);
set_flash('success', 'تمت إضافة عضو الفريق داخل الدورة الموسمية المحددة بنجاح.');
}
header('Location: ' . school_page_url('teachers.php', (int) $application['id'], $selectedCycleId));
exit;
} catch (Throwable $exception) {
@ -47,12 +54,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
}
$search = clean_text($_GET['search'] ?? '', 255);
$filters = [
'role_title' => clean_text($_GET['role_title'] ?? '', 50),
'employment_status' => clean_text($_GET['employment_status'] ?? '', 50),
'search' => $search,
];
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 10;
$limit = 15;
$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;
$teachers = $isApprovedSchool && $selectedCycleId > 0 ? list_school_teachers_by_cycle((int) $application['id'], $selectedCycleId, $filters, $limit, $offset) : [];
$totalTeachers = $isApprovedSchool && $selectedCycleId > 0 ? count_school_teachers_by_cycle((int) $application['id'], $selectedCycleId, $filters) : 0;
$metrics = $isApprovedSchool && $selectedCycleId > 0 ? school_teacher_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
'total' => 0,
@ -88,7 +101,7 @@ render_flash($flash);
?>
<section class="py-4 py-lg-5">
<div class="container-xxl">
<div class="row g-4 align-items-start">
<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>
@ -131,12 +144,8 @@ render_flash($flash);
<span><?= e((string) $metrics['supervisors']) ?> أدوار إشرافية</span>
</div>
<div class="cta-stack mt-4">
<a class="btn btn-dark" href="admin.php">لوحة الإدارة</a>
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>">العودة لصفحة المركز</a>
<a class="btn btn-outline-secondary" href="<?= e($studentsUrl) ?>">تسجيل الطلاب</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="<?= e($applicationDetailUrl) ?>">ملف الاعتماد</a>
</div>
</div>
<div class="col-lg-4">
@ -147,7 +156,7 @@ render_flash($flash);
<div class="summary-row"><span>جاهزون للتواصل</span><strong><?= e((string) $metrics['email_ready']) ?> ببريد موثق</strong></div>
<div class="summary-row"><span>الطلاب النشطون</span><strong><?= e((string) $studentMetrics['active']) ?> طالب/طالبة</strong></div>
</div>
<p class="section-subtle mb-0">وجود سجل واضح للمعلمين يسهّل لاحقاً ربط الحصص، التقييمات، والمتابعة اليومية لكل مدرسة معتمدة.</p>
<p class="section-subtle mb-0">وجود سجل واضح للمعلمين يسهّل لاحقاً ربط الحصص والتقييمات بكل مدرسة.</p>
</div>
</div>
</div>
@ -156,12 +165,12 @@ render_flash($flash);
<?php if ($selectedCycle): ?>
<?php $cycleStatusMap = school_cycle_status_map(); ?>
<div class="row g-4 mb-4 align-items-start">
<div class="col-lg-7">
<div class="col-lg-<?= is_super_admin() ? '7' : '12' ?>">
<div class="app-card h-100">
<div class="section-head mb-3">
<div>
<div class="section-title">الدورة الموسمية الحالية</div>
<div class="section-copy">أعضاء الفريق في هذه الصفحة مرتبطون بالدورة <strong><?= e($cycleLabel) ?></strong>. عند أرشفة الموسم ستبقى القائمة محفوظة للرجوع إليها لاحقاً.</div>
<div class="section-copy">أعضاء الفريق في هذه الصفحة مرتبطون بالدورة <strong><?= e($cycleLabel) ?></strong>. عند أرشفة الموسم ستبقى القائمة محفوظة.</div>
</div>
<?= school_cycle_status_badge((string) $selectedCycle['status']) ?>
</div>
@ -170,29 +179,23 @@ render_flash($flash);
<div class="col-md-4"><div class="school-data-item"><strong>الفترة</strong><span><?= e((string) $selectedCycle['start_date']) ?> → <?= e((string) $selectedCycle['end_date']) ?></span></div></div>
<div class="col-md-4"><div class="school-data-item"><strong>عدد الدورات</strong><span><?= e((string) count($cycleContext['cycles'])) ?> دورة للمركز</span></div></div>
</div>
<div class="cta-stack mt-3">
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>#cycles">إدارة الدورات الموسمية</a>
</div>
<?php if ($isCycleReadOnly): ?>
<div class="alert alert-warning mt-3 mb-0">هذه الدورة مؤرشفة، لذلك تبقى صفحة الفريق للقراءة فقط حالياً.</div>
<?php endif; ?>
</div>
</div>
<?php if (is_super_admin()): ?>
<div class="col-lg-5">
<div class="app-card sidebar-card h-100">
<div class="section-title mb-3">التبديل بين الدورات</div>
<p class="section-subtle mb-3">بدّل مباشرة إلى نفس صفحة الفريق في أي دورة سابقة أو حالية لمراجعة الكادر بدون الرجوع إلى صفحة المركز.</p>
<div class="quick-link-stack">
<?php foreach ($cycleContext['cycles'] as $cycle): ?>
<?php
$isCurrentCycleLink = (int) $cycle['id'] === $selectedCycleId;
$isActiveCycleLink = (int) $cycle['id'] === (int) (($cycleContext['active']['id'] ?? 0));
$cycleStatusLabel = (string) ($cycleStatusMap[$cycle['status']]['label'] ?? 'غير معروف');
$cycleMetaLine = (string) $cycle['start_date'] . ' → ' . (string) $cycle['end_date'] . ' — ' . $cycleStatusLabel . ($isActiveCycleLink ? ' — النشطة حالياً' : '');
$cycleMetaLine = (string) $cycle['start_date'] . ' → ' . (string) $cycle['end_date'] . ' — ' . $cycleStatusLabel;
?>
<a class="quick-link-item <?= $isCurrentCycleLink ? 'is-current' : '' ?>" href="<?= e(school_page_url('teachers.php', (int) $application['id'], (int) $cycle['id'])) ?>">
<div>
<strong><?= e((string) $cycle['cycle_name']) ?><?= $isCurrentCycleLink ? ' — المعروضة الآن' : '' ?></strong>
<strong><?= e((string) $cycle['cycle_name']) ?><?= $isCurrentCycleLink ? ' — المعروضة' : '' ?></strong>
<span><?= e($cycleMetaLine) ?></span>
</div>
</a>
@ -200,125 +203,70 @@ render_flash($flash);
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="row g-4 mb-4">
<div class="col-md-6 col-xl-3"><div class="app-card stat-tile"><div class="mini-stat-label">إجمالي الفريق</div><div class="mini-stat-value"><?= e((string) $metrics['total']) ?></div><div class="mini-stat-copy">عدد أعضاء الكادر المسجلين لهذا المركز.</div></div></div>
<div class="col-md-6 col-xl-3"><div class="app-card stat-tile"><div class="mini-stat-label">مفعلون</div><div class="mini-stat-value"><?= e((string) $metrics['active']) ?></div><div class="mini-stat-copy">جاهزون للبدء الفعلي ضمن الخطة التشغيلية.</div></div></div>
<div class="col-md-6 col-xl-3"><div class="app-card stat-tile"><div class="mini-stat-label">بانتظار التفعيل</div><div class="mini-stat-value"><?= e((string) $metrics['pending']) ?></div><div class="mini-stat-copy">يحتاجون استكمال الجدول أو الموافقة النهائية.</div></div></div>
<div class="col-md-6 col-xl-3"><div class="app-card stat-tile"><div class="mini-stat-label">أدوار إشرافية</div><div class="mini-stat-value"><?= e((string) $metrics['supervisors']) ?></div><div class="mini-stat-copy">أعضاء في أدوار إشرافية أو قيادية.</div></div></div>
</div>
<div class="row g-4 align-items-start">
<div class="col-lg-4">
<div class="app-card sidebar-card mb-4">
<div class="section-head mb-3">
<div>
<div class="section-title">إضافة عضو جديد</div>
<div class="section-copy">أضف المعلمين، المشرفين، والمنسقين في صفحة مستقلة للمركز.</div>
</div>
</div>
<div class="col-lg-12">
<?php if (!empty($errors['form'])): ?>
<div class="alert alert-danger mb-4"><?= e($errors['form']) ?></div>
<?php endif; ?>
<?php if (isset($errors['form'])): ?>
<div class="alert alert-danger"><?= e($errors['form']) ?></div>
<?php endif; ?>
<?php if ($isCycleReadOnly): ?>
<div class="alert alert-warning mb-0">هذه الدورة مؤرشفة. يمكنك مراجعة الفريق فقط، أو فتح دورة جديدة من صفحة المركز.</div>
<?php else: ?>
<form method="post" class="vstack gap-3" novalidate>
<div>
<label class="form-label" for="full_name">الاسم الكامل</label>
<input class="form-control <?= isset($errors['full_name']) ? 'is-invalid' : '' ?>" id="full_name" name="full_name" value="<?= e($values['full_name']) ?>" required>
<?php if (isset($errors['full_name'])): ?><div class="invalid-feedback"><?= e($errors['full_name']) ?></div><?php endif; ?>
</div>
<div>
<label class="form-label" for="role_title">الدور الوظيفي</label>
<select class="form-select <?= isset($errors['role_title']) ? 'is-invalid' : '' ?>" id="role_title" name="role_title" required>
<option value="">اختر الدور</option>
<?php foreach (teacher_role_options() as $role): ?>
<option value="<?= e($role) ?>" <?= $values['role_title'] === $role ? 'selected' : '' ?>><?= e($role) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['role_title'])): ?><div class="invalid-feedback"><?= e($errors['role_title']) ?></div><?php endif; ?>
</div>
<div>
<label class="form-label" for="specialization">التخصص / المسار</label>
<input class="form-control" id="specialization" name="specialization" value="<?= e($values['specialization']) ?>" placeholder="مثال: الرياضيات، القرآن، الأنشطة العلمية">
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="phone">الهاتف</label>
<input class="form-control <?= isset($errors['phone']) ? 'is-invalid' : '' ?>" id="phone" name="phone" value="<?= e($values['phone']) ?>" dir="ltr" inputmode="tel">
<?php if (isset($errors['phone'])): ?><div class="invalid-feedback"><?= e($errors['phone']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="email">البريد الإلكتروني</label>
<input class="form-control <?= isset($errors['email']) ? 'is-invalid' : '' ?>" id="email" name="email" type="email" value="<?= e($values['email']) ?>" dir="ltr">
<?php if (isset($errors['email'])): ?><div class="invalid-feedback"><?= e($errors['email']) ?></div><?php endif; ?>
</div>
</div>
<div>
<label class="form-label" for="employment_status">الحالة</label>
<select class="form-select <?= isset($errors['employment_status']) ? 'is-invalid' : '' ?>" id="employment_status" name="employment_status">
<?php foreach (teacher_employment_status_map() as $status => $meta): ?>
<option value="<?= e($status) ?>" <?= $values['employment_status'] === $status ? 'selected' : '' ?>><?= e($meta['label']) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['employment_status'])): ?><div class="invalid-feedback"><?= e($errors['employment_status']) ?></div><?php endif; ?>
</div>
<div>
<label class="form-label" for="notes">ملاحظات</label>
<textarea class="form-control" id="notes" name="notes" rows="4" placeholder="مثال: مسؤول عن مجموعة الصفوف العليا أو بانتظار اعتماد الجدول."><?= e($values['notes']) ?></textarea>
</div>
<div class="d-grid">
<button class="btn btn-dark" type="submit">حفظ عضو الفريق</button>
</div>
</form>
<?php endif; ?>
</div>
<div class="app-card sidebar-card">
<div class="section-title mb-3">الخطوة التالية بعد الفريق</div>
<ul class="module-roadmap-list mb-0">
<li><strong>التقييمات والأوزان</strong><span class="section-subtle">الصفحة أصبحت جاهزة الآن لبناء أنواع التقييم وربط الأوزان التشغيلية بالمركز.</span><div class="mt-2"><a class="btn btn-sm btn-outline-secondary" href="<?= e($assessmentsUrl) ?>">فتح صفحة التقييمات</a></div></li>
<li><strong>غياب الطلاب</strong><span class="section-subtle">الصفحة أصبحت جاهزة الآن لتسجيل الغياب وربطه بالطلاب المعتمدين داخل المركز.</span><div class="mt-2"><a class="btn btn-sm btn-outline-secondary" href="<?= e($attendanceUrl) ?>">فتح صفحة الغياب</a></div></li>
</ul>
</div>
</div>
<div class="col-lg-8">
<div class="app-card mb-4">
<div class="section-head mb-3">
<div>
<div class="section-title">كشف الفريق التعليمي</div>
<div class="section-copy">جميع المعلمين والمشرفين المرتبطين بهذا المركز فقط.</div>
</div>
<span class="header-chip"><?= e((string) $metrics['email_ready']) ?> بريد جاهز / <?= e((string) $metrics['pending']) ?> بانتظار التفعيل</span>
<div class="d-flex gap-2">
<span class="header-chip"><?= e((string) $metrics['email_ready']) ?> بريد جاهز / <?= e((string) $metrics['pending']) ?> بانتظار التفعيل</span>
<?php if (!$isCycleReadOnly): ?>
<button type="button" class="btn btn-dark btn-sm" data-bs-toggle="modal" data-bs-target="#teacherModal" onclick="resetTeacherModal()">
+ إضافة عضو للفريق
</button>
<?php endif; ?>
</div>
</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>
<div class="col-md-4"><div class="school-data-item"><strong>أدوار قيادية</strong><span><?= e((string) $metrics['supervisors']) ?> إشرافي</span></div></div>
<div class="col-md-4"><div class="school-data-item"><strong>مع الطلاب النشطين</strong><span><?= e((string) $studentMetrics['active']) ?> طالب/طالبة</span></div></div>
</div>
<form method="get" class="mb-4 bg-light p-3 rounded">
<input type="hidden" name="id" value="<?= e((string) $application['id']) ?>">
<input type="hidden" name="cycle" value="<?= e((string) $selectedCycleId) ?>">
<div class="row g-2">
<div class="col-md-4">
<input type="text" name="search" class="form-control form-control-sm" placeholder="ابحث بالاسم، البريد، الهاتف، التخصص..." value="<?= e($search) ?>">
</div>
<div class="col-md-3">
<select name="role_title" class="form-select form-select-sm">
<option value="">كل الأدوار</option>
<?php foreach (teacher_role_options() as $option): ?>
<option value="<?= e($option) ?>" <?= $filters['role_title'] === $option ? 'selected' : '' ?>><?= e($option) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<select name="employment_status" class="form-select form-select-sm">
<option value="">كل الحالات</option>
<?php foreach (teacher_employment_status_map() as $key => $meta): ?>
<option value="<?= e($key) ?>" <?= $filters['employment_status'] === $key ? 'selected' : '' ?>><?= e($meta['label']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-outline-secondary btn-sm w-100">تصفية</button>
</div>
</div>
</form>
<?php if ($teachers === []): ?>
<div class="empty-state text-center p-4">
<div class="empty-title mb-2">لا يوجد أعضاء فريق مسجلون بعد</div>
<p class="text-muted mb-0">ابدأ من النموذج في الجانب الأيمن لإضافة أول معلم أو مشرف إلى سجل المدرسة.</p>
<div class="empty-title mb-2">لا يوجد أعضاء فريق مطابقون</div>
<p class="text-muted mb-0">ابدأ من إضافة عضو أو قم بتغيير الفلاتر.</p>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table app-table align-middle">
<table class="table app-table align-middle table-hover">
<thead>
<tr>
<th>الاسم</th>
@ -326,7 +274,7 @@ render_flash($flash);
<th>التخصص</th>
<th>التواصل</th>
<th>الحالة</th>
<th>الإضافة</th>
<?php if (!$isCycleReadOnly): ?><th>إجراءات</th><?php endif; ?>
</tr>
</thead>
<tbody>
@ -334,16 +282,23 @@ render_flash($flash);
<tr>
<td>
<strong><?= e((string) $teacher['full_name']) ?></strong>
<?php if (!empty($teacher['notes'])): ?><small><?= e((string) $teacher['notes']) ?></small><?php endif; ?>
<?php if (!empty($teacher['notes'])): ?><div class="text-muted small"><?= e((string) $teacher['notes']) ?></div><?php endif; ?>
</td>
<td><?= e((string) $teacher['role_title']) ?></td>
<td><?= e((string) ($teacher['specialization'] ?: '—')) ?></td>
<td>
<strong><?= e((string) ($teacher['phone'] ?: 'بدون هاتف')) ?></strong>
<small><?= e((string) ($teacher['email'] ?: 'بدون بريد إلكتروني')) ?></small>
<div class="text-muted small"><?= e((string) ($teacher['email'] ?: 'بدون بريد')) ?></div>
</td>
<td><?= teacher_employment_status_badge((string) $teacher['employment_status']) ?></td>
<td><?= e(substr((string) $teacher['created_at'], 0, 10)) ?></td>
<?php if (!$isCycleReadOnly): ?>
<td>
<button type="button" class="btn btn-sm btn-outline-secondary"
data-bs-toggle="modal" data-bs-target="#teacherModal"
data-teacher='<?= htmlspecialchars(json_encode($teacher), ENT_QUOTES, 'UTF-8') ?>'
onclick="editTeacherModal(this)">تعديل</button>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
@ -353,21 +308,6 @@ render_flash($flash);
<?php endif; ?>
</div>
<div class="app-card">
<div class="section-title mb-3">سياق المدرسة</div>
<div class="row g-3">
<div class="col-md-6"><div class="school-data-item"><strong>مدير المركز</strong><span><?= e((string) $application['director_name']) ?></span></div></div>
<div class="col-md-6"><div class="school-data-item"><strong>الطاقة الاستيعابية</strong><span><?= e((string) $application['expected_students']) ?> طالب</span></div></div>
<div class="col-md-6"><div class="school-data-item"><strong>قيد الطلاب الحالي</strong><span><?= e((string) $studentMetrics['total']) ?> طالب/طالبة</span></div></div>
<div class="col-md-6"><div class="school-data-item"><strong>البريد الرسمي</strong><span><?= e((string) $application['email']) ?></span></div></div>
</div>
<div class="cta-stack mt-4">
<a class="btn btn-outline-secondary" href="<?= e($studentsUrl) ?>">العودة إلى الطلاب</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="<?= e($approvedSchoolUrl) ?>">صفحة المركز</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
@ -376,4 +316,113 @@ render_flash($flash);
</div>
</section>
<?php render_page_end(); ?>
<!-- Teacher Modal -->
<div class="modal fade" id="teacherModal" tabindex="-1" aria-labelledby="teacherModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form method="post" id="teacherForm" novalidate>
<div class="modal-header">
<h5 class="modal-title" id="teacherModalLabel">إضافة عضو جديد للفريق</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="إغلاق"></button>
</div>
<div class="modal-body">
<input type="hidden" name="action" id="formAction" value="add">
<input type="hidden" name="teacher_id" id="formTeacherId" value="">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="full_name">الاسم الكامل</label>
<input class="form-control <?= isset($errors['full_name']) ? 'is-invalid' : '' ?>" id="full_name" name="full_name" value="<?= e($values['full_name']) ?>" required>
<?php if (isset($errors['full_name'])): ?><div class="invalid-feedback"><?= e($errors['full_name']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="role_title">الدور الوظيفي</label>
<select class="form-select <?= isset($errors['role_title']) ? 'is-invalid' : '' ?>" id="role_title" name="role_title" required>
<option value="">اختر الدور</option>
<?php foreach (teacher_role_options() as $role): ?>
<option value="<?= e($role) ?>" <?= $values['role_title'] === $role ? 'selected' : '' ?>><?= e($role) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['role_title'])): ?><div class="invalid-feedback"><?= e($errors['role_title']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="specialization">التخصص / المسار</label>
<input class="form-control" id="specialization" name="specialization" value="<?= e($values['specialization']) ?>" placeholder="مثال: الرياضيات، القرآن، الأنشطة">
</div>
<div class="col-md-6">
<label class="form-label" for="employment_status">الحالة</label>
<select class="form-select <?= isset($errors['employment_status']) ? 'is-invalid' : '' ?>" id="employment_status" name="employment_status">
<?php foreach (teacher_employment_status_map() as $status => $meta): ?>
<option value="<?= e($status) ?>" <?= $values['employment_status'] === $status ? 'selected' : '' ?>><?= e($meta['label']) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['employment_status'])): ?><div class="invalid-feedback"><?= e($errors['employment_status']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="phone">الهاتف</label>
<input class="form-control <?= isset($errors['phone']) ? 'is-invalid' : '' ?>" id="phone" name="phone" value="<?= e($values['phone']) ?>" dir="ltr" inputmode="tel">
<?php if (isset($errors['phone'])): ?><div class="invalid-feedback"><?= e($errors['phone']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="email">البريد الإلكتروني</label>
<input class="form-control <?= isset($errors['email']) ? 'is-invalid' : '' ?>" id="email" name="email" type="email" value="<?= e($values['email']) ?>" dir="ltr">
<?php if (isset($errors['email'])): ?><div class="invalid-feedback"><?= e($errors['email']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label class="form-label" for="notes">ملاحظات</label>
<textarea class="form-control" id="notes" name="notes" rows="2" placeholder="مثال: مسؤول عن مجموعة الصفوف العليا."><?= e($values['notes']) ?></textarea>
</div>
</div>
</div>
<div class="modal-footer d-flex justify-content-between">
<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>
<script>
function resetTeacherModal() {
document.getElementById('teacherModalLabel').innerText = 'إضافة عضو جديد للفريق';
document.getElementById('formAction').value = 'add';
document.getElementById('formTeacherId').value = '';
const form = document.getElementById('teacherForm');
form.reset();
document.getElementById('employment_status').value = 'active';
}
function editTeacherModal(btn) {
const teacher = JSON.parse(btn.getAttribute('data-teacher'));
document.getElementById('teacherModalLabel').innerText = 'تعديل عضو: ' + teacher.full_name;
document.getElementById('formAction').value = 'edit';
document.getElementById('formTeacherId').value = teacher.id;
document.getElementById('full_name').value = teacher.full_name || '';
document.getElementById('role_title').value = teacher.role_title || '';
document.getElementById('specialization').value = teacher.specialization || '';
document.getElementById('employment_status').value = teacher.employment_status || '';
document.getElementById('phone').value = teacher.phone || '';
document.getElementById('email').value = teacher.email || '';
document.getElementById('notes').value = teacher.notes || '';
}
// Show modal if there are errors (from POST)
<?php if (!empty($errors) && $_SERVER['REQUEST_METHOD'] === 'POST'): ?>
document.addEventListener('DOMContentLoaded', function() {
var myModal = new bootstrap.Modal(document.getElementById('teacherModal'));
myModal.show();
<?php if (isset($_POST['action']) && $_POST['action'] === 'edit'): ?>
document.getElementById('teacherModalLabel').innerText = 'تعديل عضو الفريق';
document.getElementById('formAction').value = 'edit';
document.getElementById('formTeacherId').value = '<?= e($_POST['teacher_id'] ?? '') ?>';
<?php endif; ?>
});
<?php endif; ?>
</script>
<?php render_page_end(); ?>