440 lines
30 KiB
PHP
440 lines
30 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/includes/app.php';
|
|
|
|
$flash = consume_flash();
|
|
$applicationId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?: 0;
|
|
$requestedCycleId = filter_input(INPUT_GET, 'cycle', FILTER_VALIDATE_INT) ?: 0;
|
|
$requestedAssessmentId = filter_input(INPUT_GET, 'assessment_id', FILTER_VALIDATE_INT) ?: 0;
|
|
$application = $applicationId > 0 ? get_application($applicationId) : null;
|
|
$isApprovedSchool = $application && (string) $application['status'] === 'approved';
|
|
$errors = [];
|
|
$search = clean_text($_GET['search'] ?? '', 255);
|
|
$cycleContext = ['cycles' => [], 'selected' => null, 'active' => null, 'read_only' => false];
|
|
$selectedCycle = null;
|
|
$selectedCycleId = 0;
|
|
$isCycleReadOnly = false;
|
|
$cycleLabel = 'لا توجد دورة بعد';
|
|
$values = [
|
|
'assessment_type_id' => '',
|
|
'teacher_id' => '',
|
|
'assessed_on' => date('Y-m-d'),
|
|
'assessment_max_score' => 0.0,
|
|
'entries' => [],
|
|
];
|
|
|
|
if ($application && $isApprovedSchool) {
|
|
$cycleContext = resolve_school_cycle_context((int) $application['id'], $application, $requestedCycleId);
|
|
$selectedCycle = $cycleContext['selected'];
|
|
$selectedCycleId = $selectedCycle ? (int) ($selectedCycle['id'] ?? 0) : 0;
|
|
$isCycleReadOnly = (bool) $cycleContext['read_only'];
|
|
$cycleLabel = $selectedCycle ? (string) $selectedCycle['cycle_name'] : $cycleLabel;
|
|
}
|
|
|
|
$assessmentOptions = $isApprovedSchool && $selectedCycleId > 0 ? school_assessment_type_options_by_cycle((int) $application['id'], $selectedCycleId, true) : [];
|
|
$teacherOptions = $isApprovedSchool && $selectedCycleId > 0 ? school_teacher_options_by_cycle((int) $application['id'], $selectedCycleId, true) : [];
|
|
$studentFilters = ['enrollment_status' => 'active'];
|
|
$students = $isApprovedSchool && $selectedCycleId > 0 ? list_school_students_by_cycle((int) $application['id'], $selectedCycleId, $search, 0, 0, $studentFilters) : [];
|
|
|
|
$selectedAssessmentId = $requestedAssessmentId;
|
|
if ($selectedAssessmentId <= 0 && $assessmentOptions !== []) {
|
|
$keys = array_keys($assessmentOptions);
|
|
$selectedAssessmentId = (int) ($keys[0] ?? 0);
|
|
}
|
|
if ($selectedAssessmentId > 0) {
|
|
$values['assessment_type_id'] = (string) $selectedAssessmentId;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
|
|
if (!$isApprovedSchool) {
|
|
$errors['form'] = 'لا يمكن إدخال الدرجات قبل اعتماد المركز.';
|
|
} elseif ($selectedCycleId <= 0) {
|
|
$errors['form'] = 'يرجى إنشاء دورة موسمية أولاً من صفحة المركز.';
|
|
} elseif ($isCycleReadOnly) {
|
|
$errors['form'] = 'هذه الدورة مؤرشفة للقراءة فقط. افتح دورة جديدة أو اختر دورة نشطة لإدخال درجات جديدة.';
|
|
} else {
|
|
[$values, $errors, $selectedAssessmentMeta] = validate_assessment_scores_batch_input((int) $application['id'], $selectedCycleId, $_POST);
|
|
$selectedAssessmentId = (int) ($values['assessment_type_id'] ?? 0);
|
|
if ($errors === []) {
|
|
try {
|
|
$savedRows = save_assessment_scores_in_cycle((int) $application['id'], $selectedCycleId, $values);
|
|
set_flash('success', 'تم حفظ درجات ' . $savedRows . ' طالب/طالبة في هذا التقييم.');
|
|
header('Location: ' . school_page_url('assessment_scores.php', (int) $application['id'], $selectedCycleId) . '&assessment_id=' . urlencode((string) $selectedAssessmentId) . ($search !== '' ? '&search=' . urlencode($search) : ''));
|
|
exit;
|
|
} catch (Throwable $exception) {
|
|
$errors['form'] = 'تعذر حفظ الدرجات حالياً. يرجى المحاولة مرة أخرى.';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$selectedAssessment = $selectedAssessmentId > 0 ? ($assessmentOptions[$selectedAssessmentId] ?? null) : null;
|
|
$scoreMap = $isApprovedSchool && $selectedCycleId > 0 && $selectedAssessmentId > 0
|
|
? school_assessment_score_map_by_assessment((int) $application['id'], $selectedCycleId, $selectedAssessmentId)
|
|
: [];
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $scoreMap !== []) {
|
|
$firstRecord = reset($scoreMap);
|
|
if (is_array($firstRecord)) {
|
|
if (!empty($firstRecord['teacher_id'])) {
|
|
$values['teacher_id'] = (string) ((int) $firstRecord['teacher_id']);
|
|
}
|
|
if (!empty($firstRecord['assessed_on'])) {
|
|
$values['assessed_on'] = (string) $firstRecord['assessed_on'];
|
|
}
|
|
}
|
|
}
|
|
|
|
$assessmentMetrics = $isApprovedSchool && $selectedCycleId > 0 ? school_assessment_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
|
|
'total' => 0, 'active' => 0, 'inactive' => 0, 'total_weight' => 0.0, 'active_weight' => 0.0,
|
|
'average_max_score' => 0.0, 'percentage' => 0, 'points' => 0, 'rubric' => 0,
|
|
];
|
|
$studentMetrics = $isApprovedSchool && $selectedCycleId > 0 ? school_student_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
|
|
'total' => 0, 'boys' => 0, 'girls' => 0, 'active' => 0, 'waiting' => 0, 'withdrawn' => 0,
|
|
];
|
|
$teacherMetrics = $isApprovedSchool && $selectedCycleId > 0 ? school_teacher_metrics_by_cycle((int) $application['id'], $selectedCycleId) : [
|
|
'total' => 0, 'active' => 0, 'pending' => 0, 'inactive' => 0, 'teachers' => 0, 'supervisors' => 0,
|
|
];
|
|
$scoreMetrics = $isApprovedSchool && $selectedCycleId > 0 ? school_assessment_score_metrics_by_cycle((int) $application['id'], $selectedCycleId, $selectedAssessmentId) : [
|
|
'total' => 0, 'present' => 0, 'absent' => 0, 'excused' => 0, 'average_score' => 0.0, 'latest_date' => '',
|
|
];
|
|
|
|
$pageTitle = $application ? 'إدخال درجات الطلاب: ' . (string) $application['center_name'] . ($selectedCycle ? ' — ' . $cycleLabel : '') : 'إدخال درجات الطلاب';
|
|
$pageDescription = 'صفحة مستقلة تسمح للمعلم أو الإدارة الأكاديمية بإدخال درجات الطلاب لكل تقييم داخل دورة موسمية محددة.';
|
|
$approvedSchoolUrl = $application ? school_page_url('approved_school.php', (int) $application['id'], $selectedCycleId) : 'approved_school.php';
|
|
$studentsUrl = $application ? school_page_url('students.php', (int) $application['id'], $selectedCycleId) : 'students.php';
|
|
$teachersUrl = $application ? school_page_url('teachers.php', (int) $application['id'], $selectedCycleId) : 'teachers.php';
|
|
$assessmentsUrl = $application ? school_page_url('assessments.php', (int) $application['id'], $selectedCycleId) : 'assessments.php';
|
|
$attendanceUrl = $application ? school_page_url('attendance.php', (int) $application['id'], $selectedCycleId) : 'attendance.php';
|
|
$assessmentSwitchBaseUrl = $application ? school_page_url('assessment_scores.php', (int) $application['id'], $selectedCycleId) : 'assessment_scores.php';
|
|
$latestScoreDate = $scoreMetrics['latest_date'] !== '' ? $scoreMetrics['latest_date'] : 'لا يوجد';
|
|
$averageScoreDisplay = $selectedAssessment && $scoreMetrics['present'] > 0
|
|
? number_format((float) $scoreMetrics['average_score'], 2, '.', '') . ' / ' . rtrim(rtrim(number_format((float) $selectedAssessment['max_score'], 2, '.', ''), '0'), '.')
|
|
: 'لا يوجد';
|
|
|
|
if (!$application) {
|
|
http_response_code(404);
|
|
}
|
|
|
|
render_page_start($pageTitle, 'approved', $pageDescription, (string) ($application['favicon'] ?? ''));
|
|
render_flash($flash);
|
|
?>
|
|
<section class="py-4 py-lg-5">
|
|
<div class="container-xxl">
|
|
<div class="row g-4 align-items-start">
|
|
<div class="col-lg-3">
|
|
<?php if ($application) { require __DIR__ . '/includes/center_sidebar.php'; } else { require __DIR__ . '/includes/sidebar.php'; } ?>
|
|
</div>
|
|
<div class="col-lg-9">
|
|
|
|
<?php if (!$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="page-banner mb-4 mb-lg-5">
|
|
<div class="row g-4 align-items-center">
|
|
<div class="col-lg-8">
|
|
<span class="eyebrow mb-3">الدرجات تُفتح بعد الاعتماد</span>
|
|
<h1 class="page-title mb-3"><?= e((string) $application['center_name']) ?></h1>
|
|
<p class="page-copy mb-3">تم تجهيز صفحة إدخال الدرجات، لكنها لا تعمل إلا بعد اعتماد المركز وفتح الدورة الأكاديمية الخاصة به.</p>
|
|
<div class="cta-stack mt-4">
|
|
<a class="btn btn-primary" href="<?= e($approvedSchoolUrl) ?>">صفحة المركز</a>
|
|
<a class="btn btn-outline-secondary" href="application_detail.php?id=<?= e((string) $application['id']) ?>">ملف الاعتماد</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="page-banner approved-hero mb-4 mb-lg-5">
|
|
<div class="row g-4 align-items-start">
|
|
<div class="col-lg-8">
|
|
<span class="approved-kicker mb-3">خطوة التنفيذ بعد تصميم ورقة التقييم</span>
|
|
<h1 class="page-title mb-3">إدخال درجات الطلاب — <?= e((string) $application['center_name']) ?></h1>
|
|
<p class="page-copy mb-3">اختر التقييم، حدّد المعلّم إن رغبت، ثم أدخل درجات الطلاب مباشرة داخل نفس الدورة <strong><?= e($cycleLabel) ?></strong>. كل درجة تُحفَظ على مستوى الطالب والتقييم والموسم الحالي.</p>
|
|
<div class="hero-meta">
|
|
<span><?= e((string) $studentMetrics['active']) ?> طلاب نشطون</span>
|
|
<span><?= e((string) $assessmentMetrics['active']) ?> تقييمات مفعلة</span>
|
|
<span><?= e((string) $teacherMetrics['active']) ?> معلمين نشطين</span>
|
|
</div>
|
|
<div class="cta-stack mt-4">
|
|
<a class="btn btn-outline-secondary" href="<?= e($assessmentsUrl) ?>">إدارة التقييمات</a>
|
|
<a class="btn btn-outline-secondary" href="<?= e($attendanceUrl) ?>">سجلات الغياب</a>
|
|
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>">صفحة المركز</a>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4">
|
|
<div class="app-card approved-note h-100">
|
|
<div class="section-title mb-3">ملخص الدرجة الحالية</div>
|
|
<div class="summary-stack mb-3">
|
|
<div class="summary-row"><span>السجلات المحفوظة</span><strong><?= e((string) $scoreMetrics['total']) ?> سجل</strong></div>
|
|
<div class="summary-row"><span>درجات فعلية</span><strong><?= e((string) $scoreMetrics['present']) ?> طالب</strong></div>
|
|
<div class="summary-row"><span>آخر تحديث</span><strong><?= e($latestScoreDate) ?></strong></div>
|
|
</div>
|
|
<p class="section-subtle mb-0">المتوسط الحالي: <strong><?= e($averageScoreDisplay) ?></strong><?php if ($selectedAssessment): ?> ضمن التقييم <strong><?= e($selectedAssessment['label']) ?></strong><?php endif; ?>.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($selectedCycle): ?>
|
|
<?php $cycleStatusMap = school_cycle_status_map(); ?>
|
|
<div class="row g-4 mb-4 align-items-start">
|
|
<div class="col-lg-<?= is_super_admin() ? '7' : '12' ?>">
|
|
<div class="app-card h-100">
|
|
<div class="section-head mb-3">
|
|
<div>
|
|
<div class="section-title">الدورة الموسمية الحالية</div>
|
|
<div class="section-copy">كل الدرجات في هذه الصفحة مرتبطة بالدورة <strong><?= e($cycleLabel) ?></strong>. أرشفة الدورة تجعل الصفحة للقراءة فقط بدون خلط النتائج مع الموسم التالي.</div>
|
|
</div>
|
|
<?= school_cycle_status_badge((string) $selectedCycle['status']) ?>
|
|
</div>
|
|
<div class="row g-3">
|
|
<div class="col-md-4"><div class="school-data-item"><strong>اسم الدورة</strong><span><?= e($cycleLabel) ?></span></div></div>
|
|
<div class="col-md-4"><div class="school-data-item"><strong>الفترة</strong><span><?= e((string) $selectedCycle['start_date']) ?> → <?= e((string) $selectedCycle['end_date']) ?></span></div></div>
|
|
<div class="col-md-4"><div class="school-data-item"><strong>عدد الدورات</strong><span><?= e((string) count($cycleContext['cycles'])) ?> دورة للمركز</span></div></div>
|
|
</div>
|
|
<?php if ($isCycleReadOnly): ?>
|
|
<div class="alert alert-warning mt-3 mb-0">هذه الدورة مؤرشفة، لذلك تبقى صفحة إدخال الدرجات للقراءة فقط حالياً.</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php if (is_super_admin()): ?>
|
|
<div class="col-lg-5">
|
|
<div class="app-card sidebar-card h-100">
|
|
<div class="section-title mb-3">التبديل بين الدورات</div>
|
|
<p class="section-subtle mb-3">يمكنك فتح دفتر الدرجات لنفس المركز في أي موسم سابق أو حالي من هنا مباشرة.</p>
|
|
<div class="quick-link-stack">
|
|
<?php foreach ($cycleContext['cycles'] as $cycle): ?>
|
|
<?php
|
|
$isCurrentCycleLink = (int) $cycle['id'] === $selectedCycleId;
|
|
$isActiveCycleLink = (int) $cycle['id'] === (int) (($cycleContext['active']['id'] ?? 0));
|
|
$cycleStatusLabel = (string) ($cycleStatusMap[$cycle['status']]['label'] ?? 'غير معروف');
|
|
$cycleMetaLine = (string) $cycle['start_date'] . ' → ' . (string) $cycle['end_date'] . ' — ' . $cycleStatusLabel . ($isActiveCycleLink ? ' — النشطة حالياً' : '');
|
|
?>
|
|
<a class="quick-link-item <?= $isCurrentCycleLink ? 'is-current' : '' ?>" href="<?= e(school_page_url('assessment_scores.php', (int) $application['id'], (int) $cycle['id'])) ?><?= $selectedAssessmentId > 0 ? '&assessment_id=' . e((string) $selectedAssessmentId) : '' ?>">
|
|
<div>
|
|
<strong><?= e((string) $cycle['cycle_name']) ?><?= $isCurrentCycleLink ? ' — المعروضة الآن' : '' ?></strong>
|
|
<span><?= e($cycleMetaLine) ?></span>
|
|
</div>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="row g-4 align-items-start">
|
|
<div class="col-lg-4">
|
|
<div class="app-card sidebar-card mb-4">
|
|
<div class="section-title mb-3">اختيار التقييم والمعلّم</div>
|
|
<p class="section-subtle mb-3">اختر نوع التقييم أولاً، ثم أدخل الدرجات دفعة واحدة لكل الطلاب الظاهرين في القائمة.</p>
|
|
|
|
<?php if (isset($errors['form'])): ?>
|
|
<div class="alert alert-danger"><?= e($errors['form']) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($assessmentOptions === []): ?>
|
|
<div class="alert alert-warning mb-0">ابدأ أولاً من صفحة التقييمات لإضافة تقييم واحد على الأقل قبل إدخال درجات الطلاب.</div>
|
|
<?php elseif ($students === []): ?>
|
|
<div class="alert alert-warning mb-0">لا يوجد طلاب نشطون لعرضهم حالياً. أضف الطلاب أولاً أو غيّر البحث الحالي.</div>
|
|
<?php else: ?>
|
|
<?php if ($isCycleReadOnly): ?>
|
|
<div class="alert alert-warning mb-0">هذه الدورة مؤرشفة. يمكنك مراجعة الدرجات فقط أو فتح دورة جديدة من صفحة المركز.</div>
|
|
<?php else: ?>
|
|
<form method="get" class="vstack gap-3 mb-4" novalidate>
|
|
<input type="hidden" name="id" value="<?= e((string) $application['id']) ?>">
|
|
<input type="hidden" name="cycle" value="<?= e((string) $selectedCycleId) ?>">
|
|
<div>
|
|
<label class="form-label" for="assessment_id_switch">التقييم النشط</label>
|
|
<select class="form-select" id="assessment_id_switch" name="assessment_id" onchange="this.form.submit()">
|
|
<?php foreach ($assessmentOptions as $assessmentId => $assessmentMeta): ?>
|
|
<option value="<?= e((string) $assessmentId) ?>" <?= $selectedAssessmentId === $assessmentId ? 'selected' : '' ?>><?= e($assessmentMeta['label']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label" for="search">بحث داخل كشف الطلاب</label>
|
|
<input class="form-control" id="search" name="search" value="<?= e($search) ?>" placeholder="ابحث باسم الطالب أو الكود المرجعي">
|
|
</div>
|
|
<div class="d-grid">
|
|
<button class="btn btn-outline-secondary" type="submit">تحديث الكشف</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="vstack gap-3">
|
|
<input type="hidden" name="assessment_type_id" value="<?= e((string) $selectedAssessmentId) ?>">
|
|
<div>
|
|
<label class="form-label" for="teacher_id">المعلّم / المقيم (اختياري)</label>
|
|
<select class="form-select <?= isset($errors['teacher_id']) ? 'is-invalid' : '' ?>" id="teacher_id" name="teacher_id" form="scoreBatchForm">
|
|
<option value="0">بدون تحديد معلم</option>
|
|
<?php foreach ($teacherOptions as $teacherId => $teacherMeta): ?>
|
|
<option value="<?= e((string) $teacherId) ?>" <?= $values['teacher_id'] === (string) $teacherId ? 'selected' : '' ?>><?= e($teacherMeta['label']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<?php if (isset($errors['teacher_id'])): ?><div class="invalid-feedback"><?= e($errors['teacher_id']) ?></div><?php endif; ?>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="form-label" for="assessed_on">تاريخ الرصد</label>
|
|
<input class="form-control <?= isset($errors['assessed_on']) ? 'is-invalid' : '' ?>" type="date" id="assessed_on" name="assessed_on" value="<?= e($values['assessed_on']) ?>" form="scoreBatchForm">
|
|
<?php if (isset($errors['assessed_on'])): ?><div class="invalid-feedback"><?= e($errors['assessed_on']) ?></div><?php endif; ?>
|
|
</div>
|
|
|
|
<div class="school-data-item">
|
|
<strong>التقييم المختار</strong>
|
|
<span><?= e($selectedAssessment['label'] ?? '—') ?></span>
|
|
</div>
|
|
<div class="school-data-item">
|
|
<strong>الدرجة النهائية</strong>
|
|
<span><?= e($selectedAssessment ? rtrim(rtrim(number_format((float) $selectedAssessment['max_score'], 2, '.', ''), '0'), '.') : '—') ?></span>
|
|
</div>
|
|
<div class="school-data-item">
|
|
<strong>الوزن</strong>
|
|
<span><?= e($selectedAssessment ? rtrim(rtrim(number_format((float) $selectedAssessment['weight_percentage'], 2, '.', ''), '0'), '.') . '%' : '—') ?></span>
|
|
</div>
|
|
|
|
<div class="d-grid">
|
|
<button class="btn btn-primary" type="submit" form="scoreBatchForm">حفظ درجات الطلاب الظاهرين</button>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="app-card sidebar-card">
|
|
<div class="section-title mb-3">قبل البدء</div>
|
|
<ul class="module-roadmap-list mb-0">
|
|
<li><strong>1. تعريف التقييم</strong><span class="section-subtle">أنشئ التقييم من صفحة التقييمات وحدد الدرجة النهائية والوزن.</span></li>
|
|
<li><strong>2. تجهيز الطلاب</strong><span class="section-subtle">هذه الصفحة تعرض الطلاب النشطين فقط لتقليل الأخطاء أثناء الرصد.</span></li>
|
|
<li><strong>3. حفظ متكرر</strong><span class="section-subtle">يمكنك العودة لاحقاً لنفس التقييم وتعديل درجات أي طالب، وسيتم تحديث سجله بدل إنشاء نسخة جديدة.</span></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-8">
|
|
<div class="app-card mb-4">
|
|
<div class="section-head mb-3">
|
|
<div>
|
|
<div class="section-title">كشف إدخال الدرجات</div>
|
|
<div class="section-copy">كل صف يمثل طالباً واحداً ضمن التقييم المختار. عند حفظ النموذج يتم تحديث الصفوف التي تحتوي على درجة أو حالة خاصة أو ملاحظة.</div>
|
|
</div>
|
|
<span class="header-chip"><?= e((string) count($students)) ?> طلاب ظاهرون</span>
|
|
</div>
|
|
|
|
<?php if ($assessmentOptions === []): ?>
|
|
<div class="empty-state text-center p-4">
|
|
<div class="empty-title mb-2">لا توجد تقييمات مفعلة بعد</div>
|
|
<p class="text-muted mb-0">أنشئ أول تقييم من صفحة التقييمات، ثم عد هنا لبدء رصد النتائج.</p>
|
|
</div>
|
|
<?php elseif ($students === []): ?>
|
|
<div class="empty-state text-center p-4">
|
|
<div class="empty-title mb-2">لا يوجد طلاب مطابقون لهذا العرض</div>
|
|
<p class="text-muted mb-0">جرّب إزالة البحث الحالي أو أضف طلاباً نشطين من صفحة الطلاب.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<form method="post" id="scoreBatchForm" novalidate>
|
|
<input type="hidden" name="assessment_type_id" value="<?= e((string) $selectedAssessmentId) ?>">
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-3"><div class="school-data-item"><strong>المحفوظ حالياً</strong><span><?= e((string) $scoreMetrics['total']) ?> سجل</span></div></div>
|
|
<div class="col-md-3"><div class="school-data-item"><strong>درجات مرصودة</strong><span><?= e((string) $scoreMetrics['present']) ?> طالب</span></div></div>
|
|
<div class="col-md-3"><div class="school-data-item"><strong>غياب / عذر</strong><span><?= e((string) ($scoreMetrics['absent'] + $scoreMetrics['excused'])) ?> حالة</span></div></div>
|
|
<div class="col-md-3"><div class="school-data-item"><strong>المتوسط</strong><span><?= e($averageScoreDisplay) ?></span></div></div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table app-table align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th>الطالب</th>
|
|
<th style="width: 140px;">الحالة</th>
|
|
<th style="width: 140px;">الدرجة</th>
|
|
<th>ملاحظات</th>
|
|
<th>آخر رصد</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($students as $student): ?>
|
|
<?php
|
|
$studentId = (int) ($student['id'] ?? 0);
|
|
$existing = $scoreMap[$studentId] ?? null;
|
|
$entryValues = $values['entries'][$studentId] ?? [];
|
|
$statusValue = (string) ($entryValues['status'] ?? ($existing['status'] ?? 'present'));
|
|
$scoreValue = (string) ($entryValues['score_raw'] ?? (($existing && (string) ($existing['status'] ?? '') === 'present' && $existing['score'] !== null) ? rtrim(rtrim(number_format((float) $existing['score'], 2, '.', ''), '0'), '.') : ''));
|
|
$notesValue = (string) ($entryValues['notes'] ?? ($existing['notes'] ?? ''));
|
|
$rowError = $errors['entries_' . $studentId] ?? null;
|
|
$teacherName = (string) ($existing['teacher_name'] ?? '');
|
|
$assessedOn = (string) ($existing['assessed_on'] ?? '');
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<strong><?= e((string) $student['full_name']) ?></strong>
|
|
<small><?= e((string) $student['student_code']) ?> — <?= e((string) $student['grade_level']) ?></small>
|
|
</td>
|
|
<td>
|
|
<select class="form-select form-select-sm" name="entries[<?= e((string) $studentId) ?>][status]">
|
|
<?php foreach (assessment_score_status_map() as $statusKey => $statusMeta): ?>
|
|
<option value="<?= e($statusKey) ?>" <?= $statusValue === $statusKey ? 'selected' : '' ?>><?= e($statusMeta['label']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<input class="form-control form-control-sm <?= $rowError ? 'is-invalid' : '' ?>" type="number" step="0.01" min="0" max="<?= e($selectedAssessment ? (string) $selectedAssessment['max_score'] : '100') ?>" name="entries[<?= e((string) $studentId) ?>][score]" value="<?= e($scoreValue) ?>" placeholder="<?= e($selectedAssessment ? 'من ' . rtrim(rtrim(number_format((float) $selectedAssessment['max_score'], 2, '.', ''), '0'), '.') : 'درجة') ?>">
|
|
<?php if ($rowError): ?><div class="invalid-feedback"><?= e((string) $rowError) ?></div><?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<textarea class="form-control form-control-sm" rows="2" name="entries[<?= e((string) $studentId) ?>][notes]" placeholder="ملاحظة سريعة عند الحاجة"><?= e($notesValue) ?></textarea>
|
|
</td>
|
|
<td>
|
|
<?php if ($existing): ?>
|
|
<?= assessment_score_status_badge((string) ($existing['status'] ?? 'present')) ?>
|
|
<small><?= e($assessedOn !== '' ? $assessedOn : '—') ?><?= $teacherName !== '' ? ' — ' . e($teacherName) : '' ?></small>
|
|
<?php else: ?>
|
|
<span class="text-muted">—</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<?php if (!$isCycleReadOnly): ?>
|
|
<div class="d-flex justify-content-between align-items-center pt-3 border-top mt-3 flex-wrap gap-2">
|
|
<div class="section-subtle">سيتم حفظ الصفوف التي تحتوي على بيانات فقط، وتحديث السجل السابق لنفس الطالب داخل هذا التقييم.</div>
|
|
<button class="btn btn-primary" type="submit">حفظ الدرجات</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
</form>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="app-card">
|
|
<div class="section-title mb-3">سياق المدرسة</div>
|
|
<div class="row g-3">
|
|
<div class="col-md-6"><div class="school-data-item"><strong>الطلاب النشطون</strong><span><?= e((string) $studentMetrics['active']) ?> طالب/طالبة</span></div></div>
|
|
<div class="col-md-6"><div class="school-data-item"><strong>المعلمون النشطون</strong><span><?= e((string) $teacherMetrics['active']) ?> عضو</span></div></div>
|
|
<div class="col-md-6"><div class="school-data-item"><strong>التقييمات المفعلة</strong><span><?= e((string) $assessmentMetrics['active']) ?> نوع</span></div></div>
|
|
<div class="col-md-6"><div class="school-data-item"><strong>آخر رصد محفوظ</strong><span><?= e($latestScoreDate) ?></span></div></div>
|
|
</div>
|
|
<div class="cta-stack mt-4">
|
|
<a class="btn btn-outline-secondary" href="<?= e($assessmentsUrl) ?>">العودة إلى التقييمات</a>
|
|
<a class="btn btn-outline-secondary" href="<?= e($teachersUrl) ?>">العودة إلى المعلمين</a>
|
|
<a class="btn btn-outline-secondary" href="<?= e($studentsUrl) ?>">العودة إلى الطلاب</a>
|
|
<a class="btn btn-outline-secondary" href="<?= e($approvedSchoolUrl) ?>">صفحة المركز</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<?php render_page_end();
|