From f6cf18ce31f042b47883c98c15d110a7d0644314 Mon Sep 17 00:00:00 2001 From: Markus Kreth Date: Sun, 27 Jan 2019 23:30:41 +0100 Subject: [PATCH] Person and Contact delete able. --- .../vaadinclubhelper/dao/PersonDao.java | 5 + .../vaadinclubhelper/dao/PersonDaoImpl.java | 38 +++++-- .../vaadinclubhelper/data/BaseEntity.java | 41 +++---- .../vaadinclubhelper/data/Contact.java | 58 +++++++++- .../vaadinclubhelper/data/Person.java | 5 +- .../vaadinclubhelper/ui/LoginUI.java | 4 +- .../vaadinclubhelper/ui/MainUi.java | 4 +- .../vaadinclubhelper/ui/PersonEditView.java | 1 + .../ui/components/AbstractDataGrid.java | 75 +++++++++++++ .../ui/components/ConfirmDialog.java | 10 ++ .../ui/components/ContactGrid.java | 35 ++++++ .../ui/components/PersonEditDetails.java | 101 ++++++------------ .../ui/components/PersonGrid.java | 96 +++++++++++++++-- .../ui/components/RelationComponent.java | 33 ++++++ .../themes/vaadin-clubhelpertheme/favicon.ico | Bin 0 -> 1150 bytes .../images/arrowLeft.png | Bin 2572 -> 1911 bytes .../images/arrowRight.png | Bin 2661 -> 1898 bytes 17 files changed, 396 insertions(+), 110 deletions(-) create mode 100644 src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/AbstractDataGrid.java create mode 100644 src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/ContactGrid.java create mode 100644 src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/RelationComponent.java create mode 100644 src/main/webapp/VAADIN/themes/vaadin-clubhelpertheme/favicon.ico diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/dao/PersonDao.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/dao/PersonDao.java index 01d5534..c8d9d40 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/dao/PersonDao.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/dao/PersonDao.java @@ -4,6 +4,7 @@ import java.util.List; import javax.persistence.NoResultException; +import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Contact; import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Person; import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Relation; @@ -20,4 +21,8 @@ public interface PersonDao extends IDao { Person findLoginUser(String username, String password) throws NoResultException; List findRelationsFor(Person p); + + void delete(Contact c); + + void delete(Person p); } diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/dao/PersonDaoImpl.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/dao/PersonDaoImpl.java index 16ad967..f85fc3d 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/dao/PersonDaoImpl.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/dao/PersonDaoImpl.java @@ -1,6 +1,7 @@ package de.kreth.vaadin.clubhelper.vaadinclubhelper.dao; import java.util.ArrayList; +import java.util.Date; import java.util.List; import javax.persistence.Query; @@ -45,10 +46,13 @@ public class PersonDaoImpl extends AbstractDaoImpl implements PersonDao @Transactional public void persistOrUpdate(EntityAccessor c) { - if (c.hasValidId() == false) { - entityManager.persist(c); - } else { + Date now = new Date(); + c.setChanged(now); + if (entityManager.contains(c) || c.hasValidId()) { entityManager.merge(c); + } else { + c.setCreated(now); + entityManager.persist(c); } } @@ -78,12 +82,34 @@ public class PersonDaoImpl extends AbstractDaoImpl implements PersonDao } private Relation toRelative(Object[] r, int ignoring) { - + Person p = null; + String relation = null; if (r[1].equals(ignoring)) { - return new Relation(entityManager.find(Person.class, r[2]), r[3].toString()); + p = entityManager.find(Person.class, r[2]); + relation = r[3].toString(); } else if (r[2].equals(ignoring)) { - return new Relation(entityManager.find(Person.class, r[1]), r[4].toString()); + p = entityManager.find(Person.class, r[1]); + relation = r[4].toString(); + } + if (p != null && p.getDeleted() == null) { + return new Relation(p, relation); } return null; } + + @Override + @Transactional + public void delete(Contact c) { + c.setDeleted(new Date()); + entityManager.merge(c); + Person person = c.getPerson(); + person.getContacts().remove(c); + } + + @Override + @Transactional + public void delete(Person p) { + p.setDeleted(new Date()); + entityManager.merge(p); + } } diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/BaseEntity.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/BaseEntity.java index d7abceb..1bef52e 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/BaseEntity.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/BaseEntity.java @@ -34,22 +34,33 @@ public abstract class BaseEntity implements EntityAccessor { } public Date getChanged() { + if (changed == null) { + return null; + } return new Date(this.changed.getTime()); } + @Override public void setChanged(Date changed) { this.changed = new Date(changed.getTime()); } public Date getCreated() { + if (created == null) { + return null; + } return new Date(this.created.getTime()); } + @Override public void setCreated(Date created) { this.created = new Date(created.getTime()); } public Date getDeleted() { + if (deleted == null) { + return null; + } return new Date(this.deleted.getTime()); } @@ -66,39 +77,33 @@ public abstract class BaseEntity implements EntityAccessor { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((changed == null) ? 0 : changed.hashCode()); result = prime * result + ((created == null) ? 0 : created.hashCode()); - result = prime * result + ((deleted == null) ? 0 : deleted.hashCode()); result = prime * result + id; return result; } @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } BaseEntity other = (BaseEntity) obj; - if (changed == null) { - if (other.changed != null) - return false; - } else if (!changed.equals(other.changed)) - return false; if (created == null) { - if (other.created != null) - return false; - } else if (!created.equals(other.created)) - return false; - if (deleted == null) { - if (other.deleted != null) + if (other.created != null) { return false; - } else if (!deleted.equals(other.deleted)) + } + } else if (!created.equals(other.created)) { return false; - if (id != other.id) + } + if (id != other.id) { return false; + } return true; } diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/Contact.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/Contact.java index 100121b..75bbbdf 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/Contact.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/Contact.java @@ -9,15 +9,14 @@ import javax.persistence.ManyToOne; import javax.persistence.NamedQuery; import javax.persistence.Table; - /** * The persistent class for the contact database table. * */ @Entity -@Table(name="contact") +@Table(name = "contact") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) -@NamedQuery(name="Contact.findAll", query="SELECT c FROM Contact c") +@NamedQuery(name = "Contact.findAll", query = "SELECT c FROM Contact c WHERE c.deleted is not null") public class Contact extends BaseEntity implements Serializable { private static final long serialVersionUID = -7631864028095077913L; @@ -26,7 +25,7 @@ public class Contact extends BaseEntity implements Serializable { private String value; - //bi-directional many-to-one association to Person + // bi-directional many-to-one association to Person @ManyToOne private Person person; @@ -57,4 +56,55 @@ public class Contact extends BaseEntity implements Serializable { this.person = person; } + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((person == null) ? 0 : person.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Contact other = (Contact) obj; + if (person == null) { + if (other.person != null) { + return false; + } + } else if (!person.equals(other.person)) { + return false; + } + if (type == null) { + if (other.type != null) { + return false; + } + } else if (!type.equals(other.type)) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Contact [type=" + type + ", value=" + value + "]"; + } + } \ No newline at end of file diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/Person.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/Person.java index 86e0764..3ee92fb 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/Person.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/data/Person.java @@ -26,8 +26,9 @@ import javax.persistence.Table; @Entity @Table(name = "person") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) -@NamedQuery(name = Person.QUERY_FINDALL, query = "SELECT p FROM Person p") -@NamedQuery(name = Person.QUERY_FINDLOGIN, query = "FROM Person WHERE username = :username AND password = :password") +@NamedQuery(name = Person.QUERY_FINDALL, query = "SELECT p FROM Person p WHERE p.deleted is null") +@NamedQuery(name = Person.QUERY_FINDLOGIN, query = "FROM Person WHERE username = :username AND password = :password AND deleted is" + + " null") public class Person extends BaseEntity implements Serializable { public static final String SESSION_LOGIN = "SESSION_LOGIN_USER"; diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/LoginUI.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/LoginUI.java index c11e865..4c25caf 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/LoginUI.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/LoginUI.java @@ -38,8 +38,8 @@ public class LoginUI extends VerticalLayout implements NamedView { } catch (final Exception ex) { ex.printStackTrace(); logger.error("Error on login for User={}", e.getLoginParameter("username"), ex); - String message = "Incorrect user or password:" + ex.getMessage() + e.getLoginParameter("username") + ":" - + e.getLoginParameter("password"); + String message = "Incorrect user or password for " + e.getLoginParameter("username") + "\n" + + ex.getMessage(); Notification.show(message, Notification.Type.ERROR_MESSAGE); } }); diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/MainUi.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/MainUi.java index 570ad8f..dd1b2be 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/MainUi.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/MainUi.java @@ -8,6 +8,7 @@ import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.annotations.Push; import com.vaadin.annotations.Theme; import com.vaadin.navigator.Navigator; +import com.vaadin.server.ThemeResource; import com.vaadin.server.VaadinRequest; import com.vaadin.shared.communication.PushMode; import com.vaadin.spring.annotation.SpringUI; @@ -56,8 +57,7 @@ public class MainUi extends UI { navigator.addView(MainView.VIEW_NAME, new MainView(personDao, groupDao, eventBusiness, securityGroupVerifier)); navigator.addView(LoginUI.VIEW_NAME, new LoginUI(personDao, securityGroupVerifier)); navigator.addView(PersonEditView.VIEW_NAME, new PersonEditView(groupDao, personDao)); - navigator.addView(EventDetails.VIEW_NAME, - new EventDetails(personDao, groupDao, eventBusiness, pflichtenDao, securityGroupVerifier)); + navigator.addView(EventDetails.VIEW_NAME, new EventDetails(personDao, groupDao, eventBusiness, pflichtenDao)); navigator.navigateTo(MainView.VIEW_NAME); } diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/PersonEditView.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/PersonEditView.java index 405a541..e77fd4b 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/PersonEditView.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/PersonEditView.java @@ -34,6 +34,7 @@ public class PersonEditView extends VerticalLayout implements NamedView { personGrid.setSizeFull(); personGrid.onPersonEdit(); personGrid.onPersonSelect(ev -> selectedPerson(ev)); + personGrid.enableDeleteColumn(true); personDetails = new PersonEditDetails(groupDao.listAll(), personDao, false); personDetails.setSizeFull(); diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/AbstractDataGrid.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/AbstractDataGrid.java new file mode 100644 index 0000000..6c0bf6c --- /dev/null +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/AbstractDataGrid.java @@ -0,0 +1,75 @@ +package de.kreth.vaadin.clubhelper.vaadinclubhelper.ui.components; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +import com.vaadin.data.Binder; +import com.vaadin.data.provider.DataProvider; +import com.vaadin.data.provider.ListDataProvider; +import com.vaadin.icons.VaadinIcons; +import com.vaadin.ui.Button; +import com.vaadin.ui.Component; +import com.vaadin.ui.Grid; +import com.vaadin.ui.components.grid.Editor; + +import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Person; + +public abstract class AbstractDataGrid extends Grid { + + private static final long serialVersionUID = -3404971410481135696L; + private final List contactSource; + private final ListDataProvider contactDataProvider; + private boolean hasChanges; + private Column deleteButtonColumn; + private Consumer deleteConsumer; + + public AbstractDataGrid() { + + this.contactSource = new ArrayList<>(); + this.contactDataProvider = DataProvider.ofCollection(contactSource); + setDataProvider(contactDataProvider); + + Editor editor = getEditor(); + editor.setEnabled(true); + Binder binder = editor.getBinder(); + editor.addSaveListener(ev -> hasChanges = true); + + createColumnAndBinding(binder); + + deleteButtonColumn = addComponentColumn(c -> { + Button deleteButton = new Button(VaadinIcons.TRASH); + deleteButton.addClickListener(ev -> deleteConsumer.accept(c)); + return deleteButton; + }); + deleteButtonColumn.setHidden(true); + } + + protected abstract void createColumnAndBinding(Binder binder); + + public void setDeleteConsumer(Consumer deleteConsumer) { + this.deleteConsumer = deleteConsumer; + if (deleteConsumer != null) { + deleteButtonColumn.setHidden(false); + } else { + deleteButtonColumn.setHidden(true); + } + } + + public final void setPerson(Person person) { + hasChanges = false; + contactSource.clear(); + if (person != null) { + contactSource.addAll(readValues(person)); + } + contactDataProvider.refreshAll(); + } + + protected abstract Collection readValues(Person person); + + public final boolean hasChanges() { + return hasChanges; + } + +} diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/ConfirmDialog.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/ConfirmDialog.java index 9f1b2c8..a7bd991 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/ConfirmDialog.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/ConfirmDialog.java @@ -74,6 +74,16 @@ public class ConfirmDialog extends Window { return this; } + public Builder yesNo() { + buttons = EnumSet.of(Buttons.YES, Buttons.NO); + return this; + } + + public Builder yesCancel() { + buttons = EnumSet.of(Buttons.YES, Buttons.CANCEL); + return this; + } + public ConfirmDialog build() { return new ConfirmDialog(this); } diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/ContactGrid.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/ContactGrid.java new file mode 100644 index 0000000..1dba196 --- /dev/null +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/ContactGrid.java @@ -0,0 +1,35 @@ +package de.kreth.vaadin.clubhelper.vaadinclubhelper.ui.components; + +import java.util.Collection; +import java.util.stream.Collectors; + +import com.vaadin.data.Binder; +import com.vaadin.data.Binder.Binding; +import com.vaadin.ui.TextField; + +import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Contact; +import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Person; + +public class ContactGrid extends AbstractDataGrid { + + /** + * + */ + private static final long serialVersionUID = -2573761302198992085L; + + @Override + public void createColumnAndBinding(Binder binder) { + Binding typeBinding = binder.bind(new ContactTypeComponent(), Contact::getType, + Contact::setType); + Binding valueBinding = binder.bind(new TextField(), Contact::getValue, Contact::setValue); + + addColumn(Contact::getType).setCaption("Kontaktart").setEditorBinding(typeBinding); + addColumn(Contact::getValue).setCaption("Wert").setEditorBinding(valueBinding); + } + + @Override + protected Collection readValues(Person person) { + return person.getContacts().stream().filter(e -> e.getDeleted() == null).collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/PersonEditDetails.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/PersonEditDetails.java index 148c5df..8e4c1eb 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/PersonEditDetails.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/PersonEditDetails.java @@ -1,22 +1,17 @@ package de.kreth.vaadin.clubhelper.vaadinclubhelper.ui.components; -import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import org.vaadin.teemu.switchui.Switch; import com.vaadin.data.Binder; -import com.vaadin.data.Binder.Binding; import com.vaadin.data.BinderValidationStatus; import com.vaadin.data.ValidationResult; -import com.vaadin.data.provider.DataProvider; -import com.vaadin.data.provider.ListDataProvider; import com.vaadin.ui.Button; import com.vaadin.ui.ComboBox; import com.vaadin.ui.Component; import com.vaadin.ui.DateField; -import com.vaadin.ui.Grid; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Notification; import com.vaadin.ui.TabSheet; @@ -24,11 +19,9 @@ import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; import de.kreth.vaadin.clubhelper.vaadinclubhelper.dao.PersonDao; -import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Contact; import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Gender; import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.GroupDef; import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Person; -import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Relation; import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Startpass; public class PersonEditDetails extends HorizontalLayout { @@ -44,9 +37,8 @@ public class PersonEditDetails extends HorizontalLayout { private Consumer personChangeHandler; private Button okButton; - private VerticalLayout relationshipLayout; - private List contactSource; - private ListDataProvider contactDataProvider; + private ContactGrid contactLayout; + private RelationComponent relationshipLayout; public PersonEditDetails(List groups, PersonDao dao) { this(groups, dao, true); @@ -117,8 +109,24 @@ public class PersonEditDetails extends HorizontalLayout { okButton); Component groupLayout = createGroupPanel(groups); - Component contactLayout = createContactLayout(); - Component relationshipLayout = createRelationshipLayout(); + contactLayout = new ContactGrid(); + contactLayout.setDeleteConsumer(c -> { + + ConfirmDialog dlg = ConfirmDialog + .builder().setCaption("Kontakt löschen").setMessage(c.getPerson().getPrename() + " " + + c.getPerson().getSurname() + " \"" + c + "\" wirklich löschen?") + .yesCancel().setResultHandler(button -> { + if (button == ConfirmDialog.Buttons.YES) { + if (binder.validate().isOk()) { + dao.delete(c); + } + } + }).build(); + + getUI().addWindow(dlg); + }); + + relationshipLayout = new RelationComponent(dao); TabSheet sheet = new TabSheet(); sheet.addTab(groupLayout, "Gruppen"); sheet.addTab(contactLayout, "Kontakte"); @@ -129,32 +137,6 @@ public class PersonEditDetails extends HorizontalLayout { } - private Component createRelationshipLayout() { - relationshipLayout = new VerticalLayout(); - return relationshipLayout; - } - - private Component createContactLayout() { - Grid contactLayout = new Grid<>(); - contactSource = new ArrayList<>(); - - contactDataProvider = DataProvider.ofCollection(contactSource); - contactLayout.setDataProvider(contactDataProvider); - - contactLayout.getEditor().setEnabled(true); - Binder contactBinder = contactLayout.getEditor().getBinder(); - - ContactTypeComponent comp = new ContactTypeComponent(); - Binding typeBinding = contactBinder.bind(comp, Contact::getType, Contact::setType); - Binding valueBinding = contactBinder.bind(new TextField(), Contact::getValue, - Contact::setValue); - - contactLayout.addColumn(Contact::getType).setCaption("Kontaktart").setEditorBinding(typeBinding); - contactLayout.addColumn(Contact::getValue).setCaption("Wert").setEditorBinding(valueBinding); - - return contactLayout; - } - public Component createGroupPanel(List groups) { VerticalLayout layout = new VerticalLayout(); @@ -180,56 +162,39 @@ public class PersonEditDetails extends HorizontalLayout { } public void setBean(Person person) { + closeWithoutSave(); binder.setBean(person); + contactLayout.setPerson(person); + relationshipLayout.setPerson(person); if (person != null) { okButton.setEnabled(true); - updateRelationshipBinding(); - updateContactBinding(); } else { okButton.setEnabled(false); - contactSource.clear(); - contactDataProvider.refreshAll(); - relationshipLayout.removeAllComponents(); - } - } - - private void updateContactBinding() { - contactSource.clear(); - contactSource.addAll(binder.getBean().getContacts()); - contactDataProvider.refreshAll(); - } - - private void updateRelationshipBinding() { - relationshipLayout.removeAllComponents(); - Person current = binder.getBean(); - List related = dao.findRelationsFor(current); - for (Relation relation : related) { - TextField textField = new TextField(relation.getRelation().getLocalized()); - textField.setValue(relation.getPerson().getPrename() + " " + relation.getPerson().getSurname()); - textField.setEnabled(false); - relationshipLayout.addComponent(textField); } } - private void closeWithoutSave() { - if (binder.hasChanges()) { + void closeWithoutSave() { + if (hasChanges()) { + final Person current = binder.getBean(); ConfirmDialog dlg = ConfirmDialog.builder().setCaption("Ungespeicherte Änderungen") - .setMessage("Die Daten wurden geändert. Sollen die Änderungen gespeichert werden?") + .setMessage(current.getPrename() + " " + current.getSurname() + + " wurde geändert. Sollen die Änderungen gespeichert werden?") .saveDiscardCancel().setResultHandler(button -> { if (button == ConfirmDialog.Buttons.SAVE) { if (binder.validate().isOk()) { - dao.save(binder.getBean()); + dao.save(current); } - } else if (button == ConfirmDialog.Buttons.DISCARD) { - binder.removeBean(); } }).build(); getUI().addWindow(dlg); - } else { } } + public boolean hasChanges() { + return binder.hasChanges() || contactLayout.hasChanges() || relationshipLayout.hasChanges(); + } + } diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/PersonGrid.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/PersonGrid.java index da21697..d563af7 100644 --- a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/PersonGrid.java +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/PersonGrid.java @@ -15,12 +15,17 @@ import com.vaadin.data.provider.DataProvider; import com.vaadin.data.provider.ListDataProvider; import com.vaadin.event.selection.SelectionListener; import com.vaadin.event.selection.SingleSelectionEvent; +import com.vaadin.icons.VaadinIcons; +import com.vaadin.shared.ui.ContentMode; +import com.vaadin.ui.Button; import com.vaadin.ui.CheckBox; import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Component; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.Column; import com.vaadin.ui.Grid.SelectionMode; import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; import com.vaadin.ui.Layout; import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; @@ -47,13 +52,17 @@ public class PersonGrid extends VerticalLayout { private final ComboBox comboGroups; private final TextField textFilter; + private final PersonDao personDao; + private List allGroups; private PersonFilter filter; private ClubEvent currentEvent; private Column startpassColumn; private Layout filters; private SelectionMode currentSelectionMode; - private Column genderColumn; + private Column genderColumn; + + private Column deleteButtonColumn; public PersonGrid(GroupDao groupDao, PersonDao personDao) { @@ -61,9 +70,12 @@ public class PersonGrid extends VerticalLayout { setCaption("Teilnehmer"); addStyleName("bold-caption"); + this.personDao = personDao; + checkIncluded = new CheckBox("Nur gemeldete"); comboGroups = new ComboBox<>("Gruppenfilter"); textFilter = new TextField("Namenfilter"); + textFilter.setIcon(VaadinIcons.SEARCH); filters = setupFilterComponents(); allGroups = groupDao.listAll(); @@ -74,13 +86,13 @@ public class PersonGrid extends VerticalLayout { dataProvider = DataProvider.ofCollection(filter.asCollection()); grid = new Grid<>(); - setupPersonGrid(personDao); + setupPersonGrid(); setMargin(false); addComponents(filters, grid); } - public void setupPersonGrid(PersonDao personDao) { + void setupPersonGrid() { filter.add(() -> { setEvent(currentEvent); }); @@ -92,7 +104,8 @@ public class PersonGrid extends VerticalLayout { grid.addColumn(Person::getPrename).setCaption("Vorname"); grid.addColumn(Person::getSurname).setCaption("Nachname"); - grid.addColumn(Person::getBirth, b -> b != null ? birthFormat.format(b) : "").setCaption("Geburtstag"); + grid.addColumn(Person::getBirth, b -> b != null ? birthFormat.format(b) : "").setCaption("Geburtstag") + .setHidable(true); startpassColumn = grid.addColumn(p -> { Startpass startpass = p.getStartpass(); @@ -102,18 +115,85 @@ public class PersonGrid extends VerticalLayout { return null; } }).setCaption("Startpass Nr."); + startpassColumn.setHidable(true); - genderColumn = grid.addColumn(p -> { + genderColumn = grid.addComponentColumn(p -> { Gender gender = p.getGender(); + VaadinIcons icon; + if (gender == null) { - return ""; + icon = VaadinIcons.QUESTION; + } else { + switch (gender) { + case FEMALE: + icon = VaadinIcons.FEMALE; + break; + case MALE: + icon = VaadinIcons.MALE; + break; + default: + icon = VaadinIcons.QUESTION; + break; + } } - return gender.localized(); - }).setCaption("Geschlecht"); + + return new Label(icon.getHtml(), ContentMode.HTML); + + }); + genderColumn.setHidable(true); startpassColumn.setHidden(false); genderColumn.setHidden(true); + + deleteButtonColumn = grid.addComponentColumn(c -> { + Button deleteButton = new Button(VaadinIcons.TRASH); + deleteButton.addClickListener(ev -> delete(c)); + deleteButton.setWidthUndefined(); + return deleteButton; + }).setCaption("Löschen"); + deleteButtonColumn.setHidden(true); + + } + + public VaadinIcons genderToImage(Gender gender) { + VaadinIcons icon; + + if (gender == null) { + icon = VaadinIcons.QUESTION; + } else { + switch (gender) { + case FEMALE: + icon = VaadinIcons.FEMALE; + break; + case MALE: + icon = VaadinIcons.MALE; + break; + default: + icon = VaadinIcons.QUESTION; + break; + } + } + + return icon; + } + + public void enableDeleteColumn(boolean enable) { + deleteButtonColumn.setHidden(!enable); + } + + private void delete(Person c) { + + ConfirmDialog dlg = ConfirmDialog.builder().setCaption("Person löschen") + .setMessage(c.getPrename() + " " + c.getSurname() + " wirklich löschen?").yesCancel() + .setResultHandler(button -> { + if (button == ConfirmDialog.Buttons.YES) { + personDao.delete(c); + dataProvider.refreshAll(); + } + }).build(); + + getUI().addWindow(dlg); } public void setSelectionMode(SelectionMode selectionMode) { diff --git a/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/RelationComponent.java b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/RelationComponent.java new file mode 100644 index 0000000..644552c --- /dev/null +++ b/src/main/java/de/kreth/vaadin/clubhelper/vaadinclubhelper/ui/components/RelationComponent.java @@ -0,0 +1,33 @@ +package de.kreth.vaadin.clubhelper.vaadinclubhelper.ui.components; + +import java.util.Collection; + +import com.vaadin.data.Binder; + +import de.kreth.vaadin.clubhelper.vaadinclubhelper.dao.PersonDao; +import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Person; +import de.kreth.vaadin.clubhelper.vaadinclubhelper.data.Relation; + +public class RelationComponent extends AbstractDataGrid { + + private static final long serialVersionUID = 7813969695936351799L; + private PersonDao dao; + + public RelationComponent(PersonDao dao) { + this.dao = dao; + getEditor().setEnabled(false); + } + + @Override + protected void createColumnAndBinding(Binder binder) { + addColumn(r -> r.getRelation().getLocalized()).setCaption("Beiehung"); + addColumn(r -> r.getPerson().getPrename() + " " + r.getPerson().getSurname()); + + } + + @Override + protected Collection readValues(Person person) { + return dao.findRelationsFor(person); + } + +} diff --git a/src/main/webapp/VAADIN/themes/vaadin-clubhelpertheme/favicon.ico b/src/main/webapp/VAADIN/themes/vaadin-clubhelpertheme/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..89bc68b74410924071fe65c6cd177b8bf97e3bea GIT binary patch literal 1150 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 literal 2572 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 UeG_!H000UA07*qoM6N<$g2wfXKmY&$ delta 2649 zcmV-f3a0hy4&@YoiBL{Q4GJ0x0000DNk~Le0001F0001F2m=5B07pD!W&i*H1ZP1_ zK>z@;j|==^1poj808mU+MF0Q*0RaI50s;dA0|W#F1qB5L1_lQQ2M7oV2?+@b3JMDg z3k(bl4Gj$r4h|0w4-pX&5)u*<6B85^6crT}78Vv78X6pb92_7ZAR;0nBO@awCMGE< zDJ?B6E-o%FFE24MF)}hTGcz+ZG&D6eH8wUjH#avpI5;^uIXXHzJ3Bi(JUl%;Jw84@ zKR-V}KtMr3K|(@8LqkJEL_|eJMMg$OM@L6UNJvRZNlHpeOG`^kOiWEpO-@cuPft%! zP*71(QBqQWQd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R=eSLj?etv&{e}I61fq{X7f`WsC zgM@^Hg@uKNhK7fShlq%XiHV7dii(Sii;Rqnjg5_tj*gFykC2d%k&%&-l9H2?la!Q{ zm6es2mX?>7mzbECnVFfInwp!No1C1Sot>SYo}QndpP-Cf>sHv%`s;a81tE;T6tgWrBuCA`HudlGMu(7eRva+(Xv$M3cw6(Rhwzjsn zx3{>sxVgExy1Kf%ySu!+yuH1>zP`S{zrVo1z`?=6!otGC!^6bH#KpzM#>U3S$H&OX z$jQmc%F4>i%gfBn%+1Zs&d$!y&(F}%(9zL<(bCe=)6>(`)YR40)z;S5*VotB*x1?G z+1lFL+uPgR+}z#W-QM2b-{0Th;Naom;o{=ruz*=jZ3>=;-O`>FVn0 z>+9?6?CkCB?e6aG@9*#M@bK~R@$&NW^Yioc^z`-h_4fAm_xJbs`1twx`TF|$`}_M? z{QUg={r&#_{{R2~00000000000000000000000000000000000000000000000000 z000000000000027(B=yO000SaNLh0L01msA`oOmL8$^p z8N~qz93dnGP#}ba@Hg}V7oIW_j?4Mj^6>sKT<&$B?>A4lJP9xV#rjdz8_B76jP%MF*DMTWsyPohagcrG z?5qof{gk?Y&OsrBv^ZKtx4s5@xg4;2`eKw0d|jLS2Q?$rZSTR%XFbCZ5^wIvY<}L} zIeirv)qRheo|2SctV6lq$^#}R91n-qv7WWGVKv57V0(VjmCK1vngn`X7clw4f|&AG zT|KKE!#SY38dWc_Cm5M=kxgydEh4Z?0M&ZXUycH>q(5(N#?+vNBCH{VJ7 zcC%A0XK@A9a`ji6tp%o2v3jt$f@+836~r?9fdpiH5H8i7`f<^GvWIl$m5 zP>>_ghrHRJg}Fn`K3~GhYW;Er`+^+d)MO^5%q}xV4Mu=9dd2b;aM`PA;Z|uE^udSDZ3)PkP4MaDw0aMX10Opl3=u$uEN2u95tRdf zci8Z;bDD#AD?q|z?^X-9)~I}N!6z{vy#Cp}VY$e*N%e4zg7+2Vlgtg|KCqGP(t7i1 zzJe#AM|jmo*Tcp~);0Z#o`V(v`|ewRd?+*QYUS^TG?WO)tC=y@**r${pK+-O$g41u zt-ufW5bMVFK9bQs1}e;Ajx`^XUCrI?}ZE`8RMD zHP9_rTg~0dsVb^kOWJ#?cOKJsKQttzQQE;28 zL~{@YH{43}J|p?y`2ySZ0-uI^`}mTz z)hJm-z(n&wz0mBO?5tXB*Q<+v=Hcn(bxdvh#q450GHk2scr}Zfo`dNqU{f&J!{ZcT zn|A6o1~4&xS(orjMtaJX>z(icSk!jb&7Dkk#&2`V$g2b-q87`2$C8t;Bqn4xVl@l9 zDc0SM>3OVBM=j(mS$xKUq z2O%OQ^LojnHoicLNW=nu2Qx?yudcD5U}A1{bbgjce?r(%OH+b)?j9U&pX;SGYFtjwdut!400000NkvXX Hu0mjf#k%&}