fix: match attestation columns to lab result styling

- use the same header order and compact action row as lab columns
- show attestation results as centered text-like selects instead of boxed inputs
- keep column widths and separators aligned with lab columns
- send the selected value with the inline attestation request
This commit is contained in:
2026-10-26 17:30:55 +03:00
parent fb9511280a
commit f5ba21b5f5
4 changed files with 37 additions and 14 deletions

View File

@@ -557,13 +557,32 @@ dialog.dlg.dlg-lg .dlg-body { max-height: 88vh; min-height: 62vh; }
.matrix tbody tr:last-child td { border-bottom: none; } .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 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); } .matrix td.cell-lab { min-width: 9.5rem; border-left: 1px solid var(--c-border); }
.matrix th.attestation-col { min-width: 10rem; border-left: 1px solid var(--c-border); border-right: 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 { min-width: 10rem; 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); } .matrix th.average-col { min-width: 7rem; border-left: 1px solid var(--c-border); border-right: 1px solid var(--c-border); }
.matrix td.average-cell { min-width: 7rem; border-left: 1px solid var(--c-border); border-right: 1px solid var(--c-border); font-weight: 600; } .matrix td.average-cell { min-width: 7rem; border-left: 1px solid var(--c-border); border-right: 1px solid var(--c-border); font-weight: 600; }
.attestation-head { display: flex; flex-direction: column; align-items: flex-start; gap: 0.25rem; text-align: left; } .attestation-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; width: 100%; max-width: 9rem; }
.attestation-kind { font-weight: 600; color: var(--c-text); } .attestation-kind { font-weight: 600; color: var(--c-text); white-space: normal; }
.attestation-select { width: 100%; min-width: 8.5rem; font-size: 0.8125rem; } .js-attestation-form { display: flex; justify-content: center; 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;
background-image:
linear-gradient(45deg, transparent 50%, currentColor 50%),
linear-gradient(135deg, currentColor 50%, transparent 50%);
background-position: calc(100% - 8px) 55%, calc(100% - 3px) 55%;
background-size: 5px 5px, 5px 5px;
background-repeat: no-repeat;
}
.attestation-select:hover { color: var(--c-accent); text-decoration: underline; }
.attestation-select:focus { outline: none; border-color: var(--c-accent); box-shadow: 0 0 0 3px var(--c-accent-soft); }
.attestation-select.none { color: var(--c-text-muted); font-weight: 400; }
.attestation-select:disabled { opacity: 0.6; cursor: wait; }
.attestation-select option { text-align: left; text-align-last: left; background: var(--c-surface); color: var(--c-text); }
.matrix td.student-cell { min-width: 12rem; } .matrix td.student-cell { min-width: 12rem; }
.student-name { font-weight: 600; font-size: 0.84375rem; } .student-name { font-weight: 600; font-size: 0.84375rem; }
.student-cell .font-mono { margin-top: 0.125rem; } .student-cell .font-mono { margin-top: 0.125rem; }

View File

@@ -678,11 +678,12 @@ function submitAttestationForm(form) {
if (form.dataset.saving === '1') return; if (form.dataset.saving === '1') return;
form.dataset.saving = '1'; form.dataset.saving = '1';
var select = form.querySelector('select[name="value"]'); var select = form.querySelector('select[name="value"]');
var payload = new FormData(form);
if (select) select.disabled = true; if (select) select.disabled = true;
fetch(form.action, { fetch(form.action, {
method: 'POST', method: 'POST',
body: new FormData(form), body: payload,
headers: { 'X-Requested-With': 'fetch' } headers: { 'X-Requested-With': 'fetch' }
}) })
.then(function (response) { .then(function (response) {

View File

@@ -75,7 +75,12 @@
</th> </th>
<th class="text-center average-col" title="Средний балл по выставленным оценкам">Средний балл</th> <th class="text-center average-col" title="Средний балл по выставленным оценкам">Средний балл</th>
<th th:each="att : ${attestations}" class="text-center attestation-col" data-nosort> <th th:each="att : ${attestations}" class="text-center attestation-col" data-nosort>
<div class="attestation-head"> <div class="lab-col-head">
<div class="attestation-title">
<span class="attestation-kind" th:text="${att.kindLabel}">Аттестация</span>
<span class="badge badge-warn js-att-hidden" th:hidden="${att.published}">скрыто</span>
</div>
<span class="lab-col-title" th:text="'Семестр ' + ${att.semester}">Семестр 1</span>
<div class="lab-head-actions"> <div class="lab-head-actions">
<form class="inline-form js-att-pub-form" method="post" <form class="inline-form js-att-pub-form" method="post"
th:action="@{/admin/groups/{gid}/attestations/{aid}/publication(gid=${group.id}, aid=${att.id})}"> th:action="@{/admin/groups/{gid}/attestations/{aid}/publication(gid=${group.id}, aid=${att.id})}">
@@ -99,9 +104,6 @@
</button> </button>
</form> </form>
</div> </div>
<span class="attestation-kind" th:text="${att.kindLabel}">Аттестация</span>
<span class="lab-col-title" th:text="'Семестр ' + ${att.semester}">Семестр 1</span>
<span class="badge badge-warn js-att-hidden" th:hidden="${att.published}">скрыто</span>
</div> </div>
</th> </th>
<th data-nosort>Пароль</th> <th data-nosort>Пароль</th>
@@ -162,8 +164,9 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="attestationId" th:value="${att.id}"> <input type="hidden" name="attestationId" th:value="${att.id}">
<input type="hidden" name="studentId" th:value="${row.studentId}"> <input type="hidden" name="studentId" th:value="${row.studentId}">
<select class="input attestation-select" name="value" data-auto-submit <select class="attestation-select" name="value" data-auto-submit
th:attr="data-student-name=${row.fullName},data-attestation-label=${att.kindLabel}"> th:classappend="${row.attestations[att.id] == null} ? 'none' : ''"
th:title="${row.fullName} + ' — ' + ${att.kindLabel} + ' (' + 'Семестр ' + ${att.semester} + ')'">
<option value="" th:selected="${row.attestations[att.id] == null}">—</option> <option value="" th:selected="${row.attestations[att.id] == null}">—</option>
<option th:each="opt : ${att.options}" th:value="${opt.value}" th:text="${opt.label}" <option th:each="opt : ${att.options}" th:value="${opt.value}" th:text="${opt.label}"
th:selected="${row.attestations[att.id]?.value == opt.value}"></option> th:selected="${row.attestations[att.id]?.value == opt.value}"></option>
@@ -431,7 +434,7 @@
<div class="dlg-body" id="assign-modal-body"></div> <div class="dlg-body" id="assign-modal-body"></div>
</dialog> </dialog>
<script th:src="@{/js/admin-group.js(v='41')}" defer></script> <script th:src="@{/js/admin-group.js(v='42')}" defer></script>
</div> </div>
</body> </body>
</html> </html>

View File

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