Allow supervisors to write a note associated with their projects #8
@ -48,6 +48,7 @@ import se.su.dsv.scipro.notifications.settings.service.ReceiverConfigurationServ
|
|||||||
import se.su.dsv.scipro.notifications.settings.service.ReceiverConfigurationServiceImpl;
|
import se.su.dsv.scipro.notifications.settings.service.ReceiverConfigurationServiceImpl;
|
||||||
import se.su.dsv.scipro.peer.*;
|
import se.su.dsv.scipro.peer.*;
|
||||||
import se.su.dsv.scipro.plagiarism.*;
|
import se.su.dsv.scipro.plagiarism.*;
|
||||||
|
import se.su.dsv.scipro.project.ProjectNoteService;
|
||||||
import se.su.dsv.scipro.project.ProjectPeopleStatisticsService;
|
import se.su.dsv.scipro.project.ProjectPeopleStatisticsService;
|
||||||
import se.su.dsv.scipro.project.ProjectPeopleStatisticsServiceImpl;
|
import se.su.dsv.scipro.project.ProjectPeopleStatisticsServiceImpl;
|
||||||
import se.su.dsv.scipro.project.ProjectService;
|
import se.su.dsv.scipro.project.ProjectService;
|
||||||
@ -139,6 +140,7 @@ public class CoreModule extends AbstractModule {
|
|||||||
bind(FirstMeetingService.class).to(FirstMeetingServiceImpl.class);
|
bind(FirstMeetingService.class).to(FirstMeetingServiceImpl.class);
|
||||||
bind(FinalSeminarCreationSubscribers.class).asEagerSingleton();
|
bind(FinalSeminarCreationSubscribers.class).asEagerSingleton();
|
||||||
bind(ProjectPartnerRepository.class).to(ProjectPartnerRepositoryImpl.class);
|
bind(ProjectPartnerRepository.class).to(ProjectPartnerRepositoryImpl.class);
|
||||||
|
bind(ProjectNoteService.class).to(ProjectServiceImpl.class);
|
||||||
|
|
||||||
install(new PlagiarismModule());
|
install(new PlagiarismModule());
|
||||||
install(new NotificationModule());
|
install(new NotificationModule());
|
||||||
|
@ -85,6 +85,13 @@ public class Project extends DomainObject {
|
|||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private Language language;
|
private Language language;
|
||||||
|
|
||||||
|
@ElementCollection(fetch = FetchType.LAZY)
|
||||||
|
@CollectionTable(name = "project_user_note", joinColumns = @JoinColumn(name = "project_id"))
|
||||||
|
@Column(name = "note")
|
||||||
|
@SuppressWarnings("JpaDataSourceORMInspection") // false warning from IntelliJ for the @MapKeyJoinColumn
|
||||||
|
@MapKeyJoinColumn(name = "user_id")
|
||||||
|
private Map<User, String> userNotes = new HashMap<>();
|
||||||
|
|
||||||
@PrePersist
|
@PrePersist
|
||||||
@PreUpdate
|
@PreUpdate
|
||||||
void cleanTitle() {
|
void cleanTitle() {
|
||||||
@ -94,6 +101,14 @@ public class Project extends DomainObject {
|
|||||||
title = title.trim();
|
title = title.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<User, String> getUserNotes() {
|
||||||
|
return userNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserNotes(Map<User, String> userNotes) {
|
||||||
|
this.userNotes = userNotes;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFinalSeminarRuleExempted() {
|
public boolean isFinalSeminarRuleExempted() {
|
||||||
return finalSeminarRuleExempted;
|
return finalSeminarRuleExempted;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package se.su.dsv.scipro.project;
|
||||||
|
|
||||||
|
import se.su.dsv.scipro.system.User;
|
||||||
|
|
||||||
|
public interface ProjectNoteService {
|
||||||
|
String getUserNote(Project project, User user);
|
||||||
|
|
||||||
|
void setUserNote(Project project, User user, String note);
|
||||||
|
}
|
@ -11,4 +11,8 @@ import java.util.List;
|
|||||||
@Transactional
|
@Transactional
|
||||||
public interface ProjectRepo extends JpaRepository<Project, Long>, QueryDslPredicateExecutor<Project> {
|
public interface ProjectRepo extends JpaRepository<Project, Long>, QueryDslPredicateExecutor<Project> {
|
||||||
List<User> findMultipleAuthors(Collection<Project> projects);
|
List<User> findMultipleAuthors(Collection<Project> projects);
|
||||||
|
|
||||||
|
String getUserNoteForProject(Project project, User user);
|
||||||
|
|
||||||
|
void setUserNoteForProject(Project project, User user, String note);
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package se.su.dsv.scipro.project;
|
package se.su.dsv.scipro.project;
|
||||||
|
|
||||||
|
import com.google.inject.persist.Transactional;
|
||||||
import se.su.dsv.scipro.system.GenericRepo;
|
import se.su.dsv.scipro.system.GenericRepo;
|
||||||
import se.su.dsv.scipro.system.User;
|
import se.su.dsv.scipro.system.User;
|
||||||
|
|
||||||
@ -30,4 +31,16 @@ public class ProjectRepoImpl extends GenericRepo<Project, Long> implements Proje
|
|||||||
query.setParameter("projects", projects);
|
query.setParameter("projects", projects);
|
||||||
return query.getResultList();
|
return query.getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUserNoteForProject(Project project, User user) {
|
||||||
|
return project.getUserNotes().get(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void setUserNoteForProject(Project project, User user, String note) {
|
||||||
|
project.getUserNotes().put(user, note);
|
||||||
|
save(project);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import java.time.Duration;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ProjectServiceImpl extends AbstractServiceImpl<Project, Long> implements ProjectService {
|
public class ProjectServiceImpl extends AbstractServiceImpl<Project, Long> implements ProjectService, ProjectNoteService {
|
||||||
|
|
||||||
public static final int MIN_TITLE_LENGTH = 3;
|
public static final int MIN_TITLE_LENGTH = 3;
|
||||||
private final ProjectRepo projectRepo;
|
private final ProjectRepo projectRepo;
|
||||||
@ -175,6 +175,16 @@ public class ProjectServiceImpl extends AbstractServiceImpl<Project, Long> imple
|
|||||||
return completed;
|
return completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUserNote(Project project, User user) {
|
||||||
|
return projectRepo.getUserNoteForProject(project, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserNote(Project project, User user, String note) {
|
||||||
|
projectRepo.setUserNoteForProject(project, user, note);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Project> findAll(Filter filter, Pageable pageable) {
|
public List<Project> findAll(Filter filter, Pageable pageable) {
|
||||||
return findAll(toPredicate(filter), pageable);
|
return findAll(toPredicate(filter), pageable);
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `project_user_note` (
|
||||||
|
`project_id` bigint NOT NULL,
|
||||||
|
`user_id` bigint NOT NULL,
|
||||||
|
`note` text NULL,
|
||||||
|
PRIMARY KEY (`project_id`, `user_id`),
|
||||||
|
CONSTRAINT `FK_project_user_note_project` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `FK_project_user_note_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
@ -0,0 +1,43 @@
|
|||||||
|
package se.su.dsv.scipro.components;
|
||||||
|
|
||||||
|
import org.apache.wicket.markup.html.basic.Label;
|
||||||
|
import org.apache.wicket.model.IModel;
|
||||||
|
import org.apache.wicket.util.convert.ConversionException;
|
||||||
|
import org.apache.wicket.util.convert.IConverter;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class MaxLengthLabel extends Label {
|
||||||
|
private static final int DEFAULT_MAX_LENGTH = 50;
|
||||||
|
|
||||||
|
private final int maxLength;
|
||||||
|
|
||||||
|
public MaxLengthLabel(String id, IModel<String> dataModel) {
|
||||||
|
this(id, dataModel, DEFAULT_MAX_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxLengthLabel(String id, IModel<String> dataModel, int maxLength) {
|
||||||
|
super(id, dataModel);
|
||||||
|
this.maxLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IConverter<?> createConverter(Class<?> type) {
|
||||||
|
return new MaxLengthConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MaxLengthConverter implements IConverter<String> {
|
||||||
|
@Override
|
||||||
|
public String convertToObject(String s, Locale locale) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convertToString(String o, Locale locale) {
|
||||||
|
if (o.length() > maxLength) {
|
||||||
|
return o.substring(0, maxLength) + "...";
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||||
|
<body>
|
||||||
|
<wicket:panel>
|
||||||
|
<div wicket:id="edit_note_modal"></div>
|
||||||
|
<span wicket:id="shortened_note"></span>
|
||||||
|
<a wicket:id="view_note">[view/edit note]</a>
|
||||||
|
</wicket:panel>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||||
|
<body>
|
||||||
|
<wicket:panel>
|
||||||
|
<form wicket:id="form">
|
||||||
|
<div wicket:id="feedback"></div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label wicket:for="note">Note</label>
|
||||||
|
<textarea wicket:id="note" rows="20" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
<button wicket:id="save" type="submit" class="btn btn-primary">Save</button>
|
||||||
|
</form>
|
||||||
|
</wicket:panel>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,108 @@
|
|||||||
|
package se.su.dsv.scipro.supervisor.panels;
|
||||||
|
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||||
|
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||||
|
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
|
||||||
|
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
|
||||||
|
import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn;
|
||||||
|
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||||
|
import org.apache.wicket.injection.Injector;
|
||||||
|
import org.apache.wicket.markup.html.form.Form;
|
||||||
|
import org.apache.wicket.markup.html.form.TextArea;
|
||||||
|
import org.apache.wicket.markup.html.panel.FeedbackPanel;
|
||||||
|
import org.apache.wicket.markup.html.panel.GenericPanel;
|
||||||
|
import org.apache.wicket.markup.repeater.Item;
|
||||||
|
import org.apache.wicket.model.IModel;
|
||||||
|
import org.apache.wicket.model.LoadableDetachableModel;
|
||||||
|
import org.apache.wicket.model.Model;
|
||||||
|
import se.su.dsv.scipro.components.LargeModalWindow;
|
||||||
|
import se.su.dsv.scipro.components.MaxLengthLabel;
|
||||||
|
import se.su.dsv.scipro.components.ModalWindowPlus;
|
||||||
|
import se.su.dsv.scipro.project.Project;
|
||||||
|
import se.su.dsv.scipro.project.ProjectNoteService;
|
||||||
|
import se.su.dsv.scipro.system.User;
|
||||||
|
|
||||||
|
import java.time.LocalTime;
|
||||||
|
|
||||||
|
public class ProjectNoteColumn extends AbstractExportableColumn<Project, String> {
|
||||||
|
@Inject
|
||||||
|
private ProjectNoteService projectNoteService;
|
||||||
|
|
||||||
|
private final IModel<User> user;
|
||||||
|
|
||||||
|
public ProjectNoteColumn(IModel<String> displayModel, IModel<User> user) {
|
||||||
|
super(displayModel);
|
||||||
|
Injector.get().inject(this);
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IModel<String> getDataModel(IModel<Project> rowModel) {
|
||||||
|
return LoadableDetachableModel.of(() -> projectNoteService.getUserNote(
|
||||||
|
rowModel.getObject(),
|
||||||
|
user.getObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void populateItem(
|
||||||
|
Item<ICellPopulator<Project>> cellItem,
|
||||||
|
String componentId,
|
||||||
|
IModel<Project> rowModel)
|
||||||
|
{
|
||||||
|
cellItem.add(new ViewAndEditNoteCellPanel(componentId, rowModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewAndEditNoteCellPanel extends GenericPanel<Project> {
|
||||||
|
public ViewAndEditNoteCellPanel(String id, IModel<Project> model) {
|
||||||
|
super(id, model);
|
||||||
|
|
||||||
|
ModalWindowPlus modal = new LargeModalWindow("edit_note_modal");
|
||||||
|
modal.setTitle(model.map(Project::getTitle).map(projectTitle -> "View/Edit note for " + projectTitle));
|
||||||
|
modal.setContent(componentId -> new ViewAndEditNoteForm(componentId, model));
|
||||||
|
add(modal);
|
||||||
|
|
||||||
|
add(new MaxLengthLabel("shortened_note", getDataModel(model)));
|
||||||
ansv7779 marked this conversation as resolved
|
|||||||
|
|
||||||
|
AjaxLink<Object> noteLink = new AjaxLink<>("view_note") {
|
||||||
|
@Override
|
||||||
|
public void onClick(AjaxRequestTarget target) {
|
||||||
|
modal.show(target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
noteLink.setBody(Model.of("View/Edit note"));
|
||||||
|
add(noteLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewAndEditNoteForm extends GenericPanel<Project> {
|
||||||
|
public ViewAndEditNoteForm(String id, IModel<Project> model) {
|
||||||
|
super(id, model);
|
||||||
|
|
||||||
|
IModel<String> note = getDataModel(model);
|
||||||
|
|
||||||
|
Form<Project> form = new Form<>("form", model) {
|
||||||
|
@Override
|
||||||
|
protected void onSubmit() {
|
||||||
|
projectNoteService.setUserNote(
|
||||||
|
model.getObject(),
|
||||||
|
user.getObject(),
|
||||||
|
note.getObject()
|
||||||
|
);
|
||||||
|
success("Note saved at " + LocalTime.now());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
add(form);
|
||||||
|
|
||||||
|
form.add(new FencedFeedbackPanel("feedback", form));
|
||||||
|
|
||||||
|
form.add(new TextArea<>("note", note));
|
||||||
ansv7779 marked this conversation as resolved
niat8586
commented
Rename model.getObject to projectModel.getObject ? Would make it more readable and conform with the projectNoteService.setUserNote method. Rename model.getObject to projectModel.getObject ?
Would make it more readable and conform with the projectNoteService.setUserNote method.
|
|||||||
|
form.add(new AjaxSubmitLink("save") {
|
||||||
|
@Override
|
||||||
|
protected void onAfterSubmit(AjaxRequestTarget target) {
|
||||||
|
target.add(form);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import org.apache.wicket.markup.html.form.LambdaChoiceRenderer;
|
|||||||
import org.apache.wicket.markup.html.panel.Panel;
|
import org.apache.wicket.markup.html.panel.Panel;
|
||||||
import org.apache.wicket.model.IModel;
|
import org.apache.wicket.model.IModel;
|
||||||
import org.apache.wicket.model.LambdaModel;
|
import org.apache.wicket.model.LambdaModel;
|
||||||
|
import org.apache.wicket.model.LoadableDetachableModel;
|
||||||
import org.apache.wicket.model.Model;
|
import org.apache.wicket.model.Model;
|
||||||
import se.su.dsv.scipro.components.*;
|
import se.su.dsv.scipro.components.*;
|
||||||
import se.su.dsv.scipro.components.datatables.MultipleUsersColumn;
|
import se.su.dsv.scipro.components.datatables.MultipleUsersColumn;
|
||||||
@ -81,6 +82,7 @@ public class SupervisorMyProjectsPanel extends Panel {
|
|||||||
return new ListAdapterModel<>(rowModel.map(Project::getProjectParticipants));
|
return new ListAdapterModel<>(rowModel.map(Project::getProjectParticipants));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
columns.add(new ProjectNoteColumn(Model.of("Note"), LoadableDetachableModel.of(this::currentUser)));
|
||||||
columns.add(new UserColumn<>(Model.of("Head supervisor"), "headSupervisor.fullName", Project::getHeadSupervisor));
|
columns.add(new UserColumn<>(Model.of("Head supervisor"), "headSupervisor.fullName", Project::getHeadSupervisor));
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user
Use of String.format could work here?
Example
modal.setTitle(model.map(Project::getTitle).map(projectTitle -> String.format("View/Edit note for %s", projectTitle)));
What do you think?
Replaced with proper i18n