fix: polish group matrix headers and cell alignment

- shorten intermediate attestation header to «Пр. атт.»
- vertically center row cell contents and left-align values
- use normal header color/weight for average, password and actions
- align attestation action buttons to equal height
- add a divider before the actions column
This commit is contained in:
2026-10-26 18:29:22 +03:00
parent c8581c620a
commit a336612ad5
6 changed files with 33 additions and 12 deletions

View File

@@ -43,6 +43,15 @@ object AttestationPresentation {
fun kindLabel(kind: String): String = KIND_LABELS[kind] ?: kind
/** Короткая подпись для шапки узкого столбца. */
fun shortKindLabel(kind: String): String =
when (kind) {
"intermediate" -> "Пр. атт."
"pass" -> "Зачёт"
"exam" -> "Экзамен"
else -> kind
}
/** Понимает и «2», и старые значения вроде «2 семестр»; вне 1–8 — 1. */
fun semesterNumber(raw: String): Int {
val digits = raw.filter { it.isDigit() }

View File

@@ -69,6 +69,7 @@ data class GroupAttestation(
val id: String = "",
val kind: String = "",
val kindLabel: String = "",
val shortLabel: String = "",
val semester: Int = 1,
val published: Boolean = false,
val options: List<AttestationOption> = emptyList(),

View File

@@ -18,6 +18,7 @@ object GroupAttestationViews {
id = column.id,
kind = column.kind,
kindLabel = AttestationPresentation.kindLabel(column.kind),
shortLabel = AttestationPresentation.shortKindLabel(column.kind),
semester = AttestationPresentation.semesterNumber(column.semester),
published = column.published,
options =

View File

@@ -546,17 +546,22 @@ dialog.dlg.dlg-lg .dlg-body { max-height: 88vh; min-height: 62vh; }
.scroll-x { overflow-x: auto; }
.matrix { border-collapse: separate; border-spacing: 0; }
.matrix th {
vertical-align: top; padding: 0.75rem 1rem; text-align: left;
vertical-align: middle; padding: 0.75rem 1rem; text-align: left;
font-size: 0.75rem; font-weight: 500; color: var(--c-text-muted);
background: var(--c-surface); white-space: nowrap;
}
.matrix td { vertical-align: top; padding: 1rem; }
.matrix td { vertical-align: middle; padding: 1rem; }
/* Непрозрачный фон обязателен: закреплённый столбец не должен просвечивать при прокрутке. */
.matrix tbody tr:hover td { background: var(--c-row-hover); }
.matrix tbody tr:hover td.matrix-sticky { background: var(--c-row-hover); }
.matrix tbody tr:last-child td { border-bottom: none; }
.matrix th.lab-col { min-width: 9.5rem; border-left: 1px solid var(--c-border); }
.matrix td.cell-lab { min-width: 9.5rem; border-left: 1px solid var(--c-border); }
#group-table th.password-col,
#group-table th.average-col,
#group-table th.actions-head { color: var(--c-text); font-weight: 400; }
#group-table th.actions-head,
#group-table td.actions-cell { border-left: 1px solid var(--c-border); }
.matrix th.attestation-col { width: 11rem; min-width: 10rem; max-width: 11rem; border-left: 1px solid var(--c-border); border-right: 1px solid var(--c-border); }
.matrix td.attestation-cell { width: 11rem; min-width: 10rem; max-width: 11rem; border-left: 1px solid var(--c-border); border-right: 1px solid var(--c-border); }
.matrix th.average-col { min-width: 7rem; border-left: 1px solid var(--c-border); border-right: 1px solid var(--c-border); }
@@ -564,14 +569,19 @@ dialog.dlg.dlg-lg .dlg-body { max-height: 88vh; min-height: 62vh; }
.attestation-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; width: 100%; max-width: 9rem; text-align: left; }
.attestation-kind { font-weight: 400; color: var(--c-text); white-space: normal; text-align: left; }
.attestation-col .lab-col-head, .attestation-col .lab-col-title { text-align: left; }
.js-attestation-form { display: flex; justify-content: center; width: 100%; }
.attestation-col .lab-head-actions .btn {
width: 1.7rem; height: 1.7rem; min-height: 1.7rem; padding: 0;
display: inline-flex; align-items: center; justify-content: center;
}
.attestation-col .lab-head-actions .ico { font-size: 0.95rem; line-height: 1; }
.js-attestation-form { display: flex; justify-content: flex-start; width: 100%; }
.attestation-select {
appearance: none; -webkit-appearance: none;
width: 9rem; max-width: 100%; min-width: 0; padding: 0.15rem 1.15rem 0.15rem 0.35rem;
border: 1px solid transparent; border-radius: var(--radius-sm);
background-color: transparent; color: var(--c-text);
font-size: 0.8125rem; font-weight: 600; font-variant-numeric: tabular-nums;
text-align: center; text-align-last: center; cursor: pointer;
text-align: left; text-align-last: left; cursor: pointer;
background-image:
linear-gradient(45deg, transparent 50%, currentColor 50%),
linear-gradient(135deg, currentColor 50%, transparent 50%);

View File

@@ -73,11 +73,11 @@
</div>
</div>
</th>
<th class="text-center average-col" title="Средний балл по выставленным оценкам">Средний балл</th>
<th class="average-col" title="Средний балл по выставленным оценкам">Средний балл</th>
<th th:each="att : ${attestations}" class="text-center attestation-col" data-nosort>
<div class="lab-col-head">
<div class="attestation-title">
<span class="attestation-kind" th:text="${att.kindLabel}">Аттестация</span>
<span class="attestation-kind" th:text="${att.shortLabel}">Пр. атт.</span>
</div>
<span class="lab-col-title" th:text="'Семестр ' + ${att.semester}">Семестр 1</span>
<div class="lab-head-actions">
@@ -105,8 +105,8 @@
</div>
</div>
</th>
<th data-nosort>Пароль</th>
<th class="text-right" data-nosort>Действия</th>
<th class="password-col" data-nosort>Пароль</th>
<th class="text-right actions-head" data-nosort>Действия</th>
</tr>
</thead>
<tbody>
@@ -155,9 +155,9 @@
</div>
</div>
</td>
<td class="text-center average-cell"
<td class="average-cell"
th:text="${row.averageScore != null ? row.averageScore : '—'}">—</td>
<td th:each="att : ${attestations}" class="text-center attestation-cell">
<td th:each="att : ${attestations}" class="attestation-cell">
<form class="inline-form js-attestation-form" method="post"
th:action="@{/admin/groups/{id}/attestation-results(id=${group.id})}">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
@@ -172,7 +172,7 @@
</select>
</form>
</td>
<td class="text-center">
<td class="password-cell">
<span th:if="${row.mustChangePassword}" class="badge badge-warn">временный</span>
<span th:unless="${row.mustChangePassword}" class="badge badge-ok">изменён</span>
</td>

View File

@@ -17,7 +17,7 @@
<!-- v=37: сброс кэша стилей после изменения геометрии; при изменении css поднимать версию -->
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/css/bootstrap-icons.min.css}">
<link rel="stylesheet" th:href="@{/css/app.css(v='42')}">
<link rel="stylesheet" th:href="@{/css/app.css(v='44')}">
<link id="hljs-light" rel="stylesheet" th:href="@{/css/hljs-github.css(v='36')}">
<link id="hljs-dark" rel="stylesheet" th:href="@{/css/hljs-github-dark.css(v='36')}" disabled>
<script th:src="@{/js/highlight.min.js(v='36')}" defer></script>