39669-vm/assessments.php
2026-04-17 03:38:41 +00:00

399 lines
23 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;
$application = $applicationId > 0 ? get_application($applicationId) : null;
$isApprovedSchool = $application && (string) $application['status'] === 'approved';
$values = assessment_defaults();
$errors = [];
$cycleContext = ['cycles' => [], 'selected' => null, 'active' => null, 'read_only' => false];
$selectedCycle = null;
$selectedCycleId = 0;
$isCycleReadOnly = false;
$cycleLabel = 'لا توجد دورة بعد';
$available_subjects = [];
if ($application) {
$center_subjects_ids = is_string($application['subjects']) ? json_decode($application['subjects'], true) : [];
if (!is_array($center_subjects_ids)) $center_subjects_ids = [];
$center_subjects_ids = array_map('strval', $center_subjects_ids);
$all_subjects = get_enabled_subjects();
$available_subjects = array_filter($all_subjects, function($s) use ($center_subjects_ids) { return in_array((string)$s["id"], $center_subjects_ids, true); });
}
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;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
$action = $_POST['action'] ?? 'add';
$assessmentId = filter_input(INPUT_POST, 'assessment_id', FILTER_VALIDATE_INT) ?: 0;
[$values, $errors] = validate_assessment_input($_POST);
if (!$isApprovedSchool) {
$errors['form'] = 'لا يمكن إعداد التقييمات قبل اعتماد المركز.';
} elseif ($selectedCycleId <= 0) {
$errors['form'] = 'يرجى إنشاء دورة موسمية أولاً من صفحة المركز.';
} elseif ($isCycleReadOnly) {
$errors['form'] = 'هذه الدورة مؤرشفة للقراءة فقط. افتح دورة جديدة أو اختر دورة نشطة لإضافة تقييمات جديدة.';
}
if ($errors === []) {
try {
if ($action === 'edit' && $assessmentId > 0) {
update_assessment_type_in_cycle((int) $application['id'], $selectedCycleId, $assessmentId, $values);
set_flash('success', 'تم تحديث التقييم بنجاح.');
} else {
create_assessment_type_in_cycle((int) $application['id'], $selectedCycleId, $values);
set_flash('success', 'تم حفظ التقييم داخل الدورة الموسمية المحددة.');
}
header('Location: ' . school_page_url('assessments.php', (int) $application['id'], $selectedCycleId) . '&' . http_build_query(array_intersect_key($_GET, array_flip(['search', 'category', 'subject_id', 'page']))));
exit;
} catch (Throwable $exception) {
$errors['form'] = 'تعذر حفظ التقييم حالياً. يرجى المحاولة مرة أخرى.';
}
}
}
$filters = [
'search' => clean_text($_GET['search'] ?? '', 255),
'category' => clean_text($_GET['category'] ?? '', 80),
'subject_id' => clean_text($_GET['subject_id'] ?? '', 20)
];
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$limit = 20;
$offset = ($page - 1) * $limit;
$assessments = $isApprovedSchool && $selectedCycleId > 0 ? list_school_assessments_by_cycle((int) $application['id'], $selectedCycleId, $filters, $limit, $offset) : [];
$totalAssessments = $isApprovedSchool && $selectedCycleId > 0 ? count_school_assessments_by_cycle((int) $application['id'], $selectedCycleId, $filters) : 0;
$metrics = $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,
];
$activeWeight = round((float) $metrics['active_weight'], 2);
$weightGap = round(100 - $activeWeight, 2);
$pageTitle = $application ? 'التقييمات والأوزان: ' . (string) $application['center_name'] . ($selectedCycle ? ' — ' . $cycleLabel : '') : 'التقييمات والأوزان';
$pageDescription = 'صفحة مستقلة لتعريف أنواع التقييم، المقاييس، والأوزان لكل مدرسة معتمدة داخل دورة موسمية محددة.';
$approvedSchoolUrl = $application ? school_page_url('approved_school.php', (int) $application['id'], $selectedCycleId) : 'approved_school.php';
$assessmentScoresUrl = $application ? school_page_url('assessment_scores.php', (int) $application['id'], $selectedCycleId) : 'assessment_scores.php';
$assessmentScoreSheetBaseUrl = $application ? school_page_url('assessment_score_sheet.php', (int) $application['id'], $selectedCycleId) : 'assessment_score_sheet.php';
$criteriaBaseUrl = $application ? school_page_url('assessment_criteria.php', (int) $application['id'], $selectedCycleId) : 'assessment_criteria.php';
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="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 layout-content-column">
<?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">
<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>
<a class="btn btn-primary" href="<?= e($approvedSchoolUrl) ?>">صفحة المركز</a>
</div>
</div>
</div>
<?php else: ?>
<div class="page-banner mb-4">
<div class="row g-4 align-items-center">
<div class="col-md-8">
<h1 class="page-title mb-2">التقييمات: <?= e((string) $application['center_name']) ?></h1>
<p class="page-copy mb-0">إدارة أنواع التقييم وتوزيع الدرجات ضمن الخطة الأكاديمية للدورة <strong><?= e($cycleLabel) ?></strong>.</p>
</div>
<div class="col-md-4 text-md-end">
<div class="d-flex flex-wrap justify-content-md-end gap-2">
<a class="btn btn-outline-secondary" href="<?= e($assessmentScoresUrl) ?>">إدخال الدرجات</a>
<?php if (!$isCycleReadOnly): ?>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#assessmentModal" onclick="resetAssessmentForm()">
إضافة تقييم جديد
</button>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php if (isset($errors['form'])): ?>
<div class="alert alert-danger"><?= e($errors['form']) ?></div>
<?php endif; ?>
<?php if ($isCycleReadOnly): ?>
<div class="alert alert-warning mb-4">هذه الدورة مؤرشفة. التقييمات معروضة للقراءة فقط.</div>
<?php endif; ?>
<div class="app-card mb-4">
<form method="get" class="row g-2 align-items-center">
<input type="hidden" name="id" value="<?= e((string)$application['id']) ?>">
<input type="hidden" name="cycle" value="<?= e((string)$selectedCycleId) ?>">
<div class="col-md-4">
<input type="text" name="search" class="form-control" placeholder="ابحث باسم التقييم..." value="<?= e($filters['search']) ?>">
</div>
<div class="col-md-3">
<select name="subject_id" class="form-select">
<option value="">كل المواد</option>
<?php foreach ($available_subjects as $subj):
?><option value="<?= e((string)$subj['id']) ?>" <?= $filters['subject_id'] === (string)$subj['id'] ? 'selected' : '' ?>><?= e($subj['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<select name="category" class="form-select">
<option value="">كل الفئات</option>
<?php foreach (assessment_category_options() as $cat):
?><option value="<?= e($cat) ?>" <?= $filters['category'] === $cat ? 'selected' : '' ?>><?= e($cat) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">تصفية</button>
</div>
</form>
</div>
<div class="app-card mb-4">
<div class="row g-3 mb-3">
<div class="col-md-4"><div class="school-data-item"><strong>إجمالي الأنواع</strong><span><?= e((string) $metrics['total']) ?> نوع</span></div></div>
<div class="col-md-4"><div class="school-data-item"><strong>المفعّل الآن</strong><span><?= e((string) $metrics['active']) ?> نوع</span></div></div>
<div class="col-md-4"><div class="school-data-item"><strong>الأوزان المفعلة</strong><span><?= e(number_format($activeWeight, 2, '.', '')) ?>%</span></div></div>
</div>
<?php if ($assessments === []): ?>
<div class="empty-state text-center p-4">
<div class="empty-title mb-2">لا توجد تقييمات</div>
<p class="text-muted mb-0">لم يتم العثور على أنواع تقييم تطابق خياراتك.</p>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table app-table align-middle">
<thead>
<tr>
<th>التقييم</th>
<th>المادة</th>
<th>الفئة</th>
<th>المقياس</th>
<th>الدرجة</th>
<th>الوزن</th>
<th>البنود</th>
<th>الحالة</th>
<?php if (!$isCycleReadOnly): ?><th>الإجراء</th><?php endif; ?>
</tr>
</thead>
<tbody>
<?php foreach ($assessments as $assessment):
$subjName = 'عام';
foreach ($available_subjects as $s) {
if ((string)$s['id'] === (string)$assessment['subject_id']) {
$subjName = $s['name']; break;
}
}
?>
<tr>
<td>
<strong><?= e((string) $assessment['title']) ?></strong>
<?php if (!empty($assessment['notes'])): ?><div class="small text-muted"><?= e((string) $assessment['notes']) ?></div><?php endif; ?>
</td>
<td><span class="badge bg-secondary"><?= e($subjName) ?></span></td>
<td><?= e((string) $assessment['category']) ?></td>
<td><?= assessment_scale_type_badge((string) $assessment['scale_type']) ?></td>
<td><?= e(number_format((float) $assessment['max_score'], 2, '.', '')) ?></td>
<td><strong><?= e(number_format((float) $assessment['weight_percentage'], 2, '.', '')) ?>%</strong></td>
<td>
<?php $criteriaCount = (int) ($assessment['criteria_count'] ?? 0); ?>
<?php if ($criteriaCount > 0): ?>
<div class="fw-semibold"><?= e((string) $criteriaCount) ?> بنود</div>
<small class="text-muted">المجموع <?= e(rtrim(rtrim(number_format((float) ($assessment['criteria_total_max_score'] ?? $assessment['max_score']), 2, '.', ''), '0'), '.')) ?></small>
<?php else: ?>
<span class="text-muted">بدون بنود</span>
<?php endif; ?>
</td>
<td><?= assessment_active_badge((int) $assessment['is_active']) ?></td>
<?php if (!$isCycleReadOnly):
?><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>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php render_pagination($totalAssessments, $limit, $page, $_GET); ?>
<?php endif; ?>
</div>
<!-- Modal -->
<?php if (!$isCycleReadOnly):
?><div class="modal fade" id="assessmentModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form method="post" id="assessmentForm" novalidate>
<div class="modal-header">
<h5 class="modal-title" id="assessmentModalLabel">إضافة نوع تقييم</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="إغلاق"></button>
</div>
<div class="modal-body">
<input type="hidden" name="action" id="formAction" value="add">
<input type="hidden" name="assessment_id" id="formAssessmentId" value="">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label" for="title">اسم التقييم</label>
<input class="form-control" id="title" name="title" required>
</div>
<div class="col-md-6">
<label class="form-label" for="subject_id">المادة الدراسية</label>
<select class="form-select" id="subject_id" name="subject_id">
<option value="">عام (لا يخص مادة معينة)</option>
<?php foreach ($available_subjects as $subj):
?><option value="<?= e((string)$subj['id']) ?>"><?= e($subj['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="category">الفئة</label>
<select class="form-select" id="category" name="category">
<?php foreach (assessment_category_options() as $option):
?><option value="<?= e($option) ?>"><?= e($option) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="scale_type">المقياس</label>
<select class="form-select" id="scale_type" name="scale_type">
<?php foreach (assessment_scale_type_map() as $key => $meta):
?><option value="<?= e($key) ?>"><?= e((string) $meta['label']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="max_score">الدرجة النهائية</label>
<input type="number" step="0.01" min="0" max="1000" class="form-control" id="max_score" name="max_score" value="100">
<div class="form-text">إذا أضفت بنوداً تفصيلية لاحقاً فسيتم تحديث هذا الرقم تلقائياً من مجموع البنود النشطة.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="weight_percentage">الوزن (%)</label>
<input type="number" step="0.01" min="0" max="100" class="form-control" id="weight_percentage" name="weight_percentage" value="10">
</div>
<div class="col-md-6">
<label class="form-label" for="is_active">حالة التفعيل</label>
<select class="form-select" id="is_active" name="is_active">
<option value="1">مفعل داخل الخطة</option>
<option value="0">مؤرشف / تحضيري</option>
</select>
</div>
<div class="col-md-12">
<label class="form-label" for="notes">ملاحظات</label>
<textarea class="form-control" id="notes" name="notes" rows="3"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">إلغاء</button>
<button type="submit" class="btn btn-primary">حفظ التقييم</button>
</div>
</form>
</div>
</div>
</div>
<script>
let assessmentModal;
document.addEventListener("DOMContentLoaded", () => {
const m = document.getElementById('assessmentModal');
if (m) assessmentModal = new bootstrap.Modal(m);
});
function resetAssessmentForm() {
document.getElementById('assessmentForm').reset();
document.getElementById('formAction').value = 'add';
document.getElementById('formAssessmentId').value = '';
document.getElementById('assessmentModalLabel').innerText = 'إضافة نوع تقييم';
}
function editAssessment(data) {
resetAssessmentForm();
document.getElementById('formAction').value = 'edit';
document.getElementById('formAssessmentId').value = data.id;
document.getElementById('assessmentModalLabel').innerText = 'تعديل التقييم';
document.getElementById('title').value = data.title || '';
document.getElementById('subject_id').value = data.subject_id || '';
document.getElementById('category').value = data.category || '';
document.getElementById('scale_type').value = data.scale_type || '';
document.getElementById('max_score').value = data.max_score || '100';
document.getElementById('weight_percentage').value = data.weight_percentage || '0';
document.getElementById('is_active').value = data.is_active;
document.getElementById('notes').value = data.notes || '';
if (assessmentModal) assessmentModal.show();
}
<?php if (!empty($errors) && isset($_POST['action'])):
?>document.addEventListener("DOMContentLoaded", () => {
if (assessmentModal) assessmentModal.show();
});
<?php endif; ?>
</script>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</div>
</section>
<?php render_page_end(); ?>