diff --git a/backend/src/controllers/assignmentController.js b/backend/src/controllers/assignmentController.js
index b635729..1e6df80 100644
--- a/backend/src/controllers/assignmentController.js
+++ b/backend/src/controllers/assignmentController.js
@@ -2,6 +2,7 @@ const db = require('../db/db');
const { pushNotif } = require('../utils/notifications');
const { stripTags } = require('../utils/sanitize');
const { SESSION_MODES } = require('../constants');
+const AssignmentUtils = require('../../../frontend/js/assignment-utils.js'); // единый источник: тип/«сдано»
const VALID_ASSIGN_MODES = SESSION_MODES;
@@ -344,14 +345,6 @@ function myAssignments(req, res) {
res.json(assignmentRowsForUser(req.user.id));
}
-/* Тип задания — те же признаки, что на /homework (asgnType). */
-function _assignTypeOf(r) {
- if (r.textbook_id) return 'textbook';
- if (r.file_id) return 'file';
- if (r.is_homework && (r.count == null || r.count <= 1) && (!r.subject_slug || r.subject_slug === 'other')) return 'upload';
- return 'test';
-}
-
/* ── GET /api/classes/:id/outstanding ── что «висит» у каждого ученика класса ──
Учитель/админ видят по каждому ученику его НЕзакрытые задания (классовые + личные
от этого учителя) со статусом: не начато / в процессе / на доработке / просрочено. */
@@ -388,23 +381,16 @@ function classOutstanding(req, res) {
);
const pending = [];
for (const r of rows) {
- const type = _assignTypeOf(r);
+ const t = AssignmentUtils.type(r);
+ const st = (t === 'upload' || t === 'file') ? subMap.get(r.id + '_' + m.id) : null;
+ // Учительская семантика: любая сдача не на доработке = не долг (default opts).
+ if (AssignmentUtils.isDone(r, st ? { status: st } : null)) continue;
const overdue = r.deadline && new Date(r.deadline).getTime() < now;
- let done = false, status = overdue ? 'overdue' : 'not_started';
- if (type === 'textbook') {
- done = !!(r.textbook_all_read || r.completed_at);
- } else if (type === 'upload' || type === 'file') {
- const st = subMap.get(r.id + '_' + m.id);
- if (st && st !== 'revision') done = true; // сдал — на проверке/принято
- else if (st === 'revision') status = 'revision'; // вернули на доработку
- } else { // test
- const maxAtt = r.max_attempts || 0, used = r.attempts_used || 0;
- if (maxAtt > 0 && used >= maxAtt) done = true;
- else if (r.session_status === 'completed' && r.mode !== 'repeat') done = true;
- else if (r.session_status === 'in_progress') status = 'in_progress';
- }
- if (!done) pending.push({
- assignment_id: r.id, title: r.title, type, deadline: r.deadline,
+ let status = overdue ? 'overdue' : 'not_started';
+ if (st === 'revision') status = 'revision'; // вернули на доработку
+ else if (t === 'test' && r.session_status === 'in_progress') status = 'in_progress';
+ pending.push({
+ assignment_id: r.id, title: r.title, type: t, deadline: r.deadline,
status, is_homework: r.is_homework ? 1 : 0,
scope: r.class_id === cidNum ? 'class' : 'direct',
});
diff --git a/frontend/dashboard.html b/frontend/dashboard.html
index 5797f9a..3a85a0a 100644
--- a/frontend/dashboard.html
+++ b/frontend/dashboard.html
@@ -1889,6 +1889,7 @@
+
@@ -2381,15 +2382,8 @@
body.classList.toggle('collapsed');
}
- /* ── Urgency sort score (lower = shown first) ── */
- function urgencyScore(a) {
- if (a.session_status === 'in_progress') return -4; // in progress top
- const dlMs = a.deadline ? new Date(a.deadline) - Date.now() : Infinity;
- if (dlMs < 0) return -3; // overdue
- if (dlMs < 24 * 3600 * 1000) return -2; // urgent <24h
- if (dlMs < Infinity) return dlMs; // sorted by deadline
- return 1e12; // no deadline last
- }
+ /* ── Urgency sort score (lower = shown first) — общий модуль ── */
+ function urgencyScore(a) { return AssignmentUtils.urgencyScore(a); }
/* ── Is assignment urgent for teacher (within 48h) ── */
function isTeacherUrgent(a) {
@@ -2457,7 +2451,7 @@
}
/* ── Upload-only homework (no test, no file) ── */
- if (a.is_homework && !a.file_id && !a.session_id && a.count <= 1 && (!a.subject_slug || a.subject_slug === 'other')) {
+ if (AssignmentUtils.type(a) === 'upload') {
const over = a.deadline && new Date(a.deadline) < new Date();
const sub = _mySubmissions.get(a.id);
const metaParts = [classStr, dl ? `до ${dl}` : null,
@@ -2694,18 +2688,22 @@
reIcons(); return;
}
- // Classify
+ // Classify (active/overdue/done) — тип и «сдано» из общего модуля AssignmentUtils.
function classify(a) {
- const maxAtt = a.max_attempts || 0;
- const usedAtt = a.attempts_used ?? 0;
- if (a.textbook_id) {
- if (a.completed_at || a.textbook_all_read) return 'done';
+ const t = AssignmentUtils.type(a);
+ if (t === 'textbook') {
+ if (AssignmentUtils.isDone(a)) return 'done';
if (a.deadline && new Date(a.deadline) < now) return 'overdue';
return 'active';
}
- if (maxAtt > 0 && usedAtt >= maxAtt) return 'done';
- if (a.session_status === 'completed' && a.mode !== 'repeat') return 'done';
- if (!a.file_id && a.deadline && new Date(a.deadline) < now && a.session_status !== 'in_progress') return 'overdue';
+ if (t === 'test') {
+ if (AssignmentUtils.isDone(a)) return 'done';
+ if (a.deadline && new Date(a.deadline) < now && a.session_status !== 'in_progress') return 'overdue';
+ return 'active';
+ }
+ // upload / file: «сдано» по сабмишену здесь не считаем (как и раньше — статус
+ // показывает чип сдачи в карточке); upload просрочивается по дедлайну, file — всегда активен.
+ if (t === 'upload' && a.deadline && new Date(a.deadline) < now) return 'overdue';
return 'active';
}
diff --git a/frontend/homework.html b/frontend/homework.html
index 2f0578c..507e987 100644
--- a/frontend/homework.html
+++ b/frontend/homework.html
@@ -258,6 +258,7 @@
+