diff --git a/src/main/java/nl/tudelft/submit/controller/ModuleController.java b/src/main/java/nl/tudelft/submit/controller/ModuleController.java index 85f3fc59987b0f2f67382d4f1e5d3ce450b58ed9..08a52f75ceef14170e514719c289f79eb142dc05 100644 --- a/src/main/java/nl/tudelft/submit/controller/ModuleController.java +++ b/src/main/java/nl/tudelft/submit/controller/ModuleController.java @@ -17,8 +17,7 @@ */ package nl.tudelft.submit.controller; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -30,8 +29,10 @@ import nl.tudelft.submit.dto.create.DivisionsCreateTypeDTO; import nl.tudelft.submit.dto.create.ModuleCreateWithDivisionsDTO; import nl.tudelft.submit.dto.create.grading.GradingFormulaCreateDTO; import nl.tudelft.submit.dto.create.note.GroupNoteCreateDTO; +import nl.tudelft.submit.dto.id.GroupId; import nl.tudelft.submit.dto.patch.ModulePatchWithDivisionsDTO; import nl.tudelft.submit.dto.view.labracore.SubmitAssignmentSummaryDTO; +import nl.tudelft.submit.dto.view.labracore.SubmitGroupSummaryDTO; import nl.tudelft.submit.dto.view.labracore.SubmitModuleViewDTO; import nl.tudelft.submit.enums.GradableType; import nl.tudelft.submit.security.AuthorizationService; @@ -73,6 +74,9 @@ public class ModuleController { @Autowired private StatisticsService statisticsService; + @Autowired + private ModuleDivisionService divisionService; + /** * Gets the module page. * @@ -96,26 +100,61 @@ public class ModuleController { /** * Gets the groups page for a module * - * @param id the id of the module - * @param model the model to add details to - * @return the page to load + * @param person the authenticated person + * @param id the id of the module + * @param model the model to add details to + * @return the page to load */ @GetMapping("/{id}/groups") @PreAuthorize("@authorizationService.hasRoleInModule(#id)") public String getModuleGroups(@AuthenticatedPerson Person person, @PathVariable Long id, Model model) { boolean staff = authService.hasStaffRoleInModule(id); - Optional<StudentGroupDetailsDTO> group = groupService.getGroupForPersonInModule(person.getId(), id); - - if (!staff && group.isPresent()) { - return "redirect:/group/" + group.get().getId(); - } + ModuleDetailsDTO module = moduleService.getModuleById(id); - model.addAttribute("module", moduleService.getModuleById(id)); - model.addAttribute("person", person); - model.addAttribute("groupCreate", new StudentGroupCreateDTO().module(new ModuleIdDTO())); - model.addAttribute("groups", groupService.showAllGroupsInModule(id, staff)); - model.addAttribute("note", new GroupNoteCreateDTO(null, null, null)); model.addAttribute("staff", staff); + model.addAttribute("module", module); + model.addAttribute("selfEnroll", editionService + .getSubmitEdition(new EditionIdDTO().id(module.getEdition().getId())) + .map(e -> true /* TODO e.canStudentsSelfEnroll() */).orElse(false)); + + if (staff) { + + List<SubmitGroupSummaryDTO> groups = groupService.showAllGroupsInModule(id, true); + List<ModuleDivisionDetailsDTO> divisions = divisionService.getDivisionsByModule(id); + Map<Long, Integer> divisionIndices = new HashMap<>(); + for (int i = 0; i < divisions.size(); i++) { + divisionIndices.put(divisions.get(i).getId(), i + 1); + } + + model.addAttribute("groups", groups); + model.addAttribute("divisions", divisions); + model.addAttribute("divisionIndices", divisionIndices); + model.addAttribute("note", new GroupNoteCreateDTO(null, new GroupId(), null)); + + } else { + + Optional<StudentGroupDetailsDTO> group = groupService.getGroupForPersonInModule(person.getId(), + id); + if (group.isPresent()) { + return "redirect:/group/" + group.get().getId(); + } + + List<StudentGroupSummaryDTO> allGroups = groupService.getAllGroupsInModule(id); + List<ModuleDivisionDetailsDTO> divisions = divisionService.getDivisionsByModule(id); + Optional<ModuleDivisionDetailsDTO> division = divisions.parallelStream() + .filter(d -> d.getPeople().parallelStream() + .anyMatch(p -> p.getId().equals(person.getId()))) + .findFirst(); + List<StudentGroupSummaryDTO> groups = division + .map(d -> groupService.getAllGroupsInDivision(d.getId())) + .orElse(Collections.emptyList()); + groups.addAll( + allGroups.stream().filter(g -> g.getDivision() == null).collect(Collectors.toList())); + + model.addAttribute("groups", groups); + model.addAttribute("person", person); + + } return "module/groups"; } diff --git a/src/main/java/nl/tudelft/submit/service/StudentGroupService.java b/src/main/java/nl/tudelft/submit/service/StudentGroupService.java index e9aa9e14988be5d69709c33dfbfb24e5a5d54496..1ce0e52829cd9c2469634df5433d6d966a03b72d 100644 --- a/src/main/java/nl/tudelft/submit/service/StudentGroupService.java +++ b/src/main/java/nl/tudelft/submit/service/StudentGroupService.java @@ -159,6 +159,16 @@ public class StudentGroupService { return groupApi.getAllGroupsInModule(moduleId).collectList().block(); } + /** + * Gets all the groups in a division. + * + * @param divisionId The id of the division + * @return The list of all groups + */ + public List<StudentGroupSummaryDTO> getAllGroupsInDivision(Long divisionId) { + return groupApi.getAllGroupsInDivision(divisionId).collectList().block(); + } + /** * Gets the SubmitGroup with corresponding id. If it does not exist, creates one. * diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 59372174e6583a0e1413409539b6a08c385d6f33..914eff712fbdc0cdb1ae31bd14edb85a2e113bad 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -152,6 +152,7 @@ module.assignments = Assignments module.search_groups = Group / member name module.no_group = No group module.no_assignments = No assignments +module.any_division = Any division # DivisionsCreateType - enum module.create.divisions.NO_ACTION = No changes, keep old structure module.create.divisions.JUST_ONE = One division @@ -206,6 +207,7 @@ group.students_can_create = Students create groups group.allow_student_create = Students are allowed to create groups group.name_field=Group name group.create = Create group +group.no_division = No division group.no_members = No members group.no_note = No note diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css index 12790cddb8f7fa606132caa18004c3d93974dabd..978f07fc4e1d5fbb000f3c0195284172fa483933 100644 --- a/src/main/resources/static/css/main.css +++ b/src/main/resources/static/css/main.css @@ -1177,6 +1177,7 @@ a.form-explain { .list-row-buttons { display: flex; + justify-self: end; } .list-row-buttons > * + * { diff --git a/src/main/resources/templates/edition/staff.html b/src/main/resources/templates/edition/staff.html index 3f9d9b1b54399a05b596b83ded059fd674d48fb0..0d9f8d764066e13ed62e676098bbf3c8053298b9 100644 --- a/src/main/resources/templates/edition/staff.html +++ b/src/main/resources/templates/edition/staff.html @@ -138,7 +138,7 @@ <span class="center-label" th:text="#{generic.identifier}"></span> <div class="tooltip"> <i class="tooltip-icon fas fa-question-circle"></i> - <p class="tooltip-text" th:text="#{assignment.import_participants.identifier_help}"></p> + <p class="tooltip-text" th:text="#{edition.import_participants.identifier_help}"></p> </div> </div> <select id="id-column" th:name="idColumn" class="selectbox" aria-label="Identifier column" disabled></select> diff --git a/src/main/resources/templates/module/groups.html b/src/main/resources/templates/module/groups.html index 21c63bbb794c84cbdbc4185cf23302081b3f4d13..5078d42786020ed7ad61326fea3f882ef407f9e0 100644 --- a/src/main/resources/templates/module/groups.html +++ b/src/main/resources/templates/module/groups.html @@ -45,27 +45,55 @@ <div th:if="${!groups.isEmpty() or !staff}" class="boxed-group"> <div class="list-header"> <h1 th:text="|#{module.groups}|"></h1> + <div th:if="${staff}" th:remove="tag"> + <a class="button" th:text="#{generic.add}"></a> + <button class="button" th:text="#{generic.import}"></button> +<!-- TODO group create--> + </div> </div> - <div th:if="${staff}" class="list-search"> - <input id="group-search" type="text" class="textfield" th:placeholder="#{module.search_groups}" - aria-label="Group or member name" - oninput="search('group', ['members'])"/> + <div class="list-search"> + <input id="group-search" type="text" class="textfield" th:placeholder="#{module.search_groups}" aria-label="Group or member name" + oninput="filter('group', ['division'], ['members'])"/> + <select th:if="${staff}" id="group-division" class="selectbox" aria-label="Filter on division" onchange="filter('group', ['division'], ['members'])"> + <option value="all" th:text="#{module.any_division}"></option> + <option th:each="division, iter : ${divisions}" th:value="${division.id}" th:text="#{module.division(${divisionIndices[division.id]})}"></option> + <option value="-1" th:text="#{group.no_division}"></option> + </select> </div> <div id="group-list" class="list-content"> - <div th:if="${!staff and groups.isEmpty()}" class="list-row"> - <span th:text="#{module.no_groups}"></span> + <div id="submission-header" class="table-row"> + <b th:text="#{group.text}"></b> + <b th:if="${staff}" th:text="#{module.division.name}"></b> + <b></b> </div> - <div th:each="group : ${groups}" th:id="|group-${group.name}|" class="list-row"> + <div th:each="group : ${groups}" th:id="|group-${group.name}|" class="table-row"> <span th:id="|group-${group.name}-name|" th:text="${group.name}"></span> - <span th:if="${staff}" th:id="|group-${group.name}-members|" - th:text="${group.memberUsernames.toString()}" class="hidden"></span> + <span th:if="${staff}" th:text="${group.division == null} ? #{group.no_division} : #{module.division(${divisionIndices[group.division.id]})}"></span> + <span th:if="${staff}" th:id="|group-${group.name}-division|" th:text="${group?.division?.id} ?: -1" class="hidden"></span> + <span th:id="|group-${group.name}-members|" th:text="${group.memberUsernames.toString()}" class="hidden"></span> <div class="list-row-buttons"> - <button th:if="${staff}" type="button" class="button" th:text="#{generic.note}" - th:onclick="|toggleOverlay('${group.id}-note-overlay')|"></button> - <a th:if="${staff}" class="button" th:href="@{|/group/${group.id}|}" th:text="#{generic.view}"></a> <form th:unless="${staff}" th:action="@{|/group/${person.id}/join/${group.id}|}" method="post"> - <button class="button" type="submit" th:disabled="${group.memberUsernames.size() == group.capacity}" th:text="#{group.join}"></button> + <button type="submit" class="button" th:text="#{group.join}"></button> </form> + <div th:if="${staff}" th:remove="tag"> + <button type="button" class="button" th:text="#{generic.note}" + th:onclick="|toggleOverlay('${group.id}-note-overlay')|"></button> + <a class="button" th:href="@{|/group/${group.id}|}" th:text="#{generic.view}"></a> + <button type="button" class="button delete" th:text="#{generic.remove}" + th:onclick="|toggleOverlay('remove-submit-overlay-${group.id}')|"></button> + <div th:id="|remove-submit-overlay-${group.id}|" class="overlay fit-to-content"> + <div class="boxed-group submit-form"> + <form th:action="@{|/group/remove/${group.id}|}" method="post"> + <h1>Are you sure</h1> + <div class="form-buttons"> + <button class="button delete" type="submit" th:text="#{generic.remove}"></button> + <button class="button" type="button" th:text="#{generic.cancel}" + th:onclick="|toggleOverlay('remove-submit-overlay-${group.id}')|"></button> + </div> + </form> + </div> + </div> + </div> </div> <div th:if="${staff}" th:id="|${group.id}-note-overlay|" class="overlay"> diff --git a/src/test/java/nl/tudelft/submit/controller/ModuleControllerTest.java b/src/test/java/nl/tudelft/submit/controller/ModuleControllerTest.java index c675e3e77b21f701e4b759f34a61aabf4c151cc8..ac83a5a8713d92eb2d2985a968852d66fa6896c2 100644 --- a/src/test/java/nl/tudelft/submit/controller/ModuleControllerTest.java +++ b/src/test/java/nl/tudelft/submit/controller/ModuleControllerTest.java @@ -41,11 +41,7 @@ import nl.tudelft.submit.dto.view.statistics.ModuleStatisticsDTO; import nl.tudelft.submit.enums.DivisionsCreateType; import nl.tudelft.submit.enums.GradableType; import nl.tudelft.submit.security.AuthorizationService; -import nl.tudelft.submit.service.EditionService; -import nl.tudelft.submit.service.FormulaService; -import nl.tudelft.submit.service.ModuleService; -import nl.tudelft.submit.service.StatisticsService; -import nl.tudelft.submit.service.StudentGroupService; +import nl.tudelft.submit.service.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -108,6 +104,9 @@ class ModuleControllerTest { @MockBean private EditionService editionService; + @MockBean + private ModuleDivisionService divisionService; + @MockBean private AuthorizationService authService; @@ -181,7 +180,7 @@ class ModuleControllerTest { .andExpect(model().attribute("staff", false)); verify(authService).hasRoleInModule(MODULE_ID); - verify(groupService).showAllGroupsInModule(MODULE_ID, false); + verify(groupService).getAllGroupsInModule(MODULE_ID); } @Test @@ -191,6 +190,7 @@ class ModuleControllerTest { when(authService.hasStaffRoleInModule(anyLong())).thenReturn(false); when(groupService.getGroupForPersonInModule(anyLong(), anyLong())) .thenReturn(Optional.of(new StudentGroupDetailsDTO().id(GROUP_ID))); + when(moduleService.getModuleById(anyLong())).thenReturn(MODULE_VIEW); mockMvc.perform(get("/module/{id}/groups", MODULE_ID)) .andExpect(status().is3xxRedirection())