Compare commits
2 Commits
6d1b36f02e
...
a73226b2ab
| Author | SHA1 | Date | |
|---|---|---|---|
| a73226b2ab | |||
| ed8d853d2a |
@@ -56,13 +56,6 @@ class SubmissionController(
|
||||
@RequestParam labId: UUID,
|
||||
): List<SubmissionDto> = submissions.latestPerStudent(labId)
|
||||
|
||||
/** Общее число решений (всех попыток) по лабораторной. */
|
||||
@GetMapping("/submissions/count")
|
||||
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
|
||||
fun submissionCount(
|
||||
@RequestParam labId: UUID,
|
||||
): Map<String, Long> = mapOf("count" to submissions.countForLab(labId))
|
||||
|
||||
@GetMapping("/submissions/{id}/download")
|
||||
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
|
||||
fun downloadForAdmin(
|
||||
|
||||
@@ -101,10 +101,6 @@ class SubmissionService(
|
||||
return zip to name
|
||||
}
|
||||
|
||||
/** Общее число решений (всех попыток) по лабораторной. */
|
||||
@Transactional(readOnly = true)
|
||||
fun countForLab(labId: UUID): Long = submissions.countByLabId(labId)
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun latestPerStudent(labId: UUID): List<SubmissionDto> {
|
||||
val perStudent = mutableMapOf<UUID, Submission>()
|
||||
|
||||
@@ -14,8 +14,6 @@ interface SubmissionRepository : JpaRepository<Submission, UUID> {
|
||||
|
||||
fun findByLabIdOrderByCreatedAtDesc(labId: UUID): List<Submission>
|
||||
|
||||
fun countByLabId(labId: UUID): Long
|
||||
|
||||
fun findFirstByStudentIdAndLabIdOrderBySeqDesc(
|
||||
studentId: UUID,
|
||||
labId: UUID,
|
||||
|
||||
@@ -22,6 +22,7 @@ import ru.ysu.lms.web.api.GradeSetPayload
|
||||
import ru.ysu.lms.web.api.GroupDetailDto
|
||||
import ru.ysu.lms.web.api.LabSummaryDto
|
||||
import ru.ysu.lms.web.api.StudentCandidateDto
|
||||
import ru.ysu.lms.web.api.SubmissionDto
|
||||
import ru.ysu.lms.web.api.createGroup
|
||||
import ru.ysu.lms.web.api.downloadSubmissionAsAdmin
|
||||
import ru.ysu.lms.web.api.getLabAssignments
|
||||
@@ -37,7 +38,6 @@ import ru.ysu.lms.web.api.listGroups
|
||||
import ru.ysu.lms.web.api.listLabs
|
||||
import ru.ysu.lms.web.api.publishGrades
|
||||
import ru.ysu.lms.web.api.setGrade
|
||||
import ru.ysu.lms.web.api.submissionCount
|
||||
import ru.ysu.lms.web.api.suggestComments
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
import java.net.URLEncoder
|
||||
@@ -192,7 +192,7 @@ class AdminGroupController(
|
||||
)
|
||||
}
|
||||
|
||||
fillGroupModel(model, groupViewModel(t, id, GroupRenderData(group, labs, rows, publishedLabs)))
|
||||
fillGroupModel(model, groupViewModel(t, id, GroupRenderData(group, labs, rows, publishedLabs, submissionsBy)))
|
||||
return "admin/group"
|
||||
}
|
||||
|
||||
@@ -206,6 +206,7 @@ class AdminGroupController(
|
||||
val labs: List<LabSummaryDto>,
|
||||
val rows: List<GroupStudentRow>,
|
||||
val publishedLabs: Set<String>,
|
||||
val submissionsBy: Map<LabSummaryDto, Map<String, SubmissionDto>>,
|
||||
)
|
||||
|
||||
private fun groupViewModel(
|
||||
@@ -213,7 +214,14 @@ class AdminGroupController(
|
||||
id: String,
|
||||
data: GroupRenderData,
|
||||
): GroupViewModel {
|
||||
val submissionCounts = data.labs.associate { it.id to api.submissionCount(token, it.id) }
|
||||
val memberIds =
|
||||
data.group.students
|
||||
.map { it.id }
|
||||
.toSet()
|
||||
val submissionCounts =
|
||||
data.labs.associate { lab ->
|
||||
lab.id to (data.submissionsBy[lab]?.keys?.count { it in memberIds } ?: 0)
|
||||
}
|
||||
val candidates = runCatching { api.groupCandidates(token, id) }.getOrDefault(emptyList())
|
||||
return GroupViewModel(data.group, data.labs, candidates, data.rows, data.publishedLabs, submissionCounts)
|
||||
}
|
||||
@@ -270,7 +278,14 @@ class AdminGroupController(
|
||||
mapOf(
|
||||
"id" to level.id,
|
||||
"title" to level.title,
|
||||
"variants" to level.variants.map { v -> mapOf("id" to v.id, "number" to v.number) },
|
||||
"variants" to
|
||||
level.variants.map { v ->
|
||||
mapOf(
|
||||
"id" to v.id,
|
||||
"number" to v.number,
|
||||
"statementHtml" to markdown.render(v.statementMd),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -6,23 +6,6 @@ import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.util.LinkedMultiValueMap
|
||||
|
||||
fun ApiClient.submissionCount(
|
||||
token: String,
|
||||
labId: String,
|
||||
): Int {
|
||||
val type = object : ParameterizedTypeReference<Map<String, Long>>() {}
|
||||
val result =
|
||||
call {
|
||||
client
|
||||
.get()
|
||||
.uri("/api/v1/submissions/count?labId={id}", labId)
|
||||
.header(HttpHeaders.AUTHORIZATION, bearer(token))
|
||||
.retrieve()
|
||||
.body(type)
|
||||
}
|
||||
return (result["count"] ?: 0L).toInt()
|
||||
}
|
||||
|
||||
fun ApiClient.distribute(
|
||||
token: String,
|
||||
payload: DistributeRequestPayload,
|
||||
|
||||
@@ -195,6 +195,8 @@ function openDefense(btn) {
|
||||
|
||||
/* ---------- Назначение варианта (вручную или случайно из уровня) ---------- */
|
||||
|
||||
var VARIANT_CACHE = {};
|
||||
|
||||
function openVariant(btn) {
|
||||
var student = btn.getAttribute('data-student') || '';
|
||||
var lab = btn.getAttribute('data-lab') || '';
|
||||
@@ -210,6 +212,7 @@ function openVariant(btn) {
|
||||
|
||||
var levelSel = $id('variant-level');
|
||||
levelSel.innerHTML = '';
|
||||
VARIANT_CACHE = {};
|
||||
fetch('/admin/labs/' + encodeURIComponent(lab) + '/schema')
|
||||
.then(function (r) { return r.ok ? r.json() : { levels: [] }; })
|
||||
.then(function (data) {
|
||||
@@ -218,8 +221,19 @@ function openVariant(btn) {
|
||||
opt.value = lvl.id;
|
||||
opt.textContent = lvl.title || ('Уровень ' + (i + 1));
|
||||
opt.__variants = lvl.variants || [];
|
||||
(lvl.variants || []).forEach(function (v) { VARIANT_CACHE[v.id] = v; });
|
||||
levelSel.appendChild(opt);
|
||||
});
|
||||
// если вариант уже назначен — открываем уровень, в котором он лежит
|
||||
if (currentVariant) {
|
||||
Array.prototype.some.call(levelSel.options, function (opt) {
|
||||
if ((opt.__variants || []).some(function (v) { return v.id === currentVariant; })) {
|
||||
levelSel.value = opt.value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
levelSel.onchange = function () { fillVariantSelect(currentVariant); };
|
||||
fillVariantSelect(currentVariant);
|
||||
});
|
||||
@@ -243,7 +257,27 @@ function fillVariantSelect(currentVariant) {
|
||||
});
|
||||
$id('variant-random-level').value = levelSel.value || '';
|
||||
$id('variant-pin-id').value = variantSel.value || '';
|
||||
variantSel.onchange = function () { $id('variant-pin-id').value = variantSel.value || ''; };
|
||||
variantSel.onchange = function () {
|
||||
$id('variant-pin-id').value = variantSel.value || '';
|
||||
showVariantTask();
|
||||
};
|
||||
showVariantTask();
|
||||
}
|
||||
|
||||
function showVariantTask() {
|
||||
var variantSel = $id('variant-variant');
|
||||
var wrap = $id('variant-task-wrap');
|
||||
var box = $id('variant-task');
|
||||
if (!wrap || !box) return;
|
||||
var sel = variantSel.selectedOptions[0];
|
||||
var v = sel && VARIANT_CACHE[sel.value];
|
||||
if (v && v.statementHtml) {
|
||||
box.innerHTML = v.statementHtml;
|
||||
wrap.hidden = false;
|
||||
} else {
|
||||
box.innerHTML = '';
|
||||
wrap.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Таймеры защиты в ячейках ---------- */
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
|
||||
<!-- Диалог: индивидуальное задание (защита) -->
|
||||
<!-- Диалог: назначение варианта -->
|
||||
<dialog id="modal-variant" class="modal">
|
||||
<dialog id="modal-variant" class="modal modal-lg">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Назначить вариант</h2>
|
||||
<button class="modal-close" type="button" onclick="closeModal('modal-variant')">✕</button>
|
||||
@@ -162,6 +162,10 @@
|
||||
<select class="input" id="variant-level"></select></div>
|
||||
<div><label class="label" for="variant-variant">Вариант</label>
|
||||
<select class="input" id="variant-variant"></select></div>
|
||||
<div id="variant-task-wrap" hidden>
|
||||
<label class="label">Текст задания</label>
|
||||
<div id="variant-task" class="md-body"></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<form class="inline-form" id="variant-random-form" method="post"
|
||||
th:action="@{/admin/groups/{gid}/set-level(gid=${group.id})}">
|
||||
|
||||
Reference in New Issue
Block a user