39669-vm/center_assessment_criteria.php
2026-04-17 07:30:11 +00:00

375 lines
22 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;
$isApprovedCenter = $application && (string) ($application['status'] ?? '') === 'approved';
$errors = [];
$values = ['criteria' => []];
$cycleContext = ['cycles' => [], 'selected' => null, 'active' => null, 'read_only' => false];
$selectedCycle = null;
$selectedCycleId = 0;
$isCycleReadOnly = false;
$cycleLabel = 'لا توجد دورة بعد';
$buildCenterAssessmentsUrl = static function (int $targetApplicationId = 0, int $targetCycleId = 0, array $extra = []): string {
$params = [];
if ($targetApplicationId > 0) {
$params['id'] = $targetApplicationId;
}
if ($targetCycleId > 0) {
$params['cycle'] = $targetCycleId;
}
foreach ($extra as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$params[$key] = $value;
}
return 'center_assessments.php' . ($params !== [] ? '?' . http_build_query($params) : '');
};
$buildCenterAssessmentCriteriaUrl = static function (int $targetApplicationId = 0, int $targetCycleId = 0, int $targetAssessmentId = 0, array $extra = []) : string {
$params = [];
if ($targetApplicationId > 0) {
$params['id'] = $targetApplicationId;
}
if ($targetCycleId > 0) {
$params['cycle'] = $targetCycleId;
}
if ($targetAssessmentId > 0) {
$params['assessment_id'] = $targetAssessmentId;
}
foreach ($extra as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
$params[$key] = $value;
}
return 'center_assessment_criteria.php' . ($params !== [] ? '?' . http_build_query($params) : '');
};
if ($isApprovedCenter) {
$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'] ?? false);
$cycleLabel = $selectedCycle ? (string) ($selectedCycle['cycle_name'] ?? $cycleLabel) : $cycleLabel;
}
$assessmentOptions = $isApprovedCenter && $selectedCycleId > 0
? center_assessment_type_options_by_cycle((int) $application['id'], $selectedCycleId, false)
: [];
$selectedAssessmentId = $requestedAssessmentId;
if ($selectedAssessmentId <= 0 && $assessmentOptions !== []) {
$keys = array_keys($assessmentOptions);
$selectedAssessmentId = (int) ($keys[0] ?? 0);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $application) {
$selectedAssessmentId = filter_input(INPUT_POST, 'assessment_id', FILTER_VALIDATE_INT) ?: $selectedAssessmentId;
if (!$isApprovedCenter) {
$errors['form'] = 'لا يمكن إعداد بنود تقييم المركز قبل اعتماد المركز.';
} elseif ($selectedCycleId <= 0) {
$errors['form'] = 'يرجى إنشاء دورة موسمية أولاً من صفحة المركز.';
} elseif ($isCycleReadOnly) {
$errors['form'] = 'هذه الدورة مؤرشفة للقراءة فقط. اختر دورة نشطة لتعديل البنود.';
} elseif ($selectedAssessmentId <= 0 || !isset($assessmentOptions[$selectedAssessmentId])) {
$errors['form'] = 'يرجى اختيار تقييم مركز صحيح أولاً.';
} else {
[$values, $errors] = validate_center_assessment_criteria_input((int) $application['id'], $selectedCycleId, $selectedAssessmentId, $_POST);
if ($errors === []) {
try {
$savedRows = save_center_assessment_criteria_in_cycle((int) $application['id'], $selectedCycleId, $selectedAssessmentId, $values);
set_flash('success', 'تم حفظ ' . $savedRows . ' بند/بنود لتقييم المركز.');
header('Location: ' . $buildCenterAssessmentCriteriaUrl((int) $application['id'], $selectedCycleId, $selectedAssessmentId));
exit;
} catch (Throwable $exception) {
$errors['form'] = 'تعذر حفظ البنود حالياً. يرجى المحاولة مرة أخرى.';
}
}
}
}
$selectedAssessment = $selectedAssessmentId > 0 ? ($assessmentOptions[$selectedAssessmentId] ?? null) : null;
$criteriaRows = $isApprovedCenter && $selectedCycleId > 0 && $selectedAssessmentId > 0
? list_center_assessment_criteria_by_assessment((int) $application['id'], $selectedCycleId, $selectedAssessmentId, false)
: [];
$criteriaMetrics = $isApprovedCenter && $selectedCycleId > 0 && $selectedAssessmentId > 0
? center_assessment_criteria_metrics((int) $application['id'], $selectedCycleId, $selectedAssessmentId)
: ['total' => 0, 'active' => 0, 'active_max_score' => 0.0];
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$values['criteria'] = [];
foreach ($criteriaRows as $criterion) {
$values['criteria'][] = [
'id' => (int) ($criterion['id'] ?? 0),
'title' => (string) ($criterion['title'] ?? ''),
'max_score' => rtrim(rtrim(number_format((float) ($criterion['max_score'] ?? 0), 2, '.', ''), '0'), '.'),
'notes' => (string) ($criterion['notes'] ?? ''),
'is_active' => ((int) ($criterion['is_active'] ?? 0) === 1) ? '1' : '0',
];
}
if ($values['criteria'] === []) {
$values['criteria'][] = ['id' => 0, 'title' => 'الالتزام التشغيلي', 'max_score' => '10', 'notes' => '', 'is_active' => '1'];
$values['criteria'][] = ['id' => 0, 'title' => 'جودة التنفيذ', 'max_score' => '10', 'notes' => '', 'is_active' => '1'];
$values['criteria'][] = ['id' => 0, 'title' => 'التواصل والتقارير', 'max_score' => '10', 'notes' => '', 'is_active' => '1'];
}
}
$pageTitle = $application && $selectedAssessment
? 'بنود تقييم المركز: ' . (string) ($selectedAssessment['title'] ?? '') . ' — ' . (string) ($application['center_name'] ?? '')
: 'بنود تقييم المراكز';
$pageDescription = 'إدارة البنود التفصيلية لكل تقييم مركز داخل الدورة الموسمية، مع تحديث الدرجة القصوى تلقائياً من مجموع البنود النشطة.';
$assessmentsUrl = $application ? $buildCenterAssessmentsUrl((int) $application['id'], $selectedCycleId) : 'center_assessments.php';
if (!$application) {
http_response_code(404);
}
render_page_start($pageTitle, 'admin', $pageDescription);
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 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="center_assessments.php">تقييم المراكز</a>
</div>
<?php elseif (!$isApprovedCenter): ?>
<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): ?>
<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($assessmentsUrl) ?>">الرجوع إلى تقييمات المركز</a>
</div>
<?php elseif ($selectedAssessment === null): ?>
<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($assessmentsUrl) ?>">الرجوع إلى تقييمات المركز</a>
</div>
<?php else: ?>
<div class="page-banner mb-4 mb-lg-5">
<div class="row g-4 align-items-start">
<div class="col-lg-8">
<span class="eyebrow mb-3">مرحلة 2 — بنود تقييم المراكز</span>
<h1 class="page-title mb-3"><?= e((string) ($selectedAssessment['title'] ?? '')) ?></h1>
<p class="page-copy mb-3">أضف البنود التفصيلية التي سيُبنى عليها تقييم المركز، مثل <strong>الالتزام</strong> و<strong>جودة التنفيذ</strong> و<strong>التقارير</strong>. مجموع البنود النشطة سيُحدِّث الدرجة النهائية لهذا التقييم تلقائياً.</p>
<div class="hero-meta">
<span><?= e((string) ($application['center_name'] ?? '')) ?></span>
<span><?= e($cycleLabel) ?></span>
<span><?= e((string) ($selectedAssessment['category'] ?? '')) ?></span>
<span>الوزن <?= e(rtrim(rtrim(number_format((float) ($selectedAssessment['weight_percentage'] ?? 0), 2, '.', ''), '0'), '.')) ?>%</span>
</div>
</div>
<div class="col-lg-4">
<div class="page-banner-panel h-100">
<div class="mini-stat-label">التقييم المحدد</div>
<div class="mini-stat-value"><?= e((string) $criteriaMetrics['active']) ?></div>
<div class="mini-stat-copy mb-3">عدد البنود النشطة حالياً داخل هذا التقييم.</div>
<div class="d-grid gap-2">
<a class="btn btn-primary btn-sm" href="<?= e($assessmentsUrl) ?>">الرجوع إلى التقييمات</a>
<?php if (!$isCycleReadOnly): ?>
<button type="button" class="btn btn-outline-secondary btn-sm" id="addCriterionRow">إضافة بند</button>
<?php endif; ?>
<span class="small text-muted">الدرجة الحالية لهذا التقييم: <strong><?= e(rtrim(rtrim(number_format((float) ($selectedAssessment['max_score'] ?? 0), 2, '.', ''), '0'), '.')) ?></strong></span>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-4"><div class="app-card stat-tile"><div class="mini-stat-label">إجمالي البنود</div><div class="mini-stat-value"><?= e((string) $criteriaMetrics['total']) ?></div><div class="mini-stat-copy">كل البنود المحفوظة لهذا التقييم.</div></div></div>
<div class="col-md-4"><div class="app-card stat-tile"><div class="mini-stat-label">البنود النشطة</div><div class="mini-stat-value"><?= e((string) $criteriaMetrics['active']) ?></div><div class="mini-stat-copy">هي التي تدخل في الرصد النهائي.</div></div></div>
<div class="col-md-4"><div class="app-card stat-tile"><div class="mini-stat-label">مجموع البنود النشطة</div><div class="mini-stat-value"><?= e(rtrim(rtrim(number_format((float) $criteriaMetrics['active_max_score'], 2, '.', ''), '0'), '.')) ?></div><div class="mini-stat-copy">يحدّث الدرجة القصوى للتقييم تلقائياً.</div></div></div>
</div>
<div class="app-card mb-4">
<form method="get" class="row g-3 align-items-end">
<input type="hidden" name="id" value="<?= e((string) $application['id']) ?>">
<input type="hidden" name="cycle" value="<?= e((string) $selectedCycleId) ?>">
<div class="col-lg-8">
<label class="form-label" for="assessmentSelect">التقييم</label>
<select class="form-select" id="assessmentSelect" name="assessment_id" onchange="this.form.submit()">
<?php foreach ($assessmentOptions as $assessmentOption): ?>
<option value="<?= e((string) ($assessmentOption['id'] ?? 0)) ?>" <?= (int) ($assessmentOption['id'] ?? 0) === $selectedAssessmentId ? 'selected' : '' ?>><?= e((string) ($assessmentOption['label'] ?? '')) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-lg-4">
<div class="section-subtle mb-2">الحالة</div>
<div><?= assessment_active_badge((int) ($selectedAssessment['is_active'] ?? 0)) ?></div>
</div>
</form>
</div>
<?php if (!empty($errors['form'])): ?>
<div class="alert alert-danger mb-4"><?= e($errors['form']) ?></div>
<?php endif; ?>
<?php if ($isCycleReadOnly): ?>
<div class="alert alert-warning mb-4">هذه الدورة مؤرشفة، لذلك الصفحة معروضة للقراءة فقط.</div>
<?php endif; ?>
<div class="app-card">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-4">
<div>
<div class="section-title mb-1">بنود تقييم المركز</div>
<div class="section-subtle">يمكنك إضافة بنود جديدة أو إخفاء بند قديم دون حذفه من السجل.</div>
</div>
<?php if (!$isCycleReadOnly): ?>
<button type="button" class="btn btn-outline-secondary" id="addCriterionRowSecondary">إضافة بند</button>
<?php endif; ?>
</div>
<form method="post" novalidate>
<input type="hidden" name="assessment_id" value="<?= e((string) $selectedAssessmentId) ?>">
<div class="table-responsive">
<table class="table app-table align-middle" id="criteriaTable">
<thead>
<tr>
<th style="width: 32%">اسم البند</th>
<th style="width: 18%">الدرجة</th>
<th style="width: 30%">ملاحظات</th>
<th style="width: 12%">الحالة</th>
<th style="width: 8%">الإجراء</th>
</tr>
</thead>
<tbody>
<?php foreach ($values['criteria'] as $index => $criterion): ?>
<tr data-criterion-row data-existing="<?= !empty($criterion['id']) ? '1' : '0' ?>">
<td>
<input type="hidden" name="criteria[<?= e((string) $index) ?>][id]" value="<?= e((string) ($criterion['id'] ?? 0)) ?>">
<input class="form-control" type="text" name="criteria[<?= e((string) $index) ?>][title]" value="<?= e((string) ($criterion['title'] ?? '')) ?>" placeholder="مثال: الالتزام التشغيلي" <?= $isCycleReadOnly ? 'disabled' : '' ?>>
<?php if (isset($errors['criteria_' . $index])): ?>
<div class="text-danger small mt-1"><?= e($errors['criteria_' . $index]) ?></div>
<?php endif; ?>
</td>
<td>
<input class="form-control" type="number" step="0.01" min="0" max="1000" name="criteria[<?= e((string) $index) ?>][max_score]" value="<?= e((string) ($criterion['max_score'] ?? '')) ?>" placeholder="10" <?= $isCycleReadOnly ? 'disabled' : '' ?>>
</td>
<td>
<input class="form-control" type="text" name="criteria[<?= e((string) $index) ?>][notes]" value="<?= e((string) ($criterion['notes'] ?? '')) ?>" placeholder="اختياري" <?= $isCycleReadOnly ? 'disabled' : '' ?>>
</td>
<td>
<select class="form-select" name="criteria[<?= e((string) $index) ?>][is_active]" <?= $isCycleReadOnly ? 'disabled' : '' ?>>
<option value="1" <?= (string) ($criterion['is_active'] ?? '1') === '1' ? 'selected' : '' ?>>نشط</option>
<option value="0" <?= (string) ($criterion['is_active'] ?? '1') === '0' ? 'selected' : '' ?>>مخفي</option>
</select>
</td>
<td>
<?php if ($isCycleReadOnly): ?>
<span class="text-muted small">قراءة فقط</span>
<?php elseif (!empty($criterion['id'])): ?>
<span class="text-muted small">أوقفه</span>
<?php else: ?>
<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>
<?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>
</div>
<?php endif; ?>
</div>
</div>
</div>
</section>
<?php if ($application && !$isCycleReadOnly && $selectedAssessment): ?>
<template id="criterionRowTemplate">
<tr data-criterion-row data-existing="0">
<td>
<input type="hidden" data-row-field="id" value="0">
<input class="form-control" type="text" data-row-field="title" placeholder="مثال: الالتزام التشغيلي">
</td>
<td>
<input class="form-control" type="number" step="0.01" min="0" max="1000" data-row-field="max_score" placeholder="10">
</td>
<td>
<input class="form-control" type="text" data-row-field="notes" placeholder="اختياري">
</td>
<td>
<select class="form-select" data-row-field="is_active">
<option value="1" selected>نشط</option>
<option value="0">مخفي</option>
</select>
</td>
<td>
<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>
<script>
document.addEventListener('DOMContentLoaded', () => {
const buttons = [document.getElementById('addCriterionRow'), document.getElementById('addCriterionRowSecondary')].filter(Boolean);
const tableBody = document.querySelector('#criteriaTable tbody');
const template = document.getElementById('criterionRowTemplate');
if (buttons.length === 0 || !tableBody || !template) return;
const wireRow = (row) => {
const removeButton = row.querySelector('[data-remove-row]');
if (removeButton) {
removeButton.addEventListener('click', () => row.remove());
}
};
tableBody.querySelectorAll('[data-criterion-row]').forEach(wireRow);
const renumberRows = () => {
Array.from(tableBody.querySelectorAll('[data-criterion-row]')).forEach((row, index) => {
row.querySelectorAll('[data-row-field]').forEach((field) => {
const key = field.getAttribute('data-row-field');
field.name = `criteria[${index}][${key}]`;
});
});
};
buttons.forEach((button) => {
button.addEventListener('click', () => {
const fragment = template.content.cloneNode(true);
tableBody.appendChild(fragment);
wireRow(tableBody.lastElementChild);
renumberRows();
});
});
renumberRows();
});
</script>
<?php endif; ?>
<?php render_page_end();