update students reward

This commit is contained in:
Flatlogic Bot 2026-04-17 03:38:41 +00:00
parent 5849af849c
commit 11de03c65b
25 changed files with 1808 additions and 108 deletions

View File

@ -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">

View File

@ -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(); ?>

View File

@ -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">

View File

@ -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(); ?>

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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">

View File

@ -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>

View File

@ -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();

View File

@ -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(); ?>

View File

@ -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;

View File

@ -0,0 +1,2 @@
ALTER TABLE school_teachers
ADD COLUMN subject_ids TEXT NULL AFTER specialization;

View File

@ -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">

View File

@ -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);

View File

@ -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();

View File

@ -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
View 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();

View 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(); ?>

View File

@ -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(); ?>

View File

@ -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>

View File

@ -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(); ?>