Compare commits

...

2 Commits

Author SHA1 Message Date
kashiuno
8e70180266 Delete qrs, add additionalDoors management, update entities to fit the schema 2025-02-18 20:58:28 +03:00
kashiuno
d2d0b98083 Delete slots 2025-02-15 17:39:46 +03:00
21 changed files with 193 additions and 416 deletions

View File

@ -4,6 +4,7 @@ import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
@ -49,12 +50,14 @@ public class ClientEditor extends Composite<VerticalLayout> {
public ClientEditor() { public ClientEditor() {
var email = new EmailField("E-mail"); var email = new EmailField("E-mail");
var emailIsConfirmed = new Checkbox("e-mail подтвержден");
var save = new Button("Сохранить", VaadinIcon.CHECK.create()); var save = new Button("Сохранить", VaadinIcon.CHECK.create());
var cancel = new Button("Отмена"); var cancel = new Button("Отмена");
var delete = new Button("Удалить", VaadinIcon.TRASH.create()); var delete = new Button("Удалить", VaadinIcon.TRASH.create());
binder.forField(email).bind("email"); binder.forField(email).bind("email");
binder.forField(emailIsConfirmed).bind("emailIsConfirmed");
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
save.addClickListener(e -> saveListener.onSave(currentClient)); save.addClickListener(e -> saveListener.onSave(currentClient));
@ -65,6 +68,6 @@ public class ClientEditor extends Composite<VerticalLayout> {
cancel.addClickListener(e -> cancelListener.onCancel()); cancel.addClickListener(e -> cancelListener.onCancel());
getContent().add(email, new HorizontalLayout(save, cancel, delete)); getContent().add(email, emailIsConfirmed, new HorizontalLayout(save, cancel, delete));
} }
} }

View File

@ -16,4 +16,7 @@ public class ClientEntity {
@Column @Column
private String email; private String email;
@Column
private Boolean emailIsConfirmed;
} }

View File

@ -28,7 +28,7 @@ public class ClientView extends VerticalLayout {
var addButton = new Button("Добавить клиента", VaadinIcon.PLUS.create()); var addButton = new Button("Добавить клиента", VaadinIcon.PLUS.create());
grid = new Grid<>(ClientEntity.class); grid = new Grid<>(ClientEntity.class);
grid.setColumns("id", "email"); grid.setColumns("id", "email", "emailIsConfirmed");
editor = new ClientEditor(); editor = new ClientEditor();
var actionsLayout = new HorizontalLayout(addButton); var actionsLayout = new HorizontalLayout(addButton);

View File

@ -19,8 +19,6 @@ import ru.vyatsu.qr_access_admin.client.view.ClientView;
import ru.vyatsu.qr_access_admin.common.view.MainView; import ru.vyatsu.qr_access_admin.common.view.MainView;
import ru.vyatsu.qr_access_admin.door.view.DoorView; import ru.vyatsu.qr_access_admin.door.view.DoorView;
import ru.vyatsu.qr_access_admin.partner.view.PartnerView; import ru.vyatsu.qr_access_admin.partner.view.PartnerView;
import ru.vyatsu.qr_access_admin.qr.view.QrView;
import ru.vyatsu.qr_access_admin.slot.view.SlotView;
import ru.vyatsu.qr_access_admin.unit.view.UnitView; import ru.vyatsu.qr_access_admin.unit.view.UnitView;
import java.util.Optional; import java.util.Optional;
@ -121,8 +119,6 @@ public class MainLayout extends AppLayout {
createTab("Домашняя страница", MainView.class), createTab("Домашняя страница", MainView.class),
createTab("Устройства", UnitView.class), createTab("Устройства", UnitView.class),
createTab("Двери", DoorView.class), createTab("Двери", DoorView.class),
createTab("QR-коды", QrView.class),
createTab("Слоты", SlotView.class),
createTab("Партнеры", PartnerView.class), createTab("Партнеры", PartnerView.class),
createTab("Клиенты", ClientView.class) createTab("Клиенты", ClientView.class)
}; };

View File

@ -15,6 +15,7 @@ import com.vaadin.flow.data.binder.Binder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import ru.vyatsu.qr_access_admin.door.entity.DoorEntity; 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.component.ScheduleEditor; 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.ScheduleEntity;
import ru.vyatsu.qr_access_admin.door.schedule.entity.ScheduleRepository; import ru.vyatsu.qr_access_admin.door.schedule.entity.ScheduleRepository;
@ -22,14 +23,13 @@ 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.mapper.UnitEntityUnitComboBoxModelMapper;
import ru.vyatsu.qr_access_admin.unit.model.UnitComboBoxModel; import ru.vyatsu.qr_access_admin.unit.model.UnitComboBoxModel;
import java.util.Collection; import java.util.*;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class DoorEditor extends Composite<VerticalLayout> { public class DoorEditor extends Composite<HorizontalLayout> {
public interface SaveListener { public interface SaveListener {
void onSave(DoorEntity door, Collection<ScheduleEntity> schedule); void onSave(DoorEditInfo editInfo);
} }
public interface DeleteListener { public interface DeleteListener {
@ -44,6 +44,14 @@ public class DoorEditor extends Composite<VerticalLayout> {
private final ScheduleEditor scheduleEditor; private final ScheduleEditor scheduleEditor;
private final VerticalLayout additionalDoorsEditor = new VerticalLayout();
private final Map<String, DoorEntity> additionalDoors = new HashMap<>();
private final Map<String, DoorEntity> additionalDoorsWasLinked = new HashMap<>();
private final Collection<DoorEntity> createdAdditionalDoors = new ArrayList<>();
private final DoorRepository repository;
@Getter @Getter
@Setter @Setter
private SaveListener saveListener; private SaveListener saveListener;
@ -61,13 +69,100 @@ public class DoorEditor extends Composite<VerticalLayout> {
binder.setBean(door); binder.setBean(door);
scheduleEditor.setCurrentDoorId(door.getId()); scheduleEditor.setCurrentDoorId(door.getId());
scheduleEditor.refreshScheduleView(); scheduleEditor.refreshScheduleView();
this.refreshAdditionalDoors();
} }
public DoorEditor(UnitRepository unitRepository, ScheduleRepository scheduleRepository) { private void refreshAdditionalDoors() {
additionalDoorsEditor.removeAll();
additionalDoors.clear();
createdAdditionalDoors.clear();
List<DoorEntity> existAdditionalDoors = repository.findAllByParentDoorIdsIsNotNull();
existAdditionalDoors
.stream()
.filter(ad -> currentDoor != null && new HashSet<>(Set.of(ad.getParentDoorIds().split(","))).contains(currentDoor.getId()))
.forEach(ad -> {
HorizontalLayout choose = new HorizontalLayout();
ComboBox<DoorEntity> comboBox = new ComboBox<>("Выбрать из имеющихся");
comboBox.setItems(existAdditionalDoors);
comboBox.setItemLabelGenerator(DoorEntity::getDescription);
comboBox.setValue(ad);
comboBox.setAllowCustomValue(true);
comboBox.addValueChangeListener(e -> {
DoorEntity chosenValue = e.getValue();
DoorEntity oldValue = e.getOldValue();
if (oldValue != null) {
additionalDoors.remove(oldValue.getId());
}
if (chosenValue != null) {
additionalDoors.put(chosenValue.getId(), chosenValue);
}
});
comboBox.addCustomValueSetListener(e -> {
DoorEntity newDoor = new DoorEntity();
newDoor.setCount(0);
newDoor.setDescription(e.getDetail());
newDoor.setUnitId(currentDoor.getUnitId());
createdAdditionalDoors.add(newDoor);
existAdditionalDoors.add(newDoor);
comboBox.setItems(existAdditionalDoors);
comboBox.setValue(newDoor);
});
var deleteAdditionalDoor = new Button(VaadinIcon.TRASH.create());
additionalDoors.put(ad.getId(), ad);
additionalDoorsWasLinked.put(ad.getId(), ad);
deleteAdditionalDoor.addClickListener(ev -> {
additionalDoorsEditor.remove(choose);
additionalDoors.remove(ad.getId());
});
choose.add(comboBox, deleteAdditionalDoor);
additionalDoorsEditor.add(choose);
});
var addAdditionalDoor = new Button("Привязать дверь", VaadinIcon.PLUS.create());
addAdditionalDoor.addClickListener(e -> {
HorizontalLayout choose = new HorizontalLayout();
ComboBox<DoorEntity> comboBox = new ComboBox<>("Выбрать из имеющихся");
comboBox.setItems(existAdditionalDoors);
comboBox.setItemLabelGenerator(DoorEntity::getDescription);
comboBox.setAllowCustomValue(true);
comboBox.addValueChangeListener(ce -> {
DoorEntity chosenValue = ce.getValue();
DoorEntity oldValue = ce.getOldValue();
if (oldValue != null) {
additionalDoors.remove(oldValue.getId());
}
if (chosenValue != null) {
additionalDoors.put(chosenValue.getId(), chosenValue);
}
});
comboBox.addCustomValueSetListener(ce -> {
DoorEntity newDoor = new DoorEntity();
newDoor.setCount(0);
newDoor.setDescription(ce.getDetail());
newDoor.setUnitId(currentDoor.getUnitId());
createdAdditionalDoors.add(newDoor);
existAdditionalDoors.add(newDoor);
comboBox.setItems(existAdditionalDoors);
comboBox.setValue(newDoor);
});
var deleteAdditionalDoor = new Button(VaadinIcon.TRASH.create());
deleteAdditionalDoor.addClickListener(ev -> {
additionalDoorsEditor.remove(choose);
if (comboBox.getValue() != null) {
additionalDoors.remove(comboBox.getValue().getId());
}
});
choose.add(comboBox, deleteAdditionalDoor);
additionalDoorsEditor.addComponentAtIndex(additionalDoorsEditor.getComponentCount() - 1, choose);
});
additionalDoorsEditor.add(addAdditionalDoor);
}
public DoorEditor(UnitRepository unitRepository, ScheduleRepository scheduleRepository, DoorRepository repository) {
this.repository = repository;
ComboBox<UnitComboBoxModel> unitField = new ComboBox<>("Устройство"); ComboBox<UnitComboBoxModel> unitField = new ComboBox<>("Устройство");
unitField.setRequired(true); unitField.setRequired(true);
unitField.setPageSize(100); unitField.setPageSize(100);
Map<String, UnitComboBoxModel> units = unitRepository.findAll() Map<String, UnitComboBoxModel> units = unitRepository.findAllByAdminEditableIsTrue()
.stream() .stream()
.map(new UnitEntityUnitComboBoxModelMapper()::toModel) .map(new UnitEntityUnitComboBoxModelMapper()::toModel)
.collect(Collectors.toMap(UnitComboBoxModel::clientId, unit -> unit)); .collect(Collectors.toMap(UnitComboBoxModel::clientId, unit -> unit));
@ -76,6 +171,7 @@ public class DoorEditor extends Composite<VerticalLayout> {
IntegerField countField = new IntegerField("Количество мест"); IntegerField countField = new IntegerField("Количество мест");
TextField descriptionField = new TextField("Описание"); TextField descriptionField = new TextField("Описание");
var save = new Button("Сохранить", VaadinIcon.CHECK.create()); var save = new Button("Сохранить", VaadinIcon.CHECK.create());
var cancel = new Button("Отмена"); var cancel = new Button("Отмена");
var delete = new Button("Удалить", VaadinIcon.TRASH.create()); var delete = new Button("Удалить", VaadinIcon.TRASH.create());
@ -90,7 +186,7 @@ public class DoorEditor extends Composite<VerticalLayout> {
scheduleEditor = new ScheduleEditor(scheduleRepository); scheduleEditor = new ScheduleEditor(scheduleRepository);
save.addClickListener(e -> saveListener.onSave(currentDoor, scheduleEditor.getNewSchedule())); save.addClickListener(e -> saveListener.onSave(new DoorEditInfo(currentDoor, scheduleEditor.getNewSchedule(), additionalDoors, additionalDoorsWasLinked)));
save.addClickShortcut(Key.ENTER); save.addClickShortcut(Key.ENTER);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR); delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
@ -102,8 +198,19 @@ public class DoorEditor extends Composite<VerticalLayout> {
} }
}); });
this.refreshAdditionalDoors();
cancel.addClickListener(e -> cancelListener.onCancel()); cancel.addClickListener(e -> cancelListener.onCancel());
getContent().add(unitField, countField, descriptionField, scheduleEditor, new HorizontalLayout(save, cancel, delete)); VerticalLayout mainForm = new VerticalLayout();
mainForm.add(unitField, countField, descriptionField, scheduleEditor, new HorizontalLayout(save, cancel, delete));
getContent().add(mainForm, additionalDoorsEditor);
}
public record DoorEditInfo(DoorEntity mainDoor, Collection<ScheduleEntity> schedule,
Map<String, DoorEntity> additionalDoorsToSave,
Map<String, DoorEntity> additionalDoorsWasLinked) {
} }
} }

View File

@ -22,4 +22,7 @@ public class DoorEntity {
@Column @Column
private int count; private int count;
@Column
private String parentDoorIds;
} }

View File

@ -2,6 +2,12 @@ package ru.vyatsu.qr_access_admin.door.entity;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface DoorRepository extends JpaRepository<DoorEntity, String> { public interface DoorRepository extends JpaRepository<DoorEntity, String> {
// TODO: запрашиваем все, чтобы не городить много SQL логики, так как id хранятся через запятую. Будем парсить и фильтровать в коде.
List<DoorEntity> findAllByParentDoorIdsIsNotNull();
List<DoorEntity> findAllByParentDoorIdsIsNull();
} }

View File

@ -19,7 +19,10 @@ 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.door.schedule.entity.ScheduleRepository;
import ru.vyatsu.qr_access_admin.unit.entity.UnitRepository; import ru.vyatsu.qr_access_admin.unit.entity.UnitRepository;
import java.util.List; import java.util.*;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
@Route(value = "doors", layout = MainLayout.class) @Route(value = "doors", layout = MainLayout.class)
@PageTitle("Двери") @PageTitle("Двери")
@ -39,7 +42,8 @@ public class DoorView extends VerticalLayout {
var addButton = new Button("Добавить дверь", VaadinIcon.PLUS.create()); var addButton = new Button("Добавить дверь", VaadinIcon.PLUS.create());
grid = new Grid<>(DoorEntity.class); grid = new Grid<>(DoorEntity.class);
editor = new DoorEditor(unitRepository, scheduleRepository); grid.setColumns("description", "unitId", "count");
editor = new DoorEditor(unitRepository, scheduleRepository, repository);
var actionsLayout = new HorizontalLayout(addButton); var actionsLayout = new HorizontalLayout(addButton);
add(actionsLayout, grid, editor); add(actionsLayout, grid, editor);
@ -56,7 +60,7 @@ public class DoorView extends VerticalLayout {
} }
private void refreshDoorsGrid() { private void refreshDoorsGrid() {
List<DoorEntity> entities = repository.findAll(); List<DoorEntity> entities = repository.findAllByParentDoorIdsIsNull();
grid.setItems(entities); grid.setItems(entities);
} }
@ -73,12 +77,34 @@ public class DoorView extends VerticalLayout {
private void configureEditor() { private void configureEditor() {
editor.setVisible(false); editor.setVisible(false);
editor.setSaveListener((door, schedule) -> { editor.setSaveListener(doorEditInfo -> {
DoorEntity savedDoor = repository.save(door); DoorEntity savedDoor = repository.save(doorEditInfo.mainDoor());
for (ScheduleEntity se : schedule) { Map<String, DoorEntity> doorsToSave = new HashMap<>();
doorEditInfo.additionalDoorsToSave().values().forEach(ad -> {
if (isBlank(ad.getParentDoorIds())) {
ad.setParentDoorIds(savedDoor.getId());
} else {
Set<String> parentDoorIds = Arrays.stream(ad.getParentDoorIds().split(","))
.collect(Collectors.toSet());
parentDoorIds.add(savedDoor.getId());
ad.setParentDoorIds(String.join(",", parentDoorIds));
}
doorsToSave.put(ad.getId(), ad);
});
doorEditInfo.additionalDoorsWasLinked().forEach((id, door) -> {
if (!doorEditInfo.additionalDoorsToSave().containsKey(id)) {
Set<String> parentIds = Arrays.stream(door.getParentDoorIds().split(","))
.collect(Collectors.toSet());
parentIds.remove(savedDoor.getId());
door.setParentDoorIds(String.join(",", parentIds));
doorsToSave.put(id, door);
}
});
repository.saveAll(doorsToSave.values());
for (ScheduleEntity se : doorEditInfo.schedule()) {
se.setDoorId(savedDoor.getId()); se.setDoorId(savedDoor.getId());
} }
scheduleRepository.saveAll(schedule); scheduleRepository.saveAll(doorEditInfo.schedule());
refreshDoorsGrid(); refreshDoorsGrid();
editDoor(null); editDoor(null);
}); });

View File

@ -1,89 +0,0 @@
package ru.vyatsu.qr_access_admin.qr.component;
import com.vaadin.flow.component.Composite;
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.datetimepicker.DateTimePicker;
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.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.entity.DoorRepository;
import ru.vyatsu.qr_access_admin.qr.entity.QrEntity;
import java.util.Map;
import java.util.stream.Collectors;
public class QrEditor extends Composite<VerticalLayout> {
public interface SaveListener {
void onSave(QrEntity qr);
}
public interface DeleteListener {
void onDelete(QrEntity qr);
}
public interface CancelListener {
void onCancel();
}
private volatile QrEntity currentQr;
@Getter
@Setter
private SaveListener saveListener;
@Getter
@Setter
private DeleteListener deleteListener;
@Getter
@Setter
private CancelListener cancelListener;
private final Binder<QrEntity> binder = new BeanValidationBinder<>(QrEntity.class);
public void setCurrentQr(QrEntity qr) {
this.currentQr = qr;
binder.setBean(qr);
}
public QrEditor(DoorRepository doorRepository) {
ComboBox<DoorEntity> doorIdField = new ComboBox<>("Идентификатор двери");
doorIdField.setRequired(true);
doorIdField.setItemLabelGenerator(DoorEntity::getId);
Map<String, DoorEntity> doors = doorRepository.findAll()
.stream()
.collect(Collectors.toMap(DoorEntity::getId, e -> e));
doorIdField.setItems(doors.values());
var startDateTime = new DateTimePicker("Начало действия");
var endDateTime = new DateTimePicker("Актуален до");
var save = new Button("Сохранить", VaadinIcon.CHECK.create());
var cancel = new Button("Отмена");
var delete = new Button("Удалить", VaadinIcon.TRASH.create());
binder.forField(doorIdField)
.withConverter(DoorEntity::getId, doors::get, "Invalid value")
.bind("doorId");
binder.forField(startDateTime).bind("startDateTime");
binder.forField(endDateTime).bind("endDateTime");
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
save.addClickListener(e -> saveListener.onSave(currentQr));
save.addClickShortcut(Key.ENTER);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
delete.addClickListener(e -> deleteListener.onDelete(currentQr));
cancel.addClickListener(e -> cancelListener.onCancel());
getContent().add(doorIdField, startDateTime, endDateTime, new HorizontalLayout(save, cancel, delete));
}
}

View File

@ -1,28 +0,0 @@
package ru.vyatsu.qr_access_admin.qr.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@Table(name = "qrs")
@Getter
@Setter
public class QrEntity {
@Id
@Column
@GeneratedValue(strategy = GenerationType.UUID)
private String keyCode;
@Column
private String doorId;
@Column
private LocalDateTime startDateTime;
@Column
private LocalDateTime endDateTime;
}

View File

@ -1,6 +0,0 @@
package ru.vyatsu.qr_access_admin.qr.entity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QrRepository extends JpaRepository<QrEntity, String> {
}

View File

@ -1,78 +0,0 @@
package ru.vyatsu.qr_access_admin.qr.view;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
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.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll;
import ru.vyatsu.qr_access_admin.common.MainLayout;
import ru.vyatsu.qr_access_admin.door.entity.DoorRepository;
import ru.vyatsu.qr_access_admin.qr.component.QrEditor;
import ru.vyatsu.qr_access_admin.qr.entity.QrEntity;
import ru.vyatsu.qr_access_admin.qr.entity.QrRepository;
import java.util.List;
@Route(value = "qrs", layout = MainLayout.class)
@PageTitle("Коды")
@PermitAll
public class QrView extends VerticalLayout {
private final QrRepository repository;
private final Grid<QrEntity> grid;
private final QrEditor editor;
public QrView(QrRepository repository, DoorRepository doorRepository) {
this.repository = repository;
var addButton = new Button("Добавить код", VaadinIcon.PLUS.create());
grid = new Grid<>(QrEntity.class);
editor = new QrEditor(doorRepository);
var actionsLayout = new HorizontalLayout(addButton);
add(actionsLayout, grid, editor);
this.configureEditor();
addButton.addClickListener(e -> editQr(new QrEntity()));
grid.setHeight("200px");
grid.asSingleSelect().addValueChangeListener(e -> editQr(e.getValue()));
refreshQrGrid();
}
private void refreshQrGrid() {
List<QrEntity> entities = repository.findAll();
grid.setItems(entities);
}
private void editQr(QrEntity qr) {
if (qr == null) {
editor.setVisible(false);
} else {
editor.setVisible(true);
editor.setCurrentQr(qr);
}
}
private void configureEditor() {
editor.setVisible(false);
editor.setSaveListener(qr -> {
repository.save(qr);
refreshQrGrid();
editQr(null);
});
editor.setDeleteListener(qr -> {
repository.deleteById(qr.getKeyCode());
refreshQrGrid();
editQr(null);
});
editor.setCancelListener(() -> editQr(null));
}
}

View File

@ -1,79 +0,0 @@
package ru.vyatsu.qr_access_admin.slot.component;
import com.vaadin.flow.component.Composite;
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.datetimepicker.DateTimePicker;
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.timepicker.TimePicker;
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.entity.DoorRepository;
import ru.vyatsu.qr_access_admin.slot.entity.SlotEntity;
import java.util.Map;
import java.util.stream.Collectors;
public class SlotEditor extends Composite<VerticalLayout> {
public interface SaveListener {
void onSave(SlotEntity qr);
}
public interface DeleteListener {
void onDelete(SlotEntity qr);
}
public interface CancelListener {
void onCancel();
}
private volatile SlotEntity currentSlot;
@Getter
@Setter
private SaveListener saveListener;
@Getter
@Setter
private DeleteListener deleteListener;
@Getter
@Setter
private CancelListener cancelListener;
private final Binder<SlotEntity> binder = new BeanValidationBinder<>(SlotEntity.class);
public void setCurrentSlot(SlotEntity slot) {
this.currentSlot = slot;
binder.setBean(slot);
}
public SlotEditor() {
var startTime = new TimePicker("Начало действия");
var endTime = new TimePicker("Конец действия");
var save = new Button("Сохранить", VaadinIcon.CHECK.create());
var cancel = new Button("Отмена");
var delete = new Button("Удалить", VaadinIcon.TRASH.create());
binder.forField(startTime).bind("startTime");
binder.forField(endTime).bind("endTime");
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
save.addClickListener(e -> saveListener.onSave(currentSlot));
save.addClickShortcut(Key.ENTER);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
delete.addClickListener(e -> deleteListener.onDelete(currentSlot));
cancel.addClickListener(e -> cancelListener.onCancel());
getContent().add(startTime, endTime, new HorizontalLayout(save, cancel, delete));
}
}

View File

@ -1,24 +0,0 @@
package ru.vyatsu.qr_access_admin.slot.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalTime;
@Entity
@Table(name = "slots")
@Getter
@Setter
public class SlotEntity {
@Id
@Column
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@Column
private LocalTime startTime;
@Column
private LocalTime endTime;
}

View File

@ -1,8 +0,0 @@
package ru.vyatsu.qr_access_admin.slot.entity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SlotRepository extends JpaRepository<SlotEntity, String> {
}

View File

@ -1,78 +0,0 @@
package ru.vyatsu.qr_access_admin.slot.view;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
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.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll;
import ru.vyatsu.qr_access_admin.common.MainLayout;
import ru.vyatsu.qr_access_admin.slot.component.SlotEditor;
import ru.vyatsu.qr_access_admin.slot.entity.SlotEntity;
import ru.vyatsu.qr_access_admin.slot.entity.SlotRepository;
import java.util.List;
@Route(value = "slots", layout = MainLayout.class)
@PageTitle("Слоты")
@PermitAll
public class SlotView extends VerticalLayout {
private final SlotRepository repository;
private final Grid<SlotEntity> grid;
private final SlotEditor editor;
public SlotView(SlotRepository repository) {
this.repository = repository;
var addButton = new Button("Добавить слот", VaadinIcon.PLUS.create());
grid = new Grid<>(SlotEntity.class);
grid.setColumns("id", "startTime", "endTime");
editor = new SlotEditor();
var actionsLayout = new HorizontalLayout(addButton);
add(actionsLayout, grid, editor);
this.configureEditor();
addButton.addClickListener(e -> editSlot(new SlotEntity()));
grid.setHeight("200px");
grid.asSingleSelect().addValueChangeListener(e -> editSlot(e.getValue()));
refreshSlotGrid();
}
private void refreshSlotGrid() {
List<SlotEntity> entities = repository.findAll();
grid.setItems(entities);
}
private void editSlot(SlotEntity slot) {
if (slot == null) {
editor.setVisible(false);
} else {
editor.setVisible(true);
editor.setCurrentSlot(slot);
}
}
private void configureEditor() {
editor.setVisible(false);
editor.setSaveListener(slot -> {
repository.save(slot);
refreshSlotGrid();
editSlot(null);
});
editor.setDeleteListener(slot -> {
repository.deleteById(slot.getId());
refreshSlotGrid();
editSlot(null);
});
editor.setCancelListener(() -> editSlot(null));
}
}

View File

@ -1,10 +1,10 @@
package ru.vyatsu.qr_access_admin.unit.component; package ru.vyatsu.qr_access_admin.unit.component;
import ru.vyatsu.qr_access_admin.unit.model.UnitModel;
import com.vaadin.flow.component.Composite; import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datetimepicker.DateTimePicker; import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
@ -14,6 +14,12 @@ import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder; import com.vaadin.flow.data.binder.Binder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import ru.vyatsu.qr_access_admin.partner.entity.PartnerEntity;
import ru.vyatsu.qr_access_admin.partner.entity.PartnerRepository;
import ru.vyatsu.qr_access_admin.unit.model.UnitModel;
import java.util.Map;
import java.util.stream.Collectors;
public class UnitEditor extends Composite<VerticalLayout> { public class UnitEditor extends Composite<VerticalLayout> {
public interface SaveListener { public interface SaveListener {
@ -47,7 +53,7 @@ public class UnitEditor extends Composite<VerticalLayout> {
binder.setBean(unit); binder.setBean(unit);
} }
public UnitEditor() { public UnitEditor(PartnerRepository partnerRepository) {
var clientId = new TextField("Идентификатор устройства"); var clientId = new TextField("Идентификатор устройства");
clientId.setRequired(true); clientId.setRequired(true);
var clientSecret = new TextField("Секрет устройства"); var clientSecret = new TextField("Секрет устройства");
@ -56,6 +62,13 @@ public class UnitEditor extends Composite<VerticalLayout> {
clientSecretExpiresAt.setRequiredIndicatorVisible(true); clientSecretExpiresAt.setRequiredIndicatorVisible(true);
var clientName = new TextField("Человекочитаемое название устройства"); var clientName = new TextField("Человекочитаемое название устройства");
clientName.setRequired(true); clientName.setRequired(true);
ComboBox<PartnerEntity> partnerField = new ComboBox<>("Партнер");
partnerField.setPageSize(100);
Map<String, PartnerEntity> partners = partnerRepository.findAll()
.stream()
.collect(Collectors.toMap(PartnerEntity::getId, p -> p));
partnerField.setItemLabelGenerator(PartnerEntity::getName);
partnerField.setItems(partners.values());
var save = new Button("Сохранить", VaadinIcon.CHECK.create()); var save = new Button("Сохранить", VaadinIcon.CHECK.create());
var cancel = new Button("Отмена"); var cancel = new Button("Отмена");
@ -65,6 +78,9 @@ public class UnitEditor extends Composite<VerticalLayout> {
binder.forField(clientSecret).bind("clientSecret"); binder.forField(clientSecret).bind("clientSecret");
binder.forField(clientSecretExpiresAt).bind("clientSecretExpiresAt"); binder.forField(clientSecretExpiresAt).bind("clientSecretExpiresAt");
binder.forField(clientName).bind("clientName"); binder.forField(clientName).bind("clientName");
binder.forField(partnerField)
.withConverter(PartnerEntity::getId, partners::get, "Invalid value")
.bind("partnerId");
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
save.addClickListener(e -> saveListener.onSave(currentUnit)); save.addClickListener(e -> saveListener.onSave(currentUnit));
@ -75,6 +91,6 @@ public class UnitEditor extends Composite<VerticalLayout> {
cancel.addClickListener(e -> cancelListener.onCancel()); cancel.addClickListener(e -> cancelListener.onCancel());
getContent().add(clientId, clientSecret, clientSecretExpiresAt, clientName, new HorizontalLayout(save, cancel, delete)); getContent().add(clientId, clientSecret, clientSecretExpiresAt, clientName, partnerField, new HorizontalLayout(save, cancel, delete));
} }
} }

View File

@ -49,4 +49,6 @@ public class UnitEntity {
private String tokenSettings; private String tokenSettings;
@Column @Column
private Boolean adminEditable; private Boolean adminEditable;
@Column
private String partnerId;
} }

View File

@ -33,6 +33,7 @@ public class UnitEntityUnitModelMapper {
entity.setRedirectUris(""); entity.setRedirectUris("");
entity.setPostLogoutRedirectUris(""); entity.setPostLogoutRedirectUris("");
entity.setAdminEditable(true); entity.setAdminEditable(true);
entity.setPartnerId(model.getPartnerId());
return entity; return entity;
} }
@ -44,6 +45,7 @@ public class UnitEntityUnitModelMapper {
unitModel.setClientName(entity.getClientName()); unitModel.setClientName(entity.getClientName());
unitModel.setClientSecretExpiresAt(entity.getClientSecretExpiresAt()); unitModel.setClientSecretExpiresAt(entity.getClientSecretExpiresAt());
unitModel.setAdminEditable(entity.getAdminEditable()); unitModel.setAdminEditable(entity.getAdminEditable());
unitModel.setPartnerId(entity.getPartnerId());
return unitModel; return unitModel;
} }

View File

@ -28,4 +28,6 @@ public class UnitModel {
private String clientName; private String clientName;
private boolean adminEditable; private boolean adminEditable;
private String partnerId;
} }

View File

@ -9,6 +9,7 @@ import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import ru.vyatsu.qr_access_admin.common.MainLayout; import ru.vyatsu.qr_access_admin.common.MainLayout;
import ru.vyatsu.qr_access_admin.partner.entity.PartnerRepository;
import ru.vyatsu.qr_access_admin.unit.component.UnitEditor; import ru.vyatsu.qr_access_admin.unit.component.UnitEditor;
import ru.vyatsu.qr_access_admin.unit.entity.UnitEntity; import ru.vyatsu.qr_access_admin.unit.entity.UnitEntity;
import ru.vyatsu.qr_access_admin.unit.entity.UnitRepository; import ru.vyatsu.qr_access_admin.unit.entity.UnitRepository;
@ -27,14 +28,14 @@ public class UnitView extends VerticalLayout {
private final UnitEditor editor; private final UnitEditor editor;
private final UnitEntityUnitModelMapper entityModelMapper; private final UnitEntityUnitModelMapper entityModelMapper;
public UnitView(UnitRepository unitRepository, UnitEntityUnitModelMapper entityModelMapper) { public UnitView(UnitRepository unitRepository, UnitEntityUnitModelMapper entityModelMapper, PartnerRepository partnerRepository) {
this.unitRepository = unitRepository; this.unitRepository = unitRepository;
this.entityModelMapper = entityModelMapper; this.entityModelMapper = entityModelMapper;
var addButton = new Button("Добавить устройство", VaadinIcon.PLUS.create()); var addButton = new Button("Добавить устройство", VaadinIcon.PLUS.create());
grid = new Grid<>(UnitModel.class); grid = new Grid<>(UnitModel.class);
grid.setColumns("clientId", "clientName", "clientSecretExpiresAt"); grid.setColumns("clientId", "clientName", "clientSecretExpiresAt");
editor = new UnitEditor(); editor = new UnitEditor(partnerRepository);
var actionsLayout = new HorizontalLayout(addButton); var actionsLayout = new HorizontalLayout(addButton);
add(actionsLayout, grid, editor); add(actionsLayout, grid, editor);