update students reward
This commit is contained in:
parent
5849af849c
commit
11de03c65b
@ -16,11 +16,11 @@ render_flash($flash);
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<div class="page-banner mb-4 mb-lg-5">
|
||||
<div class="row g-4 align-items-start">
|
||||
|
||||
@ -11,6 +11,9 @@ $values = [
|
||||
'app_slogan' => $settings['app_slogan'] ?? '',
|
||||
'app_email' => $settings['app_email'] ?? '',
|
||||
'app_telephone' => $settings['app_telephone'] ?? '',
|
||||
'completion_certificate_template' => $settings['completion_certificate_template'] ?? 'modern',
|
||||
'completion_certificate_tagline' => $settings['completion_certificate_tagline'] ?? 'شهادة إتمام وتكريم',
|
||||
'completion_certificate_message' => $settings['completion_certificate_message'] ?? '',
|
||||
];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
@ -18,8 +21,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$values['app_slogan'] = clean_text($_POST['app_slogan'] ?? '', 190);
|
||||
$values['app_email'] = clean_text($_POST['app_email'] ?? '', 190);
|
||||
$values['app_telephone'] = clean_text($_POST['app_telephone'] ?? '', 60);
|
||||
$values['completion_certificate_template'] = clean_text($_POST['completion_certificate_template'] ?? 'modern', 40);
|
||||
$values['completion_certificate_tagline'] = clean_text($_POST['completion_certificate_tagline'] ?? '', 255);
|
||||
$values['completion_certificate_message'] = clean_text($_POST['completion_certificate_message'] ?? '', 1200);
|
||||
|
||||
if ($values['app_name'] === '') $errors['app_name'] = 'مطلوب';
|
||||
if (!in_array($values['completion_certificate_template'], ['modern', 'classic'], true)) {
|
||||
$values['completion_certificate_template'] = 'modern';
|
||||
}
|
||||
if ($values['completion_certificate_tagline'] === '') {
|
||||
$values['completion_certificate_tagline'] = 'شهادة إتمام وتكريم';
|
||||
}
|
||||
|
||||
$logoPath = $settings['app_logo'] ?? '';
|
||||
$faviconPath = $settings['app_favicon'] ?? '';
|
||||
@ -56,14 +68,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
if (empty($errors)) {
|
||||
try {
|
||||
$stmt = db()->prepare('UPDATE app_settings SET app_name = ?, app_slogan = ?, app_email = ?, app_telephone = ?, app_logo = ?, app_favicon = ?, updated_at = NOW() WHERE id = 1');
|
||||
$stmt = db()->prepare('UPDATE app_settings SET app_name = ?, app_slogan = ?, app_email = ?, app_telephone = ?, app_logo = ?, app_favicon = ?, completion_certificate_template = ?, completion_certificate_tagline = ?, completion_certificate_message = ?, updated_at = NOW() WHERE id = 1');
|
||||
$stmt->execute([
|
||||
$values['app_name'],
|
||||
$values['app_slogan'],
|
||||
$values['app_email'],
|
||||
$values['app_telephone'],
|
||||
$logoPath,
|
||||
$faviconPath
|
||||
$faviconPath,
|
||||
$values['completion_certificate_template'],
|
||||
$values['completion_certificate_tagline'],
|
||||
$values['completion_certificate_message']
|
||||
]);
|
||||
set_flash('success', 'تم تحديث الإعدادات العامة بنجاح.');
|
||||
header('Location: app_settings.php');
|
||||
@ -79,11 +94,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<div class="page-banner mb-4">
|
||||
<h1 class="page-title mb-2">الإعدادات العامة للنظام</h1>
|
||||
<p class="page-copy mb-0">تعديل اسم النظام، الشعار (Logo)، الأيقونة (Favicon)، وبيانات التواصل.</p>
|
||||
@ -132,6 +147,26 @@ render_flash($flash);
|
||||
<input type="file" class="form-control <?= isset($errors['favicon']) ? 'is-invalid' : '' ?>" name="favicon" accept=".ico,.png,.svg">
|
||||
<?php if (isset($errors['favicon'])): ?><div class="invalid-feedback"><?= e($errors['favicon']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="col-12"><hr class="my-1"></div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">قالب شهادة الإتمام</label>
|
||||
<select class="form-select" name="completion_certificate_template">
|
||||
<option value="modern" <?= $values['completion_certificate_template'] === 'modern' ? 'selected' : '' ?>>Modern / عصري</option>
|
||||
<option value="classic" <?= $values['completion_certificate_template'] === 'classic' ? 'selected' : '' ?>>Classic / رسمي</option>
|
||||
</select>
|
||||
<div class="form-text">يغيّر شكل شهادة الإتمام والتكريم فقط، بدون التأثير على كشف الدرجات.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">عنوان الشهادة</label>
|
||||
<input class="form-control" name="completion_certificate_tagline" value="<?= e($values['completion_certificate_tagline']) ?>">
|
||||
<div class="form-text">مثال: شهادة إتمام وتكريم أو شهادة تفوق.</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">نص شهادة الإتمام</label>
|
||||
<textarea class="form-control" name="completion_certificate_message" rows="4"><?= e($values['completion_certificate_message']) ?></textarea>
|
||||
<div class="form-text">يمكنك استخدام المتغيرات: {student} {center} {cycle} {performance} {honor} {percentage}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-4">
|
||||
@ -144,4 +179,4 @@ render_flash($flash);
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_page_end(); ?>
|
||||
<?php render_page_end(); ?>
|
||||
|
||||
@ -76,11 +76,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<div class="page-banner mb-4 mb-lg-5">
|
||||
<div class="row g-4 align-items-start">
|
||||
|
||||
@ -51,11 +51,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<div class="app-card mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3">
|
||||
<div class="section-title mb-0">إدارة طلبات فتح المراكز</div>
|
||||
@ -149,4 +149,4 @@ render_flash($flash);
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_page_end(); ?>
|
||||
<?php render_page_end(); ?>
|
||||
|
||||
@ -17,11 +17,11 @@ if (!$application) {
|
||||
?>
|
||||
<section class="py-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<div class="app-card text-center py-5">
|
||||
<div class="empty-title mb-2">المركز غير موجود</div>
|
||||
@ -151,11 +151,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<?php if (!$isApproved): ?>
|
||||
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4">
|
||||
|
||||
@ -101,11 +101,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<?php if (!$application): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
<div class="empty-title mb-2">المدرسة غير موجودة</div>
|
||||
@ -213,7 +213,10 @@ render_flash($flash);
|
||||
<?php elseif (!empty($criterion['id'])): ?>
|
||||
<span class="text-muted small">أوقف التفعيل لإخفائه</span>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn-sm btn-light" data-remove-row>حذف</button>
|
||||
<button type="button" class="btn btn-sm btn-light icon-action" data-remove-row title="حذف البند" aria-label="حذف البند">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0A.5.5 0 0 1 8.5 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/><path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1 0-2H5.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1H13.5a1 1 0 0 1 1 1ZM4 4v9a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4H4Zm2.5-2a.5.5 0 0 0-.5.5V3h4v-.5a.5.5 0 0 0-.5-.5h-3Z"/></svg>
|
||||
<span class="visually-hidden">حذف البند</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
@ -255,7 +258,10 @@ render_flash($flash);
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-light" data-remove-row>حذف</button>
|
||||
<button type="button" class="btn btn-sm btn-light icon-action" data-remove-row title="حذف البند" aria-label="حذف البند">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0A.5.5 0 0 1 8.5 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/><path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1 0-2H5.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1H13.5a1 1 0 0 1 1 1ZM4 4v9a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4H4Zm2.5-2a.5.5 0 0 0-.5.5V3h4v-.5a.5.5 0 0 0-.5-.5h-3Z"/></svg>
|
||||
<span class="visually-hidden">حذف البند</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
@ -149,11 +149,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<?php if (!$application): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
<div class="empty-title mb-2">المدرسة غير موجودة</div>
|
||||
|
||||
@ -55,11 +55,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<?php if (!$application): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
<div class="empty-title mb-2">المدرسة غير موجودة</div>
|
||||
|
||||
@ -99,11 +99,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<?php if (!$application): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
@ -237,12 +237,35 @@ render_flash($flash);
|
||||
</td>
|
||||
<td><?= assessment_active_badge((int) $assessment['is_active']) ?></td>
|
||||
<?php if (!$isCycleReadOnly):
|
||||
?><td>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm btn-primary" href="<?= e($assessmentScoreSheetBaseUrl . '&assessment_id=' . urlencode((string) $assessment['id'])) ?>">رصد الدرجات</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="<?= e($criteriaBaseUrl . '&assessment_id=' . urlencode((string) $assessment['id'])) ?>">إعداد البنود</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="editAssessment(<?= htmlspecialchars(json_encode($assessment), ENT_QUOTES, 'UTF-8') ?>)">
|
||||
تعديل
|
||||
?><td class="table-action-cell">
|
||||
<div class="table-icon-actions">
|
||||
<a
|
||||
class="btn btn-sm btn-primary icon-action"
|
||||
href="<?= e($assessmentScoreSheetBaseUrl . '&assessment_id=' . urlencode((string) $assessment['id'])) ?>"
|
||||
title="رصد الدرجات"
|
||||
aria-label="رصد الدرجات"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 1A1.5 1.5 0 0 0 1 2.5v11A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-11A1.5 1.5 0 0 0 13.5 1h-11ZM2 2.5a.5.5 0 0 1 .5-.5H4v12H2.5a.5.5 0 0 1-.5-.5v-11Zm3 11.5V2h8.5a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-.5.5H5Zm2.5-8.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H8a.5.5 0 0 1-.5-.5Zm0 2.5A.5.5 0 0 1 8 7.5h4a.5.5 0 0 1 0 1H8a.5.5 0 0 1-.5-.5Zm0 2.5A.5.5 0 0 1 8 10h2.5a.5.5 0 0 1 0 1H8a.5.5 0 0 1-.5-.5Z"/></svg>
|
||||
<span class="visually-hidden">رصد الدرجات</span>
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-sm btn-outline-secondary icon-action"
|
||||
href="<?= e($criteriaBaseUrl . '&assessment_id=' . urlencode((string) $assessment['id'])) ?>"
|
||||
title="إعداد البنود"
|
||||
aria-label="إعداد البنود"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M6 10.117V16l4-2.5 4 2.5v-5.883l-4 2.5-4-2.5Z"/><path d="M10 0a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM7 4a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z"/><path d="M2.5 1A1.5 1.5 0 0 0 1 2.5v9A1.5 1.5 0 0 0 2.5 13H5v-1H2.5a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h7.55a5.02 5.02 0 0 0-.497-1H2.5Z"/></svg>
|
||||
<span class="visually-hidden">إعداد البنود</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary icon-action"
|
||||
onclick="editAssessment(<?= htmlspecialchars(json_encode($assessment), ENT_QUOTES, 'UTF-8') ?>)"
|
||||
title="تعديل التقييم"
|
||||
aria-label="تعديل التقييم"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M12.854.146a.5.5 0 0 1 .707 0l2.586 2.586a.5.5 0 0 1 0 .707l-9.793 9.793a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168L12.854.146ZM11.207 2 3 10.207V13h2.793L14 4.793 11.207 2Z"/></svg>
|
||||
<span class="visually-hidden">تعديل التقييم</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@ -172,7 +172,42 @@ h6,
|
||||
}
|
||||
|
||||
.container-xxl {
|
||||
max-width: 1240px;
|
||||
max-width: min(1540px, calc(100vw - 2rem));
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.admin-layout.row {
|
||||
--bs-gutter-x: 0;
|
||||
--bs-gutter-y: 0;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-start;
|
||||
gap: 1.5rem;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.admin-layout.row > .layout-sidebar-column,
|
||||
.admin-layout.row > .layout-content-column {
|
||||
width: auto;
|
||||
max-width: none;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.admin-layout.row > .layout-sidebar-column {
|
||||
order: 1;
|
||||
flex: 0 0 clamp(280px, 22vw, 320px);
|
||||
max-width: clamp(280px, 22vw, 320px);
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.admin-layout.row > .layout-content-column {
|
||||
order: 2;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
@ -915,14 +950,96 @@ textarea.form-control {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.table-action-cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-icon-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.45rem;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.icon-action {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.8rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.icon-action svg {
|
||||
width: 0.95rem;
|
||||
height: 0.95rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.teacher-subject-picker {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
.teacher-subject-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.95rem;
|
||||
background: #fff;
|
||||
padding: 0.7rem 0.85rem;
|
||||
min-height: 100%;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.teacher-subject-option:hover {
|
||||
border-color: rgba(14, 165, 233, 0.35);
|
||||
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.teacher-subject-option .form-check-input {
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.teacher-subject-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.teacher-subject-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(14, 165, 233, 0.18);
|
||||
border-radius: 999px;
|
||||
background: #f8fbff;
|
||||
color: var(--primary);
|
||||
padding: 0.18rem 0.55rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
||||
/* Admin Sidebar */
|
||||
.admin-sidebar {
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 1.25rem;
|
||||
max-height: calc(100vh - 2.5rem);
|
||||
overflow: auto;
|
||||
}
|
||||
.sidebar-nav {
|
||||
display: flex;
|
||||
@ -957,3 +1074,578 @@ textarea.form-control {
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
|
||||
.student-certificate-page {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.certificate-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.certificate-toolbar-actions {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.student-certificate-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 1.8rem;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(14, 165, 233, 0.16), transparent 34%),
|
||||
radial-gradient(circle at top right, rgba(245, 158, 11, 0.18), transparent 32%),
|
||||
linear-gradient(135deg, #ffffff 0%, #f8fbff 55%, #fdf7eb 100%);
|
||||
box-shadow: 0 28px 60px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.student-certificate-card::before,
|
||||
.student-certificate-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-radius: 999px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.student-certificate-card::before {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
top: -90px;
|
||||
left: -70px;
|
||||
background: rgba(14, 165, 233, 0.12);
|
||||
}
|
||||
|
||||
.student-certificate-card::after {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
bottom: -80px;
|
||||
right: -55px;
|
||||
background: rgba(245, 158, 11, 0.14);
|
||||
}
|
||||
|
||||
.student-certificate-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.certificate-kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.5rem 0.9rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.certificate-title {
|
||||
font-size: clamp(2rem, 4vw, 3rem);
|
||||
font-weight: 700;
|
||||
margin: 1rem 0 0.75rem;
|
||||
}
|
||||
|
||||
.certificate-subtitle {
|
||||
max-width: 58rem;
|
||||
color: var(--muted);
|
||||
font-size: 1.02rem;
|
||||
}
|
||||
|
||||
.certificate-student-name {
|
||||
font-size: clamp(1.8rem, 3vw, 2.5rem);
|
||||
font-weight: 700;
|
||||
margin: 1.25rem 0 0.35rem;
|
||||
}
|
||||
|
||||
.certificate-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(280px, 0.9fr);
|
||||
gap: 1.35rem;
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
.certificate-panel {
|
||||
border-radius: 1.35rem;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
padding: 1.2rem 1.25rem;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.certificate-meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 0.9rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.certificate-meta-item {
|
||||
border-radius: 1rem;
|
||||
background: rgba(248, 250, 252, 0.95);
|
||||
padding: 0.9rem 1rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
}
|
||||
|
||||
.certificate-meta-item strong,
|
||||
.certificate-signature strong,
|
||||
.certificate-panel strong {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.performance-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.7rem 1.15rem;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
background: rgba(14, 165, 233, 0.12);
|
||||
color: #075985;
|
||||
border: 1px solid rgba(14, 165, 233, 0.18);
|
||||
}
|
||||
|
||||
.performance-pill.performance-excellent {
|
||||
background: rgba(22, 163, 74, 0.14);
|
||||
color: #166534;
|
||||
border-color: rgba(22, 163, 74, 0.2);
|
||||
}
|
||||
|
||||
.performance-pill.performance-very_good {
|
||||
background: rgba(14, 165, 233, 0.14);
|
||||
color: #075985;
|
||||
border-color: rgba(14, 165, 233, 0.18);
|
||||
}
|
||||
|
||||
.performance-pill.performance-good {
|
||||
background: rgba(245, 158, 11, 0.16);
|
||||
color: #92400e;
|
||||
border-color: rgba(245, 158, 11, 0.18);
|
||||
}
|
||||
|
||||
.performance-pill.performance-poor {
|
||||
background: rgba(239, 68, 68, 0.14);
|
||||
color: #991b1b;
|
||||
border-color: rgba(239, 68, 68, 0.18);
|
||||
}
|
||||
|
||||
.certificate-stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.9rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.certificate-stat {
|
||||
border-radius: 1rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
padding: 0.9rem 1rem;
|
||||
}
|
||||
|
||||
.certificate-stat strong {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.certificate-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.certificate-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.certificate-table th,
|
||||
.certificate-table td {
|
||||
padding: 0.85rem 0.9rem;
|
||||
border-bottom: 1px solid rgba(148, 163, 184, 0.18);
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.certificate-table thead th {
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
font-weight: 600;
|
||||
background: rgba(248, 250, 252, 0.9);
|
||||
}
|
||||
|
||||
.certificate-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.certificate-signature-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.certificate-signature {
|
||||
border-top: 1px dashed rgba(100, 116, 139, 0.55);
|
||||
padding-top: 0.85rem;
|
||||
}
|
||||
|
||||
.certificate-empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 1.25rem;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.certificate-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.site-header,
|
||||
.site-footer,
|
||||
.certificate-toolbar,
|
||||
.toast-container {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.student-certificate-page,
|
||||
.student-certificate-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.student-certificate-card,
|
||||
.certificate-panel,
|
||||
.certificate-meta-item,
|
||||
.certificate-stat {
|
||||
box-shadow: none !important;
|
||||
background: #fff !important;
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
.student-certificate-card {
|
||||
border: 2px solid rgba(148, 116, 18, 0.65);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.student-certificate-card::before,
|
||||
.student-certificate-card::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.completion-certificate-page {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.completion-certificate-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 2rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.24);
|
||||
box-shadow: 0 28px 60px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.completion-template-modern {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(14, 165, 233, 0.18), transparent 30%),
|
||||
radial-gradient(circle at bottom right, rgba(251, 191, 36, 0.18), transparent 28%),
|
||||
linear-gradient(145deg, #fffdf7 0%, #ffffff 38%, #f5fbff 100%);
|
||||
}
|
||||
|
||||
.completion-template-classic {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 251, 235, 0.9) 0%, rgba(255, 255, 255, 1) 24%, rgba(255, 251, 235, 0.82) 100%);
|
||||
}
|
||||
|
||||
.completion-template-classic::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 1.1rem;
|
||||
border: 2px solid rgba(180, 146, 42, 0.42);
|
||||
border-radius: 1.5rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.completion-certificate-layer {
|
||||
position: absolute;
|
||||
border-radius: 999px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.completion-certificate-layer-one {
|
||||
width: 260px;
|
||||
height: 260px;
|
||||
top: -90px;
|
||||
right: -80px;
|
||||
background: rgba(14, 165, 233, 0.14);
|
||||
}
|
||||
|
||||
.completion-certificate-layer-two {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
bottom: -110px;
|
||||
left: -70px;
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
}
|
||||
|
||||
.completion-template-classic .completion-certificate-layer-one,
|
||||
.completion-template-classic .completion-certificate-layer-two {
|
||||
background: rgba(180, 146, 42, 0.08);
|
||||
}
|
||||
|
||||
.completion-certificate-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 2.2rem;
|
||||
}
|
||||
|
||||
.completion-certificate-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.completion-branding {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.completion-brand-logo {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
object-fit: cover;
|
||||
border-radius: 1.35rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.24);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 16px 35px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.completion-brand-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #075985;
|
||||
}
|
||||
|
||||
.completion-overline {
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.completion-certificate-title {
|
||||
font-size: clamp(2.1rem, 4vw, 3.4rem);
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.completion-certificate-center {
|
||||
color: var(--muted);
|
||||
font-size: 1.05rem;
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
.completion-certificate-stamps {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.completion-stamp {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 2.75rem;
|
||||
padding: 0.7rem 1rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.completion-stamp-accent {
|
||||
background: rgba(250, 204, 21, 0.18);
|
||||
color: #854d0e;
|
||||
border-color: rgba(202, 138, 4, 0.22);
|
||||
}
|
||||
|
||||
.completion-certificate-content {
|
||||
text-align: center;
|
||||
max-width: 54rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.completion-intro {
|
||||
color: var(--muted);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.completion-student-name {
|
||||
font-size: clamp(2.35rem, 5vw, 4.35rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.completion-student-code {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 2.2rem;
|
||||
padding: 0.45rem 0.95rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
color: var(--muted);
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.completion-message {
|
||||
font-size: 1.2rem;
|
||||
line-height: 2;
|
||||
margin: 0 auto 0.8rem;
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.completion-honor-line {
|
||||
color: #92400e;
|
||||
font-weight: 600;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.completion-certificate-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 0.8fr) minmax(0, 1.3fr);
|
||||
gap: 1.1rem;
|
||||
margin-top: 2rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.completion-summary-card,
|
||||
.completion-meta-item,
|
||||
.completion-note,
|
||||
.completion-signature-block {
|
||||
border-radius: 1.25rem;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.completion-summary-card {
|
||||
padding: 1.25rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.completion-summary-label {
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.completion-performance-pill {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.completion-honor-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.completion-honor-subtitle {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.completion-meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.completion-meta-item {
|
||||
padding: 1rem 1.05rem;
|
||||
}
|
||||
|
||||
.completion-meta-item strong,
|
||||
.completion-signature-block strong {
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.completion-note {
|
||||
padding: 1rem 1.1rem;
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.completion-signature-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.completion-signature-block {
|
||||
padding: 1rem 1.1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.completion-certificate-header,
|
||||
.completion-branding {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.completion-certificate-stamps {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.completion-certificate-grid,
|
||||
.completion-signature-row,
|
||||
.completion-meta-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.completion-certificate-card,
|
||||
.completion-summary-card,
|
||||
.completion-meta-item,
|
||||
.completion-note,
|
||||
.completion-signature-block {
|
||||
box-shadow: none !important;
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
.completion-certificate-card {
|
||||
border: 2px solid rgba(180, 146, 42, 0.62);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,11 +119,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<?php if (!$application): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
|
||||
@ -13,11 +13,11 @@ if (!$application) {
|
||||
?>
|
||||
<section class="py-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<div class="app-card text-center py-5">
|
||||
<div class="empty-title mb-2">المركز غير موجود</div>
|
||||
<a class="btn btn-primary" href="applications.php?status=approved">العودة</a>
|
||||
@ -109,11 +109,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<div class="page-banner mb-4">
|
||||
<h1 class="page-title mb-2">إعدادات وهوية المركز</h1>
|
||||
<p class="page-copy mb-0">تعديل اسم المركز، الشعار (Logo)، الأيقونة (Favicon)، وبيانات التواصل.</p>
|
||||
|
||||
@ -22,11 +22,11 @@ if (!$application || !$isApproved) {
|
||||
?>
|
||||
<section class="py-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<div class="app-card text-center py-5">
|
||||
<div class="empty-title mb-2">المركز غير موجود</div>
|
||||
<a class="btn btn-primary" href="applications.php?status=approved">العودة</a>
|
||||
@ -73,11 +73,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<div class="page-banner mb-4">
|
||||
<h1 class="page-title mb-2">المواد الدراسية للمركز</h1>
|
||||
<p class="page-copy mb-0">تحديد وتحديث المواد الدراسية التي يتم تقديمها في هذا المركز. سيتم توفير هذه المواد لاختيارها في التقييمات والجداول.</p>
|
||||
@ -125,4 +125,4 @@ render_flash($flash);
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_page_end();
|
||||
<?php render_page_end();
|
||||
|
||||
@ -11,11 +11,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<div class="row align-items-center mb-4 bg-white rounded-4 shadow-sm overflow-hidden" style="border: 1px solid var(--border-color);">
|
||||
<div class="col-lg-7 p-4 p-lg-5">
|
||||
@ -137,4 +137,4 @@ render_flash($flash);
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_page_end(); ?>
|
||||
<?php render_page_end(); ?>
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
ALTER TABLE app_settings
|
||||
ADD COLUMN completion_certificate_template VARCHAR(40) NOT NULL DEFAULT 'modern' AFTER app_slogan,
|
||||
ADD COLUMN completion_certificate_tagline VARCHAR(255) DEFAULT 'شهادة إتمام وتكريم' AFTER completion_certificate_template,
|
||||
ADD COLUMN completion_certificate_message TEXT DEFAULT NULL AFTER completion_certificate_tagline;
|
||||
@ -0,0 +1,2 @@
|
||||
ALTER TABLE school_teachers
|
||||
ADD COLUMN subject_ids TEXT NULL AFTER specialization;
|
||||
@ -127,11 +127,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<div class="page-banner mb-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
|
||||
121
includes/app.php
121
includes/app.php
@ -131,6 +131,23 @@ function ensure_app_settings_schema(PDO $pdo): void
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
");
|
||||
|
||||
$sloganMigrationPath = __DIR__ . '/../db/migrations/20260416_alter_app_settings_slogan.sql';
|
||||
if (!schema_table_has_column($pdo, 'app_settings', 'app_slogan') && is_file($sloganMigrationPath)) {
|
||||
$sql = file_get_contents($sloganMigrationPath);
|
||||
if (is_string($sql) && trim($sql) !== '') {
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
$completionMigrationPath = __DIR__ . '/../db/migrations/20260417_alter_app_settings_completion_certificate.sql';
|
||||
if (!schema_table_has_column($pdo, 'app_settings', 'completion_certificate_template') && is_file($completionMigrationPath)) {
|
||||
$sql = file_get_contents($completionMigrationPath);
|
||||
if (is_string($sql) && trim($sql) !== '') {
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->exec("INSERT IGNORE INTO app_settings (id, app_name) VALUES (1, 'Central Admin')");
|
||||
}
|
||||
|
||||
@ -146,9 +163,18 @@ function get_app_settings(): array
|
||||
'app_telephone' => '',
|
||||
'app_logo' => '',
|
||||
'app_favicon' => '',
|
||||
'app_slogan' => ''
|
||||
'app_slogan' => '',
|
||||
'completion_certificate_template' => 'modern',
|
||||
'completion_certificate_tagline' => 'شهادة إتمام وتكريم',
|
||||
'completion_certificate_message' => 'تشهد إدارة {center} بأن الطالب/الطالبة {student} قد أتمّ/أتمّت متطلبات {cycle} بنجاح، وحقق/حققت مستوى أداء {performance} {honor}.',
|
||||
];
|
||||
}
|
||||
|
||||
$res['app_slogan'] = (string) ($res['app_slogan'] ?? '');
|
||||
$res['completion_certificate_template'] = (string) ($res['completion_certificate_template'] ?? 'modern');
|
||||
$res['completion_certificate_tagline'] = (string) ($res['completion_certificate_tagline'] ?? 'شهادة إتمام وتكريم');
|
||||
$res['completion_certificate_message'] = (string) ($res['completion_certificate_message'] ?? 'تشهد إدارة {center} بأن الطالب/الطالبة {student} قد أتمّ/أتمّت متطلبات {cycle} بنجاح، وحقق/حققت مستوى أداء {performance} {honor}.');
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
@ -185,6 +211,14 @@ function ensure_school_module_schema(PDO $pdo): void
|
||||
}
|
||||
}
|
||||
|
||||
$teacherSubjectsMigrationPath = __DIR__ . '/../db/migrations/20260417_alter_school_teachers_subject_ids.sql';
|
||||
if (!schema_table_has_column($pdo, 'school_teachers', 'subject_ids') && is_file($teacherSubjectsMigrationPath)) {
|
||||
$sql = file_get_contents($teacherSubjectsMigrationPath);
|
||||
if (is_string($sql) && trim($sql) !== '') {
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
$done = true;
|
||||
}
|
||||
|
||||
@ -804,6 +838,7 @@ function teacher_defaults(): array
|
||||
'full_name' => '',
|
||||
'role_title' => '',
|
||||
'specialization' => '',
|
||||
'subject_ids' => [],
|
||||
'phone' => '',
|
||||
'email' => '',
|
||||
'employment_status' => 'active',
|
||||
@ -840,14 +875,72 @@ function teacher_employment_status_badge(string $status): string
|
||||
return '<span class="status-badge ' . e($meta['class']) . '">' . e($meta['label']) . '</span>';
|
||||
}
|
||||
|
||||
function validate_teacher_input(array $input): array
|
||||
function normalize_id_list(mixed $value): array
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$items = $value;
|
||||
} elseif (is_string($value) && $value !== '') {
|
||||
$decoded = json_decode($value, true);
|
||||
$items = is_array($decoded) ? $decoded : [];
|
||||
} else {
|
||||
$items = [];
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
foreach ($items as $item) {
|
||||
$id = (int) $item;
|
||||
if ($id > 0) {
|
||||
$normalized[$id] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($normalized);
|
||||
}
|
||||
|
||||
function get_enabled_subject_map(): array
|
||||
{
|
||||
$map = [];
|
||||
foreach (get_enabled_subjects() as $subject) {
|
||||
$subjectId = (int) ($subject['id'] ?? 0);
|
||||
if ($subjectId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map[$subjectId] = (string) ($subject['name'] ?? '');
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
function teacher_subject_labels(array $teacher, ?array $subjectMap = null): array
|
||||
{
|
||||
$subjectMap = $subjectMap ?? get_enabled_subject_map();
|
||||
$labels = [];
|
||||
|
||||
foreach (normalize_id_list($teacher['subject_ids'] ?? []) as $subjectId) {
|
||||
if (!isset($subjectMap[$subjectId]) || $subjectMap[$subjectId] === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$labels[] = $subjectMap[$subjectId];
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
function validate_teacher_input(array $input, array $allowedSubjectIds = []): array
|
||||
{
|
||||
$data = teacher_defaults();
|
||||
foreach ($data as $key => $_value) {
|
||||
if ($key === 'subject_ids') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$limit = $key === 'notes' ? 1000 : 190;
|
||||
$data[$key] = clean_text((string) ($input[$key] ?? ''), $limit);
|
||||
}
|
||||
|
||||
$data['subject_ids'] = normalize_id_list($input['subject_ids'] ?? []);
|
||||
$errors = [];
|
||||
|
||||
if ($data['full_name'] === '') {
|
||||
@ -868,6 +961,23 @@ function validate_teacher_input(array $input): array
|
||||
$errors['employment_status'] = 'يرجى اختيار حالة توظيف صحيحة.';
|
||||
}
|
||||
|
||||
$validSubjectIds = $allowedSubjectIds !== [] ? normalize_id_list($allowedSubjectIds) : array_keys(get_enabled_subject_map());
|
||||
if ($validSubjectIds !== []) {
|
||||
$validLookup = array_fill_keys($validSubjectIds, true);
|
||||
$filteredSubjectIds = array_values(array_filter(
|
||||
$data['subject_ids'],
|
||||
static fn (int $subjectId): bool => isset($validLookup[$subjectId])
|
||||
));
|
||||
|
||||
if (count($filteredSubjectIds) !== count($data['subject_ids'])) {
|
||||
$errors['subject_ids'] = 'اختر مواداً صحيحة من قائمة المركز.';
|
||||
}
|
||||
|
||||
$data['subject_ids'] = $filteredSubjectIds;
|
||||
} else {
|
||||
$data['subject_ids'] = [];
|
||||
}
|
||||
|
||||
return [$data, $errors];
|
||||
}
|
||||
|
||||
@ -876,20 +986,23 @@ 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,
|
||||
center_application_id, full_name, role_title, specialization, subject_ids,
|
||||
phone, email, employment_status, notes,
|
||||
created_at, updated_at
|
||||
) VALUES (
|
||||
:center_application_id, :full_name, :role_title, :specialization,
|
||||
:center_application_id, :full_name, :role_title, :specialization, :subject_ids,
|
||||
:phone, :email, :employment_status, :notes,
|
||||
NOW(), NOW()
|
||||
)'
|
||||
);
|
||||
|
||||
$subjectIdsJson = !empty($data['subject_ids']) ? json_encode(array_values($data['subject_ids'])) : null;
|
||||
|
||||
$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(':subject_ids', $subjectIdsJson, $subjectIdsJson !== null ? 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);
|
||||
|
||||
@ -335,12 +335,12 @@ function ensure_default_school_cycle_record(PDO $pdo, array $application): int
|
||||
}
|
||||
|
||||
|
||||
function update_teacher_in_cycle(int $id, int $centerApplicationId, int $cycleId, array $data): void
|
||||
function update_teacher_in_cycle(int $centerApplicationId, int $cycleId, int $id, array $data): void
|
||||
{
|
||||
$pdo = db_connection();
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE school_teachers SET
|
||||
full_name = :full_name, role_title = :role_title, specialization = :specialization,
|
||||
full_name = :full_name, role_title = :role_title, specialization = :specialization, subject_ids = :subject_ids,
|
||||
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'
|
||||
@ -351,11 +351,12 @@ function update_teacher_in_cycle(int $id, int $centerApplicationId, int $cycleId
|
||||
':cycle_id' => $cycleId,
|
||||
':full_name' => $data['full_name'],
|
||||
':role_title' => $data['role_title'],
|
||||
':specialization' => $data['specialization'],
|
||||
':phone' => $data['phone'],
|
||||
':email' => $data['email'],
|
||||
':specialization' => $data['specialization'] !== '' ? $data['specialization'] : null,
|
||||
':subject_ids' => !empty($data['subject_ids']) ? json_encode(array_values($data['subject_ids'])) : null,
|
||||
':phone' => $data['phone'] !== '' ? $data['phone'] : null,
|
||||
':email' => $data['email'] !== '' ? $data['email'] : null,
|
||||
':employment_status' => $data['employment_status'],
|
||||
':notes' => $data['notes'],
|
||||
':notes' => $data['notes'] !== '' ? $data['notes'] : null,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -420,12 +421,12 @@ function copy_school_cycle_rollover(PDO $pdo, int $centerApplicationId, int $sou
|
||||
if (!empty($rollover['copy_teachers'])) {
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO school_teachers (
|
||||
center_application_id, cycle_id, full_name, role_title, specialization,
|
||||
center_application_id, cycle_id, full_name, role_title, specialization, subject_ids,
|
||||
phone, email, employment_status, notes,
|
||||
created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
center_application_id, :target_cycle_id, full_name, role_title, specialization,
|
||||
center_application_id, :target_cycle_id, full_name, role_title, specialization, subject_ids,
|
||||
phone, email, employment_status, notes,
|
||||
NOW(), NOW()
|
||||
FROM school_teachers
|
||||
@ -816,11 +817,11 @@ function create_teacher_in_cycle(int $centerApplicationId, int $cycleId, array $
|
||||
$pdo = db_connection();
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO school_teachers (
|
||||
center_application_id, cycle_id, full_name, role_title, specialization,
|
||||
center_application_id, cycle_id, full_name, role_title, specialization, subject_ids,
|
||||
phone, email, employment_status, notes,
|
||||
created_at, updated_at
|
||||
) VALUES (
|
||||
:center_application_id, :cycle_id, :full_name, :role_title, :specialization,
|
||||
:center_application_id, :cycle_id, :full_name, :role_title, :specialization, :subject_ids,
|
||||
:phone, :email, :employment_status, :notes,
|
||||
NOW(), NOW()
|
||||
)'
|
||||
@ -831,6 +832,7 @@ function create_teacher_in_cycle(int $centerApplicationId, int $cycleId, array $
|
||||
':full_name' => $data['full_name'],
|
||||
':role_title' => $data['role_title'],
|
||||
':specialization' => $data['specialization'] !== '' ? $data['specialization'] : null,
|
||||
':subject_ids' => !empty($data['subject_ids']) ? json_encode(array_values($data['subject_ids'])) : null,
|
||||
':phone' => $data['phone'] !== '' ? $data['phone'] : null,
|
||||
':email' => $data['email'] !== '' ? $data['email'] : null,
|
||||
':employment_status' => $data['employment_status'],
|
||||
@ -1843,6 +1845,218 @@ function school_student_options_by_cycle(int $centerApplicationId, int $cycleId)
|
||||
return $options;
|
||||
}
|
||||
|
||||
function school_student_record_by_cycle(int $centerApplicationId, int $cycleId, int $studentId): ?array
|
||||
{
|
||||
if ($studentId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pdo = db_connection();
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT *
|
||||
FROM school_students
|
||||
WHERE center_application_id = :center_application_id
|
||||
AND cycle_id = :cycle_id
|
||||
AND id = :student_id
|
||||
LIMIT 1'
|
||||
);
|
||||
$stmt->execute([
|
||||
':center_application_id' => $centerApplicationId,
|
||||
':cycle_id' => $cycleId,
|
||||
':student_id' => $studentId,
|
||||
]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
function student_certificate_performance_meta(float $overallPercentage): array
|
||||
{
|
||||
if ($overallPercentage >= 90) {
|
||||
return ['key' => 'excellent', 'label' => 'Excellent', 'label_ar' => 'ممتاز'];
|
||||
}
|
||||
|
||||
if ($overallPercentage >= 80) {
|
||||
return ['key' => 'very_good', 'label' => 'Very Good', 'label_ar' => 'جيد جداً'];
|
||||
}
|
||||
|
||||
if ($overallPercentage >= 65) {
|
||||
return ['key' => 'good', 'label' => 'Good', 'label_ar' => 'جيد'];
|
||||
}
|
||||
|
||||
return ['key' => 'poor', 'label' => 'Poor', 'label_ar' => 'ضعيف'];
|
||||
}
|
||||
|
||||
function student_completion_certificate_honor_meta(float $overallPercentage): array
|
||||
{
|
||||
$performance = student_certificate_performance_meta($overallPercentage);
|
||||
|
||||
return match ($performance['key']) {
|
||||
'excellent' => [
|
||||
'key' => 'highest_honors',
|
||||
'title' => 'With Highest Honors',
|
||||
'title_ar' => 'بمرتبة الشرف العليا',
|
||||
'completion_line_ar' => 'أتمّ/أتمّت الدورة بتميز استثنائي واستحقاق رفيع.',
|
||||
],
|
||||
'very_good' => [
|
||||
'key' => 'honors',
|
||||
'title' => 'With Honors',
|
||||
'title_ar' => 'بمرتبة الشرف',
|
||||
'completion_line_ar' => 'أتمّ/أتمّت الدورة بمستوى قوي يبعث على الفخر.',
|
||||
],
|
||||
'good' => [
|
||||
'key' => 'merit',
|
||||
'title' => 'With Merit',
|
||||
'title_ar' => 'بتقدير جيد',
|
||||
'completion_line_ar' => 'أتمّ/أتمّت الدورة بنجاح وأظهر/أظهرت التزاماً جيداً.',
|
||||
],
|
||||
default => [
|
||||
'key' => 'completion',
|
||||
'title' => 'Successful Completion',
|
||||
'title_ar' => 'بإتمام ناجح',
|
||||
'completion_line_ar' => 'أتمّ/أتمّت الدورة واستوفى/استوفت متطلباتها الأساسية.',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function school_student_certificate_summary(int $centerApplicationId, int $cycleId, int $studentId): array
|
||||
{
|
||||
$student = school_student_record_by_cycle($centerApplicationId, $cycleId, $studentId);
|
||||
$summary = [
|
||||
'student' => $student,
|
||||
'assessments' => [],
|
||||
'has_results' => false,
|
||||
'completed_assessments' => 0,
|
||||
'active_assessments' => 0,
|
||||
'missing_assessments' => 0,
|
||||
'absent_assessments' => 0,
|
||||
'excused_assessments' => 0,
|
||||
'overall_percentage' => 0.0,
|
||||
'score_total' => 0.0,
|
||||
'max_score_total' => 0.0,
|
||||
'latest_assessed_on' => '',
|
||||
'performance' => student_certificate_performance_meta(0.0),
|
||||
];
|
||||
|
||||
if ($student === null) {
|
||||
return $summary;
|
||||
}
|
||||
|
||||
$pdo = db_connection();
|
||||
$activeAssessmentsStmt = $pdo->prepare(
|
||||
'SELECT COUNT(*)
|
||||
FROM school_assessment_types
|
||||
WHERE center_application_id = :center_application_id
|
||||
AND cycle_id = :cycle_id
|
||||
AND is_active = 1'
|
||||
);
|
||||
$activeAssessmentsStmt->execute([
|
||||
':center_application_id' => $centerApplicationId,
|
||||
':cycle_id' => $cycleId,
|
||||
]);
|
||||
$summary['active_assessments'] = (int) $activeAssessmentsStmt->fetchColumn();
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT
|
||||
scores.assessment_type_id,
|
||||
scores.score,
|
||||
scores.max_score,
|
||||
scores.status,
|
||||
scores.notes,
|
||||
scores.assessed_on,
|
||||
assessments.title,
|
||||
assessments.category,
|
||||
assessments.weight_percentage,
|
||||
assessments.is_active,
|
||||
assessments.subject_id
|
||||
FROM school_assessment_scores scores
|
||||
INNER JOIN school_assessment_types assessments ON assessments.id = scores.assessment_type_id
|
||||
WHERE scores.center_application_id = :center_application_id
|
||||
AND scores.cycle_id = :cycle_id
|
||||
AND scores.student_id = :student_id
|
||||
AND assessments.is_active = 1
|
||||
ORDER BY scores.assessed_on ASC, assessments.weight_percentage DESC, assessments.id ASC'
|
||||
);
|
||||
$stmt->execute([
|
||||
':center_application_id' => $centerApplicationId,
|
||||
':cycle_id' => $cycleId,
|
||||
':student_id' => $studentId,
|
||||
]);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
$weightedScore = 0.0;
|
||||
$weightedTotal = 0.0;
|
||||
$plainPercentageSum = 0.0;
|
||||
$plainPercentageCount = 0;
|
||||
$scoreTotal = 0.0;
|
||||
$maxScoreTotal = 0.0;
|
||||
$latestAssessedOn = '';
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$status = (string) ($row['status'] ?? '');
|
||||
if ($status === 'absent') {
|
||||
$summary['absent_assessments']++;
|
||||
continue;
|
||||
}
|
||||
if ($status === 'excused') {
|
||||
$summary['excused_assessments']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$score = isset($row['score']) ? (float) $row['score'] : null;
|
||||
$maxScore = (float) ($row['max_score'] ?? 0);
|
||||
if ($status !== 'present' || $score === null || $maxScore <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$percentage = round(($score / $maxScore) * 100, 2);
|
||||
$weight = max(0.0, (float) ($row['weight_percentage'] ?? 0));
|
||||
if ($weight > 0) {
|
||||
$weightedScore += $percentage * $weight;
|
||||
$weightedTotal += $weight;
|
||||
}
|
||||
$plainPercentageSum += $percentage;
|
||||
$plainPercentageCount++;
|
||||
$scoreTotal += $score;
|
||||
$maxScoreTotal += $maxScore;
|
||||
|
||||
$assessedOn = (string) ($row['assessed_on'] ?? '');
|
||||
if ($assessedOn !== '' && ($latestAssessedOn === '' || strtotime($assessedOn) > strtotime($latestAssessedOn))) {
|
||||
$latestAssessedOn = $assessedOn;
|
||||
}
|
||||
|
||||
$summary['assessments'][] = [
|
||||
'assessment_type_id' => (int) ($row['assessment_type_id'] ?? 0),
|
||||
'title' => (string) ($row['title'] ?? 'تقييم'),
|
||||
'category' => (string) ($row['category'] ?? ''),
|
||||
'weight_percentage' => $weight,
|
||||
'score' => round($score, 2),
|
||||
'max_score' => round($maxScore, 2),
|
||||
'percentage' => $percentage,
|
||||
'assessed_on' => $assessedOn,
|
||||
'notes' => (string) ($row['notes'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$overallPercentage = 0.0;
|
||||
if ($weightedTotal > 0) {
|
||||
$overallPercentage = round($weightedScore / $weightedTotal, 2);
|
||||
} elseif ($plainPercentageCount > 0) {
|
||||
$overallPercentage = round($plainPercentageSum / $plainPercentageCount, 2);
|
||||
}
|
||||
|
||||
$summary['completed_assessments'] = $plainPercentageCount;
|
||||
$summary['missing_assessments'] = max(0, $summary['active_assessments'] - $summary['completed_assessments'] - $summary['absent_assessments'] - $summary['excused_assessments']);
|
||||
$summary['has_results'] = $plainPercentageCount > 0;
|
||||
$summary['overall_percentage'] = $overallPercentage;
|
||||
$summary['score_total'] = round($scoreTotal, 2);
|
||||
$summary['max_score_total'] = round($maxScoreTotal, 2);
|
||||
$summary['latest_assessed_on'] = $latestAssessedOn;
|
||||
$summary['performance'] = student_certificate_performance_meta($overallPercentage);
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
function validate_attendance_input_for_cycle(int $centerApplicationId, int $cycleId, array $input): array
|
||||
{
|
||||
$data = attendance_defaults();
|
||||
|
||||
@ -9,11 +9,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<div class="app-card mb-4">
|
||||
<div class="section-title mb-2">هيكل الصفحات والوحدات</div>
|
||||
|
||||
251
student_certificate.php
Normal file
251
student_certificate.php
Normal file
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
|
||||
function certificate_date_label(string $date): string
|
||||
{
|
||||
if ($date === '' || strtotime($date) === false) {
|
||||
return '—';
|
||||
}
|
||||
|
||||
return date('Y/m/d', strtotime($date));
|
||||
}
|
||||
|
||||
function certificate_number_value(int $value): string
|
||||
{
|
||||
return number_format($value);
|
||||
}
|
||||
|
||||
function certificate_decimal_value(float $value): string
|
||||
{
|
||||
return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
|
||||
}
|
||||
|
||||
$flash = consume_flash();
|
||||
$applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0;
|
||||
$requestedCycleId = filter_input(INPUT_GET, 'cycle', FILTER_VALIDATE_INT) ?: 0;
|
||||
$studentId = filter_input(INPUT_GET, 'student_id', FILTER_VALIDATE_INT) ?: 0;
|
||||
$application = $applicationId > 0 ? get_application($applicationId) : null;
|
||||
$isApprovedSchool = $application && (string) $application['status'] === 'approved';
|
||||
$cycleContext = ['cycles' => [], 'selected' => null, 'active' => null, 'read_only' => false];
|
||||
$selectedCycle = null;
|
||||
$selectedCycleId = 0;
|
||||
$cycleLabel = 'لا توجد دورة بعد';
|
||||
|
||||
if ($application && $isApprovedSchool) {
|
||||
$cycleContext = resolve_school_cycle_context((int) $application['id'], $application, $requestedCycleId);
|
||||
$selectedCycle = $cycleContext['selected'];
|
||||
$selectedCycleId = $selectedCycle ? (int) ($selectedCycle['id'] ?? 0) : 0;
|
||||
$cycleLabel = $selectedCycle ? (string) $selectedCycle['cycle_name'] : $cycleLabel;
|
||||
}
|
||||
|
||||
$certificate = $isApprovedSchool && $selectedCycleId > 0 && $studentId > 0
|
||||
? school_student_certificate_summary((int) $application['id'], $selectedCycleId, $studentId)
|
||||
: [
|
||||
'student' => null,
|
||||
'assessments' => [],
|
||||
'has_results' => false,
|
||||
'completed_assessments' => 0,
|
||||
'active_assessments' => 0,
|
||||
'missing_assessments' => 0,
|
||||
'absent_assessments' => 0,
|
||||
'excused_assessments' => 0,
|
||||
'overall_percentage' => 0.0,
|
||||
'score_total' => 0.0,
|
||||
'max_score_total' => 0.0,
|
||||
'latest_assessed_on' => '',
|
||||
'performance' => student_certificate_performance_meta(0.0),
|
||||
];
|
||||
$student = is_array($certificate['student'] ?? null) ? $certificate['student'] : null;
|
||||
|
||||
if (!$application || !$isApprovedSchool || $selectedCycleId <= 0 || $studentId <= 0 || $student === null) {
|
||||
http_response_code(404);
|
||||
}
|
||||
|
||||
$pageTitle = $student
|
||||
? 'شهادة الطالب: ' . (string) $student['full_name']
|
||||
: 'شهادة الإنجاز';
|
||||
$pageDescription = 'شهادة إنجاز قابلة للطباعة تعرض أداء الطالب الإجمالي بعد انتهاء الدورة، مع تصنيف الأداء النهائي وملخص التقييمات.';
|
||||
$studentsUrl = $application ? school_page_url('students.php', (int) $application['id'], $selectedCycleId) : 'students.php';
|
||||
$approvedSchoolUrl = $application ? school_page_url('approved_school.php', (int) $application['id'], $selectedCycleId) : 'approved_school.php';
|
||||
$performance = $certificate['performance'] ?? student_certificate_performance_meta(0.0);
|
||||
$issuedOn = date('Y-m-d');
|
||||
$coverageNote = '';
|
||||
if (($certificate['missing_assessments'] ?? 0) > 0) {
|
||||
$coverageNote = 'يعتمد هذا التقدير على التقييمات المرصودة حالياً فقط.';
|
||||
} elseif (($certificate['completed_assessments'] ?? 0) > 0) {
|
||||
$coverageNote = 'تم احتساب الأداء النهائي من متوسط التقييمات المرصودة في هذه الدورة.';
|
||||
}
|
||||
|
||||
render_page_start($pageTitle, 'approved', $pageDescription);
|
||||
render_flash($flash);
|
||||
?>
|
||||
<section class="student-certificate-page py-4 py-lg-5">
|
||||
<div class="container-xl">
|
||||
<div class="certificate-toolbar">
|
||||
<div>
|
||||
<div class="section-title">شهادة الإنجاز والمكافأة</div>
|
||||
<div class="section-subtle">صفحة قابلة للطباعة بعد إكمال تقييمات الطالب في الدورة الحالية.</div>
|
||||
</div>
|
||||
<div class="certificate-toolbar-actions">
|
||||
<a class="btn btn-outline-secondary" href="<?= e($studentsUrl) ?>">العودة إلى الطلاب</a>
|
||||
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>">صفحة المركز</a>
|
||||
<?php if ($student): ?>
|
||||
<button type="button" class="btn btn-primary" onclick="window.print()">طباعة الشهادة</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?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-primary" href="applications.php?status=approved">المراكز المعتمدة</a>
|
||||
</div>
|
||||
<?php elseif (!$isApprovedSchool): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
<div class="empty-title mb-2">الشهادة غير متاحة بعد</div>
|
||||
<p class="text-muted mb-0">يمكن إصدار شهادات الإنجاز فقط للمراكز المعتمدة.</p>
|
||||
</div>
|
||||
<?php elseif ($selectedCycleId <= 0 || !$student): ?>
|
||||
<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-primary" href="<?= e($studentsUrl) ?>">العودة إلى سجل الطلاب</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<article class="student-certificate-card" aria-labelledby="certificate-title">
|
||||
<div class="student-certificate-body">
|
||||
<span class="certificate-kicker">Certificate of Completion & Performance</span>
|
||||
<h1 class="certificate-title" id="certificate-title">شهادة تكريم وإتمام الدورة</h1>
|
||||
<p class="certificate-subtitle mb-0">
|
||||
تشهد إدارة <strong><?= e((string) $application['center_name']) ?></strong> بأن الطالب/الطالبة
|
||||
<strong><?= e((string) $student['full_name']) ?></strong>
|
||||
قد أتم/أتمت متطلبات الدورة <strong><?= e($cycleLabel) ?></strong>
|
||||
وتم تقييم أدائه/أدائها وفق السجلات المرصودة في النظام.
|
||||
</p>
|
||||
|
||||
<div class="certificate-student-name"><?= e((string) $student['full_name']) ?></div>
|
||||
<div class="section-subtle">Student Code: <?= e((string) $student['student_code']) ?> · <?= e((string) $student['grade_level']) ?></div>
|
||||
|
||||
<div class="certificate-meta-grid">
|
||||
<div class="certificate-meta-item">
|
||||
<strong>المركز</strong>
|
||||
<span><?= e((string) $application['center_name']) ?></span>
|
||||
</div>
|
||||
<div class="certificate-meta-item">
|
||||
<strong>الدورة</strong>
|
||||
<span><?= e($cycleLabel) ?></span>
|
||||
</div>
|
||||
<div class="certificate-meta-item">
|
||||
<strong>مدة الدورة</strong>
|
||||
<span><?= e(certificate_date_label((string) ($selectedCycle['start_date'] ?? ''))) ?> — <?= e(certificate_date_label((string) ($selectedCycle['end_date'] ?? ''))) ?></span>
|
||||
</div>
|
||||
<div class="certificate-meta-item">
|
||||
<strong>تاريخ الإصدار</strong>
|
||||
<span><?= e(certificate_date_label($issuedOn)) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="certificate-grid">
|
||||
<div class="certificate-panel">
|
||||
<strong class="mb-2">نص الشهادة</strong>
|
||||
<p class="mb-3">بناءً على حضور الطالب ومشاركته ونتائج تقييماته خلال هذه الدورة، تم منحه/منحها هذه الشهادة التقديرية مع بيان مستوى الأداء العام أدناه.</p>
|
||||
<div class="performance-pill performance-<?= e((string) ($performance['key'] ?? 'poor')) ?> mb-3">
|
||||
<?= e((string) ($performance['label_ar'] ?? 'ضعيف')) ?> — <?= e((string) ($performance['label'] ?? 'Poor')) ?>
|
||||
</div>
|
||||
<div class="certificate-stat-grid">
|
||||
<div class="certificate-stat">
|
||||
<strong><?= e(certificate_decimal_value((float) ($certificate['overall_percentage'] ?? 0))) ?>%</strong>
|
||||
<span>متوسط الأداء النهائي</span>
|
||||
</div>
|
||||
<div class="certificate-stat">
|
||||
<strong><?= e(certificate_number_value((int) ($certificate['completed_assessments'] ?? 0))) ?></strong>
|
||||
<span>تقييمات مكتملة</span>
|
||||
</div>
|
||||
<div class="certificate-stat">
|
||||
<strong><?= e(certificate_decimal_value((float) ($certificate['score_total'] ?? 0))) ?>/<?= e(certificate_decimal_value((float) ($certificate['max_score_total'] ?? 0))) ?></strong>
|
||||
<span>إجمالي الدرجات المرصودة</span>
|
||||
</div>
|
||||
<div class="certificate-stat">
|
||||
<strong><?= e(certificate_date_label((string) ($certificate['latest_assessed_on'] ?? ''))) ?></strong>
|
||||
<span>آخر تقييم مسجل</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($coverageNote !== ''): ?>
|
||||
<div class="alert alert-light border mt-3 mb-0"><?= e($coverageNote) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<aside class="certificate-panel">
|
||||
<strong class="mb-2">ملخص الأداء</strong>
|
||||
<div class="summary-stack">
|
||||
<div class="summary-row"><span>التقييمات النشطة</span><strong><?= e(certificate_number_value((int) ($certificate['active_assessments'] ?? 0))) ?></strong></div>
|
||||
<div class="summary-row"><span>المكتملة</span><strong><?= e(certificate_number_value((int) ($certificate['completed_assessments'] ?? 0))) ?></strong></div>
|
||||
<div class="summary-row"><span>الغياب</span><strong><?= e(certificate_number_value((int) ($certificate['absent_assessments'] ?? 0))) ?></strong></div>
|
||||
<div class="summary-row"><span>بعذر</span><strong><?= e(certificate_number_value((int) ($certificate['excused_assessments'] ?? 0))) ?></strong></div>
|
||||
<div class="summary-row"><span>غير المرصود بعد</span><strong><?= e(certificate_number_value((int) ($certificate['missing_assessments'] ?? 0))) ?></strong></div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($certificate['assessments'])): ?>
|
||||
<div class="certificate-panel mt-4">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||||
<strong class="mb-0">تفصيل التقييمات</strong>
|
||||
<span class="section-subtle">Overall performance is calculated from the recorded assessments in this course.</span>
|
||||
</div>
|
||||
<div class="certificate-table-wrap">
|
||||
<table class="certificate-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>التقييم</th>
|
||||
<th>الفئة</th>
|
||||
<th>الدرجة</th>
|
||||
<th>النسبة</th>
|
||||
<th>الوزن</th>
|
||||
<th>التاريخ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($certificate['assessments'] as $assessment): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><?= e((string) ($assessment['title'] ?? 'تقييم')) ?></strong>
|
||||
<?php if (!empty($assessment['notes'])): ?><small><?= e((string) $assessment['notes']) ?></small><?php endif; ?>
|
||||
</td>
|
||||
<td><?= e((string) ($assessment['category'] ?? '—')) ?></td>
|
||||
<td><?= e(certificate_decimal_value((float) ($assessment['score'] ?? 0))) ?> / <?= e(certificate_decimal_value((float) ($assessment['max_score'] ?? 0))) ?></td>
|
||||
<td><?= e(certificate_decimal_value((float) ($assessment['percentage'] ?? 0))) ?>%</td>
|
||||
<td><?= e(certificate_decimal_value((float) ($assessment['weight_percentage'] ?? 0))) ?>%</td>
|
||||
<td><?= e(certificate_date_label((string) ($assessment['assessed_on'] ?? ''))) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="certificate-panel certificate-empty-state mt-4">
|
||||
<div class="empty-title mb-2">لا توجد تقييمات مرصودة بعد</div>
|
||||
<p class="text-muted mb-3">أدخل درجات الطالب أولاً من صفحة رصد الدرجات، ثم افتح الشهادة من جديد لإظهار الأداء العام.</p>
|
||||
<a class="btn btn-outline-secondary" href="<?= e($studentsUrl) ?>">العودة إلى الطلاب</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="certificate-signature-row">
|
||||
<div class="certificate-signature">
|
||||
<strong>مدير/ة المركز</strong>
|
||||
<span><?= e((string) ($application['director_name'] ?? '')) ?></span>
|
||||
</div>
|
||||
<div class="certificate-signature">
|
||||
<strong>اعتماد الأداء النهائي</strong>
|
||||
<span><?= e((string) ($performance['label_ar'] ?? 'ضعيف')) ?> — <?= e((string) ($performance['label'] ?? 'Poor')) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_page_end();
|
||||
251
student_completion_certificate.php
Normal file
251
student_completion_certificate.php
Normal file
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
|
||||
function completion_certificate_date_label(string $date): string
|
||||
{
|
||||
if ($date === '' || strtotime($date) === false) {
|
||||
return date('Y/m/d');
|
||||
}
|
||||
|
||||
return date('Y/m/d', strtotime($date));
|
||||
}
|
||||
|
||||
function completion_certificate_decimal_value(float $value): string
|
||||
{
|
||||
return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
|
||||
}
|
||||
|
||||
function completion_certificate_message_text(string $template, array $tokens, string $fallback): string
|
||||
{
|
||||
$message = trim($template);
|
||||
if ($message === '') {
|
||||
$message = $fallback;
|
||||
}
|
||||
|
||||
return strtr($message, $tokens);
|
||||
}
|
||||
|
||||
$flash = consume_flash();
|
||||
$settings = get_app_settings();
|
||||
$applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0;
|
||||
$requestedCycleId = filter_input(INPUT_GET, 'cycle', FILTER_VALIDATE_INT) ?: 0;
|
||||
$studentId = filter_input(INPUT_GET, 'student_id', FILTER_VALIDATE_INT) ?: 0;
|
||||
$application = $applicationId > 0 ? get_application($applicationId) : null;
|
||||
$isApprovedSchool = $application && (string) $application['status'] === 'approved';
|
||||
$cycleContext = ['cycles' => [], 'selected' => null, 'active' => null, 'read_only' => false];
|
||||
$selectedCycle = null;
|
||||
$selectedCycleId = 0;
|
||||
$cycleLabel = 'الدورة الحالية';
|
||||
|
||||
if ($application && $isApprovedSchool) {
|
||||
$cycleContext = resolve_school_cycle_context((int) $application['id'], $application, $requestedCycleId);
|
||||
$selectedCycle = $cycleContext['selected'];
|
||||
$selectedCycleId = $selectedCycle ? (int) ($selectedCycle['id'] ?? 0) : 0;
|
||||
$cycleLabel = $selectedCycle ? (string) ($selectedCycle['cycle_name'] ?? 'الدورة الحالية') : $cycleLabel;
|
||||
}
|
||||
|
||||
$certificate = $isApprovedSchool && $selectedCycleId > 0 && $studentId > 0
|
||||
? school_student_certificate_summary((int) $application['id'], $selectedCycleId, $studentId)
|
||||
: [
|
||||
'student' => null,
|
||||
'assessments' => [],
|
||||
'has_results' => false,
|
||||
'completed_assessments' => 0,
|
||||
'active_assessments' => 0,
|
||||
'missing_assessments' => 0,
|
||||
'absent_assessments' => 0,
|
||||
'excused_assessments' => 0,
|
||||
'overall_percentage' => 0.0,
|
||||
'score_total' => 0.0,
|
||||
'max_score_total' => 0.0,
|
||||
'latest_assessed_on' => '',
|
||||
'performance' => student_certificate_performance_meta(0.0),
|
||||
];
|
||||
$student = is_array($certificate['student'] ?? null) ? $certificate['student'] : null;
|
||||
|
||||
if (!$application || !$isApprovedSchool || $selectedCycleId <= 0 || $studentId <= 0 || $student === null) {
|
||||
http_response_code(404);
|
||||
}
|
||||
|
||||
$performance = $certificate['performance'] ?? student_certificate_performance_meta(0.0);
|
||||
$hasResults = !empty($certificate['has_results']);
|
||||
$honor = $hasResults
|
||||
? student_completion_certificate_honor_meta((float) ($certificate['overall_percentage'] ?? 0))
|
||||
: [
|
||||
'key' => 'completion',
|
||||
'title' => 'Successful Completion',
|
||||
'title_ar' => 'إتمام ناجح',
|
||||
'completion_line_ar' => 'أتمّ/أتمّت الدورة بنجاح واستفاد/استفادت من محتواها العلمي.',
|
||||
];
|
||||
$template = (string) ($settings['completion_certificate_template'] ?? 'modern');
|
||||
if (!in_array($template, ['modern', 'classic'], true)) {
|
||||
$template = 'modern';
|
||||
}
|
||||
|
||||
$centerName = trim((string) ($application['center_name'] ?? ''));
|
||||
if ($centerName === '') {
|
||||
$centerName = (string) ($settings['app_name'] ?? project_name());
|
||||
}
|
||||
$centerLogo = trim((string) ($application['logo'] ?? ''));
|
||||
if ($centerLogo === '') {
|
||||
$centerLogo = trim((string) ($settings['app_logo'] ?? ''));
|
||||
}
|
||||
$tagline = trim((string) ($settings['completion_certificate_tagline'] ?? ''));
|
||||
if ($tagline === '') {
|
||||
$tagline = 'شهادة إتمام وتكريم';
|
||||
}
|
||||
|
||||
$overallPercentage = (float) ($certificate['overall_percentage'] ?? 0);
|
||||
$percentageLabel = $hasResults ? completion_certificate_decimal_value($overallPercentage) . '%' : '—';
|
||||
$fallbackMessage = sprintf(
|
||||
'تشهد إدارة %s بأن الطالب/الطالبة %s قد أتمّ/أتمّت متطلبات %s %s.',
|
||||
$centerName,
|
||||
(string) ($student['full_name'] ?? 'الطالب/الطالبة'),
|
||||
$cycleLabel,
|
||||
$honor['title_ar']
|
||||
);
|
||||
|
||||
$bodyMessage = completion_certificate_message_text(
|
||||
(string) ($settings['completion_certificate_message'] ?? ''),
|
||||
[
|
||||
'{student}' => (string) ($student['full_name'] ?? ''),
|
||||
'{center}' => $centerName,
|
||||
'{cycle}' => $cycleLabel,
|
||||
'{performance}' => (string) ($performance['label_ar'] ?? 'مميز'),
|
||||
'{honor}' => (string) ($honor['title_ar'] ?? 'بنجاح'),
|
||||
'{percentage}' => $hasResults ? completion_certificate_decimal_value($overallPercentage) : '0',
|
||||
],
|
||||
$fallbackMessage
|
||||
);
|
||||
|
||||
$issueDateLabel = completion_certificate_date_label((string) ($certificate['latest_assessed_on'] ?? ''));
|
||||
$studentsUrl = $application ? school_page_url('students.php', (int) $application['id'], $selectedCycleId) : 'students.php';
|
||||
$detailedCertificateUrl = $application ? school_page_url('student_certificate.php', (int) $application['id'], $selectedCycleId) . '&student_id=' . urlencode((string) $studentId) : 'student_certificate.php';
|
||||
$pageTitle = $student ? 'شهادة إتمام الدورة: ' . (string) $student['full_name'] : 'شهادة إتمام الدورة';
|
||||
$pageDescription = 'شهادة إتمام وتكريم قابلة للطباعة تعرض اسم المركز وشعاره ونتيجة الطالب النهائية بتصميم جميل ومختصر.';
|
||||
|
||||
render_page_start($pageTitle, 'approved', $pageDescription);
|
||||
render_flash($flash);
|
||||
?>
|
||||
<section class="student-certificate-page completion-certificate-page py-4 py-lg-5">
|
||||
<div class="container-xl">
|
||||
<div class="certificate-toolbar">
|
||||
<div>
|
||||
<div class="section-title">شهادة الإتمام والتكريم</div>
|
||||
<div class="section-subtle">نسخة مختصرة وأنيقة للطباعة، مستقلة عن كشف الدرجات التفصيلي.</div>
|
||||
</div>
|
||||
<div class="certificate-toolbar-actions">
|
||||
<a class="btn btn-outline-secondary" href="<?= e($studentsUrl) ?>">العودة إلى الطلاب</a>
|
||||
<?php if ($student): ?>
|
||||
<a class="btn btn-outline-secondary" href="<?= e($detailedCertificateUrl) ?>" target="_blank" rel="noopener">كشف الأداء</a>
|
||||
<button type="button" class="btn btn-primary" onclick="window.print()">طباعة الشهادة</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?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-primary" href="applications.php?status=approved">المراكز المعتمدة</a>
|
||||
</div>
|
||||
<?php elseif (!$isApprovedSchool): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
<div class="empty-title mb-2">الشهادة غير متاحة حالياً</div>
|
||||
<p class="text-muted mb-0">يمكن إصدار شهادة الإتمام للمراكز المعتمدة فقط.</p>
|
||||
</div>
|
||||
<?php elseif ($selectedCycleId <= 0 || !$student): ?>
|
||||
<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-primary" href="<?= e($studentsUrl) ?>">العودة إلى سجل الطلاب</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<article class="completion-certificate-card completion-template-<?= e($template) ?>" aria-labelledby="completion-certificate-title">
|
||||
<div class="completion-certificate-layer completion-certificate-layer-one" aria-hidden="true"></div>
|
||||
<div class="completion-certificate-layer completion-certificate-layer-two" aria-hidden="true"></div>
|
||||
|
||||
<div class="completion-certificate-inner">
|
||||
<header class="completion-certificate-header">
|
||||
<div class="completion-branding">
|
||||
<?php if ($centerLogo !== ''): ?>
|
||||
<img src="<?= e(asset_url($centerLogo)) ?>" alt="شعار <?= e($centerName) ?>" class="completion-brand-logo" width="96" height="96">
|
||||
<?php else: ?>
|
||||
<div class="completion-brand-logo completion-brand-badge" aria-hidden="true"><?= e(mb_substr($centerName, 0, 1)) ?></div>
|
||||
<?php endif; ?>
|
||||
<div>
|
||||
<div class="completion-overline">Certificate of Completion</div>
|
||||
<h1 id="completion-certificate-title" class="completion-certificate-title"><?= e($tagline) ?></h1>
|
||||
<p class="completion-certificate-center mb-0"><?= e($centerName) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="completion-certificate-stamps">
|
||||
<span class="completion-stamp"><?= e($cycleLabel) ?></span>
|
||||
<span class="completion-stamp completion-stamp-accent"><?= e((string) ($honor['title_ar'] ?? 'بنجاح')) ?></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="completion-certificate-content">
|
||||
<p class="completion-intro mb-2">تُمنح هذه الشهادة إلى</p>
|
||||
<div class="completion-student-name"><?= e((string) ($student['full_name'] ?? '')) ?></div>
|
||||
<?php if (!empty($student['student_code'])): ?>
|
||||
<div class="completion-student-code">رقم الطالب: <?= e((string) $student['student_code']) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<p class="completion-message"><?= e($bodyMessage) ?></p>
|
||||
<p class="completion-honor-line mb-0"><?= e((string) ($honor['completion_line_ar'] ?? '')) ?></p>
|
||||
</div>
|
||||
|
||||
<div class="completion-certificate-grid">
|
||||
<section class="completion-summary-card">
|
||||
<span class="completion-summary-label">التقدير النهائي</span>
|
||||
<div class="performance-pill performance-<?= e((string) ($performance['key'] ?? 'good')) ?> completion-performance-pill">
|
||||
<?= e((string) ($performance['label_ar'] ?? 'مميز')) ?>
|
||||
</div>
|
||||
<div class="completion-honor-title"><?= e((string) ($honor['title_ar'] ?? 'بنجاح')) ?></div>
|
||||
<div class="completion-honor-subtitle"><?= e((string) ($honor['title'] ?? 'Successful Completion')) ?></div>
|
||||
</section>
|
||||
|
||||
<section class="completion-meta-grid" aria-label="بيانات الشهادة">
|
||||
<div class="completion-meta-item">
|
||||
<strong>الدورة</strong>
|
||||
<span><?= e($cycleLabel) ?></span>
|
||||
</div>
|
||||
<div class="completion-meta-item">
|
||||
<strong>تاريخ الإصدار</strong>
|
||||
<span><?= e($issueDateLabel) ?></span>
|
||||
</div>
|
||||
<div class="completion-meta-item">
|
||||
<strong>نسبة الأداء</strong>
|
||||
<span><?= e($percentageLabel) ?></span>
|
||||
</div>
|
||||
<div class="completion-meta-item">
|
||||
<strong>التقييمات المكتملة</strong>
|
||||
<span><?= e((string) ((int) ($certificate['completed_assessments'] ?? 0))) ?></span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<?php if (!$hasResults): ?>
|
||||
<div class="completion-note">
|
||||
لا توجد تقييمات مرصودة لهذا الطالب بعد، لذلك تم إصدار شهادة إتمام مختصرة بدون مرتبة أداء رقمية.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<footer class="completion-signature-row">
|
||||
<div class="completion-signature-block">
|
||||
<strong>مدير/ة المركز</strong>
|
||||
<span><?= e((string) ($application['director_name'] ?? $centerName)) ?></span>
|
||||
</div>
|
||||
<div class="completion-signature-block">
|
||||
<strong>اعتماد الشهادة</strong>
|
||||
<span><?= e((string) ($honor['title_ar'] ?? 'إتمام ناجح')) ?></span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_page_end(); ?>
|
||||
58
students.php
58
students.php
@ -113,11 +113,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<?php if (!$application): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
@ -294,7 +294,7 @@ render_flash($flash);
|
||||
<th>ولي الأمر</th>
|
||||
<th>الهاتف</th>
|
||||
<th>الحالة</th>
|
||||
<?php if (!$isCycleReadOnly): ?><th>إجراءات</th><?php endif; ?>
|
||||
<th>إجراءات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -312,14 +312,48 @@ render_flash($flash);
|
||||
</td>
|
||||
<td><a href="tel:<?= e((string) $student['guardian_phone']) ?>" class="text-decoration-none text-primary"><?= e((string) $student['guardian_phone']) ?></a></td>
|
||||
<td><?= student_enrollment_status_badge((string) $student['enrollment_status']) ?></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>
|
||||
<?php $certificateUrl = school_page_url('student_certificate.php', (int) $application['id'], $selectedCycleId) . '&student_id=' . urlencode((string) ((int) $student['id'])); ?>
|
||||
<?php $completionCertificateUrl = school_page_url('student_completion_certificate.php', (int) $application['id'], $selectedCycleId) . '&student_id=' . urlencode((string) ((int) $student['id'])); ?>
|
||||
<td class="table-action-cell">
|
||||
<div class="table-icon-actions">
|
||||
<a
|
||||
href="<?= e($completionCertificateUrl) ?>"
|
||||
class="btn btn-sm btn-primary icon-action"
|
||||
title="شهادة الإتمام والتكريم"
|
||||
aria-label="شهادة الإتمام والتكريم"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M.173 5.184A1.75 1.75 0 0 1 1.87 4h12.26a1.75 1.75 0 0 1 1.697 2.184l-1.2 4.8A1.75 1.75 0 0 1 12.93 12H9.707l-1.354 2.03a.5.5 0 0 1-.706.123L6.293 12H3.07a1.75 1.75 0 0 1-1.697-1.016l-1.2-4.8ZM4.75 1.5a.75.75 0 0 1 1.5 0V3h-1.5V1.5Zm5 0a.75.75 0 0 1 1.5 0V3h-1.5V1.5ZM7 7.25a1 1 0 1 0 2 0 1 1 0 0 0-2 0Zm-2.5.5a.75.75 0 0 1 .75-.75h5.5a.75.75 0 0 1 0 1.5h-5.5a.75.75 0 0 1-.75-.75Z"/></svg>
|
||||
<span class="visually-hidden">شهادة الإتمام والتكريم</span>
|
||||
</a>
|
||||
<a
|
||||
href="<?= e($certificateUrl) ?>"
|
||||
class="btn btn-sm btn-outline-secondary icon-action"
|
||||
title="كشف الأداء التفصيلي"
|
||||
aria-label="كشف الأداء التفصيلي"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.669.864 8 0 6.331.864 4.5.5l-.864 1.669L2 4l.864 1.831L2.5 7.5l1.669.864L5 10l1.831-.864L8.5 10 11 7.5l-.364-1.669L12.5 4l-1.864-.831L9.669.864ZM8 1.153l1.214.629 1.332-.264.629 1.214 1.214.629-.264 1.332.629 1.214-1.214.629-.629 1.214-1.332-.264L8 8.847l-1.214.629-1.332.264-.629-1.214-1.214-.629.264-1.332-.629-1.214 1.214-.629.629-1.214 1.332.264L8 1.153Z"/><path d="M10 9.5v5.293l-2-1.2-2 1.2V9.5l.835-.418.754.15.411.205.411-.206.754-.15L10 9.5Z"/></svg>
|
||||
<span class="visually-hidden">كشف الأداء التفصيلي</span>
|
||||
</a>
|
||||
<?php if (!$isCycleReadOnly): ?>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary icon-action"
|
||||
data-bs-toggle="modal" data-bs-target="#studentModal"
|
||||
data-student='<?= htmlspecialchars(json_encode($student, JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8') ?>'
|
||||
onclick="editStudentModal(this)"
|
||||
title="تعديل بيانات الطالب"
|
||||
aria-label="تعديل بيانات الطالب"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M12.854.146a.5.5 0 0 1 .707 0l2.586 2.586a.5.5 0 0 1 0 .707l-9.793 9.793a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168L12.854.146ZM11.207 2 3 10.207V13h2.793L14 4.793 11.207 2Z"/></svg>
|
||||
<span class="visually-hidden">تعديل بيانات الطالب</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
@ -469,4 +503,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
|
||||
<?php render_page_end(); ?>
|
||||
<?php render_page_end(); ?>
|
||||
|
||||
@ -100,11 +100,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php require __DIR__ . '/includes/sidebar.php'; ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
<div class="app-card mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3">
|
||||
<div class="section-title mb-0">إدارة المواد الدراسية</div>
|
||||
|
||||
95
teachers.php
95
teachers.php
@ -23,10 +23,25 @@ if ($application && $isApprovedSchool) {
|
||||
$cycleLabel = $selectedCycle ? (string) $selectedCycle['cycle_name'] : $cycleLabel;
|
||||
}
|
||||
|
||||
$allEnabledSubjectMap = get_enabled_subject_map();
|
||||
$applicationSubjectIds = $application ? normalize_id_list($application['subjects'] ?? []) : [];
|
||||
$teacherSubjectMap = $allEnabledSubjectMap;
|
||||
if ($applicationSubjectIds !== [] && $allEnabledSubjectMap !== []) {
|
||||
$applicationSubjectLookup = array_fill_keys($applicationSubjectIds, true);
|
||||
$teacherSubjectMap = array_filter(
|
||||
$allEnabledSubjectMap,
|
||||
static fn (string $_label, int|string $subjectId): bool => isset($applicationSubjectLookup[(int) $subjectId]),
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
);
|
||||
}
|
||||
if ($teacherSubjectMap === []) {
|
||||
$teacherSubjectMap = $allEnabledSubjectMap;
|
||||
}
|
||||
|
||||
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);
|
||||
[$values, $errors] = validate_teacher_input($_POST, array_keys($teacherSubjectMap));
|
||||
|
||||
if (!$isApprovedSchool) {
|
||||
$errors['form'] = 'لا يمكن فتح صفحة المعلمين قبل اعتماد المركز.';
|
||||
@ -65,6 +80,11 @@ $limit = 15;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$teachers = $isApprovedSchool && $selectedCycleId > 0 ? list_school_teachers_by_cycle((int) $application['id'], $selectedCycleId, $filters, $limit, $offset) : [];
|
||||
foreach ($teachers as &$teacher) {
|
||||
$teacher['subject_ids'] = normalize_id_list($teacher['subject_ids'] ?? []);
|
||||
$teacher['subject_labels'] = teacher_subject_labels($teacher, $allEnabledSubjectMap);
|
||||
}
|
||||
unset($teacher);
|
||||
$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) : [
|
||||
@ -101,11 +121,11 @@ render_flash($flash);
|
||||
?>
|
||||
<section class="py-4 py-lg-5">
|
||||
<div class="container-xxl">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-3">
|
||||
<div class="admin-layout row g-4 align-items-start">
|
||||
<div class="col-lg-3 layout-sidebar-column">
|
||||
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-9 layout-content-column">
|
||||
|
||||
<?php if (!$application): ?>
|
||||
<div class="app-card text-center py-5">
|
||||
@ -272,6 +292,7 @@ render_flash($flash);
|
||||
<th>الاسم</th>
|
||||
<th>الدور</th>
|
||||
<th>التخصص</th>
|
||||
<th>المواد</th>
|
||||
<th>التواصل</th>
|
||||
<th>الحالة</th>
|
||||
<?php if (!$isCycleReadOnly): ?><th>إجراءات</th><?php endif; ?>
|
||||
@ -286,17 +307,38 @@ render_flash($flash);
|
||||
</td>
|
||||
<td><?= e((string) $teacher['role_title']) ?></td>
|
||||
<td><?= e((string) ($teacher['specialization'] ?: '—')) ?></td>
|
||||
<td>
|
||||
<?php if (!empty($teacher['subject_labels'])): ?>
|
||||
<div class="teacher-subject-badges">
|
||||
<?php foreach ($teacher['subject_labels'] as $subjectLabel): ?>
|
||||
<span class="teacher-subject-badge"><?= e((string) $subjectLabel) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<strong><?= e((string) ($teacher['phone'] ?: 'بدون هاتف')) ?></strong>
|
||||
<div class="text-muted small"><?= e((string) ($teacher['email'] ?: 'بدون بريد')) ?></div>
|
||||
</td>
|
||||
<td><?= teacher_employment_status_badge((string) $teacher['employment_status']) ?></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 class="table-action-cell">
|
||||
<div class="table-icon-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary icon-action"
|
||||
data-bs-toggle="modal" data-bs-target="#teacherModal"
|
||||
data-teacher='<?= htmlspecialchars(json_encode($teacher, JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8') ?>'
|
||||
onclick="editTeacherModal(this)"
|
||||
title="تعديل بيانات المعلم"
|
||||
aria-label="تعديل بيانات المعلم"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M12.854.146a.5.5 0 0 1 .707 0l2.586 2.586a.5.5 0 0 1 0 .707l-9.793 9.793a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168L12.854.146ZM11.207 2 3 10.207V13h2.793L14 4.793 11.207 2Z"/></svg>
|
||||
<span class="visually-hidden">تعديل بيانات المعلم</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
@ -359,6 +401,30 @@ render_flash($flash);
|
||||
</select>
|
||||
<?php if (isset($errors['employment_status'])): ?><div class="invalid-feedback"><?= e($errors['employment_status']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label d-block">المواد المسندة</label>
|
||||
<?php if ($teacherSubjectMap === []): ?>
|
||||
<div class="form-text text-muted">لا توجد مواد مفعّلة حالياً، لذلك لا يمكن ربط المعلم بمادة بعد.</div>
|
||||
<?php else: ?>
|
||||
<div class="teacher-subject-picker <?= isset($errors['subject_ids']) ? 'is-invalid' : '' ?>">
|
||||
<?php foreach ($teacherSubjectMap as $subjectId => $subjectName): ?>
|
||||
<label class="teacher-subject-option" for="teacher_subject_<?= e((string) $subjectId) ?>">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
name="subject_ids[]"
|
||||
value="<?= e((string) $subjectId) ?>"
|
||||
id="teacher_subject_<?= e((string) $subjectId) ?>"
|
||||
<?= in_array($subjectId, $values['subject_ids'], true) ? 'checked' : '' ?>
|
||||
>
|
||||
<span><?= e((string) $subjectName) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if (isset($errors['subject_ids'])): ?><div class="invalid-feedback d-block"><?= e($errors['subject_ids']) ?></div><?php endif; ?>
|
||||
<div class="form-text">يمكنك ربط المعلم بأكثر من مادة داخل هذه الدورة.</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">
|
||||
@ -386,6 +452,13 @@ render_flash($flash);
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function setTeacherSubjectSelection(subjectIds) {
|
||||
const selected = new Set((subjectIds || []).map(String));
|
||||
document.querySelectorAll('input[name="subject_ids[]"]').forEach((checkbox) => {
|
||||
checkbox.checked = selected.has(checkbox.value);
|
||||
});
|
||||
}
|
||||
|
||||
function resetTeacherModal() {
|
||||
document.getElementById('teacherModalLabel').innerText = 'إضافة عضو جديد للفريق';
|
||||
document.getElementById('formAction').value = 'add';
|
||||
@ -394,6 +467,7 @@ function resetTeacherModal() {
|
||||
const form = document.getElementById('teacherForm');
|
||||
form.reset();
|
||||
document.getElementById('employment_status').value = 'active';
|
||||
setTeacherSubjectSelection([]);
|
||||
}
|
||||
|
||||
function editTeacherModal(btn) {
|
||||
@ -409,6 +483,7 @@ function editTeacherModal(btn) {
|
||||
document.getElementById('phone').value = teacher.phone || '';
|
||||
document.getElementById('email').value = teacher.email || '';
|
||||
document.getElementById('notes').value = teacher.notes || '';
|
||||
setTeacherSubjectSelection(teacher.subject_ids || []);
|
||||
}
|
||||
|
||||
// Show modal if there are errors (from POST)
|
||||
@ -425,4 +500,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
|
||||
<?php render_page_end(); ?>
|
||||
<?php render_page_end(); ?>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user