Added schedule editing
This commit is contained in:
parent
13f3132947
commit
c847830a7e
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,6 +14,7 @@ build/
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
.gigaide
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
@ -1,9 +1,5 @@
|
||||
package ru.vyatsu.qr_access_admin.door.component;
|
||||
|
||||
import ru.vyatsu.qr_access_admin.door.entity.DoorEntity;
|
||||
import ru.vyatsu.qr_access_admin.unit.model.UnitComboBoxModel;
|
||||
import ru.vyatsu.qr_access_admin.unit.entity.UnitRepository;
|
||||
import ru.vyatsu.qr_access_admin.unit.mapper.UnitEntityUnitComboBoxModelMapper;
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.Key;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
@ -12,31 +8,41 @@ import com.vaadin.flow.component.combobox.ComboBox;
|
||||
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.IntegerField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.data.binder.BeanValidationBinder;
|
||||
import com.vaadin.flow.data.binder.Binder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import ru.vyatsu.qr_access_admin.door.entity.DoorEntity;
|
||||
import ru.vyatsu.qr_access_admin.door.schedule.component.ScheduleEditor;
|
||||
import ru.vyatsu.qr_access_admin.door.schedule.entity.ScheduleEntity;
|
||||
import ru.vyatsu.qr_access_admin.door.schedule.entity.ScheduleRepository;
|
||||
import ru.vyatsu.qr_access_admin.unit.entity.UnitRepository;
|
||||
import ru.vyatsu.qr_access_admin.unit.mapper.UnitEntityUnitComboBoxModelMapper;
|
||||
import ru.vyatsu.qr_access_admin.unit.model.UnitComboBoxModel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DoorEditor extends Composite<VerticalLayout> {
|
||||
|
||||
private final UnitEntityUnitComboBoxModelMapper unitMapper = new UnitEntityUnitComboBoxModelMapper();
|
||||
|
||||
public interface SaveListener {
|
||||
void onSave(DoorEntity door);
|
||||
void onSave(DoorEntity door, Collection<ScheduleEntity> schedule);
|
||||
}
|
||||
|
||||
public interface DeleteListener {
|
||||
void onDelete(DoorEntity door);
|
||||
void onDelete(DoorEntity door) throws Exception;
|
||||
}
|
||||
|
||||
public interface CancelListener {
|
||||
void onCancel();
|
||||
}
|
||||
|
||||
private volatile DoorEntity currentDoor;
|
||||
private DoorEntity currentDoor;
|
||||
|
||||
private final ScheduleEditor scheduleEditor;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ -53,18 +59,22 @@ public class DoorEditor extends Composite<VerticalLayout> {
|
||||
public void setCurrentDoor(DoorEntity door) {
|
||||
this.currentDoor = door;
|
||||
binder.setBean(door);
|
||||
scheduleEditor.setCurrentDoorId(door.getId());
|
||||
scheduleEditor.refreshScheduleView();
|
||||
}
|
||||
|
||||
public DoorEditor(UnitRepository unitRepository) {
|
||||
public DoorEditor(UnitRepository unitRepository, ScheduleRepository scheduleRepository) {
|
||||
ComboBox<UnitComboBoxModel> unitField = new ComboBox<>("Устройство");
|
||||
unitField.setRequired(true);
|
||||
unitField.setPageSize(100);
|
||||
Map<String, UnitComboBoxModel> units = unitRepository.findAll()
|
||||
.stream()
|
||||
.map(unitMapper::toModel)
|
||||
.map(new UnitEntityUnitComboBoxModelMapper()::toModel)
|
||||
.collect(Collectors.toMap(UnitComboBoxModel::clientId, unit -> unit));
|
||||
unitField.setItems(units.values());
|
||||
unitField.setItemLabelGenerator(UnitComboBoxModel::clientName);
|
||||
IntegerField countField = new IntegerField("Количество мест");
|
||||
TextField descriptionField = new TextField("Описание");
|
||||
|
||||
var save = new Button("Сохранить", VaadinIcon.CHECK.create());
|
||||
var cancel = new Button("Отмена");
|
||||
@ -73,17 +83,27 @@ public class DoorEditor extends Composite<VerticalLayout> {
|
||||
binder.forField(unitField)
|
||||
.withConverter(UnitComboBoxModel::clientId, units::get, "Invalid value")
|
||||
.bind("unitId");
|
||||
binder.forField(countField).bind("count");
|
||||
binder.forField(descriptionField).bind("description");
|
||||
|
||||
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
save.addClickListener(e -> saveListener.onSave(currentDoor));
|
||||
|
||||
scheduleEditor = new ScheduleEditor(scheduleRepository);
|
||||
|
||||
save.addClickListener(e -> saveListener.onSave(currentDoor, scheduleEditor.getNewSchedule()));
|
||||
save.addClickShortcut(Key.ENTER);
|
||||
|
||||
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||
delete.addClickListener(e -> deleteListener.onDelete(currentDoor));
|
||||
delete.addClickListener(e -> {
|
||||
try {
|
||||
deleteListener.onDelete(currentDoor);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
cancel.addClickListener(e -> cancelListener.onCancel());
|
||||
|
||||
getContent().add(unitField, new HorizontalLayout(save, cancel, delete));
|
||||
getContent().add(unitField, countField, descriptionField, scheduleEditor, new HorizontalLayout(save, cancel, delete));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,4 +16,10 @@ public class DoorEntity {
|
||||
|
||||
@Column
|
||||
private String unitId;
|
||||
|
||||
@Column
|
||||
private String description;
|
||||
|
||||
@Column
|
||||
private int count;
|
||||
}
|
||||
|
@ -0,0 +1,203 @@
|
||||
package ru.vyatsu.qr_access_admin.door.schedule.component;
|
||||
|
||||
import com.vaadin.flow.component.Composite;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.timepicker.TimePicker;
|
||||
import com.vaadin.flow.data.binder.BeanValidationBinder;
|
||||
import lombok.Setter;
|
||||
import org.springframework.util.Assert;
|
||||
import ru.vyatsu.qr_access_admin.door.schedule.entity.ScheduleEntity;
|
||||
import ru.vyatsu.qr_access_admin.door.schedule.entity.ScheduleRepository;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.vaadin.flow.component.icon.VaadinIcon.ARROW_LEFT;
|
||||
import static com.vaadin.flow.component.icon.VaadinIcon.ARROW_RIGHT;
|
||||
|
||||
public class ScheduleEditor extends Composite<VerticalLayout> {
|
||||
|
||||
private Week chosenWeek;
|
||||
@Setter
|
||||
private String currentDoorId;
|
||||
private Map<LocalDate, ScheduleEntity> newSchedule = new HashMap<>();
|
||||
private Set<Week> queriedWeeks = new HashSet<>();
|
||||
private final ScheduleRepository repository;
|
||||
private final VerticalLayout daysEditorWrapper = new VerticalLayout();
|
||||
private final Div currentWeek;
|
||||
|
||||
public ScheduleEditor(ScheduleRepository repository) {
|
||||
this.repository = repository;
|
||||
this.chosenWeek = Week.current();
|
||||
currentWeek = new Div(chosenWeek.toString());
|
||||
currentWeek.getStyle().set("align-content", "center");
|
||||
refreshScheduleView();
|
||||
|
||||
var weekChanger = new HorizontalLayout();
|
||||
|
||||
var rightArrowButton = new Button(ARROW_RIGHT.create(), e -> {
|
||||
daysEditorWrapper.removeAll();
|
||||
this.chosenWeek = new Week(this.chosenWeek.dateMonday.plusDays(7), this.chosenWeek.dateSunday.plusDays(7));
|
||||
currentWeek.setText(chosenWeek.toString());
|
||||
List<ScheduleEntity> foundSchedule;
|
||||
if (queriedWeeks.contains(chosenWeek)) {
|
||||
foundSchedule = getEditedScheduleEntriesByWeek(chosenWeek);
|
||||
} else {
|
||||
foundSchedule = repository.findByDoorIdAndDateGreaterThanEqualAndDateLessThanEqualOrderByDateAsc(currentDoorId, chosenWeek.dateMonday, chosenWeek.dateSunday);
|
||||
fillAbsentDays(foundSchedule, currentDoorId, chosenWeek);
|
||||
}
|
||||
foundSchedule.forEach(se -> {
|
||||
HorizontalLayout scheduleEntry = new HorizontalLayout();
|
||||
var entryDate = new Span(se.getDate().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - E")));
|
||||
entryDate.getStyle().set("align-content", "center");
|
||||
var timePickerFrom = new TimePicker("Время начала работы");
|
||||
var timePickerTo = new TimePicker("Время окончания работы");
|
||||
var binder = new BeanValidationBinder<>(ScheduleEntity.class);
|
||||
binder.setBean(se);
|
||||
binder.forField(timePickerFrom).bind("startTime");
|
||||
binder.forField(timePickerTo).bind("endTime");
|
||||
newSchedule.put(se.getDate(), se);
|
||||
scheduleEntry.add(entryDate, timePickerFrom, timePickerTo);
|
||||
daysEditorWrapper.add(scheduleEntry);
|
||||
});
|
||||
queriedWeeks.add(chosenWeek);
|
||||
});
|
||||
var leftArrowButton = new Button(ARROW_LEFT.create(), e -> {
|
||||
daysEditorWrapper.removeAll();
|
||||
this.chosenWeek = new Week(this.chosenWeek.dateMonday.minusDays(7), this.chosenWeek.dateSunday.minusDays(7));
|
||||
currentWeek.setText(chosenWeek.toString());
|
||||
List<ScheduleEntity> foundSchedule;
|
||||
if (queriedWeeks.contains(chosenWeek)) {
|
||||
foundSchedule = getEditedScheduleEntriesByWeek(chosenWeek);
|
||||
} else {
|
||||
foundSchedule = repository.findByDoorIdAndDateGreaterThanEqualAndDateLessThanEqualOrderByDateAsc(currentDoorId, chosenWeek.dateMonday, chosenWeek.dateSunday);
|
||||
fillAbsentDays(foundSchedule, currentDoorId, chosenWeek);
|
||||
}
|
||||
foundSchedule.forEach(se -> {
|
||||
HorizontalLayout scheduleEntry = new HorizontalLayout();
|
||||
var entryDate = new Span(se.getDate().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - E")));
|
||||
entryDate.getStyle().set("align-content", "center");
|
||||
var timePickerFrom = new TimePicker("Время начала работы");
|
||||
var timePickerTo = new TimePicker("Время окончания работы");
|
||||
var binder = new BeanValidationBinder<>(ScheduleEntity.class);
|
||||
binder.setBean(se);
|
||||
binder.forField(timePickerFrom).bind("startTime");
|
||||
binder.forField(timePickerTo).bind("endTime");
|
||||
newSchedule.put(se.getDate(), se);
|
||||
scheduleEntry.add(entryDate, timePickerFrom, timePickerTo);
|
||||
daysEditorWrapper.add(scheduleEntry);
|
||||
});
|
||||
queriedWeeks.add(chosenWeek);
|
||||
});
|
||||
weekChanger.add(leftArrowButton, currentWeek, rightArrowButton);
|
||||
|
||||
getContent().add(weekChanger, daysEditorWrapper);
|
||||
}
|
||||
|
||||
public void refreshScheduleView() {
|
||||
this.chosenWeek = Week.current();
|
||||
currentWeek.setText(chosenWeek.toString());
|
||||
newSchedule = new HashMap<>();
|
||||
queriedWeeks = new HashSet<>();
|
||||
daysEditorWrapper.removeAll();
|
||||
List<ScheduleEntity> schedule = repository.findByDoorIdAndDateGreaterThanEqualAndDateLessThanEqualOrderByDateAsc(currentDoorId, chosenWeek.dateMonday, chosenWeek.dateSunday);
|
||||
fillAbsentDays(schedule, currentDoorId, chosenWeek);
|
||||
schedule.forEach(se -> {
|
||||
HorizontalLayout scheduleEntry = new HorizontalLayout();
|
||||
var entryDate = new Span(se.getDate().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - E")));
|
||||
entryDate.getStyle().set("align-content", "center");
|
||||
var timePickerFrom = new TimePicker("Время начала работы");
|
||||
var timePickerTo = new TimePicker("Время окончания работы");
|
||||
var binder = new BeanValidationBinder<>(ScheduleEntity.class);
|
||||
binder.setBean(se);
|
||||
binder.forField(timePickerFrom).bind("startTime");
|
||||
binder.forField(timePickerTo).bind("endTime");
|
||||
newSchedule.put(se.getDate(), se);
|
||||
scheduleEntry.add(entryDate, timePickerFrom, timePickerTo);
|
||||
daysEditorWrapper.add(scheduleEntry);
|
||||
});
|
||||
queriedWeeks.add(chosenWeek);
|
||||
}
|
||||
|
||||
public Collection<ScheduleEntity> getNewSchedule() {
|
||||
return Collections.unmodifiableCollection(newSchedule.values());
|
||||
}
|
||||
|
||||
private List<ScheduleEntity> getEditedScheduleEntriesByWeek(Week chosenWeek) {
|
||||
var i = chosenWeek.iterator();
|
||||
List<ScheduleEntity> result = new ArrayList<>();
|
||||
while (i.hasNext()) {
|
||||
result.add(newSchedule.get(i.next()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void fillAbsentDays(List<ScheduleEntity> schedule, String currentDoorId, Week week) {
|
||||
Set<LocalDate> existDates = schedule.stream()
|
||||
.map(ScheduleEntity::getDate)
|
||||
.collect(Collectors.toSet());
|
||||
Iterator<LocalDate> iter = week.iterator();
|
||||
for (int i = 0; iter.hasNext(); i++) {
|
||||
LocalDate currDate = iter.next();
|
||||
if (!existDates.contains(currDate)) {
|
||||
ScheduleEntity newEntity = new ScheduleEntity();
|
||||
newEntity.setId(UUID.randomUUID().toString());
|
||||
newEntity.setDoorId(currentDoorId);
|
||||
newEntity.setStartTime(LocalTime.MIN);
|
||||
newEntity.setEndTime(LocalTime.MAX);
|
||||
newEntity.setDate(currDate);
|
||||
schedule.add(i, newEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record Week(LocalDate dateMonday, LocalDate dateSunday) implements Iterable<LocalDate> {
|
||||
public Week {
|
||||
Objects.requireNonNull(dateMonday, "dateMonday cannot be null");
|
||||
Objects.requireNonNull(dateSunday, "dateSunday cannot be null");
|
||||
|
||||
Assert.state(dateMonday.getDayOfWeek().equals(DayOfWeek.MONDAY), "dateMonday must be Monday");
|
||||
Assert.state(dateSunday.getDayOfWeek().equals(DayOfWeek.SUNDAY), "dateSunday must be Sunday");
|
||||
|
||||
Assert.state(dateSunday.isAfter(dateMonday), "dateMonday should be less than dateSunday");
|
||||
}
|
||||
|
||||
public static Week current() {
|
||||
LocalDate now = LocalDate.now();
|
||||
LocalDate monday = now.minusDays(now.getDayOfWeek().getValue() - 1);
|
||||
LocalDate sunday = now.plusDays(7 - now.getDayOfWeek().getValue());
|
||||
return new Week(monday, sunday);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy");
|
||||
return dateMonday.format(dateFormat).concat(" - ").concat(dateSunday.format(dateFormat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<LocalDate> iterator() {
|
||||
return new Iterator<>() {
|
||||
private LocalDate current = dateMonday.minusDays(1);
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return current.isBefore(dateSunday);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate next() {
|
||||
return current = current.plusDays(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package ru.vyatsu.qr_access_admin.door.schedule.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "schedule")
|
||||
@Getter
|
||||
@Setter
|
||||
public class ScheduleEntity {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
private String doorId;
|
||||
|
||||
@Column
|
||||
private LocalTime startTime;
|
||||
|
||||
@Column
|
||||
private LocalTime endTime;
|
||||
|
||||
@Column
|
||||
private LocalDate date;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package ru.vyatsu.qr_access_admin.door.schedule.entity;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface ScheduleRepository extends JpaRepository<ScheduleEntity, String> {
|
||||
List<ScheduleEntity> findByDoorIdAndDateGreaterThanEqualAndDateLessThanEqualOrderByDateAsc(String doorId, LocalDate dateStart, LocalDate dateEnd);
|
||||
|
||||
void deleteAllByDoorId(String doorId);
|
||||
}
|
@ -1,10 +1,5 @@
|
||||
package ru.vyatsu.qr_access_admin.door.view;
|
||||
|
||||
import ru.vyatsu.qr_access_admin.common.MainLayout;
|
||||
import ru.vyatsu.qr_access_admin.door.component.DoorEditor;
|
||||
import ru.vyatsu.qr_access_admin.door.entity.DoorEntity;
|
||||
import ru.vyatsu.qr_access_admin.door.entity.DoorRepository;
|
||||
import ru.vyatsu.qr_access_admin.unit.entity.UnitRepository;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
@ -12,6 +7,16 @@ 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 jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.Query;
|
||||
import ru.vyatsu.qr_access_admin.common.MainLayout;
|
||||
import ru.vyatsu.qr_access_admin.door.component.DoorEditor;
|
||||
import ru.vyatsu.qr_access_admin.door.entity.DoorEntity;
|
||||
import ru.vyatsu.qr_access_admin.door.entity.DoorRepository;
|
||||
import ru.vyatsu.qr_access_admin.door.schedule.entity.ScheduleEntity;
|
||||
import ru.vyatsu.qr_access_admin.door.schedule.entity.ScheduleRepository;
|
||||
import ru.vyatsu.qr_access_admin.unit.entity.UnitRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -20,15 +25,19 @@ import java.util.List;
|
||||
public class DoorView extends VerticalLayout {
|
||||
private final Grid<DoorEntity> grid;
|
||||
private final DoorRepository repository;
|
||||
private final ScheduleRepository scheduleRepository;
|
||||
private final DoorEditor editor;
|
||||
private final EntityManagerFactory emf;
|
||||
|
||||
public DoorView(DoorRepository repository, UnitRepository unitRepository) {
|
||||
public DoorView(DoorRepository repository, UnitRepository unitRepository, ScheduleRepository scheduleRepository, EntityManagerFactory emf) {
|
||||
this.repository = repository;
|
||||
this.scheduleRepository = scheduleRepository;
|
||||
this.emf = emf;
|
||||
|
||||
var addButton = new Button("Добавить дверь", VaadinIcon.PLUS.create());
|
||||
|
||||
grid = new Grid<>(DoorEntity.class);
|
||||
editor = new DoorEditor(unitRepository);
|
||||
editor = new DoorEditor(unitRepository, scheduleRepository);
|
||||
|
||||
var actionsLayout = new HorizontalLayout(addButton);
|
||||
add(actionsLayout, grid, editor);
|
||||
@ -62,14 +71,26 @@ public class DoorView extends VerticalLayout {
|
||||
private void configureEditor() {
|
||||
editor.setVisible(false);
|
||||
|
||||
editor.setSaveListener(door -> {
|
||||
repository.save(door);
|
||||
editor.setSaveListener((door, schedule) -> {
|
||||
DoorEntity savedDoor = repository.save(door);
|
||||
for (ScheduleEntity se : schedule) {
|
||||
se.setDoorId(savedDoor.getId());
|
||||
}
|
||||
scheduleRepository.saveAll(schedule);
|
||||
refreshDoorsGrid();
|
||||
editDoor(null);
|
||||
});
|
||||
|
||||
editor.setDeleteListener(door -> {
|
||||
repository.deleteById(door.getId());
|
||||
EntityManager em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
Query scheduleDeleteQuery = em.createQuery("delete from ScheduleEntity s where s.doorId = :doorId");
|
||||
scheduleDeleteQuery.setParameter("doorId", door.getId());
|
||||
scheduleDeleteQuery.executeUpdate();
|
||||
Query doorDeleteQuery = em.createQuery("delete from DoorEntity d where d.id = :id");
|
||||
doorDeleteQuery.setParameter("id", door.getId());
|
||||
doorDeleteQuery.executeUpdate();
|
||||
em.getTransaction().commit();
|
||||
refreshDoorsGrid();
|
||||
editDoor(null);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user