diff --git a/src/main/java/io/bcn/springConference/model/Book.java b/src/main/java/io/bcn/springConference/model/Book.java index 5638c89..d3ad0d1 100644 --- a/src/main/java/io/bcn/springConference/model/Book.java +++ b/src/main/java/io/bcn/springConference/model/Book.java @@ -6,6 +6,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.GenericGenerator; + +import java.util.List; import java.util.UUID; @@ -31,9 +33,9 @@ public class Book { @Column(nullable = false) private String author; - @Column(nullable = false, unique = true) - private String ISBN; + @Column(name = "isbn", nullable = false, unique = true) + private String isbn; -/* @OneToMany(mappedBy = "book") - private List conferences;*/ + @OneToMany(mappedBy = "book") + private List conferences; } diff --git a/src/main/java/io/bcn/springConference/model/Conference.java b/src/main/java/io/bcn/springConference/model/Conference.java index 15d79d2..c82f2ab 100644 --- a/src/main/java/io/bcn/springConference/model/Conference.java +++ b/src/main/java/io/bcn/springConference/model/Conference.java @@ -1,4 +1,55 @@ package io.bcn.springConference.model; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; + +import java.time.LocalDate; +import java.util.UUID; + +@Entity +@Table(name = "conferences") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor public class Conference { -} + + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + @Column(name = "id", updatable = false, nullable = false) + private UUID id; + + @Column(nullable = false) + private LocalDate date; + + @Column(name = "link_to_youtube_video", nullable = false) + private String linkToYoutubeVideo; + + @Column(nullable = false) + private String title; + + @Column(name = "conference_name", nullable = false) + private String conferenceName; + + @Column(columnDefinition = "TEXT") + private String content; + + @Column(nullable = false) + private int duration; + + @Column(nullable = false) + private String room; + + @ManyToOne + @JoinColumn(name = "book_id", nullable = false) + private Book book; + + @ManyToOne + @JoinColumn(name = "speaker_id", nullable = false) + private Speaker speaker; +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/model/Speaker.java b/src/main/java/io/bcn/springConference/model/Speaker.java index db76a55..bfc01d6 100644 --- a/src/main/java/io/bcn/springConference/model/Speaker.java +++ b/src/main/java/io/bcn/springConference/model/Speaker.java @@ -1,4 +1,38 @@ package io.bcn.springConference.model; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; + +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "speakers") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor public class Speaker { -} + + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + @Column(name = "id", updatable = false, nullable = false) + private UUID id; + + @Column(nullable = false) + private String name; + + @Column(columnDefinition = "TEXT") + private String bio; + + @Column(nullable = false, unique = true) + private String email; + + @OneToMany(mappedBy = "speaker") + private List conferences; +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/repository/BookRepository.java b/src/main/java/io/bcn/springConference/repository/BookRepository.java index f764c1f..289488f 100644 --- a/src/main/java/io/bcn/springConference/repository/BookRepository.java +++ b/src/main/java/io/bcn/springConference/repository/BookRepository.java @@ -2,7 +2,17 @@ import io.bcn.springConference.model.Book; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; import java.util.UUID; +@Repository public interface BookRepository extends JpaRepository { -} + + @Query("SELECT DISTINCT b.author FROM Book b") + List findAllAuthors(); + + List findByAuthor(String author); +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java b/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java new file mode 100644 index 0000000..cde54c9 --- /dev/null +++ b/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java @@ -0,0 +1,18 @@ +package io.bcn.springConference.repository; + +import io.bcn.springConference.model.Conference; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +@Repository +public interface ConferenceRepository extends JpaRepository { + // Find conferences by date + List findByDate(LocalDate date); + + // Find conferences by conference name (case-insensitive) + List findByConferenceNameContainingIgnoreCase(String conferenceName); +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java~ b/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java~ new file mode 100644 index 0000000..8bdabd0 --- /dev/null +++ b/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java~ @@ -0,0 +1,12 @@ +package io.bcn.springConference.repository; + +import io.bcn.springConference.model.Conference; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +public interface ConferenceRepository extends JpaRepository { + List findByDate(LocalDate date); +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/repository/SpeakerRepository.java b/src/main/java/io/bcn/springConference/repository/SpeakerRepository.java new file mode 100644 index 0000000..2d36694 --- /dev/null +++ b/src/main/java/io/bcn/springConference/repository/SpeakerRepository.java @@ -0,0 +1,9 @@ +package io.bcn.springConference.repository; + +import io.bcn.springConference.model.Speaker; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface SpeakerRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/view/BookView.java b/src/main/java/io/bcn/springConference/view/BookView.java new file mode 100644 index 0000000..8e8b3ad --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/BookView.java @@ -0,0 +1,132 @@ +package io.bcn.springConference.view; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.router.Route; +import io.bcn.springConference.model.Book; +import io.bcn.springConference.repository.BookRepository; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Optional; + +@Route("books") +public class BookView extends VerticalLayout { + + private final BookRepository bookRepository; + private final Grid grid = new Grid<>(Book.class); + private final ComboBox authorComboBox = new ComboBox<>("Filter by Author"); + + // Form components + private TextField titleField = new TextField("Title"); + private TextField authorField = new TextField("Author"); + private TextField isbnField = new TextField("ISBN"); + private Button saveButton = new Button("Save"); + private Button cancelButton = new Button("Cancel"); + + private Book bookToEdit = null; + + @Autowired + public BookView(BookRepository bookRepository) { + this.bookRepository = bookRepository; + createAuthorComboBox(); // Crear el ComboBox de autores + configureGrid(); + configureForm(); + + // Add components to the layout + add(authorComboBox, grid, titleField, authorField, isbnField, saveButton, cancelButton); + updateList(); // Inicializar el listado de libros + } + + // Crear el ComboBox de autores y configurar su comportamiento + private void createAuthorComboBox() { + List authors = bookRepository.findAllAuthors(); // Necesitarás implementar este método en el repositorio + authorComboBox.setItems(authors); // Poner los autores en el ComboBox + authorComboBox.addValueChangeListener(event -> updateList()); // Filtrar cuando se seleccione un autor + } + + // Configurar la tabla de libros + private void configureGrid() { + grid.setColumns("title", "author", "isbn"); // Mostrar estos campos en la tabla + grid.getColumnByKey("title").setHeader("Title"); + grid.getColumnByKey("author").setHeader("Author"); + grid.getColumnByKey("isbn").setHeader("ISBN"); + grid.setHeight("400px"); + + // Agregar acción de edición y eliminación + grid.asSingleSelect().addValueChangeListener(event -> editBook(event.getValue())); + + // Agregar botón para eliminar + grid.addComponentColumn(book -> new Button("Delete", click -> deleteBook(book))) + .setHeader("Actions"); + } + + // Configurar el formulario de edición + private void configureForm() { + saveButton.addClickListener(event -> saveBook()); + cancelButton.addClickListener(event -> clearForm()); + + cancelButton.setVisible(false); + } + + // Método para actualizar la lista de libros + private void updateList() { + if (authorComboBox.getValue() != null) { + // Filtrar libros por el autor seleccionado en el ComboBox + grid.setItems(bookRepository.findByAuthor(authorComboBox.getValue())); + } else { + // Si no se selecciona ningún autor, mostrar todos los libros + grid.setItems(bookRepository.findAll()); + } + } + + // Método para editar un libro + private void editBook(Book book) { + if (book == null) { + clearForm(); + } else { + bookToEdit = book; + titleField.setValue(book.getTitle()); + authorField.setValue(book.getAuthor()); + isbnField.setValue(book.getIsbn()); + + saveButton.setText("Update"); + cancelButton.setVisible(true); + } + } + + // Método para guardar un libro (crear o actualizar) + private void saveBook() { + if (bookToEdit == null) { + bookToEdit = new Book(); + } + + bookToEdit.setTitle(titleField.getValue()); + bookToEdit.setAuthor(authorField.getValue()); + bookToEdit.setIsbn(isbnField.getValue()); + + bookRepository.save(bookToEdit); + clearForm(); + updateList(); + } + + // Método para eliminar un libro + private void deleteBook(Book book) { + bookRepository.delete(book); + updateList(); + } + + // Limpiar el formulario + private void clearForm() { + titleField.clear(); + authorField.clear(); + isbnField.clear(); + bookToEdit = null; + saveButton.setText("Save"); + cancelButton.setVisible(false); + } +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/view/ConferenceForm.java~ b/src/main/java/io/bcn/springConference/view/ConferenceForm.java~ new file mode 100644 index 0000000..f359252 --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/ConferenceForm.java~ @@ -0,0 +1,185 @@ +package io.bcn.springConference.view; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.component.textfield.TextArea; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.shared.Registration; +import io.bcn.springConference.model.Book; +import io.bcn.springConference.model.Conference; +import io.bcn.springConference.model.Speaker; + +import java.util.List; + +public class ConferenceForm extends FormLayout { + private Conference conference; + + // Form fields + private DatePicker date = new DatePicker("Conference Date"); + private TextField linkToYoutubeVideo = new TextField("YouTube Video Link"); + private TextField title = new TextField("Conference Title"); + private TextField conferenceName = new TextField("Conference Name"); + private TextArea content = new TextArea("Content"); + private IntegerField duration = new IntegerField("Duration (minutes)"); + private TextField room = new TextField("Room"); + private ComboBox book = new ComboBox<>("Book"); + private ComboBox speaker = new ComboBox<>("Speaker"); + + private Button save = new Button("Save"); + private Button delete = new Button("Delete"); + private Button close = new Button("Cancel"); + + private Binder binder = new BeanValidationBinder<>(Conference.class); + + public ConferenceForm(List books, List speakers) { + addClassName("conference-form"); + + // Configure ComboBoxes + configureComboBoxes(books, speakers); + + // Configure required fields + configureRequiredFields(); + + // Bind form fields + binder.bindInstanceFields(this); + + // Add components to form + add( + date, + linkToYoutubeVideo, + title, + conferenceName, + content, + duration, + room, + book, + speaker, + createButtonsLayout() + ); + } + + private void configureComboBoxes(List books, List speakers) { + book.setItems(books); + book.setItemLabelGenerator(Book::getTitle); + book.setRequired(true); + + speaker.setItems(speakers); + speaker.setItemLabelGenerator(Speaker::getName); + speaker.setRequired(true); + } + + private void configureRequiredFields() { + // Configurar campos como obligatorios + date.setRequired(true); + title.setRequired(true); + conferenceName.setRequired(true); + duration.setRequired(true); + room.setRequired(true); + } + + private Component createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, conference))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + return new HorizontalLayout(save, delete, close); + } + + public void setConference(Conference conference) { + this.conference = conference; + binder.readBean(conference); + + // Establecer valores de ComboBox si existen + if (conference != null) { + book.setValue(conference.getBook()); + speaker.setValue(conference.getSpeaker()); + } + } + + private void validateAndSave() { + try { + if (!binder.validate().isOk()) { + Notification.show("Por favor, complete todos los campos obligatorios"); + return; + } + + // Obtener el objeto Conference del Binder + Conference conference = binder.getBean(); + if (conference == null) { + conference = new Conference(); + } + + // Asignar valores manualmente + conference.setBook(book.getValue()); + conference.setSpeaker(speaker.getValue()); + + fireEvent(new SaveEvent(this, conference)); + } catch (Exception e) { + Notification.show("Error al guardar: " + e.getMessage()); + e.printStackTrace(); + } + } + + // Métodos de eventos (sin cambios) + public static abstract class ConferenceFormEvent extends ComponentEvent { + private Conference conference; + + protected ConferenceFormEvent(ConferenceForm source, Conference conference) { + super(source, false); + this.conference = conference; + } + + public Conference getConference() { + return conference; + } + } + + public static class SaveEvent extends ConferenceFormEvent { + SaveEvent(ConferenceForm source, Conference conference) { + super(source, conference); + } + } + + public static class DeleteEvent extends ConferenceFormEvent { + DeleteEvent(ConferenceForm source, Conference conference) { + super(source, conference); + } + } + + public static class CloseEvent extends ConferenceFormEvent { + CloseEvent(ConferenceForm source) { + super(source, null); + } + } + + public Registration addSaveListener(ComponentEventListener listener) { + return addListener(SaveEvent.class, listener); + } + + public Registration addDeleteListener(ComponentEventListener listener) { + return addListener(DeleteEvent.class, listener); + } + + public Registration addCloseListener(ComponentEventListener listener) { + return addListener(CloseEvent.class, listener); + } +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/view/ConferenceView.java b/src/main/java/io/bcn/springConference/view/ConferenceView.java index 4e99b93..b15eebb 100644 --- a/src/main/java/io/bcn/springConference/view/ConferenceView.java +++ b/src/main/java/io/bcn/springConference/view/ConferenceView.java @@ -1,74 +1,151 @@ -/* - package io.bcn.springConference.view; -import com.vaadin.flow.component.Component; import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +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.router.Route; - - -@Route("/conference") +import io.bcn.springConference.model.Conference; +import io.bcn.springConference.model.Book; +import io.bcn.springConference.model.Speaker; +import io.bcn.springConference.repository.ConferenceRepository; +import io.bcn.springConference.repository.BookRepository; +import io.bcn.springConference.repository.SpeakerRepository; + +@Route("conferences") public class ConferenceView extends VerticalLayout { + private final ConferenceRepository conferenceRepository; + private final BookRepository bookRepository; + private final SpeakerRepository speakerRepository; + private final Grid grid; + private final DatePicker filterDatePicker; + private final Button addButton; + private final Dialog form; + private final Binder binder; + + // Campos del formulario + private DatePicker datePicker; + private TextField linkField; + private TextField titleField; + private TextField conferenceNameField; + private TextArea contentArea; + private IntegerField durationField; + private TextField roomField; + private ComboBox bookComboBox; + private ComboBox speakerComboBox; + + public ConferenceView(ConferenceRepository conferenceRepository, BookRepository bookRepository, SpeakerRepository speakerRepository) { + this.conferenceRepository = conferenceRepository; + this.bookRepository = bookRepository; + this.speakerRepository = speakerRepository; + this.grid = new Grid<>(Conference.class); + this.filterDatePicker = new DatePicker("Filter by Date"); + this.addButton = new Button("Add Conference"); + this.form = new Dialog(); + this.binder = new Binder<>(Conference.class); + + configureGrid(); + configureForm(); + + HorizontalLayout toolbar = new HorizontalLayout(filterDatePicker, addButton); + add(toolbar, grid); + + filterDatePicker.addValueChangeListener(e -> updateList()); + addButton.addClickListener(e -> { + grid.asSingleSelect().clear(); + openForm(new Conference()); + }); + + updateList(); + } + private void configureGrid() { + grid.setColumns("date", "linkToYoutubeVideo", "title", "conferenceName", "content", "duration", "room"); + grid.addColumn(conference -> conference.getBook().getId()).setHeader("Book"); + grid.addColumn(conference -> conference.getSpeaker().getId()).setHeader("Speaker"); + grid.setHeight("300px"); + grid.asSingleSelect().addValueChangeListener(e -> openForm(e.getValue())); + } - private final TextField name = new TextField("Name"); - private final TextField email = new TextField("Email"); - private final TextField phoneNumber = new TextField("Phone Number"); - private final Button save = new Button("Save"); - private final Button delete = new Button("Delete"); - - - - - // Method to create the main layout - private Component createMainLayout() { - // Create the 3-column layout - HorizontalLayout mainLayout = new HorizontalLayout(); - mainLayout.setSizeFull(); - mainLayout.setPadding(false); - mainLayout.setSpacing(false); - - // Left column (empty for spacing) - VerticalLayout leftColumn = new VerticalLayout(); - leftColumn.setWidth("20%"); - - // Center column (contains all the components) - VerticalLayout centerColumn = new VerticalLayout(); - centerColumn.setWidth("60%"); - centerColumn.setAlignItems(Alignment.CENTER); - - // Right column (empty for spacing) - VerticalLayout rightColumn = new VerticalLayout(); - rightColumn.setWidth("20%"); - - // Create a form layout - HorizontalLayout formLayout = new HorizontalLayout(name, email, phoneNumber); - formLayout.setWidth("100%"); - formLayout.setJustifyContentMode(JustifyContentMode.CENTER); + private void configureForm() { + datePicker = new DatePicker("Date"); + linkField = new TextField("YouTube Video Link"); + titleField = new TextField("Title"); + conferenceNameField = new TextField("Conference Name"); + contentArea = new TextArea("Content"); + durationField = new IntegerField("Duration"); + roomField = new TextField("Room"); + bookComboBox = new ComboBox<>("Book"); + speakerComboBox = new ComboBox<>("Speaker"); + + bookComboBox.setItems(bookRepository.findAll()); + bookComboBox.setItemLabelGenerator(Book::getTitle); // Asumiendo que Book tiene un método getTitle() + + speakerComboBox.setItems(speakerRepository.findAll()); + speakerComboBox.setItemLabelGenerator(Speaker::getName); // Asumiendo que Speaker tiene un método getName() + + binder.forField(datePicker).asRequired("Date is required").bind(Conference::getDate, Conference::setDate); + binder.forField(linkField).asRequired("YouTube link is required").bind(Conference::getLinkToYoutubeVideo, Conference::setLinkToYoutubeVideo); + binder.forField(titleField).asRequired("Title is required").bind(Conference::getTitle, Conference::setTitle); + binder.forField(conferenceNameField).asRequired("Conference name is required").bind(Conference::getConferenceName, Conference::setConferenceName); + binder.forField(contentArea).bind(Conference::getContent, Conference::setContent); + binder.forField(durationField).asRequired("Duration is required").bind(Conference::getDuration, Conference::setDuration); + binder.forField(roomField).asRequired("Room is required").bind(Conference::getRoom, Conference::setRoom); + binder.forField(bookComboBox).asRequired("Book is required").bind(Conference::getBook, Conference::setBook); + binder.forField(speakerComboBox).asRequired("Speaker is required").bind(Conference::getSpeaker, Conference::setSpeaker); + + Button saveButton = new Button("Save", e -> saveConference()); + Button deleteButton = new Button("Delete", e -> deleteConference()); + Button cancelButton = new Button("Cancel", e -> closeForm()); + + form.add(new VerticalLayout( + datePicker, linkField, titleField, conferenceNameField, contentArea, + durationField, roomField, bookComboBox, speakerComboBox, + new HorizontalLayout(saveButton, deleteButton, cancelButton) + )); + } - // Create a button layout - HorizontalLayout buttonLayout = new HorizontalLayout(save, delete); - buttonLayout.setJustifyContentMode(JustifyContentMode.CENTER); + private void openForm(Conference conference) { + if (conference == null) { + closeForm(); + } else { + binder.setBean(conference); + form.open(); + } + } - // Add components to the center column - centerColumn.add( - new H2("Customer Management"), - formLayout, - buttonLayout - ); + private void closeForm() { + form.close(); + grid.asSingleSelect().clear(); + } - // Add all columns to the main layout - mainLayout.add(leftColumn, centerColumn, rightColumn); + private void saveConference() { + Conference conference = binder.getBean(); + conferenceRepository.save(conference); + updateList(); + closeForm(); + } - return mainLayout; + private void deleteConference() { + Conference conference = binder.getBean(); + conferenceRepository.delete(conference); + updateList(); + closeForm(); } -} -*/ + private void updateList() { + if (filterDatePicker.getValue() != null) { + grid.setItems(conferenceRepository.findByDate(filterDatePicker.getValue())); + } else { + grid.setItems(conferenceRepository.findAll()); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/view/MainLayout.java b/src/main/java/io/bcn/springConference/view/MainLayout.java new file mode 100644 index 0000000..0dfbc94 --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/MainLayout.java @@ -0,0 +1,58 @@ +package io.bcn.springConference.view; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.applayout.DrawerToggle; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouterLink; +import com.vaadin.flow.router.HighlightConditions; + +@Route("") +@PageTitle("Spring Conference") +public class MainLayout extends AppLayout { + + public MainLayout() { + createHeader(); + createDrawer(); + } + + // Crea el encabezado con el título de la aplicación + private void createHeader() { + H1 logo = new H1("Spring Conference"); + logo.addClassNames("text-l", "m-m"); + + // Barra superior con el botón de menú y el logo + HorizontalLayout header = new HorizontalLayout( + new DrawerToggle(), + logo + ); + header.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER); + header.setWidth("100%"); + header.addClassNames("py-0", "px-m"); + + addToNavbar(header); // Añadir el encabezado a la barra de navegación + } + + // Crea el menú lateral con los enlaces de las vistas + private void createDrawer() { + RouterLink conferenceLink = new RouterLink("Conferences", ConferenceView.class); + RouterLink speakerLink = new RouterLink("Speakers", SpeakerView.class); + RouterLink bookLink = new RouterLink("Books", BookView.class); + + // Resaltar la vista activa (cuando la ubicación coincide) + conferenceLink.setHighlightCondition(HighlightConditions.sameLocation()); + speakerLink.setHighlightCondition(HighlightConditions.sameLocation()); + bookLink.setHighlightCondition(HighlightConditions.sameLocation()); + + // Añadir los enlaces al menú lateral + addToDrawer(new VerticalLayout( + conferenceLink, + speakerLink, + bookLink + )); + } +} \ No newline at end of file diff --git a/src/main/java/io/bcn/springConference/view/SpeakerView.java b/src/main/java/io/bcn/springConference/view/SpeakerView.java new file mode 100644 index 0000000..6ef6c56 --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/SpeakerView.java @@ -0,0 +1,164 @@ +package io.bcn.springConference.view; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.avatar.Avatar; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.component.textfield.TextArea; +import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.component.dialog.Dialog; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.confirmdialog.ConfirmDialog; +import io.bcn.springConference.model.Speaker; +import io.bcn.springConference.repository.SpeakerRepository; +import org.springframework.transaction.annotation.Transactional; + +@Route("speakers") +public class SpeakerView extends VerticalLayout { + private final SpeakerRepository speakerRepository; + private final Grid grid = new Grid<>(Speaker.class); + private final Dialog form = new Dialog(); + private final Binder binder = new Binder<>(Speaker.class); + private Speaker speaker; + + public SpeakerView(SpeakerRepository speakerRepository) { + this.speakerRepository = speakerRepository; + + configureGrid(); + configureForm(); + + Button addButton = new Button("Add Speaker", new Icon(VaadinIcon.PLUS)); + addButton.addClickListener(e -> { + speaker = new Speaker(); + binder.readBean(speaker); + form.open(); + }); + + add(addButton, grid); + updateList(); + } + + private void configureGrid() { + grid.setColumns("name", "email", "bio"); + + grid.addComponentColumn(this::createAvatar) + .setHeader("Avatar") + .setWidth("100px") + .setFlexGrow(0); + + grid.addComponentColumn(speaker -> { + HorizontalLayout actions = new HorizontalLayout(); + + Button editButton = new Button(new Icon(VaadinIcon.EDIT)); + editButton.addClickListener(e -> editSpeaker(speaker)); + + Button deleteButton = new Button(new Icon(VaadinIcon.TRASH)); + deleteButton.addClickListener(e -> confirmDelete(speaker)); + + actions.add(editButton, deleteButton); + return actions; + }).setHeader("Actions").setWidth("120px").setFlexGrow(0); + } + + private void configureForm() { + form.setHeaderTitle("Speaker Details"); + + TextField nameField = new TextField("Name"); + TextField emailField = new TextField("Email"); + TextArea bioArea = new TextArea("Bio"); + + binder.forField(nameField) + .asRequired("Name is required") + .bind(Speaker::getName, Speaker::setName); + + binder.forField(emailField) + .asRequired("Email is required") + .bind(Speaker::getEmail, Speaker::setEmail); + + binder.forField(bioArea) + .bind(Speaker::getBio, Speaker::setBio); + + Button saveButton = new Button("Save", e -> saveSpeaker()); + Button cancelButton = new Button("Cancel", e -> form.close()); + + VerticalLayout formLayout = new VerticalLayout(); + formLayout.add(nameField, emailField, bioArea, new HorizontalLayout(saveButton, cancelButton)); + form.add(formLayout); + } + + private Avatar createAvatar(Speaker speaker) { + Avatar avatar = new Avatar(); + if (speaker.getName() != null && !speaker.getName().isEmpty()) { + avatar.setAbbreviation(speaker.getName().substring(0, 1).toUpperCase()); + } + return avatar; + } + + private void editSpeaker(Speaker speaker) { + this.speaker = speaker; + binder.readBean(speaker); + form.open(); + } + + private void confirmDelete(Speaker speaker) { + ConfirmDialog dialog = new ConfirmDialog(); + dialog.setHeader("Confirm Delete"); + dialog.setText("Are you sure you want to delete this speaker? This action cannot be undone."); + + dialog.setCancelable(true); + dialog.setConfirmText("Delete"); + dialog.setConfirmButtonTheme("error primary"); + + dialog.addConfirmListener(event -> deleteSpeaker(speaker)); + + dialog.open(); + } + + @Transactional + private void deleteSpeaker(Speaker speaker) { + try { + speakerRepository.delete(speaker); + speakerRepository.flush(); // Forzar la operación + updateList(); + Notification.show("Speaker deleted successfully", + 3000, + Notification.Position.BOTTOM_START) + .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + } catch (Exception e) { + Notification.show("Error deleting speaker: " + e.getMessage(), + 5000, + Notification.Position.BOTTOM_START) + .addThemeVariants(NotificationVariant.LUMO_ERROR); + e.printStackTrace(); + } + } + + private void saveSpeaker() { + try { + binder.writeBean(speaker); + speakerRepository.save(speaker); + form.close(); + updateList(); + Notification.show("Speaker saved successfully", + 3000, + Notification.Position.BOTTOM_START) + .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + } catch (Exception e) { + Notification.show("Error saving speaker: " + e.getMessage(), + 5000, + Notification.Position.BOTTOM_START) + .addThemeVariants(NotificationVariant.LUMO_ERROR); + e.printStackTrace(); + } + } + + private void updateList() { + grid.setItems(speakerRepository.findAll()); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4db059e..443ca75 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,7 +2,7 @@ spring.application.name=springConference # application.properties -spring.profiles.active=local +spring.profiles.active=memory # H2 DATABASE SERVER spring.datasource.driverClassName=org.h2.Driver diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 42ace5a..9187f6d 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -3,4 +3,18 @@ INSERT INTO books (id, title, author, isbn) VALUES (RANDOM_UUID(), 'Spring Boot in Action', 'Craig Walls', '9781617292545'), (RANDOM_UUID(), 'Spring Security in Action', 'Laurentiu Spilca', '9781617297731'), (RANDOM_UUID(), 'Reactive Spring', 'Josh Long', '9781732910225'), -(RANDOM_UUID(), 'Native Image Definitive Guide', 'Oleg Šelajev', '9781492078531'); \ No newline at end of file +(RANDOM_UUID(), 'Native Image Definitive Guide', 'Oleg Šelajev', '9781492078531'); + +-- Insert Speakers +INSERT INTO speakers (id, name, bio, email) VALUES +(UUID(), 'Stéphane Nicoll', 'Spring Framework committer', 'snicoll@pivotal.io'), +(UUID(), 'Rob Winch', 'Spring Security lead', 'rwinch@pivotal.io'), +(UUID(), 'Josh Long', 'Spring Developer Advocate', 'jlong@pivotal.io'), +(UUID(), 'Sébastien Deleuze', 'Spring Framework committer', 'sdeleuze@vmware.com'); + +-- Insert Conferences +INSERT INTO conferences (id, date, link_to_youtube_video, title, conference_name, content, duration, room, book_id, speaker_id) VALUES +(UUID(), '2023-05-17', 'https://www.youtube.com/watch?v=TtQTF7M9xPo', 'Spring Boot 3.0 & Spring Framework 6.0: What''s New', 'Spring I/O 2023', 'Overview of new features in Spring Boot 3.0 and Spring Framework 6.0.', 50, 'Auditorium 1', (SELECT id FROM books WHERE title='Spring Boot in Action'), (SELECT id FROM speakers WHERE name='Stéphane Nicoll')), +(UUID(), '2023-05-18', 'https://www.youtube.com/watch?v=wYYKNhCXVVE', 'Securing Spring Boot 3 Applications', 'Spring I/O 2023', 'Exploration of security features in Spring Boot.', 45, 'Room B', (SELECT id FROM books WHERE title='Spring Security in Action'), (SELECT id FROM speakers WHERE name='Rob Winch')), +(UUID(), '2022-05-26', 'https://www.youtube.com/watch?v=TKK4Oi1Xc-Y', 'Reactive Spring', 'Spring I/O 2022', 'Deep dive into reactive programming with Spring.', 55, 'Main Hall', (SELECT id FROM books WHERE title='Reactive Spring'), (SELECT id FROM speakers WHERE name='Josh Long')), +(UUID(), '2024-05-23', 'https://www.youtube.com/placeholder_2024', 'Spring Native and GraalVM: The Future of Spring Applications', 'Spring I/O 2024', 'Exploring advancements in Spring Native.', 60, 'Innovation Theater', (SELECT id FROM books WHERE title='Native Image Definitive Guide'), (SELECT id FROM speakers WHERE name='Sébastien Deleuze')); \ No newline at end of file