313 lines
15 KiB
PHP
313 lines
15 KiB
PHP
<?= $this->extend('layout/main_layout') ?>
|
|
<?= $this->section('content') ?>
|
|
<?php
|
|
$statusBadges = $statusBadges ?? [];
|
|
$drafts = $drafts ?? [];
|
|
$legacyExams = $legacyExams ?? [];
|
|
$assignments = $assignments ?? [];
|
|
$selectedClassSection = $selectedClassSection ?? 0;
|
|
$examTypes = $examTypes ?? [];
|
|
$schoolYear = $schoolYear ?? '';
|
|
$semester = $semester ?? '';
|
|
$maxUploadBytes = $maxUploadBytes ?? (12 * 1024 * 1024);
|
|
?>
|
|
<style>
|
|
.teacher-drafts-page,
|
|
.teacher-drafts-page * {
|
|
font-family: Arial, sans-serif !important;
|
|
}
|
|
.teacher-drafts-table {
|
|
table-layout: auto;
|
|
width: max-content;
|
|
}
|
|
.teacher-drafts-table th {
|
|
min-width: 0;
|
|
}
|
|
.teacher-drafts-table td {
|
|
max-width: none;
|
|
}
|
|
.teacher-drafts-table th {
|
|
white-space: nowrap;
|
|
}
|
|
.teacher-drafts-table td.title-cell {
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|
|
<div class="container-xxl py-5 teacher-drafts-page">
|
|
<div class="container">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4">
|
|
<h1 class="mb-0">Exam drafts</h1>
|
|
<?php if (!empty($legacyExams)): ?>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="legacyToggle">Legacy Exams</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if (session()->getFlashdata('success')): ?>
|
|
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
|
|
<?php endif; ?>
|
|
<?php if (session()->getFlashdata('error')): ?>
|
|
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<?= form_open_multipart(base_url('teacher/exam-drafts'), ['class' => 'row g-3', 'id' => 'examDraftForm']) ?>
|
|
<?= csrf_field() ?>
|
|
<div class="col-md-6">
|
|
<label class="form-label" for="exam_type">Exam type <span class="text-danger">*</span></label>
|
|
<select name="exam_type" id="exam_type" class="form-select" required>
|
|
<option value="">— Select —</option>
|
|
<?php foreach ($examTypes as $t): ?>
|
|
<option value="<?= esc($t) ?>" <?= old('exam_type') === $t ? 'selected' : '' ?>><?= esc($t) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-8">
|
|
<label class="form-label" for="author_comment">Author comment</label>
|
|
<textarea name="author_comment" id="author_comment" class="form-control" rows="2" placeholder="Optional note for reviewers"><?= esc(old('author_comment') ?? '') ?></textarea>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<label class="form-label" for="draft_file">File (Word) <span class="text-danger">*</span></label>
|
|
<input type="file" name="draft_file" id="draft_file" class="form-control" accept=".doc,.docx" required>
|
|
<div class="form-text">Max <?= esc(number_format($maxUploadBytes / 1048576, 1)) ?> MB.</div>
|
|
</div>
|
|
<div class="col-12 d-flex flex-wrap gap-2">
|
|
<button type="submit" class="btn btn-primary">Submit for review</button>
|
|
</div>
|
|
<?= form_close() ?>
|
|
<?php if ($schoolYear !== '' || $semester !== ''): ?>
|
|
<p class="text-muted small mb-0 mt-2">School year: <?= esc($schoolYear) ?> · Semester: <?= esc($semester) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php
|
|
$renderBadge = static function (string $status, array $badges): string {
|
|
$b = $badges[$status] ?? ['label' => $status, 'class' => 'bg-secondary text-white'];
|
|
$style = !empty($b['style']) ? ' style="' . esc($b['style']) . '"' : '';
|
|
return '<span class="badge ' . esc($b['class']) . ' js-status-badge" data-status="' . esc($status) . '"' . $style . '>' . esc($b['label']) . '</span>';
|
|
};
|
|
?>
|
|
|
|
<h2 class="h5 mb-1">Visible submissions</h2>
|
|
<p class="text-muted small">Includes your uploads and submitted exam drafts from teachers assigned to the same class-section.</p>
|
|
<?php if (!empty($drafts)): ?>
|
|
<div class="table-responsive">
|
|
<table class="table table-striped align-middle teacher-drafts-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Teacher</th>
|
|
<th>Class</th>
|
|
<th>Title / type</th>
|
|
<th>Ver.</th>
|
|
<th>Status</th>
|
|
<th>File link</th>
|
|
<th>Reviewer comment</th>
|
|
<th>Last Update</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($drafts as $d): ?>
|
|
<?php
|
|
$st = strtolower((string) ($d['status'] ?? ''));
|
|
$badgeHtml = $renderBadge($st, $statusBadges);
|
|
?>
|
|
<tr data-draft-id="<?= (int) ($d['id'] ?? 0) ?>">
|
|
<td><?= esc($d['teacher_display_name'] ?? 'Teacher') ?></td>
|
|
<td><?= esc($d['class_section_name'] ?? '') ?></td>
|
|
<td class="title-cell"><?= esc($d['draft_title'] ?? $d['exam_type'] ?? '') ?></td>
|
|
<td><?= esc((string) ($d['version'] ?? '')) ?></td>
|
|
<td><?= $badgeHtml ?></td>
|
|
<td>
|
|
<?php $revNumber = max(1, (int) ($d['version'] ?? 1)); ?>
|
|
<?php if (!empty($d['teacher_file'])): ?>
|
|
<div>
|
|
<a href="<?= base_url('exam-drafts/files/teacher/' . $d['teacher_file']) ?>" target="_blank" rel="noopener">
|
|
<?= esc('Ver' . $revNumber . ' ' . ($d['teacher_filename'] ?? 'Submitted draft')) ?>
|
|
</a>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if (!empty($d['final_file'])): ?>
|
|
<div class="mt-1">
|
|
<a href="<?= base_url('exam-drafts/files/final/' . $d['final_file']) ?>" target="_blank" rel="noopener" class="link-success small">
|
|
<?= esc('Final ' . ($d['final_filename'] ?? 'Final draft')) ?>
|
|
</a>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if (!empty($d['review_files'])): ?>
|
|
<div class="mt-1">
|
|
<?php
|
|
$reviewLinks = [];
|
|
foreach ($d['review_files'] as $rf) {
|
|
$rev = max(1, (int) ($rf['review_revision'] ?? 1));
|
|
$name = $rf['final_filename'] ?? 'Review file';
|
|
$file = $rf['final_file'] ?? '';
|
|
if ($file !== '') {
|
|
$reviewLinks[] = '<a href="' . esc(base_url('exam-drafts/files/final/' . $file)) . '" target="_blank" rel="noopener" class="link-success small">' . esc('Ver' . $revNumber . '_' . $rev . ' ' . $name) . '</a>';
|
|
}
|
|
}
|
|
?>
|
|
<?= !empty($reviewLinks) ? implode(' | ', $reviewLinks) : '' ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if (empty($d['teacher_file']) && empty($d['final_file'])): ?>
|
|
—
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><?= nl2br(esc($d['reviewer_comment'] ?? $d['reviewer_comments'] ?? $d['admin_comments'] ?? '—')) ?></td>
|
|
<td><?= esc(!empty($d['updated_at']) ? local_datetime($d['updated_at'], 'm-d-Y H:i') : '') ?></td>
|
|
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php else: ?>
|
|
<p class="text-muted">No submissions yet.</p>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($legacyExams)): ?>
|
|
<div id="legacySection" class="table-responsive d-none mt-4">
|
|
<table class="table table-sm table-striped align-middle teacher-drafts-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Teacher</th>
|
|
<th>Class</th>
|
|
<th>Title</th>
|
|
<th>Ver.</th>
|
|
<th>Status</th>
|
|
<th>School year</th>
|
|
<th>Semester</th>
|
|
<th>Exam type</th>
|
|
<th>Updated</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($legacyExams as $d): ?>
|
|
<?php $st = strtolower((string) ($d['status'] ?? '')); ?>
|
|
<tr>
|
|
<td><?= esc($d['teacher_display_name'] ?? 'Teacher') ?></td>
|
|
<td><?= esc($d['class_section_name'] ?? '') ?></td>
|
|
<td class="title-cell"><?= esc($d['draft_title'] ?? $d['exam_type'] ?? '') ?></td>
|
|
<td><?= esc((string) ($d['version'] ?? '')) ?></td>
|
|
<td><?= $renderBadge($st, $statusBadges) ?></td>
|
|
<td><?= esc($d['school_year'] ?? '') ?></td>
|
|
<td><?= esc($d['semester'] ?? '') ?></td>
|
|
<td><?= esc($d['exam_type'] ?? '') ?></td>
|
|
<td><?= esc(!empty($d['updated_at']) ? local_datetime($d['updated_at'], 'm-d-Y H:i') : '') ?></td>
|
|
<td>
|
|
<?php if (!empty($d['final_file'])): ?>
|
|
<a class="btn btn-sm btn-outline-primary" href="<?= base_url('exam-drafts/files/final/' . $d['final_file']) ?>" target="_blank" rel="noopener">View</a>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?= $this->endSection() ?>
|
|
|
|
<?= $this->section('scripts') ?>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const csrfTokenName = <?= json_encode(csrf_token(), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
|
const csrfCookieNames = <?= json_encode(array_values(array_filter([
|
|
config('Security')->csrfCookieName ?? null,
|
|
config('Security')->cookieName ?? null,
|
|
'csrf_cookie_name',
|
|
])), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
|
|
|
const readCookie = (name) => {
|
|
const match = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/[$()*+.?[\\\]^{|}-]/g, '\\$&') + '=([^;]*)'));
|
|
return match ? decodeURIComponent(match[1]) : '';
|
|
};
|
|
|
|
const syncCsrfToken = (form) => {
|
|
let tokenValue = '';
|
|
csrfCookieNames.some((name) => {
|
|
tokenValue = readCookie(name);
|
|
return tokenValue !== '';
|
|
});
|
|
if (!tokenValue || !form) {
|
|
return;
|
|
}
|
|
const tokenInput = form.querySelector(`input[name="${csrfTokenName}"]`);
|
|
if (tokenInput) {
|
|
tokenInput.value = tokenValue;
|
|
}
|
|
};
|
|
|
|
const draftForm = document.getElementById('examDraftForm');
|
|
if (draftForm) {
|
|
draftForm.addEventListener('submit', () => syncCsrfToken(draftForm));
|
|
}
|
|
|
|
const statusBadges = <?= json_encode($statusBadges, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
|
const rows = new Map();
|
|
document.querySelectorAll('tr[data-draft-id]').forEach((row) => {
|
|
const id = row.getAttribute('data-draft-id');
|
|
if (id) {
|
|
rows.set(id, row);
|
|
}
|
|
});
|
|
|
|
const updateRow = (row, status, acceptanceType) => {
|
|
const badge = row.querySelector('.js-status-badge');
|
|
if (badge) {
|
|
const badgeData = statusBadges[status] || { label: status, class: 'bg-secondary text-white' };
|
|
badge.textContent = badgeData.label || status;
|
|
badge.className = `badge ${badgeData.class} js-status-badge`;
|
|
if (badgeData.style) {
|
|
badge.setAttribute('style', badgeData.style);
|
|
} else {
|
|
badge.removeAttribute('style');
|
|
}
|
|
badge.dataset.status = status;
|
|
}
|
|
const note = row.querySelector('.js-acceptance-note');
|
|
if (note) {
|
|
if (status === 'accepted' && acceptanceType) {
|
|
note.textContent = acceptanceType === 'minor_edits' ? 'With minor edits' : 'As is';
|
|
} else {
|
|
note.textContent = '—';
|
|
}
|
|
}
|
|
};
|
|
|
|
const poll = () => {
|
|
fetch('<?= base_url('teacher/exam-drafts/status') ?>', { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
|
.then((resp) => resp.ok ? resp.json() : null)
|
|
.then((data) => {
|
|
if (!data || !Array.isArray(data.drafts)) {
|
|
return;
|
|
}
|
|
data.drafts.forEach((draft) => {
|
|
const row = rows.get(String(draft.id));
|
|
if (row) {
|
|
updateRow(row, draft.status, draft.acceptance_type);
|
|
}
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
};
|
|
|
|
if (rows.size > 0) {
|
|
poll();
|
|
setInterval(poll, 15000);
|
|
}
|
|
|
|
const legacyToggle = document.getElementById('legacyToggle');
|
|
const legacySection = document.getElementById('legacySection');
|
|
if (legacyToggle && legacySection) {
|
|
legacyToggle.addEventListener('click', () => {
|
|
legacySection.classList.toggle('d-none');
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
<?= $this->endSection() ?>
|