Skip to content

Commit

Permalink
Project information can be edited with a dialog within the projectinf…
Browse files Browse the repository at this point in the history
…ormationpage (#344)

* Rewrite ProjectDetailsComponent to allow for editing via ProjectInformationDialog

* Update Javadocs

* Address Code Smells

* Address Code Smell
  • Loading branch information
Steffengreiner authored Aug 10, 2023
1 parent 84bfc17 commit 8aae772
Show file tree
Hide file tree
Showing 9 changed files with 708 additions and 386 deletions.
28 changes: 25 additions & 3 deletions vaadinfrontend/frontend/themes/datamanager/components/dialog.css
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,12 @@ Used by both project creation and experiment creation dialog, thus the unique na
align-items: baseline;
}

.experiment-group-dialog vaadin-icon{
.experiment-group-dialog vaadin-icon {
cursor: pointer;
color: var(--lumo-primary-color);
}

.experiment-group-dialog .header{
.experiment-group-dialog .header {
font-weight: bold;
}

Expand All @@ -204,7 +204,7 @@ Used by both project creation and experiment creation dialog, thus the unique na
}


.experiment-group-dialog .add-new-group-action span{
.experiment-group-dialog .add-new-group-action span {
cursor: pointer;
}

Expand All @@ -227,3 +227,25 @@ Used by both project creation and experiment creation dialog, thus the unique na
cursor: pointer;
color: var(--lumo-primary-color);
}

.project-information-dialog::part(overlay) {
width: 66vw;
}

.project-information-dialog .define-project-content {
width: 100%;
height: 100%;
gap: 1em;
display: flex;
flex-direction: column;
}

.project-information-dialog .define-project-content .information {
display: flex;
flex-direction: column;
}

.project-information-dialog .define-project-content .contacts {
display: flex;
flex-direction: column;
}
24 changes: 11 additions & 13 deletions vaadinfrontend/frontend/themes/datamanager/components/page-area.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,23 @@
gap: var(--lumo-space-s);
}

.page-area.project-details-component .details-content .no-person-assigned {
color: var(--lumo-secondary-text-color);
}

.page-area.project-details-component vaadin-multi-select-combo-box {
width: 75%
.page-area.project-details-component .header {
display: flex;
justify-content: space-between;
align-items: baseline;
}

.page-area.project-details-component .display-edit-component {
width: 100%
.page-area.project-details-component .details-content .no-person-assigned {
color: var(--lumo-secondary-text-color);
}

.page-area.project-details-component .display-edit-component .edit-component {
width: 100%;
.page-area.project-details-component .details-content .person-reference {
display: flex;
flex-flow: column;
}

.page-area.project-details-component .display-edit-component .display-component {
width: 100%;
font-size: var(--lumo-font-size-s);
.page-area.project-details-component .details-content .person-reference .email {
color: var(--lumo-secondary-text-color);
}

.page-area.sample-details-component .content {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package life.qbic.datamanager.views.projects.project.info;

import static life.qbic.logging.service.LoggerFactory.logger;

import com.vaadin.flow.component.ItemLabelGenerator;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.value.ValueChangeMode;
import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
import life.qbic.logging.api.Logger;
import life.qbic.projectmanagement.application.PersonSearchService;
import life.qbic.projectmanagement.domain.project.ExperimentalDesignDescription;
import life.qbic.projectmanagement.domain.project.PersonReference;
import life.qbic.projectmanagement.domain.project.ProjectObjective;
import life.qbic.projectmanagement.domain.project.ProjectTitle;

/**
* <b>Define Project Component</b>
*
* <p>Component to define the minimum required project information</p>
*/
public class DefineProjectComponent extends Div {

private static final Logger log = logger(DefineProjectComponent.class);
@Serial
private static final long serialVersionUID = 5220409505242578485L;
private final TextField projectTitle = new TextField("Title");
private final TextArea projectObjective = new TextArea("Objective");
private final TextArea experimentalDesign = new TextArea(
"Experimental Design Description");
private final ComboBox<PersonReference> principalInvestigator = new ComboBox<>(
"Principal Investigator");
private final ComboBox<PersonReference> responsiblePerson = new ComboBox<>(
"Project Responsible (optional)");
private final ComboBox<PersonReference> projectManager = new ComboBox<>("Project Manager");
private final List<Binder<?>> binders = new ArrayList<>();
private final transient PersonSearchService personSearchService;

/**
* Creates a new empty DefineProjectComponent.
* <p>
* This component is intended to be used within the {@link ProjectInformationDialog} and
* {@link life.qbic.datamanager.views.projects.create.AddProjectDialog} containing the validation
* and vaadin field logic for the project information
*/
public DefineProjectComponent(PersonSearchService personSearchService) {
this.personSearchService = personSearchService;
this.addClassName("define-project-content");
initProjectsDefinitionLayout();
configureComponent();
}

private void initProjectsDefinitionLayout() {
initProjectInformationLayout();
initProjectContactsLayout();
}

private void initProjectInformationLayout() {
Div projectInformationContainer = new Div();
projectInformationContainer.addClassName("information");
projectInformationContainer.add(projectTitle, projectObjective, experimentalDesign);
this.add(projectInformationContainer);
}

private void initProjectContactsLayout() {
Div projectContactsContainer = new Div();
projectContactsContainer.addClassName("contacts");
Span projectContactsTitle = new Span("Project Contacts");
projectContactsTitle.addClassName("title");
Span projectContactsDescription = new Span("Important contact people of the project");
responsiblePerson.setClearButtonVisible(true);
experimentalDesign.setClearButtonVisible(true);
projectContactsContainer.add(projectContactsTitle, projectContactsDescription,
principalInvestigator,
responsiblePerson, projectManager);
this.add(projectContactsContainer);
}

private void configureComponent() {
configureValidators();
configurePersonSearch();
}

private void configurePersonSearch() {
setUpPersonSearch(principalInvestigator);
setUpPersonSearch(projectManager);
setUpPersonSearch(responsiblePerson);
}

private void setUpPersonSearch(ComboBox<PersonReference> comboBox) {
comboBox.setItems(
query -> personSearchService.find(query.getFilter().orElse(""), query.getOffset(),
query.getLimit()).stream());
comboBox.setRenderer(
new ComponentRenderer<>(personReference -> new Text(personReference.fullName())));
comboBox.setItemLabelGenerator(
(ItemLabelGenerator<PersonReference>) PersonReference::fullName);
}

/**
* Sets the initial values for each field within this component, necessary if this content is used
* for editing functionality
*
* @param projectTitle {@link ProjectTitle} of the project to be edited
* @param projectObjective {@link ProjectObjective} of the project to be edited
* @param experimentalDesignDescription {@link ExperimentalDesignDescription} of the project to be
* edited
* @param principalInvestigator {@link PersonReference} of the principal investigator of
* the project to be edited
* @param responsiblePerson {@link PersonReference} of the responsible person to be
* contacted of the project to be edited
* @param projectManager {@link PersonReference} of the project manager of the
* project to be edited
*/
public void setProjectInformation(ProjectTitle projectTitle,
ProjectObjective projectObjective,
ExperimentalDesignDescription experimentalDesignDescription,
PersonReference principalInvestigator, PersonReference responsiblePerson,
PersonReference projectManager) {
this.projectTitle.setValue(projectTitle.title());
this.projectObjective.setValue(projectObjective.value());
this.experimentalDesign.setValue(experimentalDesignDescription.value());
this.principalInvestigator.setValue(principalInvestigator);
this.responsiblePerson.setValue(responsiblePerson);
this.projectManager.setValue(projectManager);
}

private void configureValidators() {
restrictInputLength();
projectTitle.setRequired(true);
projectObjective.setRequired(true);
principalInvestigator.setRequired(true);
projectManager.setRequired(true);
Binder<Container<String>> binderTitle = new Binder<>();
binderTitle.forField(projectTitle)
.withValidator(value -> !value.isBlank(), "Please provide a title")
.bind(Container::value, Container::setValue);
Binder<Container<String>> binderObjective = new Binder<>();
binderObjective.forField(projectObjective)
.withValidator(value -> !value.isBlank(), "Please provide an " + "objective")
.bind(Container::value, Container::setValue);
Binder<Container<PersonReference>> binderPI = new Binder<>();
binderPI.forField(principalInvestigator).asRequired("Please select at least one PI")
.bind(Container::value, Container::setValue);
Binder<Container<PersonReference>> binderPM = new Binder<>();
binderPM.forField(projectManager).asRequired("Please select at least one PM")
.bind(Container::value, Container::setValue);
binders.addAll(List.of(binderTitle, binderObjective, binderPI, binderPM));
}

private void restrictInputLength() {
projectTitle.setMaxLength((int) ProjectTitle.maxLength());
projectObjective.setMaxLength((int) ProjectObjective.maxLength());
experimentalDesign.setMaxLength(
(int) ExperimentalDesignDescription.maxLength());

projectTitle.setValueChangeMode(ValueChangeMode.EAGER);
projectObjective.setValueChangeMode(ValueChangeMode.EAGER);
experimentalDesign.setValueChangeMode(
ValueChangeMode.EAGER);

addConsumedLengthHelper(projectTitle, projectTitle.getValue());
addConsumedLengthHelper(projectObjective, projectObjective.getValue());
addConsumedLengthHelper(experimentalDesign,
experimentalDesign.getValue());

projectTitle.addValueChangeListener(e -> addConsumedLengthHelper(e.getSource(), e.getValue()));
projectObjective.addValueChangeListener(
e -> addConsumedLengthHelper(e.getSource(), e.getValue()));
experimentalDesign.addValueChangeListener(
e -> addConsumedLengthHelper(e.getSource(), e.getValue()));
}

private void addConsumedLengthHelper(TextField textField, String newValue) {
int maxLength = textField.getMaxLength();
int consumedLength = newValue.length();
textField.setHelperText(consumedLength + "/" + maxLength);
}

private void addConsumedLengthHelper(TextArea textArea, String newValue) {
int maxLength = textArea.getMaxLength();
int consumedLength = newValue.length();
textArea.setHelperText(consumedLength + "/" + maxLength);
}

/**
* Checks if any fields within this component contains an invalid value
*
* @return boolean indicating if any field of this component contains an invalid value
*/

public boolean isInputValid() {
return validateInput();
}

protected boolean validateInput() {
binders.forEach(Binder::validate);
return binders.stream().allMatch(Binder::isValid);
}

/**
* Provides the content set in the fields of this component
*
* @return {@link ProjectInformationContent} providing the information filled by the user for each
* field
*/

public ProjectInformationContent getProjectInformationContent() {
return new ProjectInformationContent(projectTitle.getValue(), projectObjective.getValue(),
experimentalDesign.getValue(), principalInvestigator.getValue(),
responsiblePerson.getValue(),
projectManager.getValue());
}

static class Container<T> {

private T value;

T value() {
return this.value;
}

void setValue(T newValue) {
this.value = newValue;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import com.vaadin.flow.spring.annotation.UIScope;
import java.io.Serial;
import java.util.Objects;
import life.qbic.datamanager.views.Context;
import life.qbic.logging.api.Logger;
import life.qbic.logging.service.LoggerFactory;
import life.qbic.projectmanagement.domain.project.ProjectId;
import org.springframework.beans.factory.annotation.Autowired;

/**
Expand Down Expand Up @@ -41,12 +41,12 @@ private void layoutComponent() {
}

/**
* Triggers the propagation of the provided {@link ProjectId} to internal components
* Propagates the context to internal components.
*
* @param projectId The projectId to be propagated
* @param context the context in which the user is.
*/
public void projectId(ProjectId projectId) {
projectDetailsComponent.projectId(projectId);
public void setContext(Context context) {
projectDetailsComponent.setContext(context);
}

}
Loading

0 comments on commit 8aae772

Please sign in to comment.