From 7818f459bd2069d742d63d0fd28381cbf98f7f3e Mon Sep 17 00:00:00 2001 From: Vladislav Kopnin Date: Fri, 11 Sep 2026 23:11:40 +0300 Subject: [PATCH] Phase 1 fixes: broken dialogs, score width, status badge, MSK date round-trip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - group/lab/labs: move dialogs/scripts INSIDE the #content fragment (they were outside — the layout fragment dropped them, which is why 'add student' did nothing) - group.html toast had swallowed the dialog (th:if createdStudent) — split properly - .input-score width no longer overridden by .inline-form .input-sm (10rem min) - lab page: remove separate 'change status' button — status badge is now a toggle button itself (confirm dialog kept) - DateUtils.localInput/toUtcIso: dates entered in datetime-local are Moscow-local, stored as UTC instant, shown back MSK — correct round-trip both ways - academic parseInstant: naive local datetimes treated as Europe/Moscow - README/module docs left as-is --- .../ru/ysu/lms/academic/lab/LabService.kt | 4 +- .../main/kotlin/ru/ysu/lms/web/DateUtils.kt | 28 ++- .../ru/ysu/lms/web/admin/AdminController.kt | 14 +- web/src/main/resources/static/css/app.css | 2 + .../main/resources/templates/admin/group.html | 59 ++--- .../main/resources/templates/admin/lab.html | 223 +++++++++--------- .../main/resources/templates/admin/labs.html | 75 +++--- 7 files changed, 215 insertions(+), 190 deletions(-) diff --git a/services/academic-service/src/main/kotlin/ru/ysu/lms/academic/lab/LabService.kt b/services/academic-service/src/main/kotlin/ru/ysu/lms/academic/lab/LabService.kt index 8ea3866..8a63766 100644 --- a/services/academic-service/src/main/kotlin/ru/ysu/lms/academic/lab/LabService.kt +++ b/services/academic-service/src/main/kotlin/ru/ysu/lms/academic/lab/LabService.kt @@ -17,7 +17,7 @@ import java.math.BigDecimal import java.time.Instant import java.time.LocalDateTime import java.time.OffsetDateTime -import java.time.ZoneOffset +import java.time.ZoneId import java.util.UUID @Service @@ -291,7 +291,7 @@ class LabService( val v = value?.takeIf { it.isNotBlank() } ?: return null return runCatching { Instant.parse(v) } .recoverCatching { OffsetDateTime.parse(v).toInstant() } - .recoverCatching { LocalDateTime.parse(v).atOffset(ZoneOffset.UTC).toInstant() } + .recoverCatching { LocalDateTime.parse(v).atZone(ZoneId.of("Europe/Moscow")).toInstant() } .getOrNull() } } \ No newline at end of file diff --git a/web/src/main/kotlin/ru/ysu/lms/web/DateUtils.kt b/web/src/main/kotlin/ru/ysu/lms/web/DateUtils.kt index b3a2bb1..93ea2dd 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/DateUtils.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/DateUtils.kt @@ -1,6 +1,7 @@ package ru.ysu.lms.web import java.time.Instant +import java.time.LocalDateTime import java.time.ZoneId import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -8,14 +9,33 @@ import java.time.format.DateTimeFormatter object DateUtils { private val MSK = ZoneId.of("Europe/Moscow") private val FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm") + private val INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm") + /** ISO instant string -> "02.11.2026 23:59" в Москве. */ fun formatMsk(value: String?): String? = - value?.let { + toZoned(value)?.format(FORMAT) + + /** ISO instant string -> "2026-11-02T02:59" для (в московском времени). */ + fun localInput(value: String?): String? = + toZoned(value)?.format(INPUT_FORMAT) ?: "" + + /** Значение datetime-local (локальное московское) -> ISO instant строка, понятная API. */ + fun toUtcIso(localValue: String?): String? = + localValue?.takeIf { it.isNotBlank() }?.let { s -> try { - val zdt = ZonedDateTime.ofInstant(Instant.parse(it), MSK) - zdt.format(FORMAT) + LocalDateTime.parse(s, INPUT_FORMAT) + .atZone(MSK) + .toInstant() + .toString() } catch (_: Exception) { - it + null } } + + private fun toZoned(value: String?): ZonedDateTime? = + try { + value?.let { ZonedDateTime.ofInstant(Instant.parse(it), MSK) } + } catch (_: Exception) { + null + } } \ No newline at end of file diff --git a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminController.kt b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminController.kt index f72ffd0..da75689 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminController.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminController.kt @@ -285,8 +285,8 @@ class AdminController( model.addAttribute("descriptionHtml", markdown.render(lab.descriptionMd)) model.addAttribute("deadlineMsk", ru.ysu.lms.web.DateUtils.formatMsk(lab.deadlineAt)) model.addAttribute("defenseMsk", ru.ysu.lms.web.DateUtils.formatMsk(lab.defenseAt)) - model.addAttribute("deadlineLocal", lab.deadlineAt?.take(16) ?: "") - model.addAttribute("defenseLocal", lab.defenseAt?.take(16) ?: "") + model.addAttribute("deadlineLocal", ru.ysu.lms.web.DateUtils.localInput(lab.deadlineAt)) + model.addAttribute("defenseLocal", ru.ysu.lms.web.DateUtils.localInput(lab.defenseAt)) return "admin/lab" } @@ -336,8 +336,8 @@ class AdminController( LabCreatePayload( number = number, title = title.trim(), - deadlineAt = deadlineAt?.takeIf { it.isNotBlank() }, - defenseAt = defenseAt?.takeIf { it.isNotBlank() }, + deadlineAt = ru.ysu.lms.web.DateUtils.toUtcIso(deadlineAt), + defenseAt = ru.ysu.lms.web.DateUtils.toUtcIso(defenseAt), pointsMax = java.math.BigDecimal(pointsMax), status = status, descriptionMd = descriptionMd, @@ -425,8 +425,8 @@ class AdminController( val lab = api.labDetail(token(session), id) // datetime-local требует формат yyyy-MM-dd'T'HH:mm model.addAttribute("lab", lab) - model.addAttribute("deadlineLocal", lab.deadlineAt?.take(16) ?: "") - model.addAttribute("defenseLocal", lab.defenseAt?.take(16) ?: "") + model.addAttribute("deadlineLocal", ru.ysu.lms.web.DateUtils.localInput(lab.deadlineAt)) + model.addAttribute("defenseLocal", ru.ysu.lms.web.DateUtils.localInput(lab.defenseAt)) return "admin/lab-edit" } @@ -444,7 +444,7 @@ class AdminController( ): String = try { api.updateLab( token(session), id, - LabUpdatePayload(title, deadlineAt?.takeIf { it.isNotBlank() }, defenseAt?.takeIf { it.isNotBlank() }, pointsMax, status, descriptionMd), + LabUpdatePayload(title, ru.ysu.lms.web.DateUtils.toUtcIso(deadlineAt), ru.ysu.lms.web.DateUtils.toUtcIso(defenseAt), pointsMax, status, descriptionMd), ) redirect.addFlashAttribute("notice", "Лабораторная сохранена") "redirect:/admin/labs/$id" diff --git a/web/src/main/resources/static/css/app.css b/web/src/main/resources/static/css/app.css index 946cefc..29ae525 100644 --- a/web/src/main/resources/static/css/app.css +++ b/web/src/main/resources/static/css/app.css @@ -165,6 +165,8 @@ tr:last-child td { border-bottom: none; } .inline-form .input-sm { width: auto; min-width: 10rem; } .input-sm { padding: 0.25rem 0.5rem; font-size: 0.8125rem; } .input-score { width: 4.5rem; } +.inline-form .input-sm { width: auto; min-width: 0; } +.inline-form .input-score, input[type="number"].input-score { width: 4.5rem; min-width: 4.5rem; flex: 0 0 auto; } .filter-select { width: auto; } .hidden { display: none !important; } diff --git a/web/src/main/resources/templates/admin/group.html b/web/src/main/resources/templates/admin/group.html index 6527211..b34beea 100644 --- a/web/src/main/resources/templates/admin/group.html +++ b/web/src/main/resources/templates/admin/group.html @@ -106,7 +106,6 @@ -
@@ -115,34 +114,36 @@

Логин: —

Пароль: —

-
- - - - + + + + + - \ No newline at end of file + diff --git a/web/src/main/resources/templates/admin/lab.html b/web/src/main/resources/templates/admin/lab.html index 8f27e34..9797a08 100644 --- a/web/src/main/resources/templates/admin/lab.html +++ b/web/src/main/resources/templates/admin/lab.html @@ -8,10 +8,10 @@

ЛР 1

- Статус: — -

+ Статус: - +
· Срок: · Защита: @@ -136,126 +136,127 @@
- - - - - - - - - - - - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/main/resources/templates/admin/labs.html b/web/src/main/resources/templates/admin/labs.html index 82f5349..e4105b8 100644 --- a/web/src/main/resources/templates/admin/labs.html +++ b/web/src/main/resources/templates/admin/labs.html @@ -34,43 +34,44 @@ - - - - - + + + + + + \ No newline at end of file