fix(projects): preserve server picker recovery
This commit is contained in:
+20
-6
@@ -164,8 +164,8 @@ public class ProjectController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all selected eligible Interns atomically or re-renders membership history with the
|
||||
* complete retained selection and a safe validation message.
|
||||
* Adds all selected eligible Interns atomically or re-renders membership history with every
|
||||
* still-eligible selection retained and a count of unavailable choices.
|
||||
*
|
||||
* @param principal authenticated user
|
||||
* @param projectId owning Project identifier
|
||||
@@ -182,16 +182,27 @@ public class ProjectController {
|
||||
BindingResult bindingResult,
|
||||
Model model) {
|
||||
long actorId = actorId(principal);
|
||||
boolean rejectedByService = false;
|
||||
if (!bindingResult.hasErrors()) {
|
||||
try {
|
||||
projects.addMembers(actorId, projectId, membersForm.internUserIds());
|
||||
return "redirect:/projects/" + projectId + "/members";
|
||||
} catch (ProjectRuleViolationException exception) {
|
||||
rejectedByService = true;
|
||||
bindingResult.rejectValue(
|
||||
"internUserIds", "project.members.ineligible", exception.getMessage());
|
||||
}
|
||||
}
|
||||
populateMembersModel(actorId, projectId, model);
|
||||
var refreshedOptions = populateMembersModel(actorId, projectId, model);
|
||||
if (rejectedByService) {
|
||||
Set<Long> refreshedIds = refreshedOptions.stream()
|
||||
.map(EligibleInternOption::userId)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
long unavailableSelectionCount = membersForm.internUserIds().stream()
|
||||
.filter(userId -> !refreshedIds.contains(userId))
|
||||
.count();
|
||||
model.addAttribute("unavailableSelectionCount", unavailableSelectionCount);
|
||||
}
|
||||
return "projects/members";
|
||||
}
|
||||
|
||||
@@ -242,7 +253,7 @@ public class ProjectController {
|
||||
return "projects/leadership";
|
||||
}
|
||||
|
||||
private void populateMembersModel(long actorId, long projectId, Model model) {
|
||||
private List<EligibleInternOption> populateMembersModel(long actorId, long projectId, Model model) {
|
||||
var project = pages.detail(actorId, projectId);
|
||||
var members = pages.members(actorId, projectId);
|
||||
model.addAttribute("project", project);
|
||||
@@ -252,10 +263,13 @@ public class ProjectController {
|
||||
.filter(member -> member.leftAt() == null)
|
||||
.map(member -> member.internUserId())
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
model.addAttribute("eligibleInternOptions", eligibleInternOptions().stream()
|
||||
var options = eligibleInternOptions().stream()
|
||||
.filter(option -> !currentMemberIds.contains(option.userId()))
|
||||
.toList());
|
||||
.toList();
|
||||
model.addAttribute("eligibleInternOptions", options);
|
||||
return options;
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private void populateLeadershipModel(long actorId, long projectId, Model model) {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<input class="control" id="leader-search" type="search" autocomplete="off" data-picker-search>
|
||||
<div class="picker-options">
|
||||
<label class="picker-option" data-picker-option th:each="option : ${eligibleInternOptions}" th:attr="data-picker-search=${option.displayName + ' ' + option.studentCode}">
|
||||
<input type="radio" th:field="*{initialLeaderUserId}" th:value="${option.userId}" required>
|
||||
<input type="radio" th:field="*{initialLeaderUserId}" th:value="${option.userId}">
|
||||
<span><strong data-picker-label th:text="|${option.displayName} (${option.studentCode})|">Intern (Code)</strong><small th:text="|${#temporals.format(option.internshipStart, 'dd/MM/yyyy')} – ${#temporals.format(option.internshipEnd, 'dd/MM/yyyy')}|">Dates</small></span>
|
||||
</label>
|
||||
<p class="picker-empty" data-picker-empty th:hidden="${!#lists.isEmpty(eligibleInternOptions)}">No matching eligible Interns.</p>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<input class="control" id="leadership-search" type="search" autocomplete="off" data-picker-search>
|
||||
<div class="picker-options">
|
||||
<label class="picker-option" data-picker-option th:each="option : ${eligibleInternOptions}" th:attr="data-picker-search=${option.displayName + ' ' + option.studentCode}">
|
||||
<input type="radio" th:field="*{internUserId}" th:value="${option.userId}" required>
|
||||
<input type="radio" th:field="*{internUserId}" th:value="${option.userId}">
|
||||
<span><strong data-picker-label th:text="|${option.displayName} (${option.studentCode})|">Intern (Code)</strong><small th:text="|${#temporals.format(option.internshipStart, 'dd/MM/yyyy')} – ${#temporals.format(option.internshipEnd, 'dd/MM/yyyy')}|">Dates</small></span>
|
||||
</label>
|
||||
<p class="picker-empty" data-picker-empty th:hidden="${!#lists.isEmpty(eligibleInternOptions)}">No matching eligible current members.</p>
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
<p class="picker-summary" data-picker-summary aria-live="polite">No Interns selected</p>
|
||||
<p class="field-help" th:if="${#lists.isEmpty(eligibleInternOptions)}">No eligible nonmembers are available.</p>
|
||||
<p class="field-error" id="member-intern-user-error" role="alert" th:if="${#fields.hasErrors('internUserIds')}" th:errors="*{internUserIds}"></p>
|
||||
<p class="field-help" role="status" th:if="${unavailableSelectionCount != null and unavailableSelectionCount > 0}"
|
||||
th:text="${unavailableSelectionCount == 1 ? '1 previously selected Intern is no longer eligible; choose a replacement.' : unavailableSelectionCount + ' previously selected Interns are no longer eligible; choose replacements.'}">Unavailable selection recovery</p>
|
||||
<dialog class="picker-drawer" data-picker-dialog aria-labelledby="member-picker-title">
|
||||
<div class="picker-header"><div><h2 class="panel-title" id="member-picker-title">Add Project members</h2><p class="field-help">Select one or more eligible Interns who are not current members.</p></div><button class="button" type="button" data-picker-cancel>Cancel</button></div>
|
||||
<div class="picker-body">
|
||||
|
||||
Reference in New Issue
Block a user