diff --git a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt index 3d840e8..97c524b 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt @@ -163,6 +163,8 @@ class AdminGroupController( defenseCode = def?.code, defenseSecondsLeft = def?.secondsLeft, defenseAssigned = def?.assignmentMd?.isNotBlank() == true, + defenseSessionId = def?.id, + defenseAssignmentMd = def?.assignmentMd, ) } GroupStudentRow( @@ -185,11 +187,8 @@ class AdminGroupController( ) } - val defenseRows = - buildDefenseRows(t, group, labs) - val candidates = runCatching { api.groupCandidates(t, id) }.getOrDefault(emptyList()) - fillGroupModel(model, GroupViewModel(group, labs, candidates, rows, defenseRows, publishedLabs)) + fillGroupModel(model, GroupViewModel(group, labs, candidates, rows, publishedLabs)) return "admin/group" } @@ -198,38 +197,11 @@ class AdminGroupController( id: String, ): Pair> = api.groupDetail(token, id) to api.listLabs(token).filter { it.status == "published" } - /** Строки для блока «Защиты»: сессии защиты по всем лабораторным группы. */ - private fun buildDefenseRows( - token: String, - group: GroupDetailDto, - labs: List, - ): List = - labs - .flatMap { lab -> - val byStudent = api.labDefenses(token, lab.id).associateBy { it.studentId } - group.students.mapNotNull { s -> - val d = byStudent[s.id] ?: return@mapNotNull null - GroupDefenseRow( - sessionId = d.id, - studentId = s.id, - fullName = s.fullName, - studentNo = s.studentNo, - labId = lab.id, - labNumber = lab.number, - status = d.status, - code = d.code?.takeIf { d.status == "waiting" }, - assignmentMd = d.assignmentMd, - secondsLeft = d.secondsLeft, - ) - } - }.sortedWith(compareBy({ it.fullName.lowercase() }, { it.labNumber })) - private data class GroupViewModel( val group: GroupDetailDto, val labs: List, val candidates: List, val rows: List, - val defenseRows: List, val publishedLabs: Set, ) @@ -241,7 +213,6 @@ class AdminGroupController( model.addAttribute("labs", view.labs) model.addAttribute("candidates", view.candidates) model.addAttribute("studentGrades", view.rows) - model.addAttribute("defenseRows", view.defenseRows) model.addAttribute("publishedLabIds", view.publishedLabs) } diff --git a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminLabView.kt b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminLabView.kt index bb95302..912d744 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminLabView.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminLabView.kt @@ -51,6 +51,8 @@ data class GroupLabCell( val defenseCode: String? = null, val defenseSecondsLeft: Long? = null, val defenseAssigned: Boolean = false, + val defenseSessionId: String? = null, + val defenseAssignmentMd: String? = null, ) /** Строка студента в матрице + комментарии для диалога. */ diff --git a/web/src/main/resources/static/css/app.css b/web/src/main/resources/static/css/app.css index bac1ec3..cd58f24 100644 --- a/web/src/main/resources/static/css/app.css +++ b/web/src/main/resources/static/css/app.css @@ -200,6 +200,33 @@ tr:last-child td { border-bottom: none; } /* Toast-уведомление */ .toast { position: fixed; top: 1rem; right: 1rem; z-index: 50; width: 20rem; } +/* Всплывающие уведомления (flash notice/error) вместо статичных алертов */ +.toast-host { + position: fixed; top: 1rem; right: 1rem; z-index: 100; + display: flex; flex-direction: column; gap: 0.5rem; + width: min(24rem, calc(100vw - 2rem)); pointer-events: none; +} +.toast-item { + pointer-events: auto; display: flex; align-items: flex-start; gap: 0.5rem; + padding: 0.7rem 0.85rem; border-radius: 0.5rem; border: 1px solid var(--c-border); + border-left-width: 4px; background: var(--c-surface); color: var(--c-text); + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); + animation: toast-in 0.18s ease-out; +} +.toast-item .toast-text { flex: 1 1 auto; min-width: 0; white-space: pre-line; overflow-wrap: anywhere; } +.toast-item.toast-success { border-left-color: #16a34a; } +.toast-item.toast-error { border-left-color: #dc2626; } +.toast-item.toast-warning { border-left-color: #d97706; } +.toast-close { + flex: 0 0 auto; border: none; background: none; cursor: pointer; + color: var(--c-text-muted); font-size: 1.05rem; line-height: 1; padding: 0 0.1rem; +} +.toast-close:hover { color: var(--c-text); } +@keyframes toast-in { + from { opacity: 0; transform: translateY(-6px); } + to { opacity: 1; transform: none; } +} + /* Modals */ dialog.modal { border: none; border-radius: 0.75rem; padding: 0; diff --git a/web/src/main/resources/static/js/admin-group.js b/web/src/main/resources/static/js/admin-group.js index 6354b16..c663b51 100644 --- a/web/src/main/resources/static/js/admin-group.js +++ b/web/src/main/resources/static/js/admin-group.js @@ -65,9 +65,10 @@ function csSerialize() { $id('grade-comments').value = JSON.stringify(out); } -/* ---------- Индивидуальные задания (защита) ---------- */ +/* ---------- Индивидуальное задание (защита) ---------- */ -function openDefenseForm(btn) { +/** Открывает диалог задания; кнопка может не нести data-* (тогда это «назначить новое»). */ +function openDefense(btn) { var form = $id('defense-form'); if (!form) return; form.reset(); @@ -76,29 +77,41 @@ function openDefenseForm(btn) { if (student) $id('defense-student-select').value = student; if (lab) $id('defense-lab-select').value = lab; $id('defense-md').value = btn.getAttribute('data-md') || ''; + + var session = btn.getAttribute('data-session') || ''; + var status = btn.getAttribute('data-status') || ''; + var actions = $id('defense-actions'); + if (session) { + $id('defense-issue-form').action = '/admin/defenses/' + session + '/issue-code'; + $id('defense-reset-form').action = '/admin/defenses/' + session + '/reset'; + if ($id('defense-action-lab')) $id('defense-action-lab').value = lab; + if ($id('defense-reset-lab')) $id('defense-reset-lab').value = lab; + $id('defense-reset-form').hidden = status !== 'ready'; + if (actions) actions.hidden = false; + } else if (actions) { + actions.hidden = true; + } + var d = $id('modal-defense'); if (d && d.showModal) d.showModal(); } -function showDefenseText(btn) { +function showDefenseFromModal() { var el = $id('defense-text-body'); - if (el) el.textContent = btn.getAttribute('data-md') || '(пусто)'; + if (el) el.textContent = $id('defense-md').value || '(пусто)'; var title = $id('defense-text-title'); - if (title) title.textContent = (btn.getAttribute('data-student-name') || '') + ' — ' + (btn.getAttribute('data-lab-name') || ''); + if (title) { + var s = $id('defense-student-select'); + var l = $id('defense-lab-select'); + var who = s && s.selectedOptions[0] ? s.selectedOptions[0].textContent : ''; + var lab = l && l.selectedOptions[0] ? l.selectedOptions[0].textContent : ''; + title.textContent = who + ' — ' + lab; + } var d = $id('modal-defense-text'); if (d && d.showModal) d.showModal(); } -function filterDefense() { - var s = $id('defense-lab-filter'); - if (!s) return; - var labId = s.value; - document.querySelectorAll('#defense-table tr[data-lab]').forEach(function (tr) { - tr.style.display = (!labId || tr.getAttribute('data-lab') === labId) ? '' : 'none'; - }); -} - -/* ---------- Матрица: фокус на лабораторной и закрепление варианта ---------- */ +/* ---------- Матрица: фокус на лабораторной ---------- */ function focusLab() { var s = $id('lab-focus'); @@ -113,4 +126,4 @@ document.addEventListener('submit', function (ev) { if (ev.target && ev.target.id === 'grade-form') csSerialize(); }, true); -window.addEventListener('DOMContentLoaded', function () { filterDefense(); focusLab(); }); +window.addEventListener('DOMContentLoaded', function () { focusLab(); }); diff --git a/web/src/main/resources/templates/admin/group.html b/web/src/main/resources/templates/admin/group.html index 5c63f02..7f7e07c 100644 --- a/web/src/main/resources/templates/admin/group.html +++ b/web/src/main/resources/templates/admin/group.html @@ -14,22 +14,18 @@ + + Скачать все работы Логины XLSX CSV -
-
-
- Новый пароль: password -
-
-

Результаты по лабораторным

+

Студенты и лабораторные

- + + +
+ + +
+ - В группе нет студентов. + В группе нет студентов.
-
-

Индивидуальные задания (защита)

-
- - - - - -
-
-
- - - - - - - - - - - - -
СтудентЛРЗаданиеСтатусДействия
-
—
-
—
-
—— - ждёт кода - код выдан - пишет ( c) - готов - -
- - - - -
- - -
- - - - -
-
Заданий для защиты пока нет.
-
- -
-

Студенты

- -
-
- - - - - - - - - - - - - - -
№ФИОЛогинОсноваВариантыПарольДействия
1——— - — - не распределены - временный - изменён -
- - -
-
- - -
-
В группе пока нет студентов.
-
- - + + @@ -359,7 +298,6 @@ - diff --git a/web/src/main/resources/templates/admin/groups.html b/web/src/main/resources/templates/admin/groups.html index 80f8cd1..35dc7ba 100644 --- a/web/src/main/resources/templates/admin/groups.html +++ b/web/src/main/resources/templates/admin/groups.html @@ -4,8 +4,6 @@

Группы

-
-

Создать группу

diff --git a/web/src/main/resources/templates/admin/lab-edit.html b/web/src/main/resources/templates/admin/lab-edit.html index ab75661..07ffdaa 100644 --- a/web/src/main/resources/templates/admin/lab-edit.html +++ b/web/src/main/resources/templates/admin/lab-edit.html @@ -5,8 +5,6 @@
← К лабораторной

Настройка лабораторной

-
-
diff --git a/web/src/main/resources/templates/admin/lab.html b/web/src/main/resources/templates/admin/lab.html index 3781610..1af052f 100644 --- a/web/src/main/resources/templates/admin/lab.html +++ b/web/src/main/resources/templates/admin/lab.html @@ -29,8 +29,6 @@
-
-

Условие лабораторной

diff --git a/web/src/main/resources/templates/admin/labs.html b/web/src/main/resources/templates/admin/labs.html index dcc3490..0027ff4 100644 --- a/web/src/main/resources/templates/admin/labs.html +++ b/web/src/main/resources/templates/admin/labs.html @@ -14,8 +14,6 @@

Статус draft — черновик (редактирование, студентам не видна). published — опубликована (доступна студентам, видны распределённые варианты). Нажмите на бейдж статуса, чтобы переключить.

-
-
diff --git a/web/src/main/resources/templates/change_password.html b/web/src/main/resources/templates/change_password.html index 7117d39..21ae338 100644 --- a/web/src/main/resources/templates/change_password.html +++ b/web/src/main/resources/templates/change_password.html @@ -12,7 +12,6 @@

Смените пароль, если хотите. После сохранения вы останетесь в системе.

-

Минимум 8 символов.

diff --git a/web/src/main/resources/templates/fragments/base.html b/web/src/main/resources/templates/fragments/base.html index a2f6178..4401f8e 100644 --- a/web/src/main/resources/templates/fragments/base.html +++ b/web/src/main/resources/templates/fragments/base.html @@ -108,5 +108,33 @@ LMS v0.2.0 +
+
+ … + +
+
+ … + +
+
+ Новый пароль: password + +
+
+ \ No newline at end of file diff --git a/web/src/main/resources/templates/learn/index.html b/web/src/main/resources/templates/learn/index.html index 7656aee..9c332f8 100644 --- a/web/src/main/resources/templates/learn/index.html +++ b/web/src/main/resources/templates/learn/index.html @@ -20,9 +20,6 @@ Изменения не сохранены… -
-
-
Материалы ещё не добавлены.
diff --git a/web/src/main/resources/templates/login.html b/web/src/main/resources/templates/login.html index e6ff71e..0458adc 100644 --- a/web/src/main/resources/templates/login.html +++ b/web/src/main/resources/templates/login.html @@ -5,7 +5,6 @@

Вход

-
Аккаунт администратора ещё не создан. Создать первый аккаунт
diff --git a/web/src/main/resources/templates/setup.html b/web/src/main/resources/templates/setup.html index d6ad736..b9b0df5 100644 --- a/web/src/main/resources/templates/setup.html +++ b/web/src/main/resources/templates/setup.html @@ -6,7 +6,6 @@

Создание администратора

Это разовая настройка. Аккаунт преподавателя создаётся один раз.

-

Минимум 8 символов.

diff --git a/web/src/main/resources/templates/student/lab.html b/web/src/main/resources/templates/student/lab.html index 35ac798..6afa306 100644 --- a/web/src/main/resources/templates/student/lab.html +++ b/web/src/main/resources/templates/student/lab.html @@ -14,9 +14,6 @@ Защита: МСК

-
-
-

Условие лабораторной