Erweiterungen

This commit is contained in:
2025-08-15 11:21:20 +02:00
parent f864022ebd
commit 37494244d7
8 changed files with 254 additions and 16 deletions

View File

@@ -40,6 +40,9 @@ public class AppUser {
@Field("app_device_id")
private ObjectId appDeviceId;
@Field("owner")
private ObjectId owner;
@Field("erstellt_am")
private LocalDateTime erstelltAm;

View File

@@ -98,9 +98,9 @@ public final class MainLayout extends AppLayout {
userContent.setSpacing(true);
// Create navigation items for the collapsible list
SideNavItem profile = new SideNavItem("Mein Profil", "7", new Icon(VaadinIcon.COG));
SideNavItem profile = new SideNavItem("Mein Profil", "edit-profile", new Icon(VaadinIcon.USER));
SideNavItem myInvoices = new SideNavItem("Meine Rechnungen", "8", new Icon(VaadinIcon.COG));
SideNavItem imprint = new SideNavItem("Impressum", "9", new Icon(VaadinIcon.COG));
SideNavItem imprint = new SideNavItem("Impressum", "impressum", new Icon(VaadinIcon.INFO_CIRCLE));
userContent.add(profile, myInvoices, imprint);
userDetails.add(userContent);

View File

@@ -32,6 +32,7 @@ public class AppUserService {
ObjectId currentUserId = securityService.getCurrentUserId();
appUser.setErstelltVon(currentUserId);
appUser.setAktualisiertVon(currentUserId);
appUser.setOwner(currentUserId);
return appUserRepository.save(appUser);
}

View File

@@ -13,10 +13,7 @@ import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.pages.service.AppUserService;
import de.assecutor.votianlt.model.AppDevice;
import de.assecutor.votianlt.pages.service.AppDeviceService;
import jakarta.annotation.security.RolesAllowed;
import org.springframework.beans.factory.annotation.Autowired;
@PageTitle("App-Nutzer")
@Route(value = "app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@@ -25,12 +22,9 @@ public class AppUserView extends VerticalLayout {
private final AppUserService appUserService;
private final Grid<AppUser> appUserGrid;
private final AppDeviceService appDeviceService;
@Autowired
public AppUserView(AppUserService appUserService, AppDeviceService appDeviceService) {
public AppUserView(AppUserService appUserService) {
this.appUserService = appUserService;
this.appDeviceService = appDeviceService;
setSizeFull();
setPadding(true);
@@ -63,13 +57,7 @@ public class AppUserView extends VerticalLayout {
appUserGrid.addColumn(AppUser::getTelefon).setHeader("Telefon").setAutoWidth(true);
appUserGrid.addColumn(AppUser::getAppCode).setHeader("App-Code").setAutoWidth(true);
appUserGrid.addColumn(AppUser::getEmail).setHeader("E-Mail").setAutoWidth(true);
appUserGrid.addColumn(appUser -> {
if (appUser.getAppDeviceId() != null) {
AppDevice device = appDeviceService.findById(appUser.getAppDeviceId());
return device != null ? device.getName() : "Unbekannt";
}
return "Nicht zugeordnet";
}).setHeader("Gerät").setAutoWidth(true);
appUserGrid.addColumn(AppUser::getGeraet).setHeader("Gerät").setAutoWidth(true);
// Make grid rows clickable
appUserGrid.setSelectionMode(Grid.SelectionMode.SINGLE);

View File

@@ -0,0 +1,170 @@
package de.assecutor.votianlt.pages.view;
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.formlayout.FormLayout;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.RolesAllowed;
@PageTitle("Profil bearbeiten")
@Route(value = "edit-profile", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@RolesAllowed({"USER","ADMIN"})
public class EditProfileView extends HorizontalLayout {
public EditProfileView() {
setSizeFull();
setPadding(true);
setSpacing(true);
setJustifyContentMode(JustifyContentMode.CENTER);
setAlignItems(Alignment.START);
// Linke Spalte: Formular
VerticalLayout formColumn = new VerticalLayout();
formColumn.setWidth("500px");
formColumn.setPadding(false);
formColumn.setSpacing(false);
FormLayout form = new FormLayout();
form.setWidthFull();
form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
// Firmenfelder
TextField companyField = new TextField("Firma*");
TextField companyAddField = new TextField("Firmenzusatz");
TextField firstnameField = new TextField("Vorname*");
TextField lastnameField = new TextField("Nachname*");
TextField phoneField = new TextField("Telefonnummer*");
TextField faxField = new TextField("Telefon (Fax)");
TextField mobileField = new TextField("Telefon (Mobil)");
EmailField emailField = new EmailField("E-Mail-Adresse (Login)*");
TextField streetField = new TextField("Straße*");
TextField houseNumberField = new TextField("Hausnr*");
TextField addressAddField = new TextField("Adresszusatz");
TextField zipField = new TextField("Postleitzahl*");
TextField cityField = new TextField("Stadt*");
// Pflichtfeldhinweis
Paragraph pflichtHinweis = new Paragraph("Die mit (*) gekennzeichneten Felder sind Pflichtfelder.");
pflichtHinweis.getStyle().set("color", "#d32f2f").set("fontWeight", "bold");
// Abweichende Rechnungsadresse
Checkbox diffInvoiceAddress = new Checkbox("Abweichende Rechnungsadresse");
diffInvoiceAddress.getStyle().set("marginTop", "1em");
// Rechnungsadresse Felder (disabled by default)
TextField invCompanyField = new TextField("Firma*");
TextField invCompanyAddField = new TextField("Firmenzusatz");
TextField invFirstnameField = new TextField("Vorname*");
TextField invLastnameField = new TextField("Nachname*");
TextField invStreetField = new TextField("Straße*");
TextField invHouseNumberField = new TextField("Hausnr*");
TextField invAddressAddField = new TextField("Adresszusatz");
TextField invZipField = new TextField("Postleitzahl*");
TextField invCityField = new TextField("Stadt*");
invCompanyField.setEnabled(false);
invCompanyAddField.setEnabled(false);
invFirstnameField.setEnabled(false);
invLastnameField.setEnabled(false);
invStreetField.setEnabled(false);
invHouseNumberField.setEnabled(false);
invAddressAddField.setEnabled(false);
invZipField.setEnabled(false);
invCityField.setEnabled(false);
diffInvoiceAddress.addValueChangeListener(e -> {
boolean enabled = e.getValue();
invCompanyField.setEnabled(enabled);
invCompanyAddField.setEnabled(enabled);
invFirstnameField.setEnabled(enabled);
invLastnameField.setEnabled(enabled);
invStreetField.setEnabled(enabled);
invHouseNumberField.setEnabled(enabled);
invAddressAddField.setEnabled(enabled);
invZipField.setEnabled(enabled);
invCityField.setEnabled(enabled);
});
// Formularfelder hinzufügen
form.add(companyField, 2);
form.add(companyAddField, 2);
form.add(firstnameField, lastnameField);
form.add(phoneField, faxField);
form.add(mobileField, 2);
form.add(emailField, 2);
form.add(streetField, houseNumberField);
form.add(addressAddField, 2);
form.add(zipField, cityField);
form.add(diffInvoiceAddress, 2);
form.add(invCompanyField, 2);
form.add(invCompanyAddField, 2);
form.add(invFirstnameField, invLastnameField);
form.add(invStreetField, invHouseNumberField);
form.add(invAddressAddField, 2);
form.add(invZipField, invCityField);
formColumn.add(form, pflichtHinweis);
// Schalter
Checkbox locateAppUser = new Checkbox("App-Nutzer orten");
Checkbox digitalProcess = new Checkbox("Digitale Abwicklung");
Checkbox invoiceViaVotianlt = new Checkbox("Rechnungslegung über votianlt");
formColumn.add(locateAppUser, digitalProcess, invoiceViaVotianlt);
// Rechte Spalte: Karte und Aktionen
VerticalLayout rightColumn = new VerticalLayout();
rightColumn.setWidth("100%");
rightColumn.setAlignItems(Alignment.CENTER);
rightColumn.setSpacing(true);
rightColumn.setPadding(false);
// Koordinatenanzeige
Label coordsLabel = new Label("53°36'25.1\"N 10°06'46.9\"E");
coordsLabel.getStyle().set("fontWeight", "bold").set("marginTop", "1em");
rightColumn.add(coordsLabel);
// Google Maps Platzhalter (iFrame)
Div mapDiv = new Div();
mapDiv.setWidth("400px");
mapDiv.setHeight("400px");
mapDiv.getElement().setProperty("innerHTML",
"<iframe width='100%' height='100%' frameborder='0' style='border:0' " +
"src='https://www.google.com/maps/embed/v1/place?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&q=53.6070,10.1125' allowfullscreen></iframe>");
rightColumn.add(mapDiv);
// 2-Faktor Auth
Checkbox twoFactor = new Checkbox("2-Faktor-Authentifizierung");
Icon twoFactorInfo = VaadinIcon.QUESTION_CIRCLE_O.create();
twoFactorInfo.getStyle().set("marginLeft", "0.3em");
HorizontalLayout twoFactorLayout = new HorizontalLayout(twoFactor, twoFactorInfo);
twoFactorLayout.setAlignItems(Alignment.CENTER);
rightColumn.add(twoFactorLayout);
// Passwort ändern Button
Button changePassword = new Button("Passwort ändern");
changePassword.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
rightColumn.add(changePassword);
// Benutzerkonto löschen Button
Button deleteAccount = new Button("Benutzerkonto löschen");
deleteAccount.addThemeVariants(ButtonVariant.LUMO_ERROR);
rightColumn.add(deleteAccount);
// Profil speichern Button (unten rechts)
Button saveProfile = new Button("Profiländerungen speichern");
saveProfile.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
saveProfile.getStyle().set("position", "absolute").set("right", "2em").set("bottom", "2em");
add(formColumn, rightColumn, saveProfile);
}
}

View File

@@ -0,0 +1,30 @@
package de.assecutor.votianlt.pages.view;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll;
@PageTitle("Impressum")
@Route(value = "impressum", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@PermitAll
public class ImprintView extends VerticalLayout {
public ImprintView() {
setSizeFull();
setPadding(true);
setSpacing(true);
setAlignItems(Alignment.CENTER);
H2 title = new H2("Impressum");
add(title);
Paragraph p1 = new Paragraph("Max Mustermann\nMusterstraße 1\n12345 Musterstadt\nDeutschland");
Paragraph p2 = new Paragraph("Telefon: +49 123 456789\nE-Mail: info@example.com");
Paragraph p3 = new Paragraph("Umsatzsteuer-ID: DE123456789\nHandelsregister: Amtsgericht Musterstadt, HRB 12345");
Paragraph p4 = new Paragraph("Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV: Max Mustermann");
add(p1, p2, p3, p4);
}
}

View File

@@ -0,0 +1,41 @@
package de.assecutor.votianlt.util;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.PasswordAuthentication;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import java.util.Properties;
public class MailUtil {
public static void sendMail(String to, String subject, String body) throws MessagingException {
// SMTP-Konfiguration (hier Beispiel für Gmail, anpassen für Produktivsystem!)
final String username = "your-email@gmail.com"; // TODO: ersetzen
final String password = "your-password"; // TODO: ersetzen
final String host = "smtp.gmail.com";
final int port = 587;
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", host);
props.put("mail.smtp.port", String.valueOf(port));
Session session = Session.getInstance(props, new jakarta.mail.Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(username));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
message.setText(body);
Transport.send(message);
}
}