Compare commits
7
Commits
c847830a7e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cea5de046 | ||
|
|
b797aed073 | ||
|
|
deb18f48c0 | ||
|
|
8e70180266 | ||
|
|
d2d0b98083 | ||
|
|
7dbfd77547 | ||
|
|
e98789a1ee |
@@ -0,0 +1,26 @@
|
|||||||
|
# Панель администратора для настройки устройств и управления данными системы
|
||||||
|
|
||||||
|
Приложение предоставляет полный доступ для управления данными в базе данных, а также возможность настройки оборудования и отслеживание заявок.
|
||||||
|
Защищена авторизацией
|
||||||
|
|
||||||
|
## Запуск приложения
|
||||||
|
|
||||||
|
Для приложения требуется БД и конфигурация подключения к БД.
|
||||||
|
Также для работы приложения нужен запущенный сервер авторизации.
|
||||||
|
|
||||||
|
Для запуска приложения нужно выполнить команду
|
||||||
|
|
||||||
|
`gradle bootRun`
|
||||||
|
|
||||||
|
## Сборка приложения
|
||||||
|
|
||||||
|
`gradle vaadinBuildFrontend bootJar`
|
||||||
|
|
||||||
|
## Основные страницы
|
||||||
|
|
||||||
|
- MainLayout - оболочка панели администратора с навигацией;
|
||||||
|
- ClientView - страница просмотра, редактирования и удаления клиентов;
|
||||||
|
- DoorView - страница просмотра, редактирования и удаления дверей. Здесь же настраиваются дополнительные двери и расписание для дверей;
|
||||||
|
- PartnerView - страница просмотра, редактирования и удаления дверей;
|
||||||
|
- RentView - страница просмотра, редактирования и удаления бронирований;
|
||||||
|
- UnitView - страница просмотра, редактирования и удаления устройств.
|
||||||
@@ -30,6 +30,8 @@ dependencies {
|
|||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("com.vaadin:vaadin-spring-boot-starter")
|
implementation("com.vaadin:vaadin-spring-boot-starter")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
|
||||||
|
|
||||||
implementation("org.yaml:snakeyaml")
|
implementation("org.yaml:snakeyaml")
|
||||||
compileOnly("org.projectlombok:lombok")
|
compileOnly("org.projectlombok:lombok")
|
||||||
annotationProcessor("org.projectlombok:lombok")
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,7 @@ public class ClientEntity {
|
|||||||
|
|
||||||
@Column
|
@Column
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private Boolean emailIsConfirmed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
|||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import com.vaadin.flow.router.PageTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
|
import jakarta.annotation.security.PermitAll;
|
||||||
import ru.vyatsu.qr_access_admin.client.component.ClientEditor;
|
import ru.vyatsu.qr_access_admin.client.component.ClientEditor;
|
||||||
import ru.vyatsu.qr_access_admin.client.entity.ClientEntity;
|
import ru.vyatsu.qr_access_admin.client.entity.ClientEntity;
|
||||||
import ru.vyatsu.qr_access_admin.client.entity.ClientRepository;
|
import ru.vyatsu.qr_access_admin.client.entity.ClientRepository;
|
||||||
@@ -16,6 +17,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Route(value = "clients", layout = MainLayout.class)
|
@Route(value = "clients", layout = MainLayout.class)
|
||||||
@PageTitle("Клиенты")
|
@PageTitle("Клиенты")
|
||||||
|
@PermitAll
|
||||||
public class ClientView extends VerticalLayout {
|
public class ClientView extends VerticalLayout {
|
||||||
private final ClientRepository repository;
|
private final ClientRepository repository;
|
||||||
private final Grid<ClientEntity> grid;
|
private final Grid<ClientEntity> grid;
|
||||||
@@ -26,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);
|
||||||
@@ -36,7 +38,7 @@ public class ClientView extends VerticalLayout {
|
|||||||
|
|
||||||
addButton.addClickListener(e -> editClient(new ClientEntity()));
|
addButton.addClickListener(e -> editClient(new ClientEntity()));
|
||||||
|
|
||||||
grid.setHeight("200px");
|
grid.setHeight("600px");
|
||||||
grid.asSingleSelect().addValueChangeListener(e -> editClient(e.getValue()));
|
grid.asSingleSelect().addValueChangeListener(e -> editClient(e.getValue()));
|
||||||
|
|
||||||
refreshClientGrid();
|
refreshClientGrid();
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ 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.rent.view.RentView;
|
||||||
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,9 +120,8 @@ 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("Бронирование", RentView.class),
|
||||||
createTab("Клиенты", ClientView.class)
|
createTab("Клиенты", ClientView.class)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.vyatsu.qr_access_admin.common.config;
|
||||||
|
|
||||||
|
import com.vaadin.flow.spring.security.VaadinWebSecurity;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfiguration extends VaadinWebSecurity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
super.configure(http);
|
||||||
|
http.oauth2Login(c -> c.loginProcessingUrl("/login/oauth2/code/own"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package ru.vyatsu.qr_access_admin.common.view;
|
package ru.vyatsu.qr_access_admin.common.view;
|
||||||
|
|
||||||
import ru.vyatsu.qr_access_admin.common.MainLayout;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import com.vaadin.flow.router.PageTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
|
import jakarta.annotation.security.PermitAll;
|
||||||
|
import ru.vyatsu.qr_access_admin.common.MainLayout;
|
||||||
|
|
||||||
@Route(value = "", layout = MainLayout.class)
|
@Route(value = "", layout = MainLayout.class)
|
||||||
@PageTitle("Start")
|
@PageTitle("Start")
|
||||||
|
@PermitAll
|
||||||
public class MainView extends VerticalLayout {
|
public class MainView extends VerticalLayout {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.vaadin.flow.component.combobox.ComboBox;
|
|||||||
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;
|
||||||
|
import com.vaadin.flow.component.textfield.BigDecimalField;
|
||||||
import com.vaadin.flow.component.textfield.IntegerField;
|
import com.vaadin.flow.component.textfield.IntegerField;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import com.vaadin.flow.data.binder.BeanValidationBinder;
|
import com.vaadin.flow.data.binder.BeanValidationBinder;
|
||||||
@@ -15,6 +16,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 +24,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 +45,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 +70,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));
|
||||||
@@ -75,6 +171,7 @@ public class DoorEditor extends Composite<VerticalLayout> {
|
|||||||
unitField.setItemLabelGenerator(UnitComboBoxModel::clientName);
|
unitField.setItemLabelGenerator(UnitComboBoxModel::clientName);
|
||||||
IntegerField countField = new IntegerField("Количество мест");
|
IntegerField countField = new IntegerField("Количество мест");
|
||||||
TextField descriptionField = new TextField("Описание");
|
TextField descriptionField = new TextField("Описание");
|
||||||
|
BigDecimalField price = new BigDecimalField("Цена за час");
|
||||||
|
|
||||||
var save = new Button("Сохранить", VaadinIcon.CHECK.create());
|
var save = new Button("Сохранить", VaadinIcon.CHECK.create());
|
||||||
var cancel = new Button("Отмена");
|
var cancel = new Button("Отмена");
|
||||||
@@ -84,13 +181,14 @@ public class DoorEditor extends Composite<VerticalLayout> {
|
|||||||
.withConverter(UnitComboBoxModel::clientId, units::get, "Invalid value")
|
.withConverter(UnitComboBoxModel::clientId, units::get, "Invalid value")
|
||||||
.bind("unitId");
|
.bind("unitId");
|
||||||
binder.forField(countField).bind("count");
|
binder.forField(countField).bind("count");
|
||||||
|
binder.forField(price).bind("price");
|
||||||
binder.forField(descriptionField).bind("description");
|
binder.forField(descriptionField).bind("description");
|
||||||
|
|
||||||
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
|
||||||
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 +200,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, price, 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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import jakarta.persistence.*;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "doors")
|
@Table(name = "doors")
|
||||||
@Getter
|
@Getter
|
||||||
@@ -22,4 +24,10 @@ public class DoorEntity {
|
|||||||
|
|
||||||
@Column
|
@Column
|
||||||
private int count;
|
private int count;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String parentDoorIds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
|||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
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.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.EntityManagerFactory;
|
import jakarta.persistence.EntityManagerFactory;
|
||||||
import jakarta.persistence.Query;
|
import jakarta.persistence.Query;
|
||||||
@@ -18,10 +19,14 @@ 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("Двери")
|
||||||
|
@PermitAll
|
||||||
public class DoorView extends VerticalLayout {
|
public class DoorView extends VerticalLayout {
|
||||||
private final Grid<DoorEntity> grid;
|
private final Grid<DoorEntity> grid;
|
||||||
private final DoorRepository repository;
|
private final DoorRepository repository;
|
||||||
@@ -37,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);
|
||||||
@@ -46,15 +52,14 @@ public class DoorView extends VerticalLayout {
|
|||||||
|
|
||||||
addButton.addClickListener(e -> editDoor(new DoorEntity()));
|
addButton.addClickListener(e -> editDoor(new DoorEntity()));
|
||||||
|
|
||||||
grid.setHeight("200px");
|
grid.setHeight("600px");
|
||||||
|
|
||||||
grid.asSingleSelect().addValueChangeListener(e -> editDoor(e.getValue()));
|
grid.asSingleSelect().addValueChangeListener(e -> editDoor(e.getValue()));
|
||||||
|
|
||||||
refreshDoorsGrid();
|
refreshDoorsGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshDoorsGrid() {
|
private void refreshDoorsGrid() {
|
||||||
List<DoorEntity> entities = repository.findAll();
|
List<DoorEntity> entities = repository.findAllByParentDoorIdsIsNull();
|
||||||
|
|
||||||
grid.setItems(entities);
|
grid.setItems(entities);
|
||||||
}
|
}
|
||||||
@@ -71,12 +76,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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ import ru.vyatsu.qr_access_admin.partner.entity.PartnerEntity;
|
|||||||
public class PartnerEditor extends Composite<VerticalLayout> {
|
public class PartnerEditor extends Composite<VerticalLayout> {
|
||||||
|
|
||||||
public interface SaveListener {
|
public interface SaveListener {
|
||||||
void onSave(PartnerEntity qr);
|
void onSave(PartnerEntity partner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DeleteListener {
|
public interface DeleteListener {
|
||||||
void onDelete(PartnerEntity qr);
|
void onDelete(PartnerEntity partner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface CancelListener {
|
public interface CancelListener {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
|||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import com.vaadin.flow.router.PageTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
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.common.MainLayout;
|
||||||
import ru.vyatsu.qr_access_admin.partner.component.PartnerEditor;
|
import ru.vyatsu.qr_access_admin.partner.component.PartnerEditor;
|
||||||
import ru.vyatsu.qr_access_admin.partner.entity.PartnerEntity;
|
import ru.vyatsu.qr_access_admin.partner.entity.PartnerEntity;
|
||||||
@@ -16,6 +17,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Route(value = "partners", layout = MainLayout.class)
|
@Route(value = "partners", layout = MainLayout.class)
|
||||||
@PageTitle("Партнеры")
|
@PageTitle("Партнеры")
|
||||||
|
@PermitAll
|
||||||
public class PartnerView extends VerticalLayout {
|
public class PartnerView extends VerticalLayout {
|
||||||
private final PartnerRepository repository;
|
private final PartnerRepository repository;
|
||||||
private final Grid<PartnerEntity> grid;
|
private final Grid<PartnerEntity> grid;
|
||||||
@@ -36,7 +38,7 @@ public class PartnerView extends VerticalLayout {
|
|||||||
|
|
||||||
addButton.addClickListener(e -> editPartner(new PartnerEntity()));
|
addButton.addClickListener(e -> editPartner(new PartnerEntity()));
|
||||||
|
|
||||||
grid.setHeight("200px");
|
grid.setHeight("600px");
|
||||||
grid.asSingleSelect().addValueChangeListener(e -> editPartner(e.getValue()));
|
grid.asSingleSelect().addValueChangeListener(e -> editPartner(e.getValue()));
|
||||||
|
|
||||||
refreshPartnerGrid();
|
refreshPartnerGrid();
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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> {
|
|
||||||
}
|
|
||||||
@@ -1,76 +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 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("Коды")
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package ru.vyatsu.qr_access_admin.rent.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.checkbox.Checkbox;
|
||||||
|
import com.vaadin.flow.component.combobox.ComboBox;
|
||||||
|
import com.vaadin.flow.component.datepicker.DatePicker;
|
||||||
|
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.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.client.entity.ClientEntity;
|
||||||
|
import ru.vyatsu.qr_access_admin.client.entity.ClientRepository;
|
||||||
|
import ru.vyatsu.qr_access_admin.door.entity.DoorEntity;
|
||||||
|
import ru.vyatsu.qr_access_admin.door.entity.DoorRepository;
|
||||||
|
import ru.vyatsu.qr_access_admin.rent.entity.RentEntity;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class RentEditor extends Composite<VerticalLayout> {
|
||||||
|
|
||||||
|
public interface SaveListener {
|
||||||
|
void onSave(RentEntity rent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DeleteListener {
|
||||||
|
void onDelete(RentEntity rent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface CancelListener {
|
||||||
|
void onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile RentEntity currentRent;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private SaveListener saveListener;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private DeleteListener deleteListener;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private CancelListener cancelListener;
|
||||||
|
|
||||||
|
private final Binder<RentEntity> binder = new BeanValidationBinder<>(RentEntity.class);
|
||||||
|
|
||||||
|
public void setCurrentRent(RentEntity rent) {
|
||||||
|
this.currentRent = rent;
|
||||||
|
binder.setBean(rent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RentEditor(DoorRepository doorRepository, ClientRepository clientRepository) {
|
||||||
|
var startTimeField = new TimePicker("Время начала действия");
|
||||||
|
var endTimeField = new TimePicker("Время окончания действия");
|
||||||
|
var dateField = new DatePicker("Дата бронирования");
|
||||||
|
var qrCodeField = new TextField("Закодированный идентификатор");
|
||||||
|
var payedField = new Checkbox("Оплачено");
|
||||||
|
ComboBox<DoorEntity> doorField = new ComboBox<>("Дверь");
|
||||||
|
doorField.setItemLabelGenerator(DoorEntity::getDescription);
|
||||||
|
ComboBox<ClientEntity> clientField = new ComboBox<>("Клиент");
|
||||||
|
clientField.setItemLabelGenerator(ClientEntity::getEmail);
|
||||||
|
Map<String, DoorEntity> doors = doorRepository.findAllByParentDoorIdsIsNull()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(DoorEntity::getId, d -> d));
|
||||||
|
Map<String, ClientEntity> clients = clientRepository.findAll()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(ClientEntity::getId, c -> c));
|
||||||
|
doorField.setItems(doors.values());
|
||||||
|
clientField.setItems(clients.values());
|
||||||
|
|
||||||
|
binder.forField(doorField)
|
||||||
|
.withConverter(DoorEntity::getId, doors::get)
|
||||||
|
.bind("doorId");
|
||||||
|
binder.forField(clientField)
|
||||||
|
.withConverter(ClientEntity::getId, clients::get)
|
||||||
|
.bind("clientId");
|
||||||
|
binder.forField(startTimeField).bind("startTime");
|
||||||
|
binder.forField(endTimeField).bind("endTime");
|
||||||
|
binder.forField(dateField).bind("date");
|
||||||
|
binder.forField(qrCodeField).bind("qrCode");
|
||||||
|
binder.forField(payedField).bind("payed");
|
||||||
|
|
||||||
|
var save = new Button("Сохранить", VaadinIcon.CHECK.create());
|
||||||
|
var cancel = new Button("Отмена");
|
||||||
|
var delete = new Button("Удалить", VaadinIcon.TRASH.create());
|
||||||
|
|
||||||
|
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
save.addClickListener(e -> saveListener.onSave(currentRent));
|
||||||
|
save.addClickShortcut(Key.ENTER);
|
||||||
|
|
||||||
|
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||||
|
delete.addClickListener(e -> deleteListener.onDelete(currentRent));
|
||||||
|
|
||||||
|
cancel.addClickListener(e -> cancelListener.onCancel());
|
||||||
|
|
||||||
|
getContent().add(startTimeField, endTimeField, dateField, doorField, clientField, qrCodeField, payedField, new HorizontalLayout(save, cancel, delete));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package ru.vyatsu.qr_access_admin.rent.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
import org.hibernate.annotations.SourceType;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
|
||||||
|
@Table(name = "rents")
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class RentEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private LocalTime startTime;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private LocalTime endTime;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private LocalDate date;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String doorId;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String qrCode;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private Boolean payed;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@CreationTimestamp(source = SourceType.DB)
|
||||||
|
private LocalDateTime dateCreated;
|
||||||
|
}
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
package ru.vyatsu.qr_access_admin.slot.entity;
|
package ru.vyatsu.qr_access_admin.rent.entity;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface SlotRepository extends JpaRepository<SlotEntity, String> {
|
public interface RentRepository extends JpaRepository<RentEntity, String> {
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package ru.vyatsu.qr_access_admin.rent.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.client.entity.ClientRepository;
|
||||||
|
import ru.vyatsu.qr_access_admin.common.MainLayout;
|
||||||
|
import ru.vyatsu.qr_access_admin.door.entity.DoorRepository;
|
||||||
|
import ru.vyatsu.qr_access_admin.rent.component.RentEditor;
|
||||||
|
import ru.vyatsu.qr_access_admin.rent.entity.RentEntity;
|
||||||
|
import ru.vyatsu.qr_access_admin.rent.entity.RentRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Route(value = "rent", layout = MainLayout.class)
|
||||||
|
@PageTitle("Бронирование")
|
||||||
|
@PermitAll
|
||||||
|
public class RentView extends VerticalLayout {
|
||||||
|
private final RentRepository repository;
|
||||||
|
private final Grid<RentEntity> grid;
|
||||||
|
private final RentEditor editor;
|
||||||
|
|
||||||
|
public RentView(RentRepository repository, DoorRepository doorRepository, ClientRepository clientRepository) {
|
||||||
|
this.repository = repository;
|
||||||
|
|
||||||
|
var addButton = new Button("Добавить бронирование", VaadinIcon.PLUS.create());
|
||||||
|
grid = new Grid<>(RentEntity.class);
|
||||||
|
grid.setColumns("clientId", "date", "startTime", "endTime", "payed", "dateCreated");
|
||||||
|
editor = new RentEditor(doorRepository, clientRepository);
|
||||||
|
|
||||||
|
var actionsLayout = new HorizontalLayout(addButton);
|
||||||
|
add(actionsLayout, grid, editor);
|
||||||
|
|
||||||
|
this.configureEditor();
|
||||||
|
|
||||||
|
addButton.addClickListener(e -> editRent(new RentEntity()));
|
||||||
|
|
||||||
|
grid.setHeight("600px");
|
||||||
|
grid.asSingleSelect().addValueChangeListener(e -> editRent(e.getValue()));
|
||||||
|
|
||||||
|
refreshRentGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshRentGrid() {
|
||||||
|
List<RentEntity> entities = repository.findAll();
|
||||||
|
grid.setItems(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editRent(RentEntity rent) {
|
||||||
|
if (rent == null) {
|
||||||
|
editor.setVisible(false);
|
||||||
|
} else {
|
||||||
|
editor.setVisible(true);
|
||||||
|
editor.setCurrentRent(rent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureEditor() {
|
||||||
|
editor.setVisible(false);
|
||||||
|
|
||||||
|
editor.setSaveListener(rent -> {
|
||||||
|
repository.save(rent);
|
||||||
|
refreshRentGrid();
|
||||||
|
editRent(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.setDeleteListener(rent -> {
|
||||||
|
repository.deleteById(rent.getId());
|
||||||
|
refreshRentGrid();
|
||||||
|
editRent(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.setCancelListener(() -> editRent(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,76 +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 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("Слоты")
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,4 +47,8 @@ public class UnitEntity {
|
|||||||
@Column
|
@Column
|
||||||
@JdbcTypeCode(SqlTypes.JSON)
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
private String tokenSettings;
|
private String tokenSettings;
|
||||||
|
@Column
|
||||||
|
private Boolean adminEditable;
|
||||||
|
@Column
|
||||||
|
private String partnerId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package ru.vyatsu.qr_access_admin.unit.entity;
|
|||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface UnitRepository extends JpaRepository<UnitEntity, String> {
|
public interface UnitRepository extends JpaRepository<UnitEntity, String> {
|
||||||
|
List<UnitEntity> findAllByAdminEditableIsTrue();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-10
@@ -1,10 +1,7 @@
|
|||||||
package ru.vyatsu.qr_access_admin.unit.mapper;
|
package ru.vyatsu.qr_access_admin.unit.mapper;
|
||||||
|
|
||||||
import ru.vyatsu.qr_access_admin.unit.model.UnitComboBoxModel;
|
|
||||||
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.model.UnitComboBoxModel;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class UnitEntityUnitComboBoxModelMapper {
|
public class UnitEntityUnitComboBoxModelMapper {
|
||||||
|
|
||||||
@@ -14,10 +11,4 @@ public class UnitEntityUnitComboBoxModelMapper {
|
|||||||
}
|
}
|
||||||
return new UnitComboBoxModel(entity.getClientId(), entity.getClientName());
|
return new UnitComboBoxModel(entity.getClientId(), entity.getClientName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UnitComboBoxModel> toModels(Collection<UnitEntity> entities) {
|
|
||||||
return entities.stream()
|
|
||||||
.map(this::toModel)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
package ru.vyatsu.qr_access_admin.unit.mapper;
|
package ru.vyatsu.qr_access_admin.unit.mapper;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
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.model.UnitModel;
|
import ru.vyatsu.qr_access_admin.unit.model.UnitModel;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class UnitEntityUnitModelMapper {
|
public class UnitEntityUnitModelMapper {
|
||||||
|
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public static final String DEFAULT_CLIENT_SETTINGS = "{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.client.require-proof-key\":false,\"settings.client.require-authorization-consent\":false}";
|
public static final String DEFAULT_CLIENT_SETTINGS = "{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.client.require-proof-key\":false,\"settings.client.require-authorization-consent\":false}";
|
||||||
private static final String DEFAULT_TOKEN_SETTINGS = """
|
private static final String DEFAULT_TOKEN_SETTINGS = """
|
||||||
{"@class":"java.util.Collections$UnmodifiableMap","settings.token.reuse-refresh-tokens":true,"settings.token.x509-certificate-bound-access-tokens":false,"settings.token.id-token-signature-algorithm":["org.springframework.security.oauth2.jose.jws.SignatureAlgorithm","RS256"],"settings.token.access-token-time-to-live":["java.time.Duration",31540000.000000000],"settings.token.access-token-format":{"@class":"org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat","value":"self-contained"},"settings.token.refresh-token-time-to-live":["java.time.Duration",3600.000000000],"settings.token.authorization-code-time-to-live":["java.time.Duration",300.000000000],"settings.token.device-code-time-to-live":["java.time.Duration",300.000000000]}
|
{"@class":"java.util.Collections$UnmodifiableMap","settings.token.reuse-refresh-tokens":true,"settings.token.x509-certificate-bound-access-tokens":false,"settings.token.id-token-signature-algorithm":["org.springframework.security.oauth2.jose.jws.SignatureAlgorithm","RS256"],"settings.token.access-token-time-to-live":["java.time.Duration",31540000.000000000],"settings.token.access-token-format":{"@class":"org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat","value":"self-contained"},"settings.token.refresh-token-time-to-live":["java.time.Duration",3600.000000000],"settings.token.authorization-code-time-to-live":["java.time.Duration",300.000000000],"settings.token.device-code-time-to-live":["java.time.Duration",300.000000000]}
|
||||||
@@ -19,19 +26,18 @@ public class UnitEntityUnitModelMapper {
|
|||||||
UnitEntity entity = new UnitEntity();
|
UnitEntity entity = new UnitEntity();
|
||||||
entity.setId(model.getId());
|
entity.setId(model.getId());
|
||||||
entity.setClientId(model.getClientId());
|
entity.setClientId(model.getClientId());
|
||||||
// TODO: Приделать PasswordEncoder когда заедет security
|
entity.setClientSecret(passwordEncoder.encode(model.getClientSecret()));
|
||||||
entity.setClientSecret(model.getClientSecret());
|
|
||||||
entity.setClientSecretExpiresAt(model.getClientSecretExpiresAt());
|
entity.setClientSecretExpiresAt(model.getClientSecretExpiresAt());
|
||||||
entity.setClientName(model.getClientName());
|
entity.setClientName(model.getClientName());
|
||||||
entity.setScopes("");
|
entity.setScopes("");
|
||||||
// TODO: Использовать enum, когда заедет oauth2
|
entity.setAuthorizationGrantTypes(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue());
|
||||||
entity.setAuthorizationGrantTypes("client_credentials");
|
entity.setClientAuthenticationMethods(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue());
|
||||||
// TODO: Использовать enum, когда заедет oauth2
|
|
||||||
entity.setClientAuthenticationMethods("client_secret_post");
|
|
||||||
entity.setClientSettings(DEFAULT_CLIENT_SETTINGS);
|
entity.setClientSettings(DEFAULT_CLIENT_SETTINGS);
|
||||||
entity.setTokenSettings(DEFAULT_TOKEN_SETTINGS);
|
entity.setTokenSettings(DEFAULT_TOKEN_SETTINGS);
|
||||||
entity.setRedirectUris("");
|
entity.setRedirectUris("");
|
||||||
entity.setPostLogoutRedirectUris("");
|
entity.setPostLogoutRedirectUris("");
|
||||||
|
entity.setAdminEditable(true);
|
||||||
|
entity.setPartnerId(model.getPartnerId());
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +48,8 @@ public class UnitEntityUnitModelMapper {
|
|||||||
unitModel.setClientSecret(entity.getClientSecret());
|
unitModel.setClientSecret(entity.getClientSecret());
|
||||||
unitModel.setClientName(entity.getClientName());
|
unitModel.setClientName(entity.getClientName());
|
||||||
unitModel.setClientSecretExpiresAt(entity.getClientSecretExpiresAt());
|
unitModel.setClientSecretExpiresAt(entity.getClientSecretExpiresAt());
|
||||||
|
unitModel.setAdminEditable(entity.getAdminEditable());
|
||||||
|
unitModel.setPartnerId(entity.getPartnerId());
|
||||||
return unitModel;
|
return unitModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package ru.vyatsu.qr_access_admin.unit.model;
|
package ru.vyatsu.qr_access_admin.unit.model;
|
||||||
|
|
||||||
import ru.vyatsu.qr_access_admin.common.validation.IsAfter;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import ru.vyatsu.qr_access_admin.common.validation.IsAfter;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@@ -26,4 +26,8 @@ public class UnitModel {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
@Size(max = 255)
|
@Size(max = 255)
|
||||||
private String clientName;
|
private String clientName;
|
||||||
|
|
||||||
|
private boolean adminEditable;
|
||||||
|
|
||||||
|
private String partnerId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
|||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import com.vaadin.flow.router.PageTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
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.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;
|
||||||
@@ -18,6 +20,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Route(value = "units", layout = MainLayout.class)
|
@Route(value = "units", layout = MainLayout.class)
|
||||||
@PageTitle("Устройства")
|
@PageTitle("Устройства")
|
||||||
|
@PermitAll
|
||||||
public class UnitView extends VerticalLayout {
|
public class UnitView extends VerticalLayout {
|
||||||
|
|
||||||
private final UnitRepository unitRepository;
|
private final UnitRepository unitRepository;
|
||||||
@@ -25,13 +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);
|
||||||
editor = new UnitEditor();
|
grid.setColumns("clientId", "clientName", "clientSecretExpiresAt");
|
||||||
|
editor = new UnitEditor(partnerRepository);
|
||||||
|
|
||||||
var actionsLayout = new HorizontalLayout(addButton);
|
var actionsLayout = new HorizontalLayout(addButton);
|
||||||
add(actionsLayout, grid, editor);
|
add(actionsLayout, grid, editor);
|
||||||
@@ -40,14 +44,14 @@ public class UnitView extends VerticalLayout {
|
|||||||
|
|
||||||
addButton.addClickListener(e -> editUnit(new UnitModel()));
|
addButton.addClickListener(e -> editUnit(new UnitModel()));
|
||||||
|
|
||||||
grid.setHeight("200px");
|
grid.setHeight("600px");
|
||||||
grid.asSingleSelect().addValueChangeListener(e -> editUnit(e.getValue()));
|
grid.asSingleSelect().addValueChangeListener(e -> editUnit(e.getValue()));
|
||||||
|
|
||||||
refreshUnitsGrid();
|
refreshUnitsGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshUnitsGrid() {
|
private void refreshUnitsGrid() {
|
||||||
List<UnitEntity> entities = unitRepository.findAll();
|
List<UnitEntity> entities = unitRepository.findAllByAdminEditableIsTrue();
|
||||||
|
|
||||||
grid.setItems(entityModelMapper.mapEntityToModel(entities));
|
grid.setItems(entityModelMapper.mapEntityToModel(entities));
|
||||||
}
|
}
|
||||||
@@ -65,6 +69,7 @@ public class UnitView extends VerticalLayout {
|
|||||||
editor.setVisible(false);
|
editor.setVisible(false);
|
||||||
|
|
||||||
editor.setSaveListener(unit -> {
|
editor.setSaveListener(unit -> {
|
||||||
|
// TODO: Сделать, чтобы секрет клиента при изменении не перехешировался, иначе станет не рабочим
|
||||||
UnitEntity entity = entityModelMapper.mapModelToEntity(unit);
|
UnitEntity entity = entityModelMapper.mapModelToEntity(unit);
|
||||||
unitRepository.save(entity);
|
unitRepository.save(entity);
|
||||||
refreshUnitsGrid();
|
refreshUnitsGrid();
|
||||||
@@ -72,9 +77,11 @@ public class UnitView extends VerticalLayout {
|
|||||||
});
|
});
|
||||||
|
|
||||||
editor.setDeleteListener(unit -> {
|
editor.setDeleteListener(unit -> {
|
||||||
|
if (unit.isAdminEditable()) {
|
||||||
unitRepository.deleteById(unit.getId());
|
unitRepository.deleteById(unit.getId());
|
||||||
refreshUnitsGrid();
|
refreshUnitsGrid();
|
||||||
editUnit(null);
|
editUnit(null);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.setCancelListener(() -> editUnit(null));
|
editor.setCancelListener(() -> editUnit(null));
|
||||||
|
|||||||
@@ -9,6 +9,20 @@ spring:
|
|||||||
password: 123
|
password: 123
|
||||||
security:
|
security:
|
||||||
oauth2:
|
oauth2:
|
||||||
resourceserver:
|
client:
|
||||||
jwt:
|
registration:
|
||||||
jwk-set-uri: http://localhost:8081/oauth2/jwks
|
own:
|
||||||
|
clientId: ${CLIENT_ID:admin}
|
||||||
|
clientSecret: ${CLIENT_SECRET:}
|
||||||
|
authorizationGrantType: authorization_code
|
||||||
|
clientAuthenticationMethod: none
|
||||||
|
scope:
|
||||||
|
- admin
|
||||||
|
- openid
|
||||||
|
redirectUri: "{baseUrl}/login/oauth2/code/{registrationId}"
|
||||||
|
provider:
|
||||||
|
own:
|
||||||
|
issuerUri: http://127.0.0.1:8081
|
||||||
|
jwkSetUri: http://127.0.0.1:8081/oauth2/jwks
|
||||||
|
tokenUri: http://127.0.0.1:8081/oauth2/token
|
||||||
|
authorizationUri: http://127.0.0.1:8081/oauth2/authorize
|
||||||
Reference in New Issue
Block a user