306 lines
20 KiB
HTML
306 lines
20 KiB
HTML
{% extends 'lms/base.html' %}
|
|
{% load lms_tags %}
|
|
|
|
{% block title %}{{ assignment.title }} | ЕГЭ LMS{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Page Header -->
|
|
<div class="mb-7">
|
|
<a href="{% url 'lms:dashboard' %}" class="inline-flex items-center gap-1.5 text-sm font-medium text-txt-muted hover:text-white transition-colors font-heading mb-4 bg-obsidian-card border border-obsidian-border hover:border-obsidian-borderLight px-3 py-1.5 rounded-lg">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path d="M10 19l-7-7m0 0l7-7m-7 7h18" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
|
|
</svg>
|
|
Назад к списку
|
|
</a>
|
|
|
|
<div class="flex flex-col md:flex-row md:items-start justify-between gap-4">
|
|
<div>
|
|
<h1 class="text-2xl sm:text-3xl font-bold text-white tracking-tight font-heading">{{ assignment.title }}</h1>
|
|
<div class="mt-2 flex items-center gap-3">
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-[11px] font-mono uppercase tracking-wider bg-obsidian-card text-txt-secondary border border-obsidian-border">
|
|
Предмет: {{ assignment.subject.name }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Description block -->
|
|
<div class="bg-obsidian-card rounded-xl border border-obsidian-border p-6 sm:p-8 mb-8 shadow-sm">
|
|
<h3 class="text-lg font-bold text-white mb-4 font-heading border-b border-obsidian-border pb-3">Описание задания</h3>
|
|
<div class="prose prose-invert max-w-none text-txt-secondary font-body prose-a:text-accent-blue hover:prose-a:text-accent-blueHover">
|
|
{{ assignment.description|safe }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-obsidian-card rounded-xl border border-obsidian-border p-6 sm:p-8 shadow-sm">
|
|
<h3 class="text-lg font-bold text-white mb-6 font-heading border-b border-obsidian-border pb-3">Ваш ответ</h3>
|
|
|
|
{% if submission and submission.status != 'IN_PROGRESS' %}
|
|
<div class="mb-8 p-5 rounded-lg border {% if submission.status == 'GRADED' %}bg-emerald-950/20 text-emerald-300 border-emerald-800/40{% else %}bg-amber-950/20 text-amber-300 border-amber-800/40{% endif %}">
|
|
{% if submission.status == 'GRADED' %}
|
|
<h4 class="font-bold mb-2 flex items-center gap-2 font-heading">
|
|
<svg class="w-5 h-5 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
|
|
</svg>
|
|
Оценка: {{ submission.grade|default:"Не выставлена" }}
|
|
</h4>
|
|
{% else %}
|
|
<h4 class="font-bold mb-2 flex items-center gap-2 font-heading">
|
|
<span class="w-2 h-2 rounded-full bg-amber-400 animate-pulse"></span>
|
|
Ваша работа отправлена и ожидает проверки.
|
|
</h4>
|
|
{% endif %}
|
|
|
|
{% if submission.teacher_feedback %}
|
|
<div class="mt-4 p-4 rounded bg-obsidian-base border border-obsidian-borderLight">
|
|
<p class="font-medium text-white text-sm mb-1">Общий комментарий преподавателя:</p>
|
|
<p class="text-sm text-txt-secondary">{{ submission.teacher_feedback }}</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<h4 class="font-bold mb-5 text-white font-heading">Ваши ответы на задачи:</h4>
|
|
<div class="space-y-6">
|
|
{% for answer in submission.answers.all %}
|
|
<div class="p-5 bg-obsidian-base border border-obsidian-border rounded-lg relative overflow-hidden">
|
|
<!-- Status indicator line -->
|
|
<div class="absolute left-0 top-0 bottom-0 w-1 {% if answer.is_correct %}bg-emerald-500{% elif answer.is_correct == False %}bg-rose-500{% else %}bg-txt-faint{% endif %}"></div>
|
|
|
|
<h5 class="font-medium text-sm text-txt-muted mb-3 pl-2">Задача {{ forloop.counter }}: {{ answer.question.text|striptags|truncatewords:10 }}</h5>
|
|
|
|
<div class="pl-2">
|
|
{% if answer.question.question_type == 'TEXT_AUTO' %}
|
|
<div class="bg-obsidian-card p-3 rounded border border-obsidian-borderLight mb-3">
|
|
<span class="text-xs text-txt-faint uppercase font-mono block mb-1">Ваш ответ</span>
|
|
<p class="text-white font-medium">{{ answer.text_answer|default:"Нет" }}</p>
|
|
</div>
|
|
{% elif answer.question.question_type == 'TABLE_AUTO' %}
|
|
<span class="text-xs text-txt-faint uppercase font-mono block mb-2">Ваш ответ (Таблица)</span>
|
|
{% with table_data=answer.text_answer|parse_json %}
|
|
{% if table_data %}
|
|
<div class="overflow-x-auto mb-3">
|
|
<table class="w-full border-collapse border border-obsidian-borderLight text-sm text-left">
|
|
{% for row in table_data %}
|
|
<tr>
|
|
<td class="border border-obsidian-borderLight p-2.5 text-txt-secondary bg-obsidian-card">{{ row.0 }}</td>
|
|
<td class="border border-obsidian-borderLight p-2.5 text-white bg-obsidian-base">{{ row.1 }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-txt-muted mb-3">Нет ответа</p>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% else %}
|
|
{% if answer.file_answer %}
|
|
<p class="text-txt-secondary mb-3 text-sm">
|
|
<strong class="text-white">Прикрепленный файл:</strong>
|
|
<a href="{{ answer.file_answer.url }}" class="inline-flex items-center gap-1 text-accent-blue hover:text-accent-blueHover">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path></svg>
|
|
Скачать
|
|
</a>
|
|
</p>
|
|
{% else %}
|
|
<p class="text-txt-muted mb-3 text-sm">Файл не прикреплен</p>
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
<div class="flex flex-wrap items-center gap-4 text-sm mt-3 pt-3 border-t border-obsidian-borderLight">
|
|
<div class="flex items-center gap-1.5 {% if answer.is_correct %}text-emerald-400{% elif answer.is_correct == False %}text-rose-400{% else %}text-txt-muted{% endif %} font-medium">
|
|
{% if answer.is_correct %}
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path></svg>
|
|
Верно
|
|
{% elif answer.is_correct == False %}
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path></svg>
|
|
Неверно
|
|
{% else %}
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path></svg>
|
|
Ожидает проверки
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if answer.points is not None %}
|
|
<div class="px-2 py-0.5 rounded bg-obsidian-card border border-obsidian-borderLight font-mono text-accent-blue text-xs">
|
|
Баллы: {{ answer.points }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if answer.comment %}
|
|
<div class="mt-3 p-3 text-sm bg-obsidian-card border border-obsidian-borderLight rounded-r-lg border-l-2 border-l-accent-blue">
|
|
<span class="text-txt-faint text-xs font-mono uppercase block mb-1">Комментарий преподавателя</span>
|
|
<span class="text-txt-secondary">{{ answer.comment }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{% elif needs_start %}
|
|
<div class="p-8 text-center rounded-2xl border bg-obsidian-base border-accent-blue/30 shadow-lg relative overflow-hidden">
|
|
<div class="absolute inset-0 bg-accent-blue/5 pointer-events-none"></div>
|
|
|
|
<svg class="w-16 h-16 mx-auto text-accent-blue mb-5 relative z-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
|
|
<h4 class="text-2xl font-bold mb-3 text-white font-heading relative z-10">Задание на время</h4>
|
|
<p class="text-txt-secondary text-base mb-8 max-w-xl mx-auto relative z-10">На выполнение этого задания отводится <strong class="text-white font-mono bg-obsidian-card px-2 py-0.5 rounded border border-obsidian-border">{{ assignment.time_limit }} мин.</strong> Как только вы нажмете кнопку ниже, запустится обратный отсчет. По истечении времени ваши ответы будут отправлены автоматически.</p>
|
|
|
|
<form method="post" class="relative z-10">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="start">
|
|
<button type="submit" class="inline-flex items-center gap-2 px-8 py-3.5 bg-accent-blue text-white font-bold rounded-lg shadow-lg shadow-accent-blue/20 hover:bg-accent-blueHover hover:shadow-accent-blue/40 transition-all active:scale-95 font-heading text-lg">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path><path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
Начать выполнение
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
{% elif not can_submit %}
|
|
<div class="p-5 rounded-lg border bg-rose-950/20 text-rose-300 border-rose-800/40">
|
|
<h4 class="font-bold mb-2 flex items-center gap-2 font-heading">
|
|
<svg class="w-5 h-5 text-rose-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
|
Прием ответов завершен
|
|
</h4>
|
|
<p class="text-sm">Вы больше не можете отправлять ответы, так как дедлайн истек или время на выполнение закончилось.</p>
|
|
</div>
|
|
|
|
{% else %}
|
|
{% if time_left_seconds is not None %}
|
|
<div id="timer-banner" class="sticky top-16 sm:top-20 z-40 mb-8 p-4 rounded-xl border bg-obsidian-base border-accent-blue shadow-lg shadow-accent-blue/10 flex justify-between items-center transition-colors duration-300">
|
|
<div class="flex items-center gap-2.5">
|
|
<svg class="w-6 h-6 text-accent-blue animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
<span class="font-medium text-sm sm:text-base text-white">Осталось времени:</span>
|
|
</div>
|
|
<div class="text-2xl sm:text-3xl font-mono font-bold tracking-wider text-white bg-obsidian-card px-3 py-1 rounded-lg border border-obsidian-border" id="countdown-display">
|
|
--:--:--
|
|
</div>
|
|
</div>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
let timeLeft = {{ time_left_seconds }};
|
|
const display = document.getElementById('countdown-display');
|
|
const banner = document.getElementById('timer-banner');
|
|
const form = document.getElementById('assignment-form');
|
|
|
|
function updateTimer() {
|
|
if (timeLeft <= 0) {
|
|
display.textContent = '00:00:00';
|
|
display.classList.add('text-rose-400', 'border-rose-500/50', 'bg-rose-950/30');
|
|
display.classList.remove('text-white', 'bg-obsidian-card', 'border-obsidian-border');
|
|
banner.classList.add('border-rose-500', 'shadow-rose-500/20');
|
|
banner.classList.remove('border-accent-blue', 'shadow-accent-blue/10');
|
|
|
|
// Auto submit
|
|
if (form) {
|
|
form.onsubmit = null;
|
|
const btn = form.querySelector('button[type="submit"]');
|
|
if (btn) btn.disabled = true;
|
|
|
|
const input = document.createElement('input');
|
|
input.type = 'hidden';
|
|
input.name = 'auto_submit';
|
|
input.value = '1';
|
|
form.appendChild(input);
|
|
|
|
form.submit();
|
|
}
|
|
return;
|
|
}
|
|
|
|
const h = Math.floor(timeLeft / 3600);
|
|
const m = Math.floor((timeLeft % 3600) / 60);
|
|
const s = timeLeft % 60;
|
|
display.textContent =
|
|
(h > 0 ? (h < 10 ? '0' + h : h) + ':' : '') +
|
|
(m < 10 ? '0' + m : m) + ':' +
|
|
(s < 10 ? '0' + s : s);
|
|
|
|
if (timeLeft < 60 && !banner.classList.contains('border-rose-500')) {
|
|
display.classList.add('text-rose-400');
|
|
banner.classList.add('border-rose-500', 'bg-rose-950/10');
|
|
banner.classList.remove('border-accent-blue', 'bg-obsidian-base');
|
|
}
|
|
|
|
timeLeft--;
|
|
setTimeout(updateTimer, 1000);
|
|
}
|
|
|
|
if (timeLeft > 0) {
|
|
updateTimer();
|
|
}
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
|
|
<form id="assignment-form" method="post" enctype="multipart/form-data" class="space-y-6">
|
|
{% csrf_token %}
|
|
|
|
{% for q, field_type, field in question_fields %}
|
|
<div class="p-5 sm:p-6 border rounded-xl bg-obsidian-base border-obsidian-border transition-colors focus-within:border-obsidian-borderLight">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-obsidian-card border border-obsidian-borderLight text-txt-secondary font-mono text-sm font-bold">
|
|
{{ forloop.counter }}
|
|
</span>
|
|
<h4 class="font-semibold text-lg text-white font-heading">Задача</h4>
|
|
</div>
|
|
|
|
<div class="prose prose-invert max-w-none text-txt-secondary mb-6 font-body">
|
|
{{ q.text|safe }}
|
|
</div>
|
|
|
|
{% if q.attached_file %}
|
|
<div class="mb-6">
|
|
<a href="{{ q.attached_file.url }}" class="inline-flex items-center gap-2 px-4 py-2 bg-obsidian-card border border-obsidian-borderLight text-txt-secondary rounded-lg hover:bg-obsidian-border hover:text-white transition-colors text-sm font-medium" target="_blank">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path></svg>
|
|
Скачать файл к задаче
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="bg-obsidian-card p-5 rounded-lg border border-obsidian-borderLight">
|
|
{% if field_type == 'table' %}
|
|
<p class="block text-sm font-medium text-txt-secondary mb-3">Заполните таблицу:</p>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full border-collapse border border-obsidian-borderLight mb-1 text-sm bg-obsidian-base rounded-lg overflow-hidden">
|
|
{% for row_fields in field %}
|
|
<tr>
|
|
<td class="border border-obsidian-borderLight p-2 w-1/2">
|
|
<div class="relative">{{ row_fields.0 }}</div>
|
|
</td>
|
|
<td class="border border-obsidian-borderLight p-2 w-1/2">
|
|
<div class="relative">{{ row_fields.1 }}</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<label for="{{ field.id_for_label }}" class="block text-sm font-medium text-txt-secondary mb-2">{{ field.label }}</label>
|
|
{{ field }}
|
|
{% if field.errors %}
|
|
<p class="mt-2 text-sm text-rose-400 flex items-center gap-1">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
|
{{ field.errors.0 }}
|
|
</p>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<div class="pt-6 border-t border-obsidian-border flex justify-end">
|
|
<button type="submit" class="inline-flex items-center gap-2 px-8 py-3 bg-accent-blue text-white font-bold rounded-lg shadow-md hover:bg-accent-blueHover hover:shadow-accent-blue/20 transition-all active:scale-95 font-heading">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path></svg>
|
|
Отправить ответ
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|