3204 Assign targets by specific periods
This commit is contained in:
parent
a123fd1932
commit
4b727167e5
core/src/main/java/se/su/dsv/scipro/reviewing
view/src/main/java/se/su/dsv/scipro/admin/pages
@ -14,4 +14,6 @@ public interface ReviewerCapacityService {
|
||||
List<User> getAllActiveReviewers();
|
||||
|
||||
List<User> getActiveReviewersOnUnit(Unit unit);
|
||||
|
||||
int getTarget(User reviewerObject, DateRange dateRange);
|
||||
}
|
@ -63,6 +63,13 @@ class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAs
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTarget(User reviewerObject, DateRange dateRange) {
|
||||
return getTarget(reviewerObject, dateRange.from())
|
||||
.map(ReviewerTarget::getTarget)
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
private Optional<ReviewerTarget> getTarget(User reviewer, LocalDate date) {
|
||||
return reviewerTargetRepository.getReviewerTargets(reviewer, date)
|
||||
.stream()
|
||||
|
@ -1,5 +1,6 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import com.google.inject.persist.Transactional;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import se.su.dsv.scipro.system.AbstractRepository;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
@ -16,6 +17,7 @@ public class ReviewerTargetRepositoryImpl extends AbstractRepository implements
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void save(ReviewerTarget reviewerTarget) {
|
||||
EntityManager entityManager = em();
|
||||
if (entityManager.contains(reviewerTarget)) {
|
||||
|
@ -11,10 +11,7 @@
|
||||
administrators during the assignment process.
|
||||
</p>
|
||||
<p>
|
||||
You can use the below "default period" to quickly set the number of reviews for all
|
||||
reviewers within a single period. If you want to set the number of reviews for a single
|
||||
reviewer, you can expand the reviewer by clicking on their box and add individual targets
|
||||
for each period.
|
||||
Use the period selector below to select the period you want to manage targets for.
|
||||
</p>
|
||||
<p>
|
||||
You also have the ability to mark reviewers as completely unavailable for a given period.
|
||||
@ -40,34 +37,34 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h3>Default period</h3>
|
||||
<div class="mb-3 row">
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" wicket:id="default_from_date">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto align-self-center">
|
||||
—
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" wicket:id="default_to_date">
|
||||
</div>
|
||||
</div>
|
||||
<h3>Period</h3>
|
||||
<div class="mb-3">
|
||||
<select class="form-select" wicket:id="periods"></select>
|
||||
<small class="text-muted">Select the period you want to manage targets for.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6" wicket:id="reviewers_container">
|
||||
<div class="col-12 col-xl-8" wicket:id="reviewers_container">
|
||||
<div class="card mb-3" wicket:id="reviewers">
|
||||
<div class="row g-0">
|
||||
<div class="row g-0" wicket:id="reviewer">
|
||||
<div class="col-auto">
|
||||
<img class="img-fluid rounded-start profile-picture-md" wicket:id="profile_image">
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="col-auto">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title" wicket:id="name">[John Doe]</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<form class="card-body row justify-content-end" wicket:id="form">
|
||||
<div class="col-auto" wicket:id="feedback"></div>
|
||||
<div class="col-auto">
|
||||
<input type="number" class="form-control form-control-sm" min="0" wicket:id="target">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-success btn-sm">Set target for selected period</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-auto w-64-px align-self-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><defs><clipPath><path fill="#00f" fill-opacity=".514" d="m-7 1024.36h34v34h-34z"/></clipPath><clipPath><path fill="#aade87" fill-opacity=".472" d="m-6 1028.36h32v32h-32z"/></clipPath></defs><path d="m345.44 248.29l-194.29 194.28c-12.359 12.365-32.397 12.365-44.75 0-12.354-12.354-12.354-32.391 0-44.744l171.91-171.91-171.91-171.9c-12.354-12.359-12.354-32.394 0-44.748 12.354-12.359 32.391-12.359 44.75 0l194.29 194.28c6.177 6.18 9.262 14.271 9.262 22.366 0 8.099-3.091 16.196-9.267 22.373" transform="matrix(.03541-.00013.00013.03541 2.98 3.02)" fill="#4d4d4d"/></svg>
|
||||
</div>
|
||||
|
@ -2,40 +2,47 @@ package se.su.dsv.scipro.admin.pages;
|
||||
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
|
||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.form.DropDownChoice;
|
||||
import org.apache.wicket.markup.html.form.FormComponent;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.form.LambdaChoiceRenderer;
|
||||
import org.apache.wicket.markup.html.form.TextField;
|
||||
import org.apache.wicket.markup.html.form.NumberTextField;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import se.su.dsv.scipro.components.BootstrapDatePicker;
|
||||
import se.su.dsv.scipro.data.DetachableServiceModel;
|
||||
import se.su.dsv.scipro.profile.UserLabel;
|
||||
import se.su.dsv.scipro.profile.UserProfileImage;
|
||||
import se.su.dsv.scipro.reviewing.DateRange;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
|
||||
import se.su.dsv.scipro.springdata.services.UnitService;
|
||||
import se.su.dsv.scipro.system.Unit;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.Serializable;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.YearMonth;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AdminReviewerCapacityManagementPage extends AbstractAdminProjectPage {
|
||||
@Inject
|
||||
ReviewerCapacityService reviewerCapacityService;
|
||||
@Inject
|
||||
UnitService unitService;
|
||||
@Inject
|
||||
Clock clock;
|
||||
|
||||
private final WebMarkupContainer reviewerList;
|
||||
|
||||
private IModel<Unit> selectedUnit;
|
||||
private IModel<LocalDate> fromDate = new Model<>();
|
||||
private IModel<LocalDate> toDate = new Model<>();
|
||||
private IModel<ReviewerPeriod> selectedPeriod = new Model<>();
|
||||
|
||||
public AdminReviewerCapacityManagementPage() {
|
||||
IModel<List<Unit>> units = LoadableDetachableModel.of(() ->
|
||||
@ -56,13 +63,18 @@ public class AdminReviewerCapacityManagementPage extends AbstractAdminProjectPag
|
||||
});
|
||||
add(unitDropDownChoice);
|
||||
|
||||
FormComponent<LocalDate> fromDateField = new TextField<>("default_from_date", fromDate, LocalDate.class);
|
||||
fromDateField.add(new BootstrapDatePicker());
|
||||
add(fromDateField);
|
||||
|
||||
FormComponent<LocalDate> toDateField = new TextField<>("default_to_date", toDate, LocalDate.class);
|
||||
toDateField.add(new BootstrapDatePicker());
|
||||
add(toDateField);
|
||||
DropDownChoice<ReviewerPeriod> periods = new DropDownChoice<>(
|
||||
"periods",
|
||||
selectedPeriod,
|
||||
getPeriods(),
|
||||
new LambdaChoiceRenderer<>(ReviewerPeriod::displayString));
|
||||
periods.add(new AjaxFormComponentUpdatingBehavior("change") {
|
||||
@Override
|
||||
protected void onUpdate(AjaxRequestTarget target) {
|
||||
target.add(reviewerList);
|
||||
}
|
||||
});
|
||||
add(periods);
|
||||
|
||||
IModel<List<User>> reviewers = LoadableDetachableModel.of(() -> {
|
||||
if (selectedUnit.getObject() == null) {
|
||||
@ -74,13 +86,90 @@ public class AdminReviewerCapacityManagementPage extends AbstractAdminProjectPag
|
||||
});
|
||||
reviewerList = new WebMarkupContainer("reviewers_container");
|
||||
reviewerList.add(new ListView<>("reviewers", reviewers) {
|
||||
{
|
||||
setReuseItems(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateItem(ListItem<User> item) {
|
||||
item.add(new UserProfileImage("profile_image", item.getModel(), UserProfileImage.Size.MEDIUM));
|
||||
item.add(new UserLabel("name", item.getModel()));
|
||||
item.add(new ReviewerCard("reviewer", item.getModel()));
|
||||
}
|
||||
});
|
||||
reviewerList.setOutputMarkupId(true);
|
||||
add(reviewerList);
|
||||
}
|
||||
|
||||
private List<ReviewerPeriod> getPeriods() {
|
||||
YearMonth now = YearMonth.now(clock);
|
||||
ReviewerPeriod current = ReviewerPeriod.fromYearMonth(now);
|
||||
return Stream.iterate(current, ReviewerPeriod::next)
|
||||
.limit(3)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private record ReviewerPeriod(LocalDate from, LocalDate to) implements Serializable {
|
||||
public static ReviewerPeriod fromYearMonth(YearMonth yearMonth) {
|
||||
if (yearMonth.getMonth().compareTo(Month.JUNE) <= 0) {
|
||||
return new ReviewerPeriod(
|
||||
LocalDate.of(yearMonth.getYear(), Month.JANUARY, 1),
|
||||
LocalDate.of(yearMonth.getYear(), Month.JUNE, 30));
|
||||
}
|
||||
else {
|
||||
return new ReviewerPeriod(
|
||||
LocalDate.of(yearMonth.getYear(), Month.JULY, 1),
|
||||
LocalDate.of(yearMonth.getYear(), Month.DECEMBER, 31));
|
||||
}
|
||||
}
|
||||
|
||||
public ReviewerPeriod next() {
|
||||
return fromYearMonth(YearMonth.from(to.plusDays(1)));
|
||||
}
|
||||
|
||||
public String displayString() {
|
||||
return String.format("%s - %s", from, to);
|
||||
}
|
||||
|
||||
public DateRange toDateRange() {
|
||||
return new DateRange(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
private class ReviewerCard extends WebMarkupContainer {
|
||||
public ReviewerCard(String id, IModel<User> reviewer) {
|
||||
super(id, reviewer);
|
||||
|
||||
add(new UserProfileImage("profile_image", reviewer, UserProfileImage.Size.MEDIUM));
|
||||
add(new UserLabel("name", reviewer));
|
||||
add(new AssignTargetForm("form", reviewer));
|
||||
}
|
||||
|
||||
private class AssignTargetForm extends Form<User> {
|
||||
private IModel<Integer> target;
|
||||
|
||||
public AssignTargetForm(String id, IModel<User> reviewer) {
|
||||
super(id, reviewer);
|
||||
|
||||
add(new FencedFeedbackPanel("feedback", this));
|
||||
|
||||
target = LoadableDetachableModel.of(() -> {
|
||||
if (selectedPeriod.getObject() == null) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return reviewerCapacityService.getTarget(reviewer.getObject(), selectedPeriod.getObject().toDateRange());
|
||||
}
|
||||
});
|
||||
|
||||
NumberTextField<Integer> targetField = new NumberTextField<>("target", target, Integer.class);
|
||||
targetField.setMinimum(0);
|
||||
add(targetField);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSubmit() {
|
||||
reviewerCapacityService.assignTarget(getModelObject(), selectedPeriod.getObject().toDateRange(), target.getObject());
|
||||
success("Target assigned");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user