Admin group: fix script loading (extra div), total submissions counter, label cleanup

This commit is contained in:
2026-09-28 17:22:09 +03:00
parent 684567a839
commit 4e1e0d1488
6 changed files with 35 additions and 7 deletions

View File

@@ -56,6 +56,13 @@ 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(

View File

@@ -101,6 +101,10 @@ 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>()

View File

@@ -14,6 +14,8 @@ interface SubmissionRepository : JpaRepository<Submission, UUID> {
fun findByLabIdOrderByCreatedAtDesc(labId: UUID): List<Submission>
fun countByLabId(labId: UUID): Long
fun findFirstByStudentIdAndLabIdOrderBySeqDesc(
studentId: UUID,
labId: UUID,

View File

@@ -22,7 +22,6 @@ 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
@@ -38,6 +37,7 @@ 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, submissionsBy)))
fillGroupModel(model, groupViewModel(t, id, GroupRenderData(group, labs, rows, publishedLabs)))
return "admin/group"
}
@@ -206,7 +206,6 @@ class AdminGroupController(
val labs: List<LabSummaryDto>,
val rows: List<GroupStudentRow>,
val publishedLabs: Set<String>,
val submissionsBy: Map<LabSummaryDto, Map<String, SubmissionDto>>,
)
private fun groupViewModel(
@@ -214,7 +213,7 @@ class AdminGroupController(
id: String,
data: GroupRenderData,
): GroupViewModel {
val submissionCounts = data.labs.associate { it.id to (data.submissionsBy[it]?.size ?: 0) }
val submissionCounts = data.labs.associate { it.id to api.submissionCount(token, it.id) }
val candidates = runCatching { api.groupCandidates(token, id) }.getOrDefault(emptyList())
return GroupViewModel(data.group, data.labs, candidates, data.rows, data.publishedLabs, submissionCounts)
}

View File

@@ -6,6 +6,23 @@ 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,

View File

@@ -12,7 +12,7 @@
<div class="flex flex-wrap gap-2">
<button class="btn btn-primary" type="button" onclick="openModal('modal-add-student')">+ Студент</button>
<a class="btn btn-secondary" th:href="@{/admin/groups/{id}/credentials(id=${group.id}, format='xlsx')}">Скачать пароли</a>
<button class="btn btn-secondary" type="button" onclick="openModal('modal-defense-import')">Импорт заданий (CSV)</button>
<button class="btn btn-secondary" type="button" onclick="openModal('modal-defense-import')">Импорт заданий</button>
</div>
</div>
@@ -225,7 +225,6 @@
</form>
</div>
</div>
</div>
</div>
</dialog>
@@ -234,7 +233,7 @@
<!-- Диалог импорта CSV -->
<dialog id="modal-defense-import" class="modal">
<div class="modal-header">
<h2 class="modal-title">Импорт заданий (CSV)</h2>
<h2 class="modal-title">Импорт заданий</h2>
<button class="modal-close" type="button" onclick="closeModal('modal-defense-import')">✕</button>
</div>
<div class="modal-body">