Improve the UX when creating groups as a supervisor #123
@ -2,85 +2,45 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<form wicket:id="form">
|
||||
<div class="line-length-limit">
|
||||
<form wicket:id="form">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div wicket:id="feedback"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div wicket:id="feedback"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-10">
|
||||
<label wicket:for="title">Title: </label>
|
||||
<input type="text" wicket:id="title" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label wicket:for="title" class="form-label">Title</label>
|
||||
<input type="text" wicket:id="title" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-10">
|
||||
<label wicket:for="description">Description: </label>
|
||||
<textarea wicket:id="description" class="form-control"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label wicket:for="description" class="form-label">Description</label>
|
||||
<textarea wicket:id="description" class="form-control"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-10">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" wicket:id="active" type="checkbox"/>
|
||||
<label class="form-check-label" wicket:for="active">Active</label>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" wicket:id="active" type="checkbox"/>
|
||||
<label class="form-check-label" wicket:for="active">Active</label>
|
||||
</div>
|
||||
|
||||
<fieldset class="mb-3">
|
||||
<legend>Projects</legend>
|
||||
<div class="group-project-grid">
|
||||
<label wicket:id="available_projects">
|
||||
<div>
|
||||
<input class="form-check-input mt-0" type="checkbox" wicket:id="selected">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div wicket:id="wmc">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-10">
|
||||
<strong>Add projects to group: </strong>
|
||||
<div wicket:id="projectTypes"></div>
|
||||
<select class="form-select" wicket:id="addProjects"></select>
|
||||
</div>
|
||||
<div>
|
||||
<h4 wicket:id="title"></h4>
|
||||
<h5 class="" wicket:id="type"></h5>
|
||||
<div wicket:id="authors">
|
||||
<span wicket:id="author"></span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<strong>Projects in group: </strong>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Title</th>
|
||||
<th>Authors</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr wicket:id="projects">
|
||||
<td><span wicket:id="type"></span></td>
|
||||
<td><span wicket:id="title"></span></td>
|
||||
<td><div wicket:id="authors">
|
||||
<div wicket:id="author"></div>
|
||||
</div></td>
|
||||
<td><a wicket:id="remove"><span class="fa fa-times"></span></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div wicket:id="noProjects"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" class="btn btn-success" >Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</fieldset>
|
||||
<button type="submit" class="btn btn-success">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
|
@ -2,9 +2,7 @@ package se.su.dsv.scipro.group;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.*;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.extensions.model.AbstractCheckBoxModel;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.*;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
@ -14,10 +12,6 @@ import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.model.util.ListModel;
|
||||
import se.su.dsv.scipro.components.AjaxCheckBoxMultipleChoice;
|
||||
import se.su.dsv.scipro.components.AjaxDropDownChoice;
|
||||
import se.su.dsv.scipro.components.ListAdapterModel;
|
||||
import se.su.dsv.scipro.profile.UserLinkPanel;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
@ -25,8 +19,6 @@ import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.project.ProjectStatus;
|
||||
import se.su.dsv.scipro.project.ProjectTeamMemberRoles;
|
||||
import se.su.dsv.scipro.session.SciProSession;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ProjectTypeService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
public class EditGroupPanel extends Panel {
|
||||
@ -37,9 +29,6 @@ public class EditGroupPanel extends Panel {
|
||||
@Inject
|
||||
private GroupService groupService;
|
||||
|
||||
@Inject
|
||||
private ProjectTypeService projectTypeService;
|
||||
|
||||
public EditGroupPanel(String id, final IModel<Group> model) {
|
||||
super(id, model);
|
||||
add(new GroupForm("form", model));
|
||||
@ -47,139 +36,80 @@ public class EditGroupPanel extends Panel {
|
||||
|
||||
private class GroupForm extends Form<Group> {
|
||||
|
||||
private final AjaxDropDownChoice<Project> addProjects;
|
||||
private final ListView<Project> projects;
|
||||
private final List<Project> currentProjects;
|
||||
private final AjaxCheckBoxMultipleChoice<ProjectType> projectTypes;
|
||||
|
||||
public GroupForm(String form, final IModel<Group> model) {
|
||||
super(form, model);
|
||||
final FeedbackPanel feedbackPanel = new FeedbackPanel("feedback");
|
||||
feedbackPanel.setOutputMarkupId(true);
|
||||
add(feedbackPanel);
|
||||
|
||||
currentProjects = new ArrayList<>(getModelObject().getProjects());
|
||||
add(
|
||||
new ListView<>("available_projects", LoadableDetachableModel.of(this::getAllRelevantProjects)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<Project> item) {
|
||||
CheckBox checkbox = new CheckBox("selected", new SelectProjectModel(model, item.getModel()));
|
||||
checkbox.setOutputMarkupId(true);
|
||||
item.add(checkbox);
|
||||
item.add(new Label("title", item.getModel().map(Project::getTitle)));
|
||||
item.add(new Label("type", item.getModel().map(Project::getProjectTypeName)));
|
||||
IModel<SortedSet<User>> authors = item.getModel().map(Project::getProjectParticipants);
|
||||
item.add(
|
||||
new ListView<>("authors", new ListAdapterModel<>(authors)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<User> item) {
|
||||
item.add(new UserLinkPanel("author", item.getModel()));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(new RequiredTextField<>("title", LambdaModel.of(model, Group::getTitle, Group::setTitle)));
|
||||
add(new TextArea<>("description", LambdaModel.of(model, Group::getDescription, Group::setDescription)));
|
||||
add(
|
||||
new CheckBox("active", LambdaModel.of(model, Group::isActive, Group::setActive)).setOutputMarkupId(true)
|
||||
);
|
||||
|
||||
final WebMarkupContainer wmc = new WebMarkupContainer("wmc");
|
||||
wmc.setOutputMarkupId(true);
|
||||
|
||||
projectTypes = projectTypeSelection(wmc);
|
||||
wmc.add(projectTypes);
|
||||
|
||||
addProjects = new AjaxDropDownChoice<>(
|
||||
"addProjects",
|
||||
new Model<>(),
|
||||
getSelectableProjects(currentProjects),
|
||||
new LambdaChoiceRenderer<>(Project::getTitle, Project::getId)
|
||||
) {
|
||||
@Override
|
||||
public void onNewSelection(AjaxRequestTarget target, Project objectSelected) {
|
||||
if (objectSelected != null && !currentProjects.contains(objectSelected)) {
|
||||
currentProjects.add(objectSelected);
|
||||
projects.setList(currentProjects);
|
||||
addProjects.setChoices(getSelectableProjects(currentProjects));
|
||||
target.add(wmc);
|
||||
}
|
||||
}
|
||||
};
|
||||
addProjects.setRequired(false);
|
||||
addProjects.setNullValid(true);
|
||||
wmc.add(addProjects);
|
||||
|
||||
projects = new ListView<>("projects", new ArrayList<>(currentProjects)) {
|
||||
@Override
|
||||
protected void populateItem(final ListItem<Project> item) {
|
||||
item.add(new Label("type", item.getModel().map(Project::getProjectTypeName)));
|
||||
item.add(new Label("title", item.getModel().map(Project::getTitle)));
|
||||
item.add(
|
||||
new ListView<>(
|
||||
"authors",
|
||||
new ListAdapterModel<>(
|
||||
getLoaded(item.getModelObject()).map(Project::getProjectParticipants)
|
||||
)
|
||||
) {
|
||||
@Override
|
||||
public void populateItem(ListItem<User> item) {
|
||||
item.add(new UserLinkPanel("author", item.getModel()));
|
||||
}
|
||||
}
|
||||
);
|
||||
item.add(
|
||||
new AjaxLink<>("remove", item.getModel()) {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
currentProjects.remove(item.getModelObject());
|
||||
projects.setList(currentProjects);
|
||||
addProjects.setChoices(getSelectableProjects(currentProjects));
|
||||
target.add(wmc);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
wmc.add(projects);
|
||||
|
||||
wmc.add(
|
||||
new Label("noProjects", "None") {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisibilityAllowed(currentProjects.isEmpty());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(wmc);
|
||||
}
|
||||
|
||||
private AjaxCheckBoxMultipleChoice<ProjectType> projectTypeSelection(final WebMarkupContainer wmc) {
|
||||
return new AjaxCheckBoxMultipleChoice<>(
|
||||
"projectTypes",
|
||||
projectTypeService.findAllActive(),
|
||||
projectTypeService.findAllActive(),
|
||||
new LambdaChoiceRenderer<>(ProjectType::getName, ProjectType::getId)
|
||||
) {
|
||||
@Override
|
||||
public void onUpdate(AjaxRequestTarget target) {
|
||||
addProjects.setChoices(getSelectableProjects(currentProjects));
|
||||
target.add(wmc);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSubmit() {
|
||||
Group group = getModelObject();
|
||||
group.setProjects(new HashSet<>(currentProjects));
|
||||
groupService.save(group);
|
||||
info(getString("saved"));
|
||||
}
|
||||
|
||||
private ListModel<Project> getSelectableProjects(List<Project> currentProjects) {
|
||||
private List<Project> getAllRelevantProjects() {
|
||||
final ProjectService.Filter filter = new ProjectService.Filter();
|
||||
filter.setSupervisor(SciProSession.get().getUser());
|
||||
filter.setRoles(Collections.singleton(ProjectTeamMemberRoles.CO_SUPERVISOR));
|
||||
filter.setStatuses(Collections.singletonList(ProjectStatus.ACTIVE));
|
||||
filter.setProjectTypes(projectTypes.getModelObject());
|
||||
List<Project> all = projectService.findAll(filter);
|
||||
all.removeAll(currentProjects);
|
||||
all.remove(null);
|
||||
return new ListModel<>(all);
|
||||
return projectService.findAll(filter);
|
||||
}
|
||||
|
||||
private LoadableDetachableModel<Project> getLoaded(final Project project) {
|
||||
return new LoadableDetachableModel<>() {
|
||||
@Override
|
||||
protected Project load() {
|
||||
return projectService.findOne(project.getId());
|
||||
}
|
||||
};
|
||||
private static final class SelectProjectModel extends AbstractCheckBoxModel {
|
||||
|
||||
private final IModel<Group> groupModel;
|
||||
private final IModel<Project> projectModel;
|
||||
|
||||
public SelectProjectModel(IModel<Group> groupModel, IModel<Project> projectModel) {
|
||||
this.groupModel = groupModel;
|
||||
this.projectModel = projectModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
return groupModel.getObject().getProjects().contains(projectModel.getObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void select() {
|
||||
groupModel.getObject().getProjects().add(projectModel.getObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unselect() {
|
||||
groupModel.getObject().getProjects().remove(projectModel.getObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -607,3 +607,27 @@ th.wicket_orderUp, th.sorting_asc {
|
||||
.line-length-limit {
|
||||
max-width: 80em;
|
||||
}
|
||||
.group-project-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
grid-template-columns: repeat(auto-fill, minmax(30em, 1fr));
|
||||
}
|
||||
.group-project-grid > * {
|
||||
background: linear-gradient(to left, white 40%, var(--bs-success-bg-subtle) 60%) right;
|
||||
background-size: 250% 100%;
|
||||
transition: background 0.4s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid black;
|
||||
border-radius: 0.25em;
|
||||
display: flex;
|
||||
padding: 0.5em;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.group-project-grid > *:has(:checked) {
|
||||
background-position: left;
|
||||
}
|
||||
.group-project-grid label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ public class EditGroupPanelTest extends SciProTest {
|
||||
group.setId(1L);
|
||||
Project project = createProject();
|
||||
group.setProjects(new HashSet<>(Collections.singletonList(project)));
|
||||
when(projectService.findOne(anyLong())).thenReturn(project);
|
||||
startPanel();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user