Autosave: 20260416-065856

This commit is contained in:
Flatlogic Bot 2026-04-16 06:58:53 +00:00
parent c7366417c4
commit c9ac266bd6
8 changed files with 1409 additions and 1 deletions

View File

@ -40,6 +40,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($reviewErrors === []) {
try {
update_application_review($applicationId, $status, $adminNotes, $evaluationScore === false ? null : $evaluationScore);
if ($status === 'approved') {
set_flash('success', 'تم اعتماد المركز بنجاح، وهذه هي صفحة الهبوط الخاصة به بعد الموافقة.');
header('Location: approved_school.php?id=' . urlencode((string) $applicationId));
exit;
}
set_flash('success', 'تم تحديث حالة الطلب وملاحظات التقييم بنجاح.');
header('Location: application_detail.php?id=' . urlencode((string) $applicationId));
exit;
@ -74,6 +79,9 @@ render_flash($flash);
<div class="mini-stat-label">الحالة الحالية</div>
<div class="mb-3"><?= status_badge((string) $application['status']) ?></div>
<div class="mini-stat-copy">هذا الملف مخصص لاتخاذ القرار وتوثيق الملاحظات بدلاً من خلطه مع قائمة الطلبات.</div>
<?php if ((string) $application['status'] === 'approved'): ?>
<a class="btn btn-dark btn-sm mt-3" href="approved_school.php?id=<?= e((string) $application['id']) ?>">فتح صفحة المركز المعتمد</a>
<?php endif; ?>
</div>
</div>
</div>
@ -165,6 +173,9 @@ render_flash($flash);
</div>
<div class="d-grid gap-2">
<button class="btn btn-dark" type="submit">حفظ التحديث</button>
<?php if ((string) $application['status'] === 'approved'): ?>
<a class="btn btn-outline-dark" href="approved_school.php?id=<?= e((string) $application['id']) ?>">معاينة صفحة المركز</a>
<?php endif; ?>
<a class="btn btn-outline-secondary" href="applications.php">العودة إلى القائمة</a>
</div>
</form>

View File

@ -111,7 +111,13 @@ render_flash($flash);
</td>
<td><?= status_badge((string) $application['status']) ?></td>
<td><?= $application['evaluation_score'] !== null ? e((string) $application['evaluation_score']) . '%' : '—' ?></td>
<td><a class="btn btn-outline-secondary btn-sm px-3" href="application_detail.php?id=<?= e((string) $application['id']) ?>">فتح الملف</a></td>
<td>
<?php if ((string) $application['status'] === 'approved'): ?>
<a class="btn btn-dark btn-sm px-3" href="approved_school.php?id=<?= e((string) $application['id']) ?>">صفحة المركز</a>
<?php else: ?>
<a class="btn btn-outline-secondary btn-sm px-3" href="application_detail.php?id=<?= e((string) $application['id']) ?>">فتح الملف</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>

189
approved_school.php Normal file
View File

@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
$flash = consume_flash();
$applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0;
$application = $applicationId > 0 ? get_application($applicationId) : null;
if (!$application) {
http_response_code(404);
render_page_start('صفحة المركز غير موجودة', 'approved', 'تعذر العثور على المركز المطلوب لعرض صفحة الهبوط بعد الاعتماد.');
render_flash($flash);
?>
<section class="py-5">
<div class="container-xxl">
<div class="app-card text-center py-5">
<div class="empty-title mb-2">المركز غير موجود</div>
<p class="text-muted mb-3">تحقق من رقم الطلب أو ارجع إلى قائمة المراكز المعتمدة.</p>
<a class="btn btn-dark" href="applications.php?status=approved">عرض المراكز المعتمدة</a>
</div>
</div>
</section>
<?php
render_page_end();
exit;
}
$isApproved = (string) $application['status'] === 'approved';
$scoreValue = $application['evaluation_score'] !== null ? max(0, min(100, (int) $application['evaluation_score'])) : null;
$durationDays = 0;
$startTs = strtotime((string) $application['start_date']);
$endTs = strtotime((string) $application['end_date']);
if ($startTs !== false && $endTs !== false && $endTs >= $startTs) {
$durationDays = (int) floor(($endTs - $startTs) / 86400) + 1;
}
$pageTitle = $isApproved ? 'صفحة المركز المعتمد: ' . (string) $application['center_name'] : 'صفحة المركز بعد الاعتماد';
$pageDescription = $isApproved
? 'صفحة هبوط تشغيلية للمركز المعتمد تعرض الجاهزية، بيانات التواصل، والخطوات التالية بعد الموافقة.'
: 'هذه الصفحة تصبح متاحة بعد اعتماد طلب المركز من المشرف العام.';
render_page_start($pageTitle, 'approved', $pageDescription);
render_flash($flash);
?>
<section class="py-4 py-lg-5">
<div class="container-xxl">
<?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">
<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>
<div class="hero-meta">
<span>الحالة الحالية: <?= e(status_meta((string) $application['status'])['label']) ?></span>
<span>مرجع الطلب #<?= e((string) $application['id']) ?></span>
</div>
<div class="cta-stack mt-4">
<a class="btn btn-dark" href="application_detail.php?id=<?= e((string) $application['id']) ?>">العودة لملف المراجعة</a>
<a class="btn btn-outline-secondary" href="applications.php">لوحة الطلبات</a>
</div>
</div>
</div>
</div>
<?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">
<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>
<div class="hero-meta">
<span><?= e((string) $application['city']) ?></span>
<span><?= e((string) $application['center_type']) ?> — <?= e((string) $application['gender_scope']) ?></span>
<span>من <?= e((string) $application['start_date']) ?> إلى <?= e((string) $application['end_date']) ?></span>
</div>
<div class="cta-stack mt-4">
<a class="btn btn-dark" href="application_detail.php?id=<?= e((string) $application['id']) ?>">فتح ملف الاعتماد</a>
<a class="btn btn-outline-secondary" href="students.php?id=<?= e((string) $application['id']) ?>">تسجيل الطلاب</a>
<a class="btn btn-outline-secondary" href="teachers.php?id=<?= e((string) $application['id']) ?>">فريق المعلمين</a>
<a class="btn btn-outline-secondary" href="applications.php?status=approved">كل المراكز المعتمدة</a>
<a class="btn btn-outline-secondary" href="dashboard.php">لوحة القيادة</a>
</div>
</div>
<div class="col-lg-4">
<div class="app-card h-100 approved-note">
<div class="section-title mb-3">ملخص الجاهزية</div>
<?php if ($scoreValue !== null): ?>
<div class="score-display mb-3"><strong><?= e((string) $scoreValue) ?>%</strong><span>درجة التقييم</span></div>
<div class="score-bar mb-3" aria-label="درجة التقييم النهائية"><span style="width: <?= e((string) $scoreValue) ?>%"></span></div>
<?php endif; ?>
<div class="summary-stack">
<div class="summary-row"><span>مدير المركز</span><strong><?= e((string) $application['director_name']) ?></strong></div>
<div class="summary-row"><span>السعة المتوقعة</span><strong><?= e((string) $application['expected_students']) ?> طالب</strong></div>
<div class="summary-row"><span>مدة البرنامج</span><strong><?= e((string) ($durationDays > 0 ? $durationDays : '—')) ?> يوم</strong></div>
</div>
</div>
</div>
</div>
</div>
<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">معتمد</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) $application['expected_students']) ?></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) ($durationDays > 0 ? $durationDays : '—')) ?></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) $application['id']) ?></div><div class="mini-stat-copy">استخدم هذا الرقم في أي متابعة إدارية لاحقة.</div></div></div>
</div>
<div class="row g-4 align-items-start">
<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>
</div>
<p class="mb-0 text-muted"><?= nl2br(e((string) ($application['notes'] ?: 'مركز صيفي معتمد لتقديم برنامج منظم يجمع بين الأنشطة التعليمية والتربوية والمهارية تحت إشراف الولاية.'))) ?></p>
</div>
<div class="app-card mb-4">
<div class="section-title mb-3">أسباب الجاهزية والاعتماد</div>
<div class="highlight-grid">
<div class="highlight-item">
<strong>ملف مكتمل</strong>
<p>تمت مراجعة البيانات الأساسية، المسؤول المباشر، وفترة التشغيل المقترحة قبل إصدار الاعتماد.</p>
</div>
<div class="highlight-item">
<strong>خطة تشغيل واضحة</strong>
<p>توجد سعة متوقعة وبرنامج زمني محدد يساعدان على بدء التسجيل وإسناد المهام بسرعة.</p>
</div>
<div class="highlight-item">
<strong>قابلية للإطلاق</strong>
<p>يمكن اعتماد هذه الصفحة كنقطة انتقال من المراجعة إلى الإدارة اليومية ثم التوسع لوحدات الطلاب والغياب والتقييم.</p>
</div>
</div>
</div>
<div class="app-card">
<div class="section-title mb-3">الخطوات التالية بعد الموافقة</div>
<div class="next-step-grid">
<div class="next-step-card">
<strong>1) فتح التسجيل</strong>
<p>المرحلة التالية المنطقية أصبحت جاهزة الآن: يمكن فتح صفحة مستقلة لتسجيل الطلاب وربطها مباشرة بالمركز المعتمد.</p>
</div>
<div class="next-step-card">
<strong>2) تجهيز الفريق</strong>
<p>صفحة الفريق أصبحت جاهزة الآن لإضافة المعلمين والمشرفين وربط أدوارهم التشغيلية مباشرة بالمركز المعتمد.</p>
</div>
<div class="next-step-card">
<strong>3) متابعة التشغيل</strong>
<p>بعد الانطلاق يمكن توسيع هذه الصفحة لاحقاً بمؤشرات حضور يومية، تقييمات، وتنبيهات تشغيلية.</p>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="app-card sidebar-card mb-4">
<div class="section-title mb-3">بيانات التواصل</div>
<div class="contact-stack">
<div class="contact-item"><strong>مدير المركز</strong><span><?= e((string) $application['director_name']) ?></span></div>
<div class="contact-item"><strong>الهاتف</strong><span><?= e((string) $application['phone']) ?></span></div>
<div class="contact-item"><strong>البريد الإلكتروني</strong><span><?= e((string) $application['email']) ?></span></div>
<div class="contact-item"><strong>المدينة</strong><span><?= e((string) $application['city']) ?></span></div>
</div>
</div>
<div class="app-card sidebar-card mb-4">
<div class="section-title mb-3">ملاحظات الاعتماد</div>
<p class="mb-0 text-muted"><?= nl2br(e((string) ($application['admin_notes'] ?: 'لا توجد ملاحظات إضافية. يمكن اعتبار هذه الصفحة النسخة الأولى من واجهة المركز بعد الاعتماد.'))) ?></p>
</div>
<div class="app-card sidebar-card">
<div class="section-title mb-3">روابط تشغيلية سريعة</div>
<div class="quick-link-stack">
<a class="quick-link-item" href="students.php?id=<?= e((string) $application['id']) ?>"><strong>تسجيل الطلاب</strong><span>فتح صفحة القيد وكشف المدرسة لهذا المركز.</span></a>
<a class="quick-link-item" href="teachers.php?id=<?= e((string) $application['id']) ?>"><strong>فريق المعلمين</strong><span>إدارة المعلمين والمشرفين والكوادر التشغيلية للمركز.</span></a>
<a class="quick-link-item" href="application_detail.php?id=<?= e((string) $application['id']) ?>"><strong>ملف الاعتماد</strong><span>العودة إلى سجل القرار والتقييم.</span></a>
<a class="quick-link-item" href="applications.php?status=approved"><strong>المراكز المعتمدة</strong><span>عرض بقية المراكز الجاهزة للتشغيل.</span></a>
<a class="quick-link-item" href="dashboard.php"><strong>لوحة القيادة</strong><span>الرجوع إلى المؤشرات العامة على مستوى الولاية.</span></a>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</section>
<?php render_page_end();

View File

@ -748,3 +748,152 @@ textarea.form-control {
padding: 1rem;
}
}
.approved-hero {
background:
radial-gradient(circle at top left, rgba(15, 118, 110, 0.12), transparent 35%),
linear-gradient(135deg, #ffffff 0%, #f8fafc 55%, #ecfdf5 100%);
overflow: hidden;
}
.approved-hero .page-title,
.approved-hero .page-copy {
max-width: 20ch;
}
.approved-kicker {
display: inline-flex;
align-items: center;
gap: 0.45rem;
border-radius: 999px;
background: rgba(15, 118, 110, 0.1);
color: var(--accent);
padding: 0.45rem 0.8rem;
font-size: 0.82rem;
font-weight: 700;
}
.highlight-grid,
.next-step-grid,
.contact-stack,
.launch-metrics {
display: grid;
gap: 1rem;
}
.highlight-item,
.next-step-card,
.contact-item,
.launch-metric {
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface-muted);
padding: 1rem;
}
.highlight-item strong,
.next-step-card strong,
.contact-item strong,
.launch-metric strong {
display: block;
margin-bottom: 0.35rem;
font-size: 0.98rem;
}
.highlight-item p,
.next-step-card p,
.contact-item span,
.launch-metric span {
margin: 0;
color: var(--muted);
}
.approved-note {
border-inline-start: 4px solid var(--accent);
background: #f8fffd;
}
.score-display {
display: flex;
align-items: baseline;
gap: 0.4rem;
}
.score-display strong {
font-size: clamp(2rem, 4vw, 2.8rem);
line-height: 1;
color: var(--accent);
}
.score-display span {
color: var(--muted);
font-weight: 600;
}
.cta-stack {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
@media (min-width: 768px) {
.highlight-grid,
.next-step-grid,
.launch-metrics {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.capacity-note,
.section-subtle {
color: var(--muted);
}
.school-data-item {
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface-muted);
padding: 1rem;
height: 100%;
}
.school-data-item strong {
display: block;
margin-bottom: 0.3rem;
font-size: 0.82rem;
color: var(--muted);
}
.school-data-item span {
display: block;
font-size: 1rem;
font-weight: 700;
color: var(--primary);
}
.module-roadmap-list {
list-style: none;
padding: 0;
display: grid;
gap: 0.85rem;
}
.module-roadmap-list li {
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface-muted);
padding: 0.95rem 1rem;
}
.module-roadmap-list strong {
display: block;
margin-bottom: 0.15rem;
}
.app-table td small {
display: block;
margin-top: 0.2rem;
color: var(--muted);
}

View File

@ -0,0 +1,70 @@
CREATE TABLE IF NOT EXISTS school_students (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
center_application_id INT UNSIGNED NOT NULL,
student_code VARCHAR(60) NOT NULL,
full_name VARCHAR(190) NOT NULL,
gender VARCHAR(20) NOT NULL,
grade_level VARCHAR(80) NOT NULL,
guardian_name VARCHAR(150) NOT NULL,
guardian_phone VARCHAR(60) NOT NULL,
birth_date DATE NULL,
enrollment_status VARCHAR(30) NOT NULL DEFAULT 'active',
notes TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_school_student_code (center_application_id, student_code),
INDEX idx_school_students_center (center_application_id),
INDEX idx_school_students_status (enrollment_status),
CONSTRAINT fk_school_students_center_application FOREIGN KEY (center_application_id) REFERENCES center_applications(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS school_teachers (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
center_application_id INT UNSIGNED NOT NULL,
full_name VARCHAR(190) NOT NULL,
role_title VARCHAR(120) NOT NULL,
specialization VARCHAR(150) NULL,
phone VARCHAR(60) NULL,
email VARCHAR(190) NULL,
employment_status VARCHAR(30) NOT NULL DEFAULT 'active',
notes TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_school_teachers_center (center_application_id),
INDEX idx_school_teachers_status (employment_status),
CONSTRAINT fk_school_teachers_center_application FOREIGN KEY (center_application_id) REFERENCES center_applications(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS school_assessment_types (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
center_application_id INT UNSIGNED NOT NULL,
title VARCHAR(150) NOT NULL,
category VARCHAR(80) NOT NULL,
scale_type VARCHAR(40) NOT NULL DEFAULT 'percentage',
max_score DECIMAL(6,2) NOT NULL DEFAULT 100.00,
weight_percentage DECIMAL(5,2) NOT NULL DEFAULT 0.00,
is_active TINYINT(1) NOT NULL DEFAULT 1,
notes TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_school_assessments_center (center_application_id),
INDEX idx_school_assessments_active (is_active),
CONSTRAINT fk_school_assessments_center_application FOREIGN KEY (center_application_id) REFERENCES center_applications(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS school_attendance_records (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
center_application_id INT UNSIGNED NOT NULL,
student_id INT UNSIGNED NOT NULL,
attendance_date DATE NOT NULL,
attendance_status VARCHAR(30) NOT NULL DEFAULT 'absent',
absence_reason VARCHAR(190) NULL,
notes TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_school_attendance_student_date (student_id, attendance_date),
INDEX idx_school_attendance_center (center_application_id),
INDEX idx_school_attendance_date (attendance_date),
CONSTRAINT fk_school_attendance_center_application FOREIGN KEY (center_application_id) REFERENCES center_applications(id) ON DELETE CASCADE,
CONSTRAINT fk_school_attendance_student FOREIGN KEY (student_id) REFERENCES school_students(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -96,7 +96,9 @@ function db_connection(): PDO
if (!$bootstrapped) {
ensure_center_application_schema($pdo);
ensure_school_module_schema($pdo);
seed_center_application_demo_data($pdo);
seed_school_module_demo_data($pdo);
$bootstrapped = true;
}
@ -121,6 +123,102 @@ function ensure_center_application_schema(PDO $pdo): void
$done = true;
}
function ensure_school_module_schema(PDO $pdo): void
{
static $done = false;
if ($done) {
return;
}
$migrationPath = __DIR__ . '/../db/migrations/20260416_school_modules.sql';
if (is_file($migrationPath)) {
$sql = file_get_contents($migrationPath);
if (is_string($sql) && trim($sql) !== '') {
$pdo->exec($sql);
}
}
$done = true;
}
function seed_school_module_demo_data(PDO $pdo): void
{
$approvedId = (int) $pdo->query("SELECT id FROM center_applications WHERE status = 'approved' ORDER BY id ASC LIMIT 1")->fetchColumn();
if ($approvedId <= 0) {
return;
}
$studentCount = (int) $pdo->query('SELECT COUNT(*) FROM school_students')->fetchColumn();
if ($studentCount === 0) {
$studentRows = [
['ST-301', 'أحمد بن سالم الحارثي', 'طالب', 'الصف السابع', 'سالم الحارثي', '0501112233', '2013-03-11', 'active', 'مسجل في مسار القرآن والابتكار.'],
['ST-302', 'مريم بنت ناصر الكندية', 'طالبة', 'الصف الثامن', 'ناصر الكندي', '0502223344', '2012-11-05', 'active', 'بحاجة إلى متابعة في الأنشطة العلمية.'],
['ST-303', 'سيف بن راشد المقبالي', 'طالب', 'الصف السادس', 'راشد المقبالي', '0503334455', '2014-01-22', 'waiting', 'بانتظار تأكيد المقعد بعد مراجعة السعة.'],
];
$studentStmt = $pdo->prepare(
'INSERT INTO school_students (
center_application_id, student_code, full_name, gender, grade_level,
guardian_name, guardian_phone, birth_date, enrollment_status, notes,
created_at, updated_at
) VALUES (
:center_application_id, :student_code, :full_name, :gender, :grade_level,
:guardian_name, :guardian_phone, :birth_date, :enrollment_status, :notes,
NOW(), NOW()
)'
);
foreach ($studentRows as $row) {
$studentStmt->execute([
':center_application_id' => $approvedId,
':student_code' => $row[0],
':full_name' => $row[1],
':gender' => $row[2],
':grade_level' => $row[3],
':guardian_name' => $row[4],
':guardian_phone' => $row[5],
':birth_date' => $row[6],
':enrollment_status' => $row[7],
':notes' => $row[8],
]);
}
}
$teacherCount = (int) $pdo->query('SELECT COUNT(*) FROM school_teachers')->fetchColumn();
if ($teacherCount === 0) {
$teacherRows = [
['أ. هدى بنت راشد المعمرية', 'مشرفة أكاديمية', 'اللغة العربية والقرآن', '0504445566', 'huda.muamari@example.com', 'active', 'تقود خطة الإشراف الأكاديمي لهذا الموسم.'],
['أ. مازن بن سعيد البلوشي', 'منسق أنشطة', 'الأنشطة العلمية والابتكار', '0505556677', 'mazin.balushi@example.com', 'active', 'مسؤول عن الربط بين الأنشطة الصفية والفعاليات الأسبوعية.'],
['أ. عائشة بنت خالد السعدية', 'معلمة', 'الرياضيات والمهارات الرقمية', '0506667788', 'aisha.saadiya@example.com', 'pending', 'بانتظار استكمال جدولها النهائي قبل التفعيل الكامل.'],
];
$teacherStmt = $pdo->prepare(
'INSERT INTO school_teachers (
center_application_id, full_name, role_title, specialization,
phone, email, employment_status, notes,
created_at, updated_at
) VALUES (
:center_application_id, :full_name, :role_title, :specialization,
:phone, :email, :employment_status, :notes,
NOW(), NOW()
)'
);
foreach ($teacherRows as $row) {
$teacherStmt->execute([
':center_application_id' => $approvedId,
':full_name' => $row[0],
':role_title' => $row[1],
':specialization' => $row[2],
':phone' => $row[3],
':email' => $row[4],
':employment_status' => $row[5],
':notes' => $row[6],
]);
}
}
}
function seed_center_application_demo_data(PDO $pdo): void
{
$count = (int) $pdo->query('SELECT COUNT(*) FROM center_applications')->fetchColumn();
@ -376,6 +474,306 @@ function latest_applications(int $limit = 5): array
return $stmt->fetchAll();
}
function student_defaults(): array
{
return [
'student_code' => '',
'full_name' => '',
'gender' => '',
'grade_level' => '',
'guardian_name' => '',
'guardian_phone' => '',
'birth_date' => '',
'enrollment_status' => 'active',
'notes' => '',
];
}
function student_gender_options(): array
{
return ['طالب', 'طالبة'];
}
function student_grade_options(): array
{
return [
'الصف الرابع',
'الصف الخامس',
'الصف السادس',
'الصف السابع',
'الصف الثامن',
'الصف التاسع',
'الصف العاشر',
'الصف الحادي عشر',
'الصف الثاني عشر',
];
}
function student_enrollment_status_map(): array
{
return [
'active' => ['label' => 'مسجل', 'class' => 'status-approved'],
'waiting' => ['label' => 'قائمة انتظار', 'class' => 'status-review'],
'withdrawn' => ['label' => 'منسحب', 'class' => 'status-muted'],
];
}
function student_enrollment_status_badge(string $status): string
{
$map = student_enrollment_status_map();
$meta = $map[$status] ?? ['label' => 'غير محدد', 'class' => 'status-muted'];
return '<span class="status-badge ' . e($meta['class']) . '">' . e($meta['label']) . '</span>';
}
function validate_student_input(array $input): array
{
$data = student_defaults();
foreach ($data as $key => $_value) {
$limit = $key === 'notes' ? 1000 : ($key === 'student_code' ? 60 : 190);
$data[$key] = clean_text((string) ($input[$key] ?? ''), $limit);
}
$errors = [];
if ($data['student_code'] === '') {
$errors['student_code'] = 'يرجى إدخال الرقم أو الكود المرجعي للطالب.';
}
if ($data['full_name'] === '') {
$errors['full_name'] = 'يرجى إدخال اسم الطالب أو الطالبة.';
}
if (!in_array($data['gender'], student_gender_options(), true)) {
$errors['gender'] = 'يرجى تحديد النوع.';
}
if (!in_array($data['grade_level'], student_grade_options(), true)) {
$errors['grade_level'] = 'يرجى اختيار الصف الدراسي.';
}
if ($data['guardian_name'] === '') {
$errors['guardian_name'] = 'يرجى إدخال اسم ولي الأمر.';
}
if ($data['guardian_phone'] === '' || strlen($data['guardian_phone']) < 7) {
$errors['guardian_phone'] = 'يرجى إدخال رقم هاتف صحيح لولي الأمر.';
}
$birthDate = clean_text((string) ($input['birth_date'] ?? ''), 20);
$data['birth_date'] = $birthDate;
if ($birthDate !== '' && strtotime($birthDate) === false) {
$errors['birth_date'] = 'يرجى إدخال تاريخ ميلاد صحيح أو تركه فارغاً.';
}
$statusMap = student_enrollment_status_map();
if (!array_key_exists($data['enrollment_status'], $statusMap)) {
$errors['enrollment_status'] = 'يرجى اختيار حالة تسجيل صحيحة.';
}
return [$data, $errors];
}
function create_student(int $centerApplicationId, array $data): int
{
$pdo = db_connection();
$stmt = $pdo->prepare(
'INSERT INTO school_students (
center_application_id, student_code, full_name, gender, grade_level,
guardian_name, guardian_phone, birth_date, enrollment_status, notes,
created_at, updated_at
) VALUES (
:center_application_id, :student_code, :full_name, :gender, :grade_level,
:guardian_name, :guardian_phone, :birth_date, :enrollment_status, :notes,
NOW(), NOW()
)'
);
$stmt->bindValue(':center_application_id', $centerApplicationId, 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();
return (int) $pdo->lastInsertId();
}
function list_school_students(int $centerApplicationId): array
{
$pdo = db_connection();
$stmt = $pdo->prepare('SELECT * FROM school_students WHERE center_application_id = :center_application_id ORDER BY created_at DESC, id DESC');
$stmt->bindValue(':center_application_id', $centerApplicationId, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
function school_student_metrics(int $centerApplicationId): array
{
$pdo = db_connection();
$stmt = $pdo->prepare(
"SELECT
COUNT(*) AS total,
COALESCE(SUM(gender = 'طالب'), 0) AS boys,
COALESCE(SUM(gender = 'طالبة'), 0) AS girls,
COALESCE(SUM(enrollment_status = 'active'), 0) AS active_count,
COALESCE(SUM(enrollment_status = 'waiting'), 0) AS waiting_count,
COALESCE(SUM(enrollment_status = 'withdrawn'), 0) AS withdrawn_count
FROM school_students
WHERE center_application_id = :center_application_id"
);
$stmt->bindValue(':center_application_id', $centerApplicationId, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch() ?: [];
return [
'total' => (int) ($row['total'] ?? 0),
'boys' => (int) ($row['boys'] ?? 0),
'girls' => (int) ($row['girls'] ?? 0),
'active' => (int) ($row['active_count'] ?? 0),
'waiting' => (int) ($row['waiting_count'] ?? 0),
'withdrawn' => (int) ($row['withdrawn_count'] ?? 0),
];
}
function teacher_defaults(): array
{
return [
'full_name' => '',
'role_title' => '',
'specialization' => '',
'phone' => '',
'email' => '',
'employment_status' => 'active',
'notes' => '',
];
}
function teacher_role_options(): array
{
return [
'معلم',
'معلمة',
'مشرف أكاديمي',
'مشرفة أكاديمية',
'منسق أنشطة',
'مسؤول متابعة',
'إداري',
];
}
function teacher_employment_status_map(): array
{
return [
'active' => ['label' => 'مفعل', 'class' => 'status-approved'],
'pending' => ['label' => 'بانتظار التفعيل', 'class' => 'status-review'],
'inactive' => ['label' => 'غير مفعل', 'class' => 'status-muted'],
];
}
function teacher_employment_status_badge(string $status): string
{
$map = teacher_employment_status_map();
$meta = $map[$status] ?? ['label' => 'غير محدد', 'class' => 'status-muted'];
return '<span class="status-badge ' . e($meta['class']) . '">' . e($meta['label']) . '</span>';
}
function validate_teacher_input(array $input): array
{
$data = teacher_defaults();
foreach ($data as $key => $_value) {
$limit = $key === 'notes' ? 1000 : 190;
$data[$key] = clean_text((string) ($input[$key] ?? ''), $limit);
}
$errors = [];
if ($data['full_name'] === '') {
$errors['full_name'] = 'يرجى إدخال اسم عضو الفريق.';
}
if (!in_array($data['role_title'], teacher_role_options(), true)) {
$errors['role_title'] = 'يرجى اختيار الدور الوظيفي.';
}
if ($data['phone'] !== '' && strlen($data['phone']) < 7) {
$errors['phone'] = 'أدخل رقم هاتف صحيحاً أو اتركه فارغاً.';
}
if ($data['email'] !== '' && !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'أدخل بريداً إلكترونياً صحيحاً أو اتركه فارغاً.';
}
$statusMap = teacher_employment_status_map();
if (!array_key_exists($data['employment_status'], $statusMap)) {
$errors['employment_status'] = 'يرجى اختيار حالة توظيف صحيحة.';
}
return [$data, $errors];
}
function create_teacher(int $centerApplicationId, array $data): int
{
$pdo = db_connection();
$stmt = $pdo->prepare(
'INSERT INTO school_teachers (
center_application_id, full_name, role_title, specialization,
phone, email, employment_status, notes,
created_at, updated_at
) VALUES (
:center_application_id, :full_name, :role_title, :specialization,
:phone, :email, :employment_status, :notes,
NOW(), NOW()
)'
);
$stmt->bindValue(':center_application_id', $centerApplicationId, PDO::PARAM_INT);
$stmt->bindValue(':full_name', $data['full_name'], PDO::PARAM_STR);
$stmt->bindValue(':role_title', $data['role_title'], PDO::PARAM_STR);
$stmt->bindValue(':specialization', $data['specialization'] !== '' ? $data['specialization'] : null, $data['specialization'] !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
$stmt->bindValue(':phone', $data['phone'] !== '' ? $data['phone'] : null, $data['phone'] !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
$stmt->bindValue(':email', $data['email'] !== '' ? $data['email'] : null, $data['email'] !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
$stmt->bindValue(':employment_status', $data['employment_status'], PDO::PARAM_STR);
$stmt->bindValue(':notes', $data['notes'] !== '' ? $data['notes'] : null, $data['notes'] !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
$stmt->execute();
return (int) $pdo->lastInsertId();
}
function list_school_teachers(int $centerApplicationId): array
{
$pdo = db_connection();
$stmt = $pdo->prepare('SELECT * FROM school_teachers WHERE center_application_id = :center_application_id ORDER BY created_at DESC, id DESC');
$stmt->bindValue(':center_application_id', $centerApplicationId, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
function school_teacher_metrics(int $centerApplicationId): array
{
$pdo = db_connection();
$stmt = $pdo->prepare(
"SELECT
COUNT(*) AS total,
COALESCE(SUM(employment_status = 'active'), 0) AS active_count,
COALESCE(SUM(employment_status = 'pending'), 0) AS pending_count,
COALESCE(SUM(employment_status = 'inactive'), 0) AS inactive_count,
COALESCE(SUM(email IS NOT NULL AND email <> ''), 0) AS email_ready_count,
COALESCE(SUM(role_title LIKE '%مشرف%'), 0) AS supervisors_count
FROM school_teachers
WHERE center_application_id = :center_application_id"
);
$stmt->bindValue(':center_application_id', $centerApplicationId, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch() ?: [];
return [
'total' => (int) ($row['total'] ?? 0),
'active' => (int) ($row['active_count'] ?? 0),
'pending' => (int) ($row['pending_count'] ?? 0),
'inactive' => (int) ($row['inactive_count'] ?? 0),
'email_ready' => (int) ($row['email_ready_count'] ?? 0),
'supervisors' => (int) ($row['supervisors_count'] ?? 0),
];
}
function render_page_start(string $pageTitle, string $active = 'home', string $pageDescription = ''): void
{
$projectName = project_name();
@ -426,6 +824,7 @@ function render_page_start(string $pageTitle, string $active = 'home', string $p
<li class="nav-item"><a class="nav-link <?= $active === 'dashboard' ? 'active' : '' ?>" href="dashboard.php">لوحة القيادة</a></li>
<li class="nav-item"><a class="nav-link <?= $active === 'apply' ? 'active' : '' ?>" href="center_application.php">طلب فتح مركز</a></li>
<li class="nav-item"><a class="nav-link <?= $active === 'applications' ? 'active' : '' ?>" href="applications.php">لوحة الطلبات</a></li>
<li class="nav-item"><a class="nav-link <?= $active === 'approved' ? 'active' : '' ?>" href="applications.php?status=approved">المراكز المعتمدة</a></li>
<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">
@ -479,6 +878,7 @@ function render_page_end(): void
<a class="btn btn-outline-secondary btn-sm" href="dashboard.php">لوحة القيادة</a>
<a class="btn btn-outline-secondary btn-sm" href="center_application.php">إنشاء طلب جديد</a>
<a class="btn btn-outline-secondary btn-sm" href="applications.php">استعراض جميع الطلبات</a>
<a class="btn btn-outline-secondary btn-sm" href="applications.php?status=approved">المراكز المعتمدة</a>
<a class="btn btn-outline-secondary btn-sm" href="modules.php">هيكل النظام</a>
<a class="btn btn-outline-secondary btn-sm" href="healthz.php">فحص الحالة</a>
</div>

303
students.php Normal file
View File

@ -0,0 +1,303 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
$flash = consume_flash();
$applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0;
$application = $applicationId > 0 ? get_application($applicationId) : null;
$isApprovedSchool = $application && (string) $application['status'] === 'approved';
$values = student_defaults();
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
[$values, $errors] = validate_student_input($_POST);
if (!$isApprovedSchool) {
$errors['form'] = 'لا يمكن فتح تسجيل الطلاب قبل اعتماد المركز.';
}
if ($errors === []) {
try {
create_student((int) $application['id'], $values);
set_flash('success', 'تم تسجيل الطالب بنجاح وإضافته إلى كشف المدرسة.');
header('Location: students.php?id=' . urlencode((string) $application['id']));
exit;
} catch (PDOException $exception) {
$duplicateCode = isset($exception->errorInfo[1]) && (int) $exception->errorInfo[1] === 1062;
if ($duplicateCode) {
$errors['student_code'] = 'هذا الكود مستخدم مسبقاً داخل نفس المدرسة.';
} else {
$errors['form'] = 'تعذر حفظ بيانات الطالب حالياً. يرجى المحاولة مرة أخرى.';
}
} catch (Throwable $exception) {
$errors['form'] = 'تعذر حفظ بيانات الطالب حالياً. يرجى المحاولة مرة أخرى.';
}
}
}
$students = $isApprovedSchool ? list_school_students((int) $application['id']) : [];
$metrics = $isApprovedSchool ? school_student_metrics((int) $application['id']) : [
'total' => 0,
'boys' => 0,
'girls' => 0,
'active' => 0,
'waiting' => 0,
'withdrawn' => 0,
];
$expectedCapacity = $application ? (int) ($application['expected_students'] ?? 0) : 0;
$remainingSeats = max(0, $expectedCapacity - $metrics['total']);
$pageTitle = $application ? 'تسجيل الطلاب: ' . (string) $application['center_name'] : 'تسجيل الطلاب';
$pageDescription = 'صفحة مستقلة لتسجيل الطلاب وإدارة كشف المدرسة بعد الاعتماد، مع فصل واضح بين النموذج والكشف والجاهزية التشغيلية.';
if (!$application) {
http_response_code(404);
}
render_page_start($pageTitle, 'approved', $pageDescription);
render_flash($flash);
?>
<section class="py-4 py-lg-5">
<div class="container-xxl">
<?php if (!$application): ?>
<div class="app-card text-center py-5">
<div class="empty-title mb-2">المدرسة غير موجودة</div>
<p class="text-muted mb-3">تحقق من رابط المدرسة أو ارجع إلى قائمة المراكز المعتمدة.</p>
<a class="btn btn-dark" href="applications.php?status=approved">المراكز المعتمدة</a>
</div>
<?php elseif (!$isApprovedSchool): ?>
<div class="page-banner mb-4 mb-lg-5">
<div class="row g-4 align-items-center">
<div class="col-lg-8">
<span class="eyebrow mb-3">التسجيل يبدأ بعد الاعتماد</span>
<h1 class="page-title mb-3"><?= e((string) $application['center_name']) ?></h1>
<p class="page-copy mb-3">هذه الصفحة جاهزة، لكن فتح سجل الطلاب مرتبط بتحويل حالة المركز إلى <strong>معتمد</strong> أولاً حتى يبقى التسلسل الإداري منظمًا.</p>
<div class="hero-meta">
<span>الحالة الحالية: <?= e(status_meta((string) $application['status'])['label']) ?></span>
<span>المدينة: <?= e((string) $application['city']) ?></span>
</div>
<div class="cta-stack mt-4">
<a class="btn btn-dark" href="application_detail.php?id=<?= e((string) $application['id']) ?>">العودة لملف الاعتماد</a>
<a class="btn btn-outline-secondary" href="approved_school.php?id=<?= e((string) $application['id']) ?>">صفحة المركز</a>
</div>
</div>
</div>
</div>
<?php else: ?>
<div class="page-banner approved-hero mb-4 mb-lg-5">
<div class="row g-4 align-items-start">
<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>
<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="approved_school.php?id=<?= e((string) $application['id']) ?>">العودة لصفحة المركز</a>
<a class="btn btn-outline-secondary" href="teachers.php?id=<?= e((string) $application['id']) ?>">فريق المعلمين</a>
<a class="btn btn-outline-secondary" href="application_detail.php?id=<?= e((string) $application['id']) ?>">ملف الاعتماد</a>
</div>
</div>
<div class="col-lg-4">
<div class="app-card approved-note h-100">
<div class="section-title mb-3">ملخص التسجيل</div>
<div class="launch-metrics">
<div class="launch-metric"><strong><?= e((string) $metrics['total']) ?></strong><span>إجمالي الطلاب</span></div>
<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>
</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>
<?php if (!empty($errors['form'])): ?>
<div class="alert alert-danger mb-3"><?= e($errors['form']) ?></div>
<?php endif; ?>
<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>
</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="teachers.php?id=<?= e((string) $application['id']) ?>">فتح صفحة المعلمين</a></div></li>
<li><strong>التقييمات والأوزان</strong><span class="section-subtle">الأساس جاهز لتخزين نوع التقييم، المقياس، والوزن.</span></li>
<li><strong>غياب الطلاب</strong><span class="section-subtle">سجل الغياب موجود في القاعدة وسيتم بناء صفحته المنفصلة لاحقاً.</span></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>
<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>
<?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>
<?php else: ?>
<div class="table-responsive">
<table class="table app-table align-middle">
<thead>
<tr>
<th>الكود</th>
<th>الطالب</th>
<th>الصف</th>
<th>ولي الأمر</th>
<th>الهاتف</th>
<th>الحالة</th>
<th>التسجيل</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $student): ?>
<tr>
<td><strong><?= e((string) $student['student_code']) ?></strong></td>
<td>
<strong><?= e((string) $student['full_name']) ?></strong>
<small><?= e((string) $student['gender']) ?></small>
</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; ?>
</td>
<td><?= e((string) $student['guardian_phone']) ?></td>
<td><?= student_enrollment_status_badge((string) $student['enrollment_status']) ?></td>
<td><?= e(substr((string) $student['created_at'], 0, 10)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?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; ?>
</div>
</section>
<?php render_page_end(); ?>

280
teachers.php Normal file
View File

@ -0,0 +1,280 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
$flash = consume_flash();
$applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0;
$application = $applicationId > 0 ? get_application($applicationId) : null;
$isApprovedSchool = $application && (string) $application['status'] === 'approved';
$values = teacher_defaults();
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
[$values, $errors] = validate_teacher_input($_POST);
if (!$isApprovedSchool) {
$errors['form'] = 'لا يمكن فتح صفحة المعلمين قبل اعتماد المركز.';
}
if ($errors === []) {
try {
create_teacher((int) $application['id'], $values);
set_flash('success', 'تمت إضافة عضو الفريق بنجاح إلى سجل المدرسة.');
header('Location: teachers.php?id=' . urlencode((string) $application['id']));
exit;
} catch (Throwable $exception) {
$errors['form'] = 'تعذر حفظ بيانات عضو الفريق حالياً. يرجى المحاولة مرة أخرى.';
}
}
}
$teachers = $isApprovedSchool ? list_school_teachers((int) $application['id']) : [];
$metrics = $isApprovedSchool ? school_teacher_metrics((int) $application['id']) : [
'total' => 0,
'active' => 0,
'pending' => 0,
'inactive' => 0,
'email_ready' => 0,
'supervisors' => 0,
];
$studentMetrics = $isApprovedSchool ? school_student_metrics((int) $application['id']) : [
'total' => 0,
'active' => 0,
'waiting' => 0,
'withdrawn' => 0,
'boys' => 0,
'girls' => 0,
];
$pageTitle = $application ? 'فريق المعلمين: ' . (string) $application['center_name'] : 'فريق المعلمين';
$pageDescription = 'صفحة مستقلة لإدارة المعلمين والمشرفين بعد اعتماد المدرسة، مع فصل واضح بين إضافة الكادر وكشف الفريق ومؤشرات الجاهزية.';
if (!$application) {
http_response_code(404);
}
render_page_start($pageTitle, 'approved', $pageDescription);
render_flash($flash);
?>
<section class="py-4 py-lg-5">
<div class="container-xxl">
<?php if (!$application): ?>
<div class="app-card text-center py-5">
<div class="empty-title mb-2">المدرسة غير موجودة</div>
<p class="text-muted mb-3">تحقق من رابط المدرسة أو ارجع إلى قائمة المراكز المعتمدة.</p>
<a class="btn btn-dark" href="applications.php?status=approved">المراكز المعتمدة</a>
</div>
<?php elseif (!$isApprovedSchool): ?>
<div class="page-banner mb-4 mb-lg-5">
<div class="row g-4 align-items-center">
<div class="col-lg-8">
<span class="eyebrow mb-3">الفريق يُفعّل بعد الاعتماد</span>
<h1 class="page-title mb-3"><?= e((string) $application['center_name']) ?></h1>
<p class="page-copy mb-3">تم تجهيز صفحة المعلمين، لكن فتح سجل الفريق مرتبط بتحويل حالة المركز إلى <strong>معتمد</strong> أولاً حتى يبدأ التشغيل وفق الترتيب الإداري الصحيح.</p>
<div class="hero-meta">
<span>الحالة الحالية: <?= e(status_meta((string) $application['status'])['label']) ?></span>
<span>المدينة: <?= e((string) $application['city']) ?></span>
</div>
<div class="cta-stack mt-4">
<a class="btn btn-dark" href="application_detail.php?id=<?= e((string) $application['id']) ?>">العودة لملف الاعتماد</a>
<a class="btn btn-outline-secondary" href="approved_school.php?id=<?= e((string) $application['id']) ?>">صفحة المركز</a>
</div>
</div>
</div>
</div>
<?php else: ?>
<div class="page-banner approved-hero mb-4 mb-lg-5">
<div class="row g-4 align-items-start">
<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>
<div class="hero-meta">
<span><?= e((string) $application['city']) ?></span>
<span><?= e((string) $metrics['active']) ?> أعضاء مفعلون</span>
<span><?= e((string) $metrics['supervisors']) ?> أدوار إشرافية</span>
</div>
<div class="cta-stack mt-4">
<a class="btn btn-dark" href="approved_school.php?id=<?= e((string) $application['id']) ?>">العودة لصفحة المركز</a>
<a class="btn btn-outline-secondary" href="students.php?id=<?= e((string) $application['id']) ?>">تسجيل الطلاب</a>
<a class="btn btn-outline-secondary" href="application_detail.php?id=<?= e((string) $application['id']) ?>">ملف الاعتماد</a>
</div>
</div>
<div class="col-lg-4">
<div class="app-card approved-note h-100">
<div class="section-title mb-3">ملخص الفريق</div>
<div class="summary-stack mb-3">
<div class="summary-row"><span>إجمالي الكادر</span><strong><?= e((string) $metrics['total']) ?> عضو</strong></div>
<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>
</div>
</div>
</div>
</div>
<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>
<?php if (isset($errors['form'])): ?>
<div class="alert alert-danger"><?= e($errors['form']) ?></div>
<?php endif; ?>
<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>
</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></li>
<li><strong>غياب الطلاب</strong><span class="section-subtle">يمكن لاحقاً ربط الغياب بالطلاب والفريق المسؤول عن المتابعة اليومية.</span></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>
<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>
<?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>
<?php else: ?>
<div class="table-responsive">
<table class="table app-table align-middle">
<thead>
<tr>
<th>الاسم</th>
<th>الدور</th>
<th>التخصص</th>
<th>التواصل</th>
<th>الحالة</th>
<th>الإضافة</th>
</tr>
</thead>
<tbody>
<?php foreach ($teachers as $teacher): ?>
<tr>
<td>
<strong><?= e((string) $teacher['full_name']) ?></strong>
<?php if (!empty($teacher['notes'])): ?><small><?= e((string) $teacher['notes']) ?></small><?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>
</td>
<td><?= teacher_employment_status_badge((string) $teacher['employment_status']) ?></td>
<td><?= e(substr((string) $teacher['created_at'], 0, 10)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?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="students.php?id=<?= e((string) $application['id']) ?>">العودة إلى الطلاب</a>
<a class="btn btn-outline-secondary" href="approved_school.php?id=<?= e((string) $application['id']) ?>">صفحة المركز</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</section>
<?php render_page_end(); ?>