Lab assignment management UI and student variant view

- progress-service: POST /api/v1/assign (manual variant), GET /api/v1/assignments?labId=
  (enriched assignments), fixed enrich() to return real variant number/title/statement
- gateway: routes for /api/v1/assign and /api/v1/levels (exact paths)
- web: lab detail page shows students grouped with level and variant selects,
  group filter, distribute/redistribute buttons, source badge (auto/manual)
- web: student dashboard renders variant statement via libs/markdown (spoilers,
  code highlight, sanitized)
- academic-service: unquote YAML front-matter values (variant numbers were quoted)
- academic-service: LabLevelDetail.id for level references
This commit is contained in:
2026-09-10 23:25:36 +03:00
parent 0fdb9faab6
commit feed600d43
17 changed files with 408 additions and 67 deletions

View File

@@ -144,19 +144,28 @@ class LabImportParser {
val fm = mutableMapOf<String, String>()
for (line in fmLines) {
val parts = line.split(":", limit = 2)
if (parts.size == 2) fm[parts[0].trim()] = parts[1].trim()
if (parts.size == 2) fm[parts[0].trim()] = unquote(parts[1].trim())
}
val levelCode = normalizeLevel(fm["level"] ?: "basic")
if (levelMap[levelCode] == null) return null
val body = lines.drop(secondSep + 1).joinToString("\n").trim()
return VariantMeta(
level = levelCode,
number = fm["number"] ?: return null,
number = fm["number"]?.takeIf { it.isNotBlank() } ?: return null,
title = fm["title"] ?: "",
statement = body.ifBlank { "—" },
)
}
private fun unquote(value: String): String =
if (value.length >= 2 &&
((value.first() == '"' && value.last() == '"') || (value.first() == '\'' && value.last() == '\''))
) {
value.substring(1, value.length - 1)
} else {
value
}
private fun normalizeLevel(s: String): String = when (s.lowercase().trim()) {
"main", "basic", "обычная", "основная" -> "basic"
"advanced", "повышенная", "повышенной", "hard" -> "advanced"