Erweiterungen
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package de.assecutor.votianlt.config;
|
||||
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.server.ServiceInitEvent;
|
||||
import com.vaadin.flow.server.VaadinServiceInitListener;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import de.assecutor.votianlt.security.CustomUserPrincipal;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Sets the user's preferred locale on the UI BEFORE any layout or view is
|
||||
* constructed. Registered via {@code UIInitListener} → {@code BeforeEnterListener},
|
||||
* which fires prior to the router creating the layout component tree.
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
||||
|
||||
@Override
|
||||
public void serviceInit(ServiceInitEvent event) {
|
||||
event.getSource().addUIInitListener(uiInitEvent -> {
|
||||
UI ui = uiInitEvent.getUI();
|
||||
ui.addBeforeEnterListener(beforeEnterEvent -> applyLocaleFromCurrentUser(ui));
|
||||
});
|
||||
}
|
||||
|
||||
private void applyLocaleFromCurrentUser(UI ui) {
|
||||
try {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (auth == null || !auth.isAuthenticated() || auth instanceof AnonymousAuthenticationToken) {
|
||||
return;
|
||||
}
|
||||
if (!(auth.getPrincipal() instanceof CustomUserPrincipal cup)) {
|
||||
return;
|
||||
}
|
||||
Language language = cup.getUser().getLanguage();
|
||||
if (language == null) {
|
||||
return;
|
||||
}
|
||||
Locale targetLocale = getLocaleFromLanguage(language);
|
||||
if (!targetLocale.equals(ui.getLocale())) {
|
||||
ui.setLocale(targetLocale);
|
||||
log.debug("Locale set to {} for user {}", targetLocale, cup.getUsername());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not apply locale from user preferences: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Locale getLocaleFromLanguage(Language language) {
|
||||
return switch (language) {
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> Locale.of("es", "ES");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.assecutor.votianlt.config;
|
||||
|
||||
import com.vaadin.flow.i18n.I18NProvider;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Component
|
||||
public class TranslationProvider implements I18NProvider {
|
||||
|
||||
public static final String BUNDLE_PREFIX = "messages";
|
||||
|
||||
@Override
|
||||
public List<Locale> getProvidedLocales() {
|
||||
return Collections.unmodifiableList(Arrays.asList(
|
||||
Locale.GERMAN,
|
||||
Locale.ENGLISH,
|
||||
Locale.FRENCH,
|
||||
Locale.of("es", "ES")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTranslation(String key, Locale locale, Object... params) {
|
||||
if (key == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_PREFIX, locale);
|
||||
String value = bundle.getString(key);
|
||||
|
||||
if (params.length > 0) {
|
||||
value = String.format(value, params);
|
||||
}
|
||||
|
||||
return value;
|
||||
} catch (Exception e) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public String getTranslation(String key, Language language) {
|
||||
Locale locale = switch (language) {
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> Locale.of("es", "ES");
|
||||
};
|
||||
return getTranslation(key, locale);
|
||||
}
|
||||
}
|
||||
27
src/main/java/de/assecutor/votianlt/model/Language.java
Normal file
27
src/main/java/de/assecutor/votianlt/model/Language.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package de.assecutor.votianlt.model;
|
||||
|
||||
public enum Language {
|
||||
DE("Deutsch"),
|
||||
EN("English"),
|
||||
FR("Français"),
|
||||
ES("Español");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
Language(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public static Language fromString(String text) {
|
||||
for (Language language : Language.values()) {
|
||||
if (language.name().equalsIgnoreCase(text)) {
|
||||
return language;
|
||||
}
|
||||
}
|
||||
return DE; // Default to German
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import lombok.Data;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -63,4 +64,8 @@ public class User {
|
||||
|
||||
// 2-Faktor-Authentifizierung (standardmäßig aktiviert für neue Nutzer)
|
||||
private boolean twoFactorEnabled = true;
|
||||
}
|
||||
|
||||
// Spracheinstellung (standardmäßig Deutsch)
|
||||
@Field("language")
|
||||
private Language language = Language.DE;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class BarcodeTask extends BaseTask {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Barcode";
|
||||
return TaskType.BARCODE.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,7 +28,7 @@ public class CommentTask extends BaseTask {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Kommentar";
|
||||
return TaskType.COMMENT.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ConfirmationTask extends BaseTask {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Bestätigung";
|
||||
return TaskType.CONFIRMATION.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,7 +28,7 @@ public class PhotoTask extends BaseTask {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Foto";
|
||||
return TaskType.PHOTO.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,7 +16,7 @@ public class SignatureTask extends BaseTask {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Unterschrift";
|
||||
return TaskType.SIGNATURE.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,20 +1,39 @@
|
||||
package de.assecutor.votianlt.model.task;
|
||||
|
||||
import com.vaadin.flow.component.UI;
|
||||
|
||||
public enum TaskType {
|
||||
CONFIRMATION("Bestätigung"),
|
||||
SIGNATURE("Unterschrift"),
|
||||
TODOLIST("To-Do Liste"),
|
||||
PHOTO("Foto"),
|
||||
BARCODE("Barcode"),
|
||||
COMMENT("Kommentar");
|
||||
CONFIRMATION("CONFIRMATION"),
|
||||
SIGNATURE("SIGNATURE"),
|
||||
TODOLIST("TODOLIST"),
|
||||
PHOTO("PHOTO"),
|
||||
BARCODE("BARCODE"),
|
||||
COMMENT("COMMENT");
|
||||
|
||||
private final String displayName;
|
||||
private final String translationKey;
|
||||
|
||||
TaskType(String displayName) {
|
||||
this.displayName = displayName;
|
||||
TaskType(String translationKey) {
|
||||
this.translationKey = translationKey;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
// Fallback to German if UI not available
|
||||
try {
|
||||
if (UI.getCurrent() != null) {
|
||||
return UI.getCurrent().getTranslation("tasktype." + translationKey);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Fallback to German if translation fails
|
||||
}
|
||||
|
||||
// Fallback to German translations
|
||||
return switch (this) {
|
||||
case CONFIRMATION -> "Bestätigung";
|
||||
case SIGNATURE -> "Unterschrift";
|
||||
case TODOLIST -> "To-Do Liste";
|
||||
case PHOTO -> "Foto";
|
||||
case BARCODE -> "Barcode";
|
||||
case COMMENT -> "Kommentar";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class TodoListTask extends BaseTask {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "To-Do Liste";
|
||||
return TaskType.TODOLIST.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,7 +28,10 @@ import de.assecutor.votianlt.model.UserInvoiceData;
|
||||
import de.assecutor.votianlt.pages.service.AppUserService;
|
||||
import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
|
||||
import de.assecutor.votianlt.pages.view.EditProfileView;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import de.assecutor.votianlt.config.TranslationProvider;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.service.LanguageService;
|
||||
import de.assecutor.votianlt.service.MessageBadgeUpdateService;
|
||||
import de.assecutor.votianlt.service.MessageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -36,6 +39,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@AnonymousAllowed
|
||||
@Slf4j
|
||||
@@ -47,6 +51,7 @@ public final class MainLayout extends AppLayout {
|
||||
private final MessageService messageService;
|
||||
private final MessageBadgeUpdateService messageBadgeUpdateService;
|
||||
private final AppUserService appUserService;
|
||||
private final LanguageService languageService;
|
||||
private Div headerRef;
|
||||
private Scroller navRef;
|
||||
private Component userMenuRef;
|
||||
@@ -56,12 +61,13 @@ public final class MainLayout extends AppLayout {
|
||||
|
||||
public MainLayout(SecurityService securityService, UserInvoiceDataService userInvoiceDataService,
|
||||
MessageService messageService, MessageBadgeUpdateService messageBadgeUpdateService,
|
||||
AppUserService appUserService) {
|
||||
AppUserService appUserService, LanguageService languageService) {
|
||||
this.securityService = securityService;
|
||||
this.userInvoiceDataService = userInvoiceDataService;
|
||||
this.messageService = messageService;
|
||||
this.messageBadgeUpdateService = messageBadgeUpdateService;
|
||||
this.appUserService = appUserService;
|
||||
this.languageService = languageService;
|
||||
setPrimarySection(Section.DRAWER);
|
||||
|
||||
// Always build the drawer; keep references and toggle visibility on attach and
|
||||
@@ -114,7 +120,7 @@ public final class MainLayout extends AppLayout {
|
||||
|
||||
// Create Details component for "Verwaltung" with collapsible list
|
||||
Details verwaltungDetails = new Details();
|
||||
verwaltungDetails.setSummaryText("Verwaltung");
|
||||
verwaltungDetails.setSummaryText(getTranslation("nav.management"));
|
||||
verwaltungDetails.addClassNames(Margin.Horizontal.MEDIUM, FontSize.MEDIUM, FontWeight.MEDIUM, "#000000");
|
||||
|
||||
// Create collapsible content with navigation items
|
||||
@@ -123,16 +129,16 @@ public final class MainLayout extends AppLayout {
|
||||
verwaltungContent.setSpacing(true);
|
||||
|
||||
// Create navigation items for the collapsible list
|
||||
SideNavItem jobs = new SideNavItem("Aufträge", "jobs", new Icon(VaadinIcon.LIST));
|
||||
SideNavItem customers = new SideNavItem("Kunden", "customers", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem appUsers = new SideNavItem("App-Nutzer", "app-user", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem statistics = new SideNavItem("Statistiken", "statistics", new Icon(VaadinIcon.BAR_CHART));
|
||||
SideNavItem jobs = new SideNavItem(getTranslation("nav.jobs"), "jobs", new Icon(VaadinIcon.LIST));
|
||||
SideNavItem customers = new SideNavItem(getTranslation("nav.customers"), "customers", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem appUsers = new SideNavItem(getTranslation("nav.appusers"), "app-user", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem statistics = new SideNavItem(getTranslation("nav.statistics"), "statistics", new Icon(VaadinIcon.BAR_CHART));
|
||||
|
||||
verwaltungContent.add(jobs, customers, appUsers);
|
||||
verwaltungContent.add(jobs, customers, appUsers, statistics);
|
||||
|
||||
// Only show invoices menu if billing is enabled for the current user
|
||||
if (isBillingEnabledForCurrentUser()) {
|
||||
SideNavItem invoices = new SideNavItem("Rechnungen", "invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||
SideNavItem invoices = new SideNavItem(getTranslation("nav.invoices"), "invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||
verwaltungContent.add(invoices);
|
||||
}
|
||||
|
||||
@@ -141,7 +147,7 @@ public final class MainLayout extends AppLayout {
|
||||
|
||||
// Create Details component for "Verwaltung" with collapsible list
|
||||
Details userDetails = new Details();
|
||||
userDetails.setSummaryText("Benutzer");
|
||||
userDetails.setSummaryText(getTranslation("nav.users"));
|
||||
userDetails.addClassNames(Margin.Horizontal.MEDIUM, FontSize.MEDIUM, FontWeight.MEDIUM, TextColor.BODY);
|
||||
|
||||
// Create collapsible content with navigation items
|
||||
@@ -150,9 +156,9 @@ public final class MainLayout extends AppLayout {
|
||||
userContent.setSpacing(true);
|
||||
|
||||
// Create navigation items for the collapsible list
|
||||
SideNavItem profile = new SideNavItem("Mein Profil", "edit-profile", new Icon(VaadinIcon.USER));
|
||||
SideNavItem myInvoices = new SideNavItem("Meine Rechnungen", "my-invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||
SideNavItem imprint = new SideNavItem("Impressum", "impressum", new Icon(VaadinIcon.INFO_CIRCLE));
|
||||
SideNavItem profile = new SideNavItem(getTranslation("nav.profile"), "edit-profile", new Icon(VaadinIcon.USER));
|
||||
SideNavItem myInvoices = new SideNavItem(getTranslation("nav.myinvoices"), "my-invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||
SideNavItem imprint = new SideNavItem(getTranslation("nav.imprint"), "impressum", new Icon(VaadinIcon.INFO_CIRCLE));
|
||||
|
||||
userContent.add(profile, myInvoices, imprint);
|
||||
userDetails.add(userContent);
|
||||
@@ -261,9 +267,9 @@ public final class MainLayout extends AppLayout {
|
||||
userMenuItem.add(userNameSpan);
|
||||
|
||||
// Profil anzeigen mit Navigation
|
||||
userMenuItem.getSubMenu().addItem("Profil anzeigen", e -> UI.getCurrent().navigate(EditProfileView.class));
|
||||
userMenuItem.getSubMenu().addItem("Einstellungen");
|
||||
userMenuItem.getSubMenu().addItem("Abmelden", e -> securityService.logout());
|
||||
userMenuItem.getSubMenu().addItem(getTranslation("nav.showprofile"), e -> UI.getCurrent().navigate(EditProfileView.class));
|
||||
userMenuItem.getSubMenu().addItem(getTranslation("nav.settings"));
|
||||
userMenuItem.getSubMenu().addItem(getTranslation("nav.logout"), e -> securityService.logout());
|
||||
|
||||
// Update-Funktion für Benutzername und Avatar
|
||||
Runnable updateUserInfo = () -> {
|
||||
@@ -298,6 +304,9 @@ public final class MainLayout extends AppLayout {
|
||||
super.onAttach(attachEvent);
|
||||
UI ui = attachEvent.getUI();
|
||||
|
||||
// Apply user's preferred language immediately after login
|
||||
applyUserLanguagePreference();
|
||||
|
||||
// Update badge immediately when layout is attached
|
||||
updateMessagesBadge();
|
||||
|
||||
@@ -309,6 +318,42 @@ public final class MainLayout extends AppLayout {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the user's preferred language if it differs from the current UI locale.
|
||||
* The primary locale setup happens in {@code LocaleVaadinInitListener} before the
|
||||
* layout is constructed. This method handles edge cases such as language changes
|
||||
* within an active session.
|
||||
*/
|
||||
private void applyUserLanguagePreference() {
|
||||
try {
|
||||
if (securityService.isUserLoggedIn()) {
|
||||
User currentUser = securityService.getCurrentDatabaseUser();
|
||||
if (currentUser != null && currentUser.getLanguage() != null) {
|
||||
UI ui = UI.getCurrent();
|
||||
if (ui != null) {
|
||||
Locale targetLocale = getLocaleFromLanguage(currentUser.getLanguage());
|
||||
if (!targetLocale.equals(ui.getLocale())) {
|
||||
ui.setLocale(targetLocale);
|
||||
log.info("Applied locale {} for user {}", targetLocale, currentUser.getEmail());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error applying user language preference: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Locale getLocaleFromLanguage(Language language) {
|
||||
return switch (language) {
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> new Locale("es", "ES");
|
||||
default -> Locale.GERMAN;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach(DetachEvent detachEvent) {
|
||||
if (badgeUpdateRegistration != null) {
|
||||
@@ -317,4 +362,4 @@ public final class MainLayout extends AppLayout {
|
||||
}
|
||||
super.onDetach(detachEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,28 +14,27 @@ import com.vaadin.flow.component.textfield.PasswordField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.data.binder.Binder;
|
||||
import com.vaadin.flow.data.binder.ValidationException;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.AppUser;
|
||||
import de.assecutor.votianlt.pages.service.AppUserService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@PageTitle("Neuen App-Nutzer anlegen")
|
||||
@Route(value = "add-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed({ "USER", "ADMIN" })
|
||||
public class AddAppUserView extends VerticalLayout {
|
||||
public class AddAppUserView extends VerticalLayout implements HasDynamicTitle {
|
||||
private final AppUserService appUserService;
|
||||
private final Binder<AppUser> binder = new Binder<>(AppUser.class);
|
||||
|
||||
// Form fields
|
||||
private final TextField designationField = new TextField("Bezeichnung (HH H 000)");
|
||||
private final TextField firstnameField = new TextField("Vorname");
|
||||
private final TextField lastnameField = new TextField("Nachname");
|
||||
private final TextField phoneField = new TextField("Telefon (Mobil)");
|
||||
private final TextField emailField = new TextField("E-Mail-Adresse");
|
||||
private final PasswordField passwordField = new PasswordField("Passwort");
|
||||
private final PasswordField confirmPasswordField = new PasswordField("Passwort wiederholen");
|
||||
// Form fields - labels set in constructor
|
||||
private final TextField designationField = new TextField();
|
||||
private final TextField firstnameField = new TextField();
|
||||
private final TextField lastnameField = new TextField();
|
||||
private final TextField phoneField = new TextField();
|
||||
private final TextField emailField = new TextField();
|
||||
private final PasswordField passwordField = new PasswordField();
|
||||
private final PasswordField confirmPasswordField = new PasswordField();
|
||||
|
||||
@Autowired
|
||||
public AddAppUserView(AppUserService appUserService) {
|
||||
@@ -44,6 +43,15 @@ public class AddAppUserView extends VerticalLayout {
|
||||
setPadding(true);
|
||||
setSpacing(true);
|
||||
|
||||
// Set field labels via i18n
|
||||
designationField.setLabel(getTranslation("addappuser.designation"));
|
||||
firstnameField.setLabel(getTranslation("profile.firstname"));
|
||||
lastnameField.setLabel(getTranslation("profile.lastname"));
|
||||
phoneField.setLabel(getTranslation("addappuser.phone"));
|
||||
emailField.setLabel(getTranslation("profile.email"));
|
||||
passwordField.setLabel(getTranslation("addappuser.password"));
|
||||
confirmPasswordField.setLabel(getTranslation("addappuser.password.confirm"));
|
||||
|
||||
// Center content vertically
|
||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
@@ -63,10 +71,10 @@ public class AddAppUserView extends VerticalLayout {
|
||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
header.setSpacing(true);
|
||||
|
||||
H2 title = new H2("Neuen App-Nutzer anlegen");
|
||||
H2 title = new H2(getTranslation("addappuser.title"));
|
||||
title.getStyle().set("margin", "0");
|
||||
|
||||
Button backButton = new Button("Zurück", new Icon(VaadinIcon.ARROW_LEFT));
|
||||
Button backButton = new Button(getTranslation("button.back"), new Icon(VaadinIcon.ARROW_LEFT));
|
||||
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
backButton.addClickListener(e -> navigateBack());
|
||||
|
||||
@@ -82,15 +90,15 @@ public class AddAppUserView extends VerticalLayout {
|
||||
designationField.setPlaceholder("(HH H 000)");
|
||||
designationField.setWidthFull();
|
||||
designationField.setRequiredIndicatorVisible(true);
|
||||
designationField.addBlurListener(e -> validateField(designationField, "Kennung ist ein Pflichtfeld"));
|
||||
designationField.addBlurListener(e -> validateField(designationField, getTranslation("addappuser.validation.designation")));
|
||||
|
||||
firstnameField.setWidthFull();
|
||||
firstnameField.setRequiredIndicatorVisible(true);
|
||||
firstnameField.addBlurListener(e -> validateField(firstnameField, "Vorname ist ein Pflichtfeld"));
|
||||
firstnameField.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
||||
|
||||
lastnameField.setWidthFull();
|
||||
lastnameField.setRequiredIndicatorVisible(true);
|
||||
lastnameField.addBlurListener(e -> validateField(lastnameField, "Nachname ist ein Pflichtfeld"));
|
||||
lastnameField.addBlurListener(e -> validateField(lastnameField, getTranslation("profile.validation.lastname")));
|
||||
|
||||
// Create horizontal layout for firstname and lastname
|
||||
HorizontalLayout nameLayout = new HorizontalLayout();
|
||||
@@ -100,7 +108,7 @@ public class AddAppUserView extends VerticalLayout {
|
||||
|
||||
phoneField.setWidthFull();
|
||||
phoneField.setRequiredIndicatorVisible(true);
|
||||
phoneField.addBlurListener(e -> validateField(phoneField, "Telefonnummer ist ein Pflichtfeld"));
|
||||
phoneField.addBlurListener(e -> validateField(phoneField, getTranslation("addappuser.validation.phone")));
|
||||
|
||||
emailField.setWidthFull();
|
||||
emailField.setRequiredIndicatorVisible(true);
|
||||
@@ -132,7 +140,7 @@ public class AddAppUserView extends VerticalLayout {
|
||||
contentContainer.add(formLayout);
|
||||
|
||||
// Submit button
|
||||
Button submitButton = new Button("App-Nutzer anlegen", e -> createAppUser());
|
||||
Button submitButton = new Button(getTranslation("addappuser.button.submit"), e -> createAppUser());
|
||||
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
submitButton.setWidthFull();
|
||||
contentContainer.add(submitButton);
|
||||
@@ -154,13 +162,13 @@ public class AddAppUserView extends VerticalLayout {
|
||||
binder.forField(lastnameField).bind(AppUser::getNachname, AppUser::setNachname);
|
||||
binder.forField(phoneField).bind(AppUser::getTelefon, AppUser::setTelefon);
|
||||
binder.forField(emailField).bind(AppUser::getEmail, AppUser::setEmail);
|
||||
binder.forField(passwordField).asRequired("Passwort ist erforderlich").bind(AppUser::getPassword,
|
||||
binder.forField(passwordField).asRequired(getTranslation("addappuser.validation.password.required")).bind(AppUser::getPassword,
|
||||
AppUser::setPassword);
|
||||
|
||||
// Confirm password field validation
|
||||
binder.forField(confirmPasswordField).asRequired("Passwort wiederholen ist erforderlich")
|
||||
binder.forField(confirmPasswordField).asRequired(getTranslation("addappuser.validation.password.confirm"))
|
||||
.withValidator(confirmPassword -> confirmPassword.equals(passwordField.getValue()),
|
||||
"Passwörter stimmen nicht überein")
|
||||
getTranslation("addappuser.validation.password.mismatch"))
|
||||
.bind(appUser -> "", // Dummy getter - this field is not stored
|
||||
(appUser, value) -> {
|
||||
} // Dummy setter - this field is not stored
|
||||
@@ -170,7 +178,7 @@ public class AddAppUserView extends VerticalLayout {
|
||||
private void createAppUser() {
|
||||
// Validate all fields first
|
||||
if (!validateAllFields()) {
|
||||
Notification.show("Bitte füllen Sie alle Pflichtfelder korrekt aus", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addappuser.notification.validation"), 3000, Notification.Position.MIDDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,26 +191,26 @@ public class AddAppUserView extends VerticalLayout {
|
||||
appUserService.createAppUser(newAppUser);
|
||||
|
||||
// Show success message
|
||||
Notification.show("App-Nutzer erfolgreich angelegt", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addappuser.notification.success"), 3000, Notification.Position.MIDDLE);
|
||||
|
||||
// Navigate back to app user list
|
||||
navigateBack();
|
||||
|
||||
} catch (ValidationException e) {
|
||||
Notification.show("Bitte überprüfen Sie die Eingaben", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addappuser.notification.check"), 3000, Notification.Position.MIDDLE);
|
||||
} catch (org.springframework.dao.DuplicateKeyException e) {
|
||||
// Handle duplicate email error
|
||||
if (e.getMessage().contains("email")) {
|
||||
Notification.show("Diese E-Mail-Adresse ist bereits vergeben", 5000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addappuser.notification.email.duplicate"), 5000, Notification.Position.MIDDLE);
|
||||
emailField.focus();
|
||||
emailField.setInvalid(true);
|
||||
emailField.setErrorMessage("E-Mail-Adresse bereits vorhanden");
|
||||
emailField.setErrorMessage(getTranslation("addappuser.notification.email.duplicate"));
|
||||
} else {
|
||||
Notification.show("Ein Fehler ist aufgetreten: Doppelter Wert gefunden", 5000,
|
||||
Notification.show(getTranslation("addappuser.notification.check"), 5000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Notification.show("Fehler beim Anlegen des App-Nutzers: " + e.getMessage(), 5000,
|
||||
Notification.show(getTranslation("addappuser.notification.error", e.getMessage()), 5000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
@@ -230,10 +238,10 @@ public class AddAppUserView extends VerticalLayout {
|
||||
String value = emailField.getValue();
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
emailField.setInvalid(true);
|
||||
emailField.setErrorMessage("E-Mail-Adresse ist ein Pflichtfeld");
|
||||
emailField.setErrorMessage(getTranslation("addappuser.validation.email.required"));
|
||||
} else if (!value.contains("@") || !value.contains(".")) {
|
||||
emailField.setInvalid(true);
|
||||
emailField.setErrorMessage("Bitte geben Sie eine gültige E-Mail-Adresse ein");
|
||||
emailField.setErrorMessage(getTranslation("addappuser.validation.email.invalid"));
|
||||
} else {
|
||||
emailField.setInvalid(false);
|
||||
emailField.setErrorMessage("");
|
||||
@@ -244,10 +252,10 @@ public class AddAppUserView extends VerticalLayout {
|
||||
String value = passwordField.getValue();
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
passwordField.setInvalid(true);
|
||||
passwordField.setErrorMessage("Passwort ist ein Pflichtfeld");
|
||||
passwordField.setErrorMessage(getTranslation("addappuser.validation.password.required"));
|
||||
} else if (value.length() < 6) {
|
||||
passwordField.setInvalid(true);
|
||||
passwordField.setErrorMessage("Passwort muss mindestens 6 Zeichen lang sein");
|
||||
passwordField.setErrorMessage(getTranslation("addappuser.validation.password.min"));
|
||||
} else {
|
||||
passwordField.setInvalid(false);
|
||||
passwordField.setErrorMessage("");
|
||||
@@ -259,10 +267,10 @@ public class AddAppUserView extends VerticalLayout {
|
||||
String confirmPassword = confirmPasswordField.getValue();
|
||||
if (confirmPassword == null || confirmPassword.trim().isEmpty()) {
|
||||
confirmPasswordField.setInvalid(true);
|
||||
confirmPasswordField.setErrorMessage("Bitte bestätigen Sie das Passwort");
|
||||
confirmPasswordField.setErrorMessage(getTranslation("addappuser.validation.password.confirm"));
|
||||
} else if (!confirmPassword.equals(password)) {
|
||||
confirmPasswordField.setInvalid(true);
|
||||
confirmPasswordField.setErrorMessage("Passwörter stimmen nicht überein");
|
||||
confirmPasswordField.setErrorMessage(getTranslation("addappuser.validation.password.mismatch"));
|
||||
} else {
|
||||
confirmPasswordField.setInvalid(false);
|
||||
confirmPasswordField.setErrorMessage("");
|
||||
@@ -270,10 +278,10 @@ public class AddAppUserView extends VerticalLayout {
|
||||
}
|
||||
|
||||
private boolean validateAllFields() {
|
||||
validateField(designationField, "Kennung ist ein Pflichtfeld");
|
||||
validateField(firstnameField, "Vorname ist ein Pflichtfeld");
|
||||
validateField(lastnameField, "Nachname ist ein Pflichtfeld");
|
||||
validateField(phoneField, "Telefonnummer ist ein Pflichtfeld");
|
||||
validateField(designationField, getTranslation("addappuser.validation.designation"));
|
||||
validateField(firstnameField, getTranslation("profile.validation.firstname"));
|
||||
validateField(lastnameField, getTranslation("profile.validation.lastname"));
|
||||
validateField(phoneField, getTranslation("addappuser.validation.phone"));
|
||||
validateEmailField();
|
||||
validatePasswordField();
|
||||
validateConfirmPasswordField();
|
||||
@@ -282,4 +290,9 @@ public class AddAppUserView extends VerticalLayout {
|
||||
&& !phoneField.isInvalid() && !emailField.isInvalid() && !passwordField.isInvalid()
|
||||
&& !confirmPasswordField.isInvalid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.appuser.create");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.data.binder.Binder;
|
||||
import com.vaadin.flow.data.binder.ValidationException;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.votianlt.model.Company;
|
||||
@@ -19,11 +19,10 @@ import jakarta.annotation.security.RolesAllowed;
|
||||
import java.time.Clock;
|
||||
|
||||
@Route(value = "add_company", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Neuen Firma anlegen")
|
||||
// @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neue Firma
|
||||
// anlegen")
|
||||
@RolesAllowed("USER")
|
||||
public class AddCompanyView extends Main {
|
||||
public class AddCompanyView extends Main implements HasDynamicTitle {
|
||||
private final AddCompanyService addCompanyService;
|
||||
|
||||
TextField companyName;
|
||||
@@ -44,24 +43,24 @@ public class AddCompanyView extends Main {
|
||||
public AddCompanyView(AddCompanyService addCompanyService, Clock clock) {
|
||||
this.addCompanyService = addCompanyService;
|
||||
|
||||
companyName = new TextField("Firmenname");
|
||||
companyName = new TextField(getTranslation("profile.company"));
|
||||
companyName.setRequiredIndicatorVisible(true);
|
||||
binder.forField(companyName).asRequired("Firmenname ist ein Pflichtfeld") // Pflichtfeldmeldung
|
||||
binder.forField(companyName).asRequired(getTranslation("profile.validation.company.required"))
|
||||
.bind(Company::getName, Company::setName);
|
||||
|
||||
firstName = new TextField("Vorname");
|
||||
lastName = new TextField("Nachname");
|
||||
telephone = new TextField("Telefonnummer");
|
||||
fax = new TextField("Faxnummer");
|
||||
mail = new TextField("E-Mail-Adresse");
|
||||
street = new TextField("Straße");
|
||||
houseNumber = new TextField("Hausnummer");
|
||||
addressAddition = new TextField("Adresszusatz");
|
||||
zip = new TextField("Postleitzahl");
|
||||
city = new TextField("Stadt");
|
||||
firstName = new TextField(getTranslation("profile.firstname"));
|
||||
lastName = new TextField(getTranslation("profile.lastname"));
|
||||
telephone = new TextField(getTranslation("profile.phone"));
|
||||
fax = new TextField(getTranslation("profile.fax"));
|
||||
mail = new TextField(getTranslation("profile.email"));
|
||||
street = new TextField(getTranslation("profile.street"));
|
||||
houseNumber = new TextField(getTranslation("profile.housenr"));
|
||||
addressAddition = new TextField(getTranslation("profile.addressadd"));
|
||||
zip = new TextField(getTranslation("profile.zip"));
|
||||
city = new TextField(getTranslation("profile.city"));
|
||||
|
||||
// Setze den Button als primär
|
||||
submitButton = new Button("Kunden anlegen", event -> submit());
|
||||
submitButton = new Button(getTranslation("addcompany.button.submit"), event -> submit());
|
||||
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
// Erstelle ein Div als Container (oder direkt ein Layout)
|
||||
@@ -80,7 +79,7 @@ public class AddCompanyView extends Main {
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||
|
||||
add(new ViewToolbar("Neuen Kunden anlegen"));
|
||||
add(new ViewToolbar(getTranslation("addcompany.title")));
|
||||
add(formLayout);
|
||||
}
|
||||
|
||||
@@ -97,4 +96,9 @@ public class AddCompanyView extends Main {
|
||||
System.err.println("Validierungsfehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.company.create");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.data.binder.Binder;
|
||||
import com.vaadin.flow.data.binder.ValidationException;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.votianlt.model.Customer;
|
||||
@@ -21,11 +21,10 @@ import jakarta.annotation.security.RolesAllowed;
|
||||
import java.time.Clock;
|
||||
|
||||
@Route(value = "add-customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Neuen Kunden anlegen")
|
||||
// @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neuen Kunden
|
||||
// anlegen")
|
||||
@RolesAllowed("USER")
|
||||
public class AddCustomerView extends Main {
|
||||
public class AddCustomerView extends Main implements HasDynamicTitle {
|
||||
private final AddCustomerService addCustomerService;
|
||||
|
||||
TextField companyName;
|
||||
@@ -48,66 +47,66 @@ public class AddCustomerView extends Main {
|
||||
this.addCustomerService = todoService;
|
||||
|
||||
// Firma (Pflichtfeld)
|
||||
companyName = new TextField("Firma");
|
||||
companyName = new TextField(getTranslation("profile.company"));
|
||||
companyName.setRequiredIndicatorVisible(true);
|
||||
companyName.setWidthFull();
|
||||
companyName.addBlurListener(e -> validateField(companyName));
|
||||
|
||||
// Anrede (Dropdown)
|
||||
title = new ComboBox<>("Anrede");
|
||||
title.setItems("Herr", "Frau", "Divers");
|
||||
title.setPlaceholder("Anrede");
|
||||
title = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||
title.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
||||
title.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
||||
title.setWidthFull();
|
||||
|
||||
// Vorname (Pflichtfeld)
|
||||
firstName = new TextField("Vorname");
|
||||
firstName = new TextField(getTranslation("profile.firstname"));
|
||||
firstName.setRequiredIndicatorVisible(true);
|
||||
firstName.setWidthFull();
|
||||
firstName.addBlurListener(e -> validateField(firstName));
|
||||
|
||||
// Nachname (Pflichtfeld)
|
||||
lastName = new TextField("Nachname");
|
||||
lastName = new TextField(getTranslation("profile.lastname"));
|
||||
lastName.setRequiredIndicatorVisible(true);
|
||||
lastName.setWidthFull();
|
||||
lastName.addBlurListener(e -> validateField(lastName));
|
||||
|
||||
// Telefonnummer (Pflichtfeld)
|
||||
telephone = new TextField("Telefonnummer");
|
||||
telephone = new TextField(getTranslation("profile.phone"));
|
||||
telephone.setRequiredIndicatorVisible(true);
|
||||
telephone.setWidthFull();
|
||||
telephone.addBlurListener(e -> validateField(telephone));
|
||||
|
||||
// Fax (optional)
|
||||
fax = new TextField("Fax");
|
||||
fax = new TextField(getTranslation("profile.fax"));
|
||||
fax.setWidthFull();
|
||||
|
||||
// E-Mail (Pflichtfeld)
|
||||
mail = new TextField("E-Mail-Adresse");
|
||||
mail = new TextField(getTranslation("profile.email"));
|
||||
mail.setRequiredIndicatorVisible(true);
|
||||
mail.setWidthFull();
|
||||
mail.addBlurListener(e -> validateEmail());
|
||||
|
||||
// Straße (Pflichtfeld)
|
||||
street = new TextField("Straße");
|
||||
street = new TextField(getTranslation("profile.street"));
|
||||
street.setRequiredIndicatorVisible(true);
|
||||
street.addBlurListener(e -> validateField(street));
|
||||
|
||||
// Hausnummer (Pflichtfeld)
|
||||
houseNumber = new TextField("Hausnr.");
|
||||
houseNumber = new TextField(getTranslation("profile.housenr"));
|
||||
houseNumber.setRequiredIndicatorVisible(true);
|
||||
houseNumber.addBlurListener(e -> validateField(houseNumber));
|
||||
|
||||
// Adresszusatz (optional)
|
||||
addressAddition = new TextField("Adresszusatz");
|
||||
addressAddition = new TextField(getTranslation("profile.addressadd"));
|
||||
addressAddition.setWidthFull();
|
||||
|
||||
// PLZ (Pflichtfeld)
|
||||
zip = new TextField("Postleitzahl");
|
||||
zip = new TextField(getTranslation("profile.zip"));
|
||||
zip.setRequiredIndicatorVisible(true);
|
||||
zip.addBlurListener(e -> validateField(zip));
|
||||
|
||||
// Ort (Pflichtfeld)
|
||||
city = new TextField("Ort");
|
||||
city = new TextField(getTranslation("profile.city"));
|
||||
city.setRequiredIndicatorVisible(true);
|
||||
city.addBlurListener(e -> validateField(city));
|
||||
|
||||
@@ -118,7 +117,7 @@ public class AddCustomerView extends Main {
|
||||
setTestData();
|
||||
|
||||
// Setze den Button als primär
|
||||
submitButton = new Button("Kunden anlegen", event -> submit());
|
||||
submitButton = new Button(getTranslation("addcustomer.button.submit"), event -> submit());
|
||||
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
submitButton.setWidthFull();
|
||||
|
||||
@@ -156,41 +155,41 @@ public class AddCustomerView extends Main {
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||
|
||||
add(new ViewToolbar("Neuen Kunden anlegen"));
|
||||
add(new ViewToolbar(getTranslation("addcustomer.title")));
|
||||
add(container);
|
||||
}
|
||||
|
||||
private void configureBinder() {
|
||||
binder.forField(companyName).asRequired("Firma ist ein Pflichtfeld").bind(Customer::getCompanyName,
|
||||
binder.forField(companyName).asRequired(getTranslation("profile.validation.company.required")).bind(Customer::getCompanyName,
|
||||
Customer::setCompanyName);
|
||||
|
||||
binder.forField(title).bind(Customer::getTitle, Customer::setTitle);
|
||||
|
||||
binder.forField(firstName).asRequired("Vorname ist ein Pflichtfeld").bind(Customer::getFirstname,
|
||||
binder.forField(firstName).asRequired(getTranslation("profile.validation.firstname.required")).bind(Customer::getFirstname,
|
||||
Customer::setFirstname);
|
||||
|
||||
binder.forField(lastName).asRequired("Nachname ist ein Pflichtfeld").bind(Customer::getLastName,
|
||||
binder.forField(lastName).asRequired(getTranslation("profile.validation.lastname.required")).bind(Customer::getLastName,
|
||||
Customer::setLastName);
|
||||
|
||||
binder.forField(telephone).asRequired("Telefonnummer ist ein Pflichtfeld").bind(Customer::getTelephone,
|
||||
binder.forField(telephone).asRequired(getTranslation("profile.validation.phone")).bind(Customer::getTelephone,
|
||||
Customer::setTelephone);
|
||||
|
||||
binder.forField(fax).bind(Customer::getFax, Customer::setFax);
|
||||
|
||||
binder.forField(mail).asRequired("E-Mail-Adresse ist ein Pflichtfeld")
|
||||
.withValidator(email -> email.contains("@"), "Bitte geben Sie eine gültige E-Mail-Adresse ein")
|
||||
binder.forField(mail).asRequired(getTranslation("profile.validation.email.required"))
|
||||
.withValidator(email -> email.contains("@"), getTranslation("profile.validation.email.invalid"))
|
||||
.bind(Customer::getMail, Customer::setMail);
|
||||
|
||||
binder.forField(street).asRequired("Straße ist ein Pflichtfeld").bind(Customer::getStreet, Customer::setStreet);
|
||||
binder.forField(street).asRequired(getTranslation("profile.validation.street.required")).bind(Customer::getStreet, Customer::setStreet);
|
||||
|
||||
binder.forField(houseNumber).asRequired("Hausnummer ist ein Pflichtfeld").bind(Customer::getHouseNumber,
|
||||
binder.forField(houseNumber).asRequired(getTranslation("profile.validation.housenr.required")).bind(Customer::getHouseNumber,
|
||||
Customer::setHouseNumber);
|
||||
|
||||
binder.forField(addressAddition).bind(Customer::getAddressAddition, Customer::setAddressAddition);
|
||||
|
||||
binder.forField(zip).asRequired("Postleitzahl ist ein Pflichtfeld").bind(Customer::getZip, Customer::setZip);
|
||||
binder.forField(zip).asRequired(getTranslation("profile.validation.zip.required")).bind(Customer::getZip, Customer::setZip);
|
||||
|
||||
binder.forField(city).asRequired("Ort ist ein Pflichtfeld").bind(Customer::getCity, Customer::setCity);
|
||||
binder.forField(city).asRequired(getTranslation("profile.validation.city.required")).bind(Customer::getCity, Customer::setCity);
|
||||
}
|
||||
|
||||
private void setTestData() {
|
||||
@@ -202,7 +201,7 @@ public class AddCustomerView extends Main {
|
||||
boolean isValid = validateAllFields();
|
||||
|
||||
if (!isValid) {
|
||||
com.vaadin.flow.component.notification.Notification.show("Bitte füllen Sie alle Pflichtfelder aus", 3000,
|
||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.validation"), 3000,
|
||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||
return;
|
||||
}
|
||||
@@ -213,17 +212,16 @@ public class AddCustomerView extends Main {
|
||||
|
||||
addCustomerService.addCustomer(customer);
|
||||
|
||||
// Erfolg anzeigen und zur Kundenliste navigieren
|
||||
com.vaadin.flow.component.notification.Notification.show("Kunde erfolgreich angelegt", 3000,
|
||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.success"), 3000,
|
||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||
|
||||
getUI().ifPresent(ui -> ui.navigate("customers"));
|
||||
|
||||
} catch (ValidationException e) {
|
||||
com.vaadin.flow.component.notification.Notification.show("Bitte überprüfen Sie Ihre Eingaben", 3000,
|
||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.check"), 3000,
|
||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||
} catch (Exception e) {
|
||||
com.vaadin.flow.component.notification.Notification.show("Fehler beim Speichern: " + e.getMessage(), 5000,
|
||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.error", e.getMessage()), 5000,
|
||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||
}
|
||||
}
|
||||
@@ -232,7 +230,7 @@ public class AddCustomerView extends Main {
|
||||
String value = field.getValue();
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
field.setInvalid(true);
|
||||
field.setErrorMessage("Dieses Feld ist ein Pflichtfeld");
|
||||
field.setErrorMessage(getTranslation("addcustomer.validation.required"));
|
||||
} else {
|
||||
field.setInvalid(false);
|
||||
field.setErrorMessage("");
|
||||
@@ -243,10 +241,10 @@ public class AddCustomerView extends Main {
|
||||
String value = mail.getValue();
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
mail.setInvalid(true);
|
||||
mail.setErrorMessage("E-Mail-Adresse ist ein Pflichtfeld");
|
||||
mail.setErrorMessage(getTranslation("profile.email.required"));
|
||||
} else if (!value.contains("@")) {
|
||||
mail.setInvalid(true);
|
||||
mail.setErrorMessage("Bitte geben Sie eine gültige E-Mail-Adresse ein");
|
||||
mail.setErrorMessage(getTranslation("profile.validation.email.invalid"));
|
||||
} else {
|
||||
mail.setInvalid(false);
|
||||
mail.setErrorMessage("");
|
||||
@@ -268,4 +266,9 @@ public class AddCustomerView extends Main {
|
||||
&& !mail.isInvalid() && !street.isInvalid() && !houseNumber.isInvalid() && !zip.isInvalid()
|
||||
&& !city.isInvalid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.customer.create");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.textfield.TextArea;
|
||||
import com.vaadin.flow.component.tabs.TabSheet;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -69,11 +69,15 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Route(value = "add_job", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Neuen Auftrag anlegen")
|
||||
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Auftragserstellung")
|
||||
@RolesAllowed("USER")
|
||||
@Slf4j
|
||||
public class AddJobView extends Main {
|
||||
public class AddJobView extends Main implements HasDynamicTitle {
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.job.create");
|
||||
}
|
||||
|
||||
private final AddJobService addJobService;
|
||||
private final CustomerService customerService;
|
||||
@@ -204,8 +208,8 @@ public class AddJobView extends Main {
|
||||
|
||||
private void initializeComponents() {
|
||||
// Customer selection
|
||||
customerSelection = new ComboBox<>("Auftraggeber/Rechnungsempfänger");
|
||||
customerSelection.setPlaceholder("Wählen Sie einen Auftraggeber aus...");
|
||||
customerSelection = new ComboBox<>(getTranslation("addjob.customer.label"));
|
||||
customerSelection.setPlaceholder(getTranslation("addjob.customer.placeholder"));
|
||||
customerSelection.setWidthFull();
|
||||
customerSelection.setRequiredIndicatorVisible(true);
|
||||
// Mit Kunden des angemeldeten Benutzers befüllen und Mapping aufbauen
|
||||
@@ -219,7 +223,7 @@ public class AddJobView extends Main {
|
||||
: ((c.getFirstname() != null ? c.getFirstname() : "") + " "
|
||||
+ (c.getLastName() != null ? c.getLastName() : "")).trim();
|
||||
if (label.isBlank()) {
|
||||
label = "Unbenannter Kunde";
|
||||
label = getTranslation("addjob.customer.unnamed");
|
||||
}
|
||||
// Bei Duplikaten Label einzigartig machen
|
||||
String uniqueLabel = label;
|
||||
@@ -308,78 +312,78 @@ public class AddJobView extends Main {
|
||||
}
|
||||
});
|
||||
|
||||
preloadAddressButton = new Button("Vorbelegte Adressfelder leeren");
|
||||
preloadAddressButton = new Button(getTranslation("addjob.button.clearfields"));
|
||||
preloadAddressButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
preloadAddressButton.addClickListener(event -> clearAllFields());
|
||||
|
||||
// Pickup address
|
||||
pickupCompany = new ComboBox<>("Firma");
|
||||
pickupCompany.setPlaceholder("Firmenname");
|
||||
pickupCompany = new ComboBox<>(getTranslation("profile.company"));
|
||||
pickupCompany.setPlaceholder(getTranslation("addjob.address.company.placeholder"));
|
||||
pickupCompany.setAllowCustomValue(true);
|
||||
setupCompanyAutocomplete(pickupCompany, true); // true für Pickup
|
||||
pickupSalutation = new ComboBox<>("Anrede");
|
||||
pickupSalutation.setItems("Herr", "Frau", "Divers");
|
||||
pickupSalutation.setPlaceholder("Anrede wählen...");
|
||||
pickupFirstName = new TextField("Vorname");
|
||||
pickupFirstName.setPlaceholder("Vorname");
|
||||
pickupSalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||
pickupSalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
||||
pickupSalutation.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
||||
pickupFirstName = new TextField(getTranslation("profile.firstname"));
|
||||
pickupFirstName.setPlaceholder(getTranslation("profile.firstname"));
|
||||
pickupFirstName.setRequiredIndicatorVisible(true);
|
||||
pickupLastName = new TextField("Nachname");
|
||||
pickupLastName.setPlaceholder("Nachname");
|
||||
pickupLastName = new TextField(getTranslation("profile.lastname"));
|
||||
pickupLastName.setPlaceholder(getTranslation("profile.lastname"));
|
||||
pickupLastName.setRequiredIndicatorVisible(true);
|
||||
pickupPhone = new TextField("Telefonnummer");
|
||||
pickupPhone.setPlaceholder("Telefonnummer");
|
||||
pickupStreet = new TextField("Straße");
|
||||
pickupStreet.setPlaceholder("Musterstraße");
|
||||
pickupPhone = new TextField(getTranslation("profile.phone"));
|
||||
pickupPhone.setPlaceholder(getTranslation("profile.phone"));
|
||||
pickupStreet = new TextField(getTranslation("profile.street"));
|
||||
pickupStreet.setPlaceholder(getTranslation("addjob.address.street.placeholder"));
|
||||
pickupStreet.setRequiredIndicatorVisible(true);
|
||||
pickupHouseNumber = new TextField("Hausnummer");
|
||||
pickupHouseNumber.setPlaceholder("Hausnummer");
|
||||
pickupHouseNumber = new TextField(getTranslation("addjob.address.housenumber"));
|
||||
pickupHouseNumber.setPlaceholder(getTranslation("addjob.address.housenumber"));
|
||||
pickupHouseNumber.setRequiredIndicatorVisible(true);
|
||||
pickupAddressAddition = new TextField("Adresszusatz");
|
||||
pickupAddressAddition.setPlaceholder("2. OG, Hinterhaus...");
|
||||
pickupZip = new TextField("Postleitzahl");
|
||||
pickupZip.setPlaceholder("Postleitzahl");
|
||||
pickupAddressAddition = new TextField(getTranslation("profile.addressadd"));
|
||||
pickupAddressAddition.setPlaceholder(getTranslation("addjob.address.addition.placeholder"));
|
||||
pickupZip = new TextField(getTranslation("profile.zip"));
|
||||
pickupZip.setPlaceholder(getTranslation("profile.zip"));
|
||||
pickupZip.setRequiredIndicatorVisible(true);
|
||||
pickupCity = new TextField("Ort");
|
||||
pickupCity.setPlaceholder("Hamburg");
|
||||
pickupCity = new TextField(getTranslation("addjob.address.city"));
|
||||
pickupCity.setPlaceholder(getTranslation("addjob.address.city.placeholder.pickup"));
|
||||
pickupCity.setRequiredIndicatorVisible(true);
|
||||
savePickupAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern.");
|
||||
savePickupAddress = new Checkbox(getTranslation("addjob.address.save"));
|
||||
savePickupAddress.setValue(true);
|
||||
|
||||
// Delivery address
|
||||
deliveryCompany = new ComboBox<>("Firma");
|
||||
deliveryCompany.setPlaceholder("Firmenname");
|
||||
deliveryCompany = new ComboBox<>(getTranslation("profile.company"));
|
||||
deliveryCompany.setPlaceholder(getTranslation("addjob.address.company.placeholder"));
|
||||
deliveryCompany.setAllowCustomValue(true);
|
||||
setupCompanyAutocomplete(deliveryCompany, false); // false für Delivery
|
||||
deliverySalutation = new ComboBox<>("Anrede");
|
||||
deliverySalutation.setItems("Herr", "Frau", "Divers");
|
||||
deliverySalutation.setPlaceholder("Anrede wählen...");
|
||||
deliveryFirstName = new TextField("Vorname");
|
||||
deliveryFirstName.setPlaceholder("Vorname");
|
||||
deliverySalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||
deliverySalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
||||
deliverySalutation.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
||||
deliveryFirstName = new TextField(getTranslation("profile.firstname"));
|
||||
deliveryFirstName.setPlaceholder(getTranslation("profile.firstname"));
|
||||
deliveryFirstName.setRequiredIndicatorVisible(true);
|
||||
deliveryLastName = new TextField("Nachname");
|
||||
deliveryLastName.setPlaceholder("Nachname");
|
||||
deliveryLastName = new TextField(getTranslation("profile.lastname"));
|
||||
deliveryLastName.setPlaceholder(getTranslation("profile.lastname"));
|
||||
deliveryLastName.setRequiredIndicatorVisible(true);
|
||||
deliveryPhone = new TextField("Telefonnummer");
|
||||
deliveryPhone.setPlaceholder("Telefonnummer");
|
||||
deliveryStreet = new TextField("Straße");
|
||||
deliveryStreet.setPlaceholder("Beispielweg");
|
||||
deliveryPhone = new TextField(getTranslation("profile.phone"));
|
||||
deliveryPhone.setPlaceholder(getTranslation("profile.phone"));
|
||||
deliveryStreet = new TextField(getTranslation("profile.street"));
|
||||
deliveryStreet.setPlaceholder(getTranslation("addjob.address.delivery.street.placeholder"));
|
||||
deliveryStreet.setRequiredIndicatorVisible(true);
|
||||
deliveryHouseNumber = new TextField("Hausnr");
|
||||
deliveryHouseNumber.setPlaceholder("Hausnummer");
|
||||
deliveryHouseNumber = new TextField(getTranslation("profile.housenr"));
|
||||
deliveryHouseNumber.setPlaceholder(getTranslation("addjob.address.housenumber"));
|
||||
deliveryHouseNumber.setRequiredIndicatorVisible(true);
|
||||
deliveryAddressAddition = new TextField("Adresszusatz");
|
||||
deliveryAddressAddition.setPlaceholder("Erdgeschoss, links...");
|
||||
deliveryZip = new TextField("Postleitzahl");
|
||||
deliveryZip.setPlaceholder("Postleitzahl");
|
||||
deliveryAddressAddition = new TextField(getTranslation("profile.addressadd"));
|
||||
deliveryAddressAddition.setPlaceholder(getTranslation("addjob.address.delivery.addition.placeholder"));
|
||||
deliveryZip = new TextField(getTranslation("profile.zip"));
|
||||
deliveryZip.setPlaceholder(getTranslation("profile.zip"));
|
||||
deliveryZip.setRequiredIndicatorVisible(true);
|
||||
deliveryCity = new TextField("Ort");
|
||||
deliveryCity.setPlaceholder("Berlin");
|
||||
deliveryCity = new TextField(getTranslation("addjob.address.city"));
|
||||
deliveryCity.setPlaceholder(getTranslation("addjob.address.city.placeholder.delivery"));
|
||||
deliveryCity.setRequiredIndicatorVisible(true);
|
||||
saveDeliveryAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern.");
|
||||
saveDeliveryAddress = new Checkbox(getTranslation("addjob.address.save"));
|
||||
saveDeliveryAddress.setValue(true);
|
||||
|
||||
// Digital processing - set value based on user's profile setting
|
||||
digitalProcessing = new Checkbox("Digitale Abwicklung per App");
|
||||
digitalProcessing = new Checkbox(getTranslation("profile.settings.digitalprocess"));
|
||||
// Get current user's digital processing preference from profile
|
||||
try {
|
||||
User currentUser = securityService.getCurrentDatabaseUser();
|
||||
@@ -387,18 +391,18 @@ public class AddJobView extends Main {
|
||||
} catch (Exception e) {
|
||||
digitalProcessing.setValue(true); // Default to true if user not found
|
||||
}
|
||||
appUser = new ComboBox<>("App-Nutzer");
|
||||
appUser = new ComboBox<>(getTranslation("addjob.appuser.label"));
|
||||
|
||||
// Load app users for current user and set up the ComboBox
|
||||
availableAppUsers = appUserService.findByCurrentUser();
|
||||
appUser.setItems(availableAppUsers);
|
||||
appUser.setItemLabelGenerator(
|
||||
user -> user.getVorname() + " " + user.getNachname() + " (" + user.getEmail() + ")");
|
||||
appUser.setPlaceholder("App-Nutzer auswählen...");
|
||||
appUser.setPlaceholder(getTranslation("addjob.appuser.placeholder"));
|
||||
|
||||
// Services grid will be initialized in createPriceAndSubmitTab()
|
||||
// Date picker fields for appointments
|
||||
pickupDate = new DatePicker("Datum");
|
||||
pickupDate = new DatePicker(getTranslation("addjob.appointment.date"));
|
||||
pickupDate.setRequiredIndicatorVisible(true);
|
||||
pickupDate.setMin(LocalDate.now());
|
||||
pickupDate.setLocale(java.util.Locale.GERMANY); // Monday as first day of week
|
||||
@@ -409,7 +413,7 @@ public class AddJobView extends Main {
|
||||
"Freitag", "Samstag"))
|
||||
.setWeekdaysShort(java.util.Arrays.asList("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")));
|
||||
|
||||
deliveryDate = new DatePicker("Datum");
|
||||
deliveryDate = new DatePicker(getTranslation("addjob.appointment.date"));
|
||||
deliveryDate.setRequiredIndicatorVisible(true);
|
||||
deliveryDate.setMin(LocalDate.now());
|
||||
deliveryDate.setLocale(java.util.Locale.GERMANY); // Monday as first day of week
|
||||
@@ -421,7 +425,7 @@ public class AddJobView extends Main {
|
||||
.setWeekdaysShort(java.util.Arrays.asList("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")));
|
||||
|
||||
// Submit button - initially disabled until all required fields are valid
|
||||
submitButton = new Button("Auftrag anlegen", event -> submit());
|
||||
submitButton = new Button(getTranslation("addjob.button.submit"), event -> submit());
|
||||
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
submitButton.setEnabled(false);
|
||||
|
||||
@@ -436,7 +440,7 @@ public class AddJobView extends Main {
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||
|
||||
H2 title = new H2("Neuen Auftrag anlegen");
|
||||
H2 title = new H2(getTranslation("addjob.title"));
|
||||
add(title);
|
||||
|
||||
// Create TabSheet for organizing the form
|
||||
@@ -445,16 +449,16 @@ public class AddJobView extends Main {
|
||||
tabSheet.setSizeFull();
|
||||
|
||||
// Tab 1: Customer & Addresses
|
||||
addressesTab = tabSheet.add("Auftraggeber & Adressen", createCustomerAndAddressesTab());
|
||||
addressesTab = tabSheet.add(getTranslation("addjob.tab.addresses"), createCustomerAndAddressesTab());
|
||||
|
||||
// Tab 2: Appointments & Processing
|
||||
appointmentsTab = tabSheet.add("Termine & Verarbeitung", createAppointmentsAndProcessingTab());
|
||||
appointmentsTab = tabSheet.add(getTranslation("addjob.tab.appointments"), createAppointmentsAndProcessingTab());
|
||||
|
||||
// Tab 3: Cargo
|
||||
cargoTab = tabSheet.add("Ladung", createCargoTab());
|
||||
cargoTab = tabSheet.add(getTranslation("addjob.tab.cargo"), createCargoTab());
|
||||
|
||||
// Tab 4: Tasks
|
||||
tasksTab = tabSheet.add("Aufgaben", createTasksTab());
|
||||
tasksTab = tabSheet.add(getTranslation("addjob.tab.tasks"), createTasksTab());
|
||||
|
||||
// Disable tasks tab initially if digital processing is off
|
||||
if (!Boolean.TRUE.equals(digitalProcessing.getValue())) {
|
||||
@@ -466,7 +470,7 @@ public class AddJobView extends Main {
|
||||
}
|
||||
|
||||
// Tab 5: Price & Submit
|
||||
priceTab = tabSheet.add("Leistungen und Preis", createPriceAndSubmitTab());
|
||||
priceTab = tabSheet.add(getTranslation("addjob.tab.price"), createPriceAndSubmitTab());
|
||||
|
||||
// Tab-Wechsel-Listener für Adressvalidierung
|
||||
tabSheet.addSelectedChangeListener(this::onTabChange);
|
||||
@@ -549,9 +553,9 @@ public class AddJobView extends Main {
|
||||
content.add(digitalRow, appUser);
|
||||
|
||||
// Appointment (Pickup)
|
||||
H3 pickupApptTitle = new H3("Termin (Abholung)");
|
||||
H3 pickupApptTitle = new H3(getTranslation("addjob.appointment.pickup"));
|
||||
pickupApptTitle.getStyle().set("margin", "0");
|
||||
pickupTime = new TimePicker("Uhrzeit");
|
||||
pickupTime = new TimePicker(getTranslation("addjob.appointment.time"));
|
||||
pickupTime.setLocale(java.util.Locale.GERMANY);
|
||||
HorizontalLayout pickupApptRow = new HorizontalLayout(pickupDate, pickupTime);
|
||||
pickupApptRow.setWidthFull();
|
||||
@@ -561,9 +565,9 @@ public class AddJobView extends Main {
|
||||
content.add(pickupApptTitle, pickupApptRow);
|
||||
|
||||
// Appointment (Delivery)
|
||||
H3 deliveryApptTitle = new H3("Termin (Lieferung)");
|
||||
H3 deliveryApptTitle = new H3(getTranslation("addjob.appointment.delivery"));
|
||||
deliveryApptTitle.getStyle().set("margin", "0");
|
||||
deliveryTime = new TimePicker("Uhrzeit");
|
||||
deliveryTime = new TimePicker(getTranslation("addjob.appointment.time"));
|
||||
deliveryTime.setLocale(java.util.Locale.GERMANY);
|
||||
HorizontalLayout deliveryApptRow = new HorizontalLayout(deliveryDate, deliveryTime);
|
||||
deliveryApptRow.setWidthFull();
|
||||
@@ -625,7 +629,7 @@ public class AddJobView extends Main {
|
||||
routeInfoBox.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
|
||||
routeInfoBox.setVisible(false); // Initial versteckt
|
||||
|
||||
H3 routeTitle = new H3("Streckeninformation");
|
||||
H3 routeTitle = new H3(getTranslation("addjob.route.title"));
|
||||
routeTitle.getStyle().set("margin", "0");
|
||||
routeTitle.getStyle().set("color", "var(--lumo-primary-text-color)");
|
||||
|
||||
@@ -634,7 +638,7 @@ public class AddJobView extends Main {
|
||||
routeRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||
routeRow.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
|
||||
Span distanceLabel = new Span("Entfernung:");
|
||||
Span distanceLabel = new Span(getTranslation("addjob.route.distance") + ":");
|
||||
routeDistanceLabel = new Span("-");
|
||||
routeDistanceLabel.getStyle().set("font-weight", "bold");
|
||||
routeDistanceLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
||||
@@ -647,7 +651,7 @@ public class AddJobView extends Main {
|
||||
durationRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||
durationRow.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
|
||||
Span durationLabel = new Span("Fahrtzeit:");
|
||||
Span durationLabel = new Span(getTranslation("addjob.route.duration") + ":");
|
||||
routeDurationLabel = new Span("-");
|
||||
routeDurationLabel.getStyle().set("font-weight", "bold");
|
||||
routeDurationLabel.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
@@ -668,7 +672,7 @@ public class AddJobView extends Main {
|
||||
manualRouteInputBox.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
|
||||
manualRouteInputBox.setVisible(true); // Initial sichtbar
|
||||
|
||||
H3 manualRouteTitle = new H3("Streckeninformation (manuelle Eingabe)");
|
||||
H3 manualRouteTitle = new H3(getTranslation("addjob.route.manual.title"));
|
||||
manualRouteTitle.getStyle().set("margin", "0");
|
||||
manualRouteTitle.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
|
||||
@@ -676,9 +680,9 @@ public class AddJobView extends Main {
|
||||
manualInputRow.setWidthFull();
|
||||
manualInputRow.setSpacing(true);
|
||||
|
||||
manualDistanceInput = new com.vaadin.flow.component.textfield.NumberField("Entfernung (km)");
|
||||
manualDistanceInput = new com.vaadin.flow.component.textfield.NumberField(getTranslation("addjob.route.distance.km"));
|
||||
manualDistanceInput.setWidthFull();
|
||||
manualDistanceInput.setPlaceholder("z.B. 125,5");
|
||||
manualDistanceInput.setPlaceholder(getTranslation("addjob.route.distance.placeholder"));
|
||||
manualDistanceInput.setMin(0);
|
||||
manualDistanceInput.setStep(0.1);
|
||||
manualDistanceInput.setClearButtonVisible(true);
|
||||
@@ -690,16 +694,16 @@ public class AddJobView extends Main {
|
||||
}
|
||||
});
|
||||
|
||||
manualDurationInput = new com.vaadin.flow.component.textfield.IntegerField("Fahrtzeit (Minuten)");
|
||||
manualDurationInput = new com.vaadin.flow.component.textfield.IntegerField(getTranslation("addjob.route.duration.min"));
|
||||
manualDurationInput.setWidthFull();
|
||||
manualDurationInput.setPlaceholder("z.B. 90");
|
||||
manualDurationInput.setPlaceholder(getTranslation("addjob.route.duration.placeholder"));
|
||||
manualDurationInput.setMin(0);
|
||||
manualDurationInput.setStep(1);
|
||||
manualDurationInput.setClearButtonVisible(true);
|
||||
|
||||
manualInputRow.add(manualDistanceInput, manualDurationInput);
|
||||
|
||||
Span manualRouteHint = new Span("Bitte geben Sie die Entfernung und Fahrtzeit ein, da keine automatische Routenberechnung durchgeführt wurde.");
|
||||
Span manualRouteHint = new Span(getTranslation("addjob.route.manual.hint"));
|
||||
manualRouteHint.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
manualRouteHint.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
manualRouteHint.getStyle().set("font-style", "italic");
|
||||
@@ -708,7 +712,7 @@ public class AddJobView extends Main {
|
||||
content.add(manualRouteInputBox);
|
||||
|
||||
// Title
|
||||
H3 servicesTitle = new H3("Leistungen");
|
||||
H3 servicesTitle = new H3(getTranslation("addjob.services.title"));
|
||||
servicesTitle.getStyle().set("margin", "0");
|
||||
content.add(servicesTitle);
|
||||
|
||||
@@ -718,17 +722,17 @@ public class AddJobView extends Main {
|
||||
servicesGrid.setHeight("250px");
|
||||
servicesGrid.setItems(selectedServices);
|
||||
|
||||
servicesGrid.addColumn(Service::getName).setHeader("Leistung").setSortable(true);
|
||||
servicesGrid.addColumn(Service::getName).setHeader(getTranslation("common.service")).setSortable(true);
|
||||
servicesGrid.addColumn(service -> {
|
||||
if (service.getCalculationBasis() != null) {
|
||||
return switch (service.getCalculationBasis()) {
|
||||
case DISTANCE -> "Gefahrene Kilometer";
|
||||
case TIME -> "Zeit";
|
||||
case FLAT_RATE -> "Pauschal";
|
||||
case DISTANCE -> getTranslation("addjob.services.basis.distance");
|
||||
case TIME -> getTranslation("addjob.services.basis.time");
|
||||
case FLAT_RATE -> getTranslation("addjob.services.basis.flatrate");
|
||||
};
|
||||
}
|
||||
return "";
|
||||
}).setHeader("Berechnung").setSortable(true);
|
||||
}).setHeader(getTranslation("addjob.services.calculation")).setSortable(true);
|
||||
servicesGrid.addColumn(service -> {
|
||||
// Get route distance for distance-based calculations (berechnet oder manuell)
|
||||
Double routeDistance = getEffectiveRouteDistance();
|
||||
@@ -738,18 +742,18 @@ public class AddJobView extends Main {
|
||||
}
|
||||
// Show price info if no route calculated yet
|
||||
if (service.getCalculationBasis() == Service.CalculationBasis.DISTANCE && routeDistance == null) {
|
||||
return service.getPricePerKilometer().setScale(2, RoundingMode.HALF_UP) + " €/km (Route fehlt)";
|
||||
return service.getPricePerKilometer().setScale(2, RoundingMode.HALF_UP) + " €/km (" + getTranslation("addjob.services.route.missing") + ")";
|
||||
}
|
||||
return service.getEffectivePrice() != null
|
||||
? service.getEffectivePrice().setScale(2, RoundingMode.HALF_UP) + " €"
|
||||
: "";
|
||||
}).setHeader("Preis").setSortable(false);
|
||||
}).setHeader(getTranslation("common.price")).setSortable(false);
|
||||
servicesGrid.addColumn(service -> {
|
||||
if (service.getVatRate() != null) {
|
||||
return service.getVatRate().multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + " %";
|
||||
}
|
||||
return "";
|
||||
}).setHeader("USt").setSortable(true);
|
||||
}).setHeader(getTranslation("addjob.services.vat")).setSortable(true);
|
||||
servicesGrid.addComponentColumn(service -> {
|
||||
// Verbindliche Leistungen können nicht gelöscht werden
|
||||
if (service.isMandatory()) {
|
||||
@@ -766,12 +770,12 @@ public class AddJobView extends Main {
|
||||
updateTabLabels();
|
||||
});
|
||||
return removeButton;
|
||||
}).setHeader("Aktion").setAutoWidth(true).setFlexGrow(0);
|
||||
}).setHeader(getTranslation("common.actions")).setAutoWidth(true).setFlexGrow(0);
|
||||
|
||||
content.add(servicesGrid);
|
||||
|
||||
// Add Service Button
|
||||
Button addServiceButton = new Button("Leistung hinzufügen", new Icon(VaadinIcon.PLUS));
|
||||
Button addServiceButton = new Button(getTranslation("addjob.services.add"), new Icon(VaadinIcon.PLUS));
|
||||
addServiceButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
addServiceButton.addClickListener(e -> openAddServiceDialog());
|
||||
content.add(addServiceButton);
|
||||
@@ -786,7 +790,7 @@ public class AddJobView extends Main {
|
||||
summaryLayout.setWidthFull();
|
||||
summaryLayout.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
|
||||
|
||||
H3 summaryTitle = new H3("Zusammenfassung");
|
||||
H3 summaryTitle = new H3(getTranslation("addjob.summary.title"));
|
||||
summaryTitle.getStyle().set("margin", "0");
|
||||
summaryLayout.add(summaryTitle);
|
||||
|
||||
@@ -794,7 +798,7 @@ public class AddJobView extends Main {
|
||||
HorizontalLayout netRow = new HorizontalLayout();
|
||||
netRow.setWidthFull();
|
||||
netRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||
Span netLabel = new Span("Nettosumme:");
|
||||
Span netLabel = new Span(getTranslation("addjob.summary.net") + ":");
|
||||
netTotalLabel = new Span("0,00 €");
|
||||
netTotalLabel.getStyle().set("font-weight", "bold");
|
||||
netRow.add(netLabel, netTotalLabel);
|
||||
@@ -804,7 +808,7 @@ public class AddJobView extends Main {
|
||||
HorizontalLayout vatRow = new HorizontalLayout();
|
||||
vatRow.setWidthFull();
|
||||
vatRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||
Span vatLabel = new Span("Umsatzsteuer:");
|
||||
Span vatLabel = new Span(getTranslation("addjob.summary.vat") + ":");
|
||||
vatTotalLabel = new Span("0,00 €");
|
||||
vatTotalLabel.getStyle().set("font-weight", "bold");
|
||||
vatRow.add(vatLabel, vatTotalLabel);
|
||||
@@ -814,7 +818,7 @@ public class AddJobView extends Main {
|
||||
HorizontalLayout grossRow = new HorizontalLayout();
|
||||
grossRow.setWidthFull();
|
||||
grossRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||
Span grossLabel = new Span("Bruttosumme:");
|
||||
Span grossLabel = new Span(getTranslation("addjob.summary.gross") + ":");
|
||||
grossLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
||||
grossTotalLabel = new Span("0,00 €");
|
||||
grossTotalLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
||||
@@ -831,7 +835,7 @@ public class AddJobView extends Main {
|
||||
|
||||
private void openAddServiceDialog() {
|
||||
Dialog dialog = new Dialog();
|
||||
dialog.setHeaderTitle("Leistung auswählen");
|
||||
dialog.setHeaderTitle(getTranslation("addjob.services.dialog.title"));
|
||||
dialog.setWidth("500px");
|
||||
|
||||
VerticalLayout dialogContent = new VerticalLayout();
|
||||
@@ -842,7 +846,7 @@ public class AddJobView extends Main {
|
||||
List<Service> availableServices = serviceRepository
|
||||
.findByUserId(securityService.getCurrentDatabaseUser().getId().toString());
|
||||
|
||||
ComboBox<Service> serviceCombo = new ComboBox<>("Leistung");
|
||||
ComboBox<Service> serviceCombo = new ComboBox<>(getTranslation("common.service"));
|
||||
serviceCombo.setWidthFull();
|
||||
serviceCombo.setItems(availableServices);
|
||||
serviceCombo.setItemLabelGenerator(service -> {
|
||||
@@ -853,7 +857,7 @@ public class AddJobView extends Main {
|
||||
}
|
||||
return service.getName();
|
||||
});
|
||||
serviceCombo.setPlaceholder("Leistung auswählen...");
|
||||
serviceCombo.setPlaceholder(getTranslation("addjob.services.dialog.placeholder"));
|
||||
serviceCombo.setRequired(true);
|
||||
|
||||
dialogContent.add(serviceCombo);
|
||||
@@ -863,10 +867,10 @@ public class AddJobView extends Main {
|
||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
|
||||
buttonLayout.setSpacing(true);
|
||||
|
||||
Button cancelButton = new Button("Abbrechen", e -> dialog.close());
|
||||
Button cancelButton = new Button(getTranslation("button.cancel"), e -> dialog.close());
|
||||
cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
Button addButton = new Button("Hinzufügen", e -> {
|
||||
Button addButton = new Button(getTranslation("addjob.services.dialog.add"), e -> {
|
||||
if (serviceCombo.getValue() != null) {
|
||||
selectedServices.add(serviceCombo.getValue());
|
||||
servicesGrid.getDataProvider().refreshAll();
|
||||
@@ -956,7 +960,7 @@ public class AddJobView extends Main {
|
||||
section.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
|
||||
section.getStyle().set("background-color", "var(--lumo-base-color)");
|
||||
|
||||
H3 title = new H3("Abholadresse");
|
||||
H3 title = new H3(getTranslation("addjob.section.pickup"));
|
||||
title.getStyle().set("margin", "0");
|
||||
|
||||
HorizontalLayout titleLayout = new HorizontalLayout();
|
||||
@@ -1016,7 +1020,7 @@ public class AddJobView extends Main {
|
||||
section.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
|
||||
section.getStyle().set("background-color", "var(--lumo-base-color)");
|
||||
|
||||
H3 title = new H3("Lieferadresse");
|
||||
H3 title = new H3(getTranslation("addjob.section.delivery"));
|
||||
title.getStyle().set("margin", "0");
|
||||
|
||||
HorizontalLayout titleLayout = new HorizontalLayout();
|
||||
@@ -1198,12 +1202,12 @@ public class AddJobView extends Main {
|
||||
// Bind date picker fields with validation
|
||||
binder.forField(pickupDate).asRequired("")
|
||||
.withValidator(date -> date == null || !date.isBefore(LocalDate.now()),
|
||||
"Das Abholdatum darf nicht in der Vergangenheit liegen")
|
||||
getTranslation("addjob.validation.pickupdate.future"))
|
||||
.bind(Job::getPickupDate, Job::setPickupDate);
|
||||
|
||||
binder.forField(deliveryDate).asRequired("")
|
||||
.withValidator(date -> date == null || !date.isBefore(LocalDate.now()),
|
||||
"Das Lieferdatum darf nicht in der Vergangenheit liegen")
|
||||
getTranslation("addjob.validation.deliverydate.future"))
|
||||
.bind(Job::getDeliveryDate, Job::setDeliveryDate);
|
||||
|
||||
// Bind time picker fields (optional)
|
||||
@@ -1243,7 +1247,7 @@ public class AddJobView extends Main {
|
||||
boolean digital = Boolean.TRUE.equals(digitalProcessing.getValue());
|
||||
boolean hasUser = selectedUserId != null && !selectedUserId.trim().isEmpty();
|
||||
return !digital || hasUser;
|
||||
}, "Bitte App-Nutzer auswählen, wenn Digitale Abwicklung aktiv ist")
|
||||
}, getTranslation("addjob.validation.appuser.required"))
|
||||
.bind(Job::getAppUser, Job::setAppUser);
|
||||
|
||||
// Toggle required indicator and visibility for App-Nutzer based on
|
||||
@@ -1386,11 +1390,11 @@ public class AddJobView extends Main {
|
||||
|
||||
private void updateTabLabels() {
|
||||
// Check validation state for each tab and update labels with exclamation marks
|
||||
updateTabLabel(addressesTab, "Auftraggeber & Adressen", hasAddressValidationErrors());
|
||||
updateTabLabel(appointmentsTab, "Termine & Verarbeitung", hasAppointmentValidationErrors());
|
||||
updateTabLabel(cargoTab, "Ladung", hasCargoValidationErrors());
|
||||
updateTabLabel(tasksTab, "Aufgaben", hasTasksValidationErrors());
|
||||
updateTabLabel(priceTab, "Preis & Abschluss", hasPriceValidationErrors());
|
||||
updateTabLabel(addressesTab, getTranslation("addjob.tab.addresses"), hasAddressValidationErrors());
|
||||
updateTabLabel(appointmentsTab, getTranslation("addjob.tab.appointments"), hasAppointmentValidationErrors());
|
||||
updateTabLabel(cargoTab, getTranslation("addjob.tab.cargo"), hasCargoValidationErrors());
|
||||
updateTabLabel(tasksTab, getTranslation("addjob.tab.tasks"), hasTasksValidationErrors());
|
||||
updateTabLabel(priceTab, getTranslation("addjob.tab.price"), hasPriceValidationErrors());
|
||||
}
|
||||
|
||||
private void updateTabLabel(com.vaadin.flow.component.tabs.Tab tab, String baseLabel, boolean hasErrors) {
|
||||
@@ -1530,7 +1534,7 @@ public class AddJobView extends Main {
|
||||
// selected
|
||||
if (digitalProcessing.getValue() && appUser.getValue() == null) {
|
||||
Notification errorNotification = Notification.show(
|
||||
"Wenn digitale Abwicklung per App aktiviert ist, muss ein App-Nutzer ausgewählt werden.");
|
||||
getTranslation("addjob.validation.appuser.required"));
|
||||
errorNotification.setDuration(5000);
|
||||
return;
|
||||
}
|
||||
@@ -1543,7 +1547,7 @@ public class AddJobView extends Main {
|
||||
|
||||
if (cargoFilled.isEmpty()) {
|
||||
Notification errorNotification = Notification
|
||||
.show("Bitte fügen Sie mindestens eine Ladungszeile hinzu.");
|
||||
.show(getTranslation("addjob.validation.cargo.required"));
|
||||
errorNotification.setDuration(5000);
|
||||
return;
|
||||
}
|
||||
@@ -1593,13 +1597,13 @@ public class AddJobView extends Main {
|
||||
|
||||
// Erfolgsmeldung und Navigation zur Zusammenfassung
|
||||
Notification successNotification = Notification
|
||||
.show("Auftrag erfolgreich erstellt! Auftragsnummer: " + savedJob.getJobNumber());
|
||||
.show(getTranslation("addjob.notification.success", savedJob.getJobNumber()));
|
||||
successNotification.setDuration(2000);
|
||||
getUI().ifPresent(ui -> ui.navigate(JobSummaryView.class, savedJob.getId().toHexString()));
|
||||
} else {
|
||||
// Validation failed, show error message
|
||||
Notification errorNotification = Notification
|
||||
.show("Bitte füllen Sie alle Pflichtfelder aus (markiert mit *)");
|
||||
.show(getTranslation("addjob.validation.required.fields"));
|
||||
errorNotification.setDuration(5000);
|
||||
}
|
||||
|
||||
@@ -1610,7 +1614,7 @@ public class AddJobView extends Main {
|
||||
cargoError.setVisible(false);
|
||||
if (cargoAreaContainer != null)
|
||||
cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
|
||||
Notification errorNotification = Notification.show("Fehler beim Erstellen des Auftrags: " + e.getMessage());
|
||||
Notification errorNotification = Notification.show(getTranslation("addjob.notification.error", e.getMessage()));
|
||||
errorNotification.setDuration(5000);
|
||||
}
|
||||
}
|
||||
@@ -1658,7 +1662,7 @@ public class AddJobView extends Main {
|
||||
|
||||
// Benutzer informieren
|
||||
Notification notification = Notification
|
||||
.show("Entwurf wiederhergestellt. Sie können Ihre Arbeit fortsetzen.");
|
||||
.show(getTranslation("addjob.notification.draft.restored"));
|
||||
notification.setDuration(4000);
|
||||
}
|
||||
}
|
||||
@@ -1690,8 +1694,8 @@ public class AddJobView extends Main {
|
||||
cargoAreaContainer.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
|
||||
cargoAreaContainer.getStyle().set("padding", "var(--lumo-space-m)");
|
||||
|
||||
H3 cargoTitle = new H3("Ladung");
|
||||
cargoError = new Span("Bitte fügen Sie mindestens eine Ladungszeile hinzu.");
|
||||
H3 cargoTitle = new H3(getTranslation("addjob.tab.cargo"));
|
||||
cargoError = new Span(getTranslation("addjob.validation.cargo.required"));
|
||||
cargoError.getStyle().set("color", "var(--lumo-error-text-color)");
|
||||
cargoError.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
cargoError.setVisible(false);
|
||||
@@ -1709,34 +1713,33 @@ public class AddJobView extends Main {
|
||||
row.setWidthFull();
|
||||
row.setAlignItems(FlexComponent.Alignment.END);
|
||||
|
||||
ComboBox<String> desc = new ComboBox<>("Beschreibung");
|
||||
desc.setItems("Europalette", "Einwegpalette", "Düsseldorfer-Palette", "Gitterboxpalette", "Gitterwagen",
|
||||
"Paket");
|
||||
ComboBox<String> desc = new ComboBox<>(getTranslation("addjob.cargo.description"));
|
||||
desc.setItems(getTranslation("addjob.cargo.europalette"), getTranslation("addjob.cargo.disposablepalette"), getTranslation("addjob.cargo.dusseldorfpalette"), getTranslation("addjob.cargo.gridboxpalette"), getTranslation("addjob.cargo.gridcart"), getTranslation("addjob.cargo.parcel"));
|
||||
desc.setAllowCustomValue(true);
|
||||
desc.setPlaceholder("Wählen Sie eine Option oder geben Sie eigenen Text ein...");
|
||||
desc.setPlaceholder(getTranslation("addjob.cargo.description.placeholder"));
|
||||
desc.setWidth("40%");
|
||||
desc.setRequiredIndicatorVisible(true);
|
||||
|
||||
IntegerField qty = new IntegerField("Anzahl");
|
||||
IntegerField qty = new IntegerField(getTranslation("addjob.cargo.quantity"));
|
||||
qty.setMin(1);
|
||||
qty.setMax(9999); // Set reasonable maximum
|
||||
qty.setWidth("10%");
|
||||
qty.setRequiredIndicatorVisible(true);
|
||||
|
||||
NumberField weight = new NumberField("Gewicht");
|
||||
NumberField weight = new NumberField(getTranslation("addjob.cargo.weight"));
|
||||
weight.setSuffixComponent(new Span("kg"));
|
||||
weight.setWidth("15%");
|
||||
weight.setRequiredIndicatorVisible(true);
|
||||
|
||||
NumberField len = new NumberField("Länge");
|
||||
NumberField len = new NumberField(getTranslation("addjob.cargo.length"));
|
||||
len.setSuffixComponent(new Span("cm"));
|
||||
len.setWidth("12%");
|
||||
len.setRequiredIndicatorVisible(true);
|
||||
NumberField wid = new NumberField("Breite");
|
||||
NumberField wid = new NumberField(getTranslation("addjob.cargo.width"));
|
||||
wid.setSuffixComponent(new Span("cm"));
|
||||
wid.setWidth("12%");
|
||||
wid.setRequiredIndicatorVisible(true);
|
||||
NumberField hei = new NumberField("Höhe");
|
||||
NumberField hei = new NumberField(getTranslation("addjob.cargo.height"));
|
||||
hei.setSuffixComponent(new Span("cm"));
|
||||
hei.setWidth("12%");
|
||||
hei.setRequiredIndicatorVisible(true);
|
||||
@@ -1846,7 +1849,7 @@ public class AddJobView extends Main {
|
||||
}); // Show only one empty row by default
|
||||
|
||||
// Add button to add more cargo rows
|
||||
Button addCargoButton = new Button("Ladung hinzufügen", new Icon(VaadinIcon.PLUS));
|
||||
Button addCargoButton = new Button(getTranslation("addjob.cargo.add"), new Icon(VaadinIcon.PLUS));
|
||||
addCargoButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
addCargoButton.setWidthFull(); // Make button full width of container
|
||||
addCargoButton.addClickListener(e -> addCargoRow.accept("", r -> {
|
||||
@@ -1870,12 +1873,12 @@ public class AddJobView extends Main {
|
||||
content.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
|
||||
|
||||
// Aufgabentitel mit Template-Auswahl
|
||||
H3 tasksTitle = new H3("Zu quittierende Aufgaben");
|
||||
H3 tasksTitle = new H3(getTranslation("addjob.tasks.title"));
|
||||
tasksTitle.getStyle().set("margin", "0");
|
||||
tasksTitle.getStyle().set("white-space", "nowrap");
|
||||
|
||||
templateComboBox = new ComboBox<>();
|
||||
templateComboBox.setPlaceholder("Template auswählen...");
|
||||
templateComboBox.setPlaceholder(getTranslation("addjob.tasks.template.placeholder"));
|
||||
templateComboBox.setItemLabelGenerator(TaskTemplate::getTemplateName);
|
||||
templateComboBox.setClearButtonVisible(true);
|
||||
// Breite auf verbleibenden Platz einstellen
|
||||
@@ -1894,7 +1897,7 @@ public class AddJobView extends Main {
|
||||
// Icon-Button zum Speichern als Template
|
||||
Button saveAsTemplateBtn = new Button(new Icon(VaadinIcon.BOOKMARK));
|
||||
saveAsTemplateBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
saveAsTemplateBtn.setTooltipText("Aufgaben als Template speichern");
|
||||
saveAsTemplateBtn.setTooltipText(getTranslation("addjob.tasks.template.save.tooltip"));
|
||||
saveAsTemplateBtn.addClickListener(e -> saveTasksAsTemplate());
|
||||
|
||||
HorizontalLayout titleWithTemplate = new HorizontalLayout(tasksTitle, templateComboBox, saveAsTemplateBtn);
|
||||
@@ -1914,17 +1917,17 @@ public class AddJobView extends Main {
|
||||
// 1 Beispielzeile
|
||||
addTask.accept(null);
|
||||
|
||||
Button addTaskBtn = new Button("Aufgabe hinzufügen", new Icon(VaadinIcon.PLUS));
|
||||
Button addTaskBtn = new Button(getTranslation("addjob.tasks.add"), new Icon(VaadinIcon.PLUS));
|
||||
addTaskBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
addTaskBtn.addClickListener(e -> addTask.accept(null));
|
||||
|
||||
content.add(tasksList, addTaskBtn);
|
||||
|
||||
// Bemerkung
|
||||
H3 remarksTitle = new H3("Bemerkung");
|
||||
H3 remarksTitle = new H3(getTranslation("addjob.tasks.remark"));
|
||||
remarksTitle.getStyle().set("margin", "0");
|
||||
remarkArea = new TextArea();
|
||||
remarkArea.setPlaceholder("z.B. rückwärtigen Liefereingang benutzen o. ä.");
|
||||
remarkArea.setPlaceholder(getTranslation("addjob.tasks.remark.placeholder"));
|
||||
remarkArea.setWidthFull();
|
||||
remarkArea.setMinHeight("180px");
|
||||
content.add(remarksTitle, remarkArea);
|
||||
@@ -1998,7 +2001,7 @@ public class AddJobView extends Main {
|
||||
updatePriceSummary();
|
||||
|
||||
// Benutzer-Feedback
|
||||
Notification.show("Alle Felder wurden geleert", 2000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("addjob.notification.cleared"), 2000, Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2097,10 +2100,10 @@ public class AddJobView extends Main {
|
||||
taskContainer.getStyle().set("position", "relative");
|
||||
|
||||
// Task type selection
|
||||
ComboBox<TaskType> taskTypeCombo = new ComboBox<>("Aufgabentyp");
|
||||
ComboBox<TaskType> taskTypeCombo = new ComboBox<>(getTranslation("addjob.tasks.tasktype"));
|
||||
taskTypeCombo.setItems(TaskType.values());
|
||||
taskTypeCombo.setItemLabelGenerator(TaskType::getDisplayName);
|
||||
taskTypeCombo.setPlaceholder("Aufgabentyp wählen...");
|
||||
taskTypeCombo.setPlaceholder(getTranslation("addjob.tasks.tasktype.placeholder"));
|
||||
taskTypeCombo.setWidthFull();
|
||||
|
||||
// Configuration container for dynamic fields
|
||||
@@ -2226,8 +2229,8 @@ public class AddJobView extends Main {
|
||||
switch (taskType) {
|
||||
case CONFIRMATION:
|
||||
// Description field (required)
|
||||
TextField descriptionField = new TextField("Beschreibung");
|
||||
descriptionField.setPlaceholder("Beschreibung der Aufgabe...");
|
||||
TextField descriptionField = new TextField(getTranslation("addjob.tasks.description"));
|
||||
descriptionField.setPlaceholder(getTranslation("addjob.tasks.description.placeholder"));
|
||||
descriptionField.setWidthFull();
|
||||
descriptionField.setRequiredIndicatorVisible(true);
|
||||
descriptionField.setValue(task.getDescription() != null ? task.getDescription() : "");
|
||||
@@ -2252,8 +2255,8 @@ public class AddJobView extends Main {
|
||||
}
|
||||
|
||||
// Button text field (required)
|
||||
TextField buttonTextField = new TextField("Button-Text");
|
||||
buttonTextField.setPlaceholder("z.B. 'Bestätigen', 'Abgeschlossen'");
|
||||
TextField buttonTextField = new TextField(getTranslation("addjob.tasks.buttontext"));
|
||||
buttonTextField.setPlaceholder(getTranslation("addjob.tasks.buttontext.placeholder"));
|
||||
buttonTextField.setWidthFull();
|
||||
buttonTextField.setRequiredIndicatorVisible(true);
|
||||
ConfirmationTask confirmationTask = (ConfirmationTask) task;
|
||||
@@ -2287,7 +2290,7 @@ public class AddJobView extends Main {
|
||||
|
||||
case SIGNATURE:
|
||||
// No additional configuration needed
|
||||
Span info = new Span("Keine zusätzliche Konfiguration erforderlich");
|
||||
Span info = new Span(getTranslation("addjob.tasks.signature.noconfig"));
|
||||
info.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
info.getStyle().set("font-style", "italic");
|
||||
configContainer.add(info);
|
||||
@@ -2298,7 +2301,7 @@ public class AddJobView extends Main {
|
||||
todoContainer.setPadding(false);
|
||||
todoContainer.setSpacing(true);
|
||||
|
||||
H3 todoTitle = new H3("To-Do Punkte");
|
||||
H3 todoTitle = new H3(getTranslation("addjob.tasks.todolist.title"));
|
||||
todoTitle.getStyle().set("margin", "0");
|
||||
todoContainer.add(todoTitle);
|
||||
|
||||
@@ -2325,7 +2328,7 @@ public class AddJobView extends Main {
|
||||
todoRow.setAlignItems(FlexComponent.Alignment.END);
|
||||
|
||||
TextField todoField = new TextField();
|
||||
todoField.setPlaceholder("To-Do Punkt");
|
||||
todoField.setPlaceholder(getTranslation("addjob.tasks.todolist.item.placeholder"));
|
||||
todoField.setWidth("100%");
|
||||
todoField.setRequiredIndicatorVisible(true);
|
||||
// Initial red styling for empty field
|
||||
@@ -2348,7 +2351,7 @@ public class AddJobView extends Main {
|
||||
});
|
||||
};
|
||||
|
||||
Button addTodoBtn = new Button("To-Do Punkt hinzufügen", new Icon(VaadinIcon.PLUS));
|
||||
Button addTodoBtn = new Button(getTranslation("addjob.tasks.todolist.add"), new Icon(VaadinIcon.PLUS));
|
||||
addTodoBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
addTodoBtn.addClickListener(e -> addTodoItem.accept(null));
|
||||
|
||||
@@ -2363,7 +2366,7 @@ public class AddJobView extends Main {
|
||||
todoRow.setAlignItems(FlexComponent.Alignment.END);
|
||||
|
||||
TextField todoField = new TextField();
|
||||
todoField.setPlaceholder("To-Do Punkt");
|
||||
todoField.setPlaceholder(getTranslation("addjob.tasks.todolist.item.placeholder"));
|
||||
todoField.setWidth("100%");
|
||||
todoField.setRequiredIndicatorVisible(true);
|
||||
todoField.setValue(todoText != null ? todoText : ""); // Set the saved text
|
||||
@@ -2405,12 +2408,12 @@ public class AddJobView extends Main {
|
||||
photoLayout.setSpacing(true);
|
||||
|
||||
PhotoTask photoTask = (PhotoTask) task;
|
||||
IntegerField minPhotos = new IntegerField("Min. Anzahl Fotos");
|
||||
IntegerField minPhotos = new IntegerField(getTranslation("addjob.tasks.photo.min"));
|
||||
minPhotos.setPlaceholder("1");
|
||||
minPhotos.setMin(1);
|
||||
minPhotos.setValue(photoTask.getMinPhotoCount() != null ? photoTask.getMinPhotoCount() : 1);
|
||||
|
||||
IntegerField maxPhotos = new IntegerField("Max. Anzahl Fotos");
|
||||
IntegerField maxPhotos = new IntegerField(getTranslation("addjob.tasks.photo.max"));
|
||||
maxPhotos.setPlaceholder("10");
|
||||
maxPhotos.setMin(1);
|
||||
maxPhotos.setValue(photoTask.getMaxPhotoCount() != null ? photoTask.getMaxPhotoCount() : 10);
|
||||
@@ -2434,12 +2437,12 @@ public class AddJobView extends Main {
|
||||
barcodeLayout.setSpacing(true);
|
||||
|
||||
BarcodeTask barcodeTask = (BarcodeTask) task;
|
||||
IntegerField minBarcodes = new IntegerField("Min. Anzahl Barcodes");
|
||||
IntegerField minBarcodes = new IntegerField(getTranslation("addjob.tasks.barcode.min"));
|
||||
minBarcodes.setPlaceholder("1");
|
||||
minBarcodes.setMin(1);
|
||||
minBarcodes.setValue(barcodeTask.getMinBarcodeCount() != null ? barcodeTask.getMinBarcodeCount() : 1);
|
||||
|
||||
IntegerField maxBarcodes = new IntegerField("Max. Anzahl Barcodes");
|
||||
IntegerField maxBarcodes = new IntegerField(getTranslation("addjob.tasks.barcode.max"));
|
||||
maxBarcodes.setPlaceholder("10");
|
||||
maxBarcodes.setMin(1);
|
||||
maxBarcodes.setValue(barcodeTask.getMaxBarcodeCount() != null ? barcodeTask.getMaxBarcodeCount() : 10);
|
||||
@@ -2460,8 +2463,8 @@ public class AddJobView extends Main {
|
||||
case COMMENT:
|
||||
CommentTask commentTask = (CommentTask) task;
|
||||
|
||||
TextField commentTextField = new TextField("Kommentar-Platzhalter");
|
||||
commentTextField.setPlaceholder("Hinweis für den Benutzer...");
|
||||
TextField commentTextField = new TextField(getTranslation("addjob.tasks.comment.label"));
|
||||
commentTextField.setPlaceholder(getTranslation("addjob.tasks.comment.placeholder"));
|
||||
commentTextField.setWidthFull();
|
||||
commentTextField.setValue(commentTask.getCommentText() != null ? commentTask.getCommentText() : "");
|
||||
commentTextField.addValueChangeListener(ev -> {
|
||||
@@ -2469,7 +2472,7 @@ public class AddJobView extends Main {
|
||||
});
|
||||
|
||||
com.vaadin.flow.component.checkbox.Checkbox requiredCheckbox = new com.vaadin.flow.component.checkbox.Checkbox(
|
||||
"Pflichtfeld");
|
||||
getTranslation("addjob.tasks.comment.required"));
|
||||
requiredCheckbox.setValue(commentTask.isRequired());
|
||||
requiredCheckbox.addValueChangeListener(ev -> {
|
||||
commentTask.setRequired(ev.getValue());
|
||||
@@ -2509,30 +2512,30 @@ public class AddJobView extends Main {
|
||||
try {
|
||||
// Check if there are any tasks to save
|
||||
if (tasksState.isEmpty()) {
|
||||
Notification.show("Keine Aufgaben zum Speichern vorhanden", 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("addjob.tasks.template.no.tasks"), 3000, Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create dialog for template name input
|
||||
Dialog dialog = new Dialog();
|
||||
dialog.setHeaderTitle("Template speichern");
|
||||
dialog.setHeaderTitle(getTranslation("addjob.tasks.template.save.title"));
|
||||
dialog.setWidth("400px");
|
||||
|
||||
VerticalLayout dialogLayout = new VerticalLayout();
|
||||
dialogLayout.setPadding(false);
|
||||
dialogLayout.setSpacing(true);
|
||||
|
||||
TextField templateNameField = new TextField("Template-Name");
|
||||
templateNameField.setPlaceholder("Geben Sie einen Namen für das Template ein");
|
||||
TextField templateNameField = new TextField(getTranslation("addjob.tasks.template.name"));
|
||||
templateNameField.setPlaceholder(getTranslation("addjob.tasks.template.name.placeholder"));
|
||||
templateNameField.setWidthFull();
|
||||
templateNameField.setRequiredIndicatorVisible(true);
|
||||
|
||||
Button saveButton = new Button("Speichern");
|
||||
Button saveButton = new Button(getTranslation("button.savechanges"));
|
||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
saveButton.addClickListener(e -> {
|
||||
String templateName = templateNameField.getValue();
|
||||
if (templateName == null || templateName.trim().isEmpty()) {
|
||||
Notification.show("Bitte geben Sie einen Template-Namen ein", 3000,
|
||||
Notification.show(getTranslation("addjob.tasks.template.name.required"), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
@@ -2552,19 +2555,19 @@ public class AddJobView extends Main {
|
||||
|
||||
dialog.close();
|
||||
loadTemplatesIntoComboBox(templateComboBox);
|
||||
Notification.show("Template '" + templateName + "' erfolgreich gespeichert", 3000,
|
||||
Notification.show(getTranslation("addjob.tasks.template.saved", templateName), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
|
||||
} catch (RuntimeException ex) {
|
||||
Notification.show(ex.getMessage(), 4000, Notification.Position.MIDDLE);
|
||||
} catch (Exception ex) {
|
||||
log.error("Error saving task template", ex);
|
||||
Notification.show("Fehler beim Speichern des Templates: " + ex.getMessage(), 4000,
|
||||
Notification.show(getTranslation("addjob.tasks.template.save.error", ex.getMessage()), 4000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
});
|
||||
|
||||
Button cancelButton = new Button("Abbrechen");
|
||||
Button cancelButton = new Button(getTranslation("button.cancel"));
|
||||
cancelButton.addClickListener(e -> dialog.close());
|
||||
|
||||
HorizontalLayout buttonLayout = new HorizontalLayout(cancelButton, saveButton);
|
||||
@@ -2576,7 +2579,7 @@ public class AddJobView extends Main {
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error opening save template dialog", e);
|
||||
Notification.show("Fehler beim Öffnen des Dialogs: " + e.getMessage(), 4000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addjob.tasks.template.dialog.error", e.getMessage()), 4000, Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2643,7 +2646,7 @@ public class AddJobView extends Main {
|
||||
templateComboBox.setItems(templates);
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading templates", e);
|
||||
Notification.show("Fehler beim Laden der Templates: " + e.getMessage(), 4000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addjob.tasks.template.load.templates.error", e.getMessage()), 4000, Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2652,12 +2655,11 @@ public class AddJobView extends Main {
|
||||
*/
|
||||
private void loadTasksFromTemplate(TaskTemplate template, ComboBox<TaskTemplate> templateComboBox) {
|
||||
ConfirmDialog confirmDialog = new ConfirmDialog();
|
||||
confirmDialog.setHeader("Template laden");
|
||||
confirmDialog.setText("Möchten Sie wirklich das Template '" + template.getTemplateName()
|
||||
+ "' laden? Alle aktuellen Aufgaben werden ersetzt.");
|
||||
confirmDialog.setHeader(getTranslation("addjob.tasks.template.load.title"));
|
||||
confirmDialog.setText(getTranslation("addjob.tasks.template.load.text", template.getTemplateName()));
|
||||
confirmDialog.setCancelable(true);
|
||||
confirmDialog.setCancelText("Abbrechen");
|
||||
confirmDialog.setConfirmText("Laden");
|
||||
confirmDialog.setCancelText(getTranslation("button.cancel"));
|
||||
confirmDialog.setConfirmText(getTranslation("addjob.tasks.template.load.confirm"));
|
||||
confirmDialog.setConfirmButtonTheme("primary");
|
||||
|
||||
confirmDialog.addConfirmListener(e -> {
|
||||
@@ -2684,12 +2686,12 @@ public class AddJobView extends Main {
|
||||
triggerValidation();
|
||||
updateTabLabels();
|
||||
|
||||
Notification.show("Template '" + template.getTemplateName() + "' erfolgreich geladen", 3000,
|
||||
Notification.show(getTranslation("addjob.tasks.template.loaded", template.getTemplateName()), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.error("Error loading template tasks", ex);
|
||||
Notification.show("Fehler beim Laden des Templates: " + ex.getMessage(), 4000,
|
||||
Notification.show(getTranslation("addjob.tasks.template.load.error", ex.getMessage()), 4000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
});
|
||||
@@ -2720,10 +2722,10 @@ public class AddJobView extends Main {
|
||||
taskContainer.getStyle().set("position", "relative");
|
||||
|
||||
// Task type selection
|
||||
ComboBox<TaskType> taskTypeCombo = new ComboBox<>("Aufgabentyp");
|
||||
ComboBox<TaskType> taskTypeCombo = new ComboBox<>(getTranslation("addjob.tasks.tasktype"));
|
||||
taskTypeCombo.setItems(TaskType.values());
|
||||
taskTypeCombo.setItemLabelGenerator(TaskType::getDisplayName);
|
||||
taskTypeCombo.setPlaceholder("Aufgabentyp wählen...");
|
||||
taskTypeCombo.setPlaceholder(getTranslation("addjob.tasks.tasktype.placeholder"));
|
||||
taskTypeCombo.setWidthFull();
|
||||
|
||||
// Configuration container for dynamic fields
|
||||
@@ -2922,7 +2924,7 @@ public class AddJobView extends Main {
|
||||
*/
|
||||
private void showAddressValidationDialog(com.vaadin.flow.component.tabs.Tab targetTab) {
|
||||
final Dialog dialog = new Dialog();
|
||||
dialog.setHeaderTitle("Adressen werden überprüft");
|
||||
dialog.setHeaderTitle(getTranslation("addjob.validation.dialog.title"));
|
||||
dialog.setWidth("500px");
|
||||
dialog.setModal(true);
|
||||
dialog.setCloseOnOutsideClick(false);
|
||||
@@ -2933,7 +2935,7 @@ public class AddJobView extends Main {
|
||||
content.setSpacing(true);
|
||||
|
||||
// Initiale Meldung mit Progress
|
||||
final Span loadingMessage = new Span("Adressen werden bei Google Maps überprüft...");
|
||||
final Span loadingMessage = new Span(getTranslation("addjob.validation.dialog.loading"));
|
||||
loadingMessage.getStyle().set("font-style", "italic");
|
||||
loadingMessage.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
|
||||
@@ -2968,7 +2970,7 @@ public class AddJobView extends Main {
|
||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
|
||||
buttonLayout.setVisible(false);
|
||||
|
||||
final Button cancelButton = new Button("Zurück", e -> {
|
||||
final Button cancelButton = new Button(getTranslation("addjob.validation.dialog.back"), e -> {
|
||||
dialog.close();
|
||||
// Im Adress-Tab bleiben
|
||||
validationDialogOpen = false;
|
||||
@@ -2976,7 +2978,7 @@ public class AddJobView extends Main {
|
||||
});
|
||||
cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
final Button continueButton = new Button("Trotzdem wechseln", e -> {
|
||||
final Button continueButton = new Button(getTranslation("addjob.validation.dialog.continue.anyway"), e -> {
|
||||
dialog.close();
|
||||
// Zum Ziel-Tab wechseln
|
||||
tabSheet.setSelectedTab(targetTab);
|
||||
@@ -3075,10 +3077,10 @@ public class AddJobView extends Main {
|
||||
// Abholadresse anzeigen
|
||||
if (pickupResult != null) {
|
||||
if (pickupResult.isValid()) {
|
||||
pickupResultLabel.setText("✓ Abholadresse: " + pickupResult.getFormattedAddress());
|
||||
pickupResultLabel.setText("✓ " + getTranslation("addjob.validation.pickup.address") + ": " + pickupResult.getFormattedAddress());
|
||||
pickupResultLabel.getStyle().set("color", "var(--lumo-success-text-color)");
|
||||
} else {
|
||||
pickupResultLabel.setText("⚠ Abholadresse: " + pickupResult.getValidationMessage());
|
||||
pickupResultLabel.setText("⚠ " + getTranslation("addjob.validation.pickup.address") + ": " + pickupResult.getValidationMessage());
|
||||
pickupResultLabel.getStyle().set("color", "var(--lumo-error-text-color)");
|
||||
hasInvalidAddress = true;
|
||||
bothAddressesValid = false;
|
||||
@@ -3090,10 +3092,10 @@ public class AddJobView extends Main {
|
||||
// Lieferadresse anzeigen
|
||||
if (deliveryResult != null) {
|
||||
if (deliveryResult.isValid()) {
|
||||
deliveryResultLabel.setText("✓ Lieferadresse: " + deliveryResult.getFormattedAddress());
|
||||
deliveryResultLabel.setText("✓ " + getTranslation("addjob.validation.delivery.address") + ": " + deliveryResult.getFormattedAddress());
|
||||
deliveryResultLabel.getStyle().set("color", "var(--lumo-success-text-color)");
|
||||
} else {
|
||||
deliveryResultLabel.setText("⚠ Lieferadresse: " + deliveryResult.getValidationMessage());
|
||||
deliveryResultLabel.setText("⚠ " + getTranslation("addjob.validation.delivery.address") + ": " + deliveryResult.getValidationMessage());
|
||||
deliveryResultLabel.getStyle().set("color", "var(--lumo-error-text-color)");
|
||||
hasInvalidAddress = true;
|
||||
bothAddressesValid = false;
|
||||
@@ -3104,8 +3106,8 @@ public class AddJobView extends Main {
|
||||
|
||||
// Route anzeigen, wenn beide Adressen gültig sind
|
||||
if (bothAddressesValid && routeCalculationResult != null && routeCalculationResult.isValid()) {
|
||||
routeResultLabel.setText("🚛 Route: " + String.format("%.1f km", routeCalculationResult.getDistanceKm())
|
||||
+ " (Fahrtzeit: " + routeCalculationResult.getFormattedDurationLong() + ")");
|
||||
routeResultLabel.setText("🚛 " + getTranslation("addjob.validation.route") + ": " + String.format("%.1f km", routeCalculationResult.getDistanceKm())
|
||||
+ " (" + getTranslation("addjob.route.duration") + ": " + routeCalculationResult.getFormattedDurationLong() + ")");
|
||||
routeResultLabel.getStyle().set("color", "var(--lumo-primary-text-color)");
|
||||
routeResultLabel.setVisible(true);
|
||||
} else {
|
||||
@@ -3123,9 +3125,9 @@ public class AddJobView extends Main {
|
||||
|
||||
// Wenn beide Adressen gültig sind, direkt weiter
|
||||
if (!hasInvalidAddress) {
|
||||
continueButton.setText("Weiter");
|
||||
continueButton.setText(getTranslation("addjob.validation.dialog.continue"));
|
||||
} else {
|
||||
continueButton.setText("Trotzdem wechseln");
|
||||
continueButton.setText(getTranslation("addjob.validation.dialog.continue.anyway"));
|
||||
}
|
||||
|
||||
// Route-Info im Preis-Tab aktualisieren
|
||||
|
||||
@@ -10,8 +10,8 @@ import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.votianlt.model.JobStatus;
|
||||
@@ -25,11 +25,10 @@ import java.time.LocalDateTime;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Route(value = "admin-dashboard", layout = de.assecutor.votianlt.pages.base.ui.view.AdminLayout.class)
|
||||
@PageTitle("Admin Dashboard")
|
||||
@RolesAllowed("ADMIN")
|
||||
@Menu(order = 1, icon = "lumo:edit")
|
||||
@Slf4j
|
||||
public class AdminDashboardView extends Main {
|
||||
public class AdminDashboardView extends Main implements HasDynamicTitle {
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final TaskRepository taskRepository;
|
||||
@@ -64,7 +63,7 @@ public class AdminDashboardView extends Main {
|
||||
LumoUtility.Padding.MEDIUM);
|
||||
|
||||
// Header
|
||||
H1 title = new H1("Administrator Dashboard");
|
||||
H1 title = new H1(getTranslation("admindashboard.title"));
|
||||
title.addClassNames(LumoUtility.Margin.Bottom.MEDIUM, LumoUtility.Margin.Top.NONE);
|
||||
|
||||
HorizontalLayout header = new HorizontalLayout(title);
|
||||
@@ -92,7 +91,7 @@ public class AdminDashboardView extends Main {
|
||||
|
||||
// Show loading indicator
|
||||
statisticsContainer.removeAll();
|
||||
statisticsContainer.add(new Span("Lade Statistiken..."));
|
||||
statisticsContainer.add(new Span(getTranslation("admindashboard.loading")));
|
||||
|
||||
// Load statistics asynchronously
|
||||
CompletableFuture.runAsync(() -> {
|
||||
@@ -102,7 +101,7 @@ public class AdminDashboardView extends Main {
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading dashboard statistics", e);
|
||||
statisticsContainer.removeAll();
|
||||
statisticsContainer.add(new Span("Fehler beim Laden der Statistiken: " + e.getMessage()));
|
||||
statisticsContainer.add(new Span(getTranslation("admindashboard.error", e.getMessage())));
|
||||
}
|
||||
}));
|
||||
});
|
||||
@@ -140,7 +139,7 @@ public class AdminDashboardView extends Main {
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("System-Übersicht");
|
||||
H3 title = new H3(getTranslation("admindashboard.section.overview"));
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
@@ -149,19 +148,19 @@ public class AdminDashboardView extends Main {
|
||||
|
||||
// Total jobs card
|
||||
long totalJobs = jobRepository.count();
|
||||
cards.add(createStatCard("Gesamt Jobs", String.valueOf(totalJobs), VaadinIcon.PACKAGE, "blue"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.totaljobs"), String.valueOf(totalJobs), VaadinIcon.PACKAGE, "blue"));
|
||||
|
||||
// Total users card
|
||||
long totalUsers = userRepository.count();
|
||||
cards.add(createStatCard("Benutzer", String.valueOf(totalUsers), VaadinIcon.USERS, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.users"), String.valueOf(totalUsers), VaadinIcon.USERS, "green"));
|
||||
|
||||
// Total app users card
|
||||
long totalAppUsers = appUserRepository.count();
|
||||
cards.add(createStatCard("App-Benutzer", String.valueOf(totalAppUsers), VaadinIcon.MOBILE, "purple"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.appusers"), String.valueOf(totalAppUsers), VaadinIcon.MOBILE, "purple"));
|
||||
|
||||
// Current time
|
||||
String currentTime = DateTimeFormatUtil.formatDateTime(LocalDateTime.now());
|
||||
cards.add(createStatCard("Letzte Aktualisierung", currentTime, VaadinIcon.CLOCK, "gray"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.lastupdated"), currentTime, VaadinIcon.CLOCK, "gray"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
@@ -173,7 +172,7 @@ public class AdminDashboardView extends Main {
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("Job-Statistiken");
|
||||
H3 title = new H3(getTranslation("admindashboard.section.jobs"));
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
@@ -186,17 +185,17 @@ public class AdminDashboardView extends Main {
|
||||
long inProgressJobs = jobRepository.countByStatus(JobStatus.IN_PROGRESS);
|
||||
long completedJobs = jobRepository.countByStatus(JobStatus.COMPLETED);
|
||||
|
||||
cards.add(createStatCard("Offene Jobs", String.valueOf(openJobs), VaadinIcon.HOURGLASS_START, "orange"));
|
||||
cards.add(createStatCard("In Bearbeitung", String.valueOf(inProgressJobs), VaadinIcon.PLAY, "blue"));
|
||||
cards.add(createStatCard("Abgeschlossen", String.valueOf(completedJobs), VaadinIcon.CHECK_CIRCLE, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.openjobs"), String.valueOf(openJobs), VaadinIcon.HOURGLASS_START, "orange"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.inprogress"), String.valueOf(inProgressJobs), VaadinIcon.PLAY, "blue"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.completed"), String.valueOf(completedJobs), VaadinIcon.CHECK_CIRCLE, "green"));
|
||||
|
||||
// Total cargo items
|
||||
long totalCargoItems = cargoItemRepository.count();
|
||||
cards.add(createStatCard("Frachtgüter", String.valueOf(totalCargoItems), VaadinIcon.CUBE, "purple"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.cargo"), String.valueOf(totalCargoItems), VaadinIcon.CUBE, "purple"));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not load job statistics by status", e);
|
||||
cards.add(createStatCard("Status-Info", "Nicht verfügbar", VaadinIcon.WARNING, "red"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.status.info"), getTranslation("admindashboard.stat.status.unavailable"), VaadinIcon.WARNING, "red"));
|
||||
}
|
||||
|
||||
section.add(title, cards);
|
||||
@@ -209,7 +208,7 @@ public class AdminDashboardView extends Main {
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("Aufgaben-Statistiken");
|
||||
H3 title = new H3(getTranslation("admindashboard.section.tasks"));
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
@@ -218,19 +217,19 @@ public class AdminDashboardView extends Main {
|
||||
|
||||
// Total tasks
|
||||
long totalTasks = taskRepository.count();
|
||||
cards.add(createStatCard("Gesamt Aufgaben", String.valueOf(totalTasks), VaadinIcon.TASKS, "blue"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.totaltasks"), String.valueOf(totalTasks), VaadinIcon.TASKS, "blue"));
|
||||
|
||||
// Completed tasks
|
||||
long completedTasks = taskRepository.countByCompleted(true);
|
||||
cards.add(createStatCard("Abgeschlossen", String.valueOf(completedTasks), VaadinIcon.CHECK, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.completedtasks"), String.valueOf(completedTasks), VaadinIcon.CHECK, "green"));
|
||||
|
||||
// Pending tasks
|
||||
long pendingTasks = totalTasks - completedTasks;
|
||||
cards.add(createStatCard("Offen", String.valueOf(pendingTasks), VaadinIcon.CLOCK, "orange"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.pendingtasks"), String.valueOf(pendingTasks), VaadinIcon.CLOCK, "orange"));
|
||||
|
||||
// Completion rate
|
||||
double completionRate = totalTasks > 0 ? (completedTasks * 100.0 / totalTasks) : 0;
|
||||
cards.add(createStatCard("Erfolgsquote", String.format("%.1f%%", completionRate), VaadinIcon.TRENDING_UP,
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.successrate"), String.format("%.1f%%", completionRate), VaadinIcon.TRENDING_UP,
|
||||
"purple"));
|
||||
|
||||
section.add(title, cards);
|
||||
@@ -243,7 +242,7 @@ public class AdminDashboardView extends Main {
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("Benutzer-Aktivität");
|
||||
H3 title = new H3(getTranslation("admindashboard.section.users"));
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
@@ -252,16 +251,16 @@ public class AdminDashboardView extends Main {
|
||||
|
||||
// Content statistics
|
||||
long totalPhotos = photoRepository.count();
|
||||
cards.add(createStatCard("Fotos", String.valueOf(totalPhotos), VaadinIcon.CAMERA, "blue"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.photos"), String.valueOf(totalPhotos), VaadinIcon.CAMERA, "blue"));
|
||||
|
||||
long totalBarcodes = barcodeRepository.count();
|
||||
cards.add(createStatCard("Barcodes", String.valueOf(totalBarcodes), VaadinIcon.BARCODE, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.barcodes"), String.valueOf(totalBarcodes), VaadinIcon.BARCODE, "green"));
|
||||
|
||||
long totalSignatures = signatureRepository.count();
|
||||
cards.add(createStatCard("Unterschriften", String.valueOf(totalSignatures), VaadinIcon.EDIT, "purple"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.signatures"), String.valueOf(totalSignatures), VaadinIcon.EDIT, "purple"));
|
||||
|
||||
long totalComments = commentRepository.count();
|
||||
cards.add(createStatCard("Kommentare", String.valueOf(totalComments), VaadinIcon.COMMENT, "orange"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.comments"), String.valueOf(totalComments), VaadinIcon.COMMENT, "orange"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
@@ -273,7 +272,7 @@ public class AdminDashboardView extends Main {
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("System-Status");
|
||||
H3 title = new H3(getTranslation("admindashboard.section.health"));
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
@@ -283,16 +282,16 @@ public class AdminDashboardView extends Main {
|
||||
// Database connection status
|
||||
try {
|
||||
userRepository.count(); // Test database connection
|
||||
cards.add(createStatCard("Datenbank", "Verbunden", VaadinIcon.DATABASE, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.database"), getTranslation("admindashboard.stat.database.connected"), VaadinIcon.DATABASE, "green"));
|
||||
} catch (Exception e) {
|
||||
cards.add(createStatCard("Datenbank", "Fehler", VaadinIcon.DATABASE, "red"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.database"), getTranslation("admindashboard.stat.database.error"), VaadinIcon.DATABASE, "red"));
|
||||
}
|
||||
|
||||
// Messaging status
|
||||
cards.add(createStatCard("WebSocket", "Aktiv", VaadinIcon.CONNECT, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.websocket"), getTranslation("admindashboard.stat.websocket.active"), VaadinIcon.CONNECT, "green"));
|
||||
|
||||
// System uptime (placeholder)
|
||||
cards.add(createStatCard("Anwendung", "Läuft", VaadinIcon.HEART, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.app"), getTranslation("admindashboard.stat.app.running"), VaadinIcon.HEART, "green"));
|
||||
|
||||
// Memory usage (placeholder)
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
@@ -300,7 +299,7 @@ public class AdminDashboardView extends Main {
|
||||
long totalMemory = runtime.totalMemory() / 1024 / 1024; // MB
|
||||
long usedMemory = totalMemory - (runtime.freeMemory() / 1024 / 1024); // MB
|
||||
String memoryInfo = usedMemory + "/" + maxMemory + " MB";
|
||||
cards.add(createStatCard("Speicher", memoryInfo, VaadinIcon.SERVER, "blue"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.memory"), memoryInfo, VaadinIcon.SERVER, "blue"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
@@ -337,4 +336,9 @@ public class AdminDashboardView extends Main {
|
||||
card.add(content);
|
||||
return card;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.admin.dashboard");
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import com.vaadin.flow.component.html.H2;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.PriceTable;
|
||||
import de.assecutor.votianlt.pages.base.ui.view.AdminLayout;
|
||||
@@ -13,9 +13,8 @@ import de.assecutor.votianlt.repository.PriceTableRepository;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
@Route(value = "admin-price-table", layout = AdminLayout.class)
|
||||
@PageTitle("Preis-Tabelle")
|
||||
@RolesAllowed("ADMIN")
|
||||
public class AdminPricetableView extends VerticalLayout {
|
||||
public class AdminPricetableView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private final PriceTableRepository priceTableRepository;
|
||||
private final TextField monthlyBasePackage;
|
||||
@@ -30,22 +29,25 @@ public class AdminPricetableView extends VerticalLayout {
|
||||
getStyle().set("margin", "14px");
|
||||
setWidth("90%");
|
||||
|
||||
H2 title = new H2("Preis-Tabelle");
|
||||
H2 title = new H2(getTranslation("adminpricetable.title"));
|
||||
add(title);
|
||||
|
||||
VerticalLayout fieldsLayout = new VerticalLayout();
|
||||
fieldsLayout.setSpacing(true);
|
||||
fieldsLayout.setPadding(false);
|
||||
|
||||
monthlyBasePackage = new TextField("Monatliche Grundpauschale");
|
||||
monthlyBasePackage = new TextField();
|
||||
monthlyBasePackage.setLabel(getTranslation("adminpricetable.field.monthly"));
|
||||
monthlyBasePackage.setWidth("40%");
|
||||
monthlyBasePackage.setMaxWidth("40%");
|
||||
|
||||
appUsageLicense = new TextField("App-Nutzungslizenz");
|
||||
appUsageLicense = new TextField();
|
||||
appUsageLicense.setLabel(getTranslation("adminpricetable.field.applicense"));
|
||||
appUsageLicense.setWidth("40%");
|
||||
appUsageLicense.setMaxWidth("40%");
|
||||
|
||||
revenueParticipation = new TextField("Umsatzbeteiligung in Prozent");
|
||||
revenueParticipation = new TextField();
|
||||
revenueParticipation.setLabel(getTranslation("adminpricetable.field.revenue"));
|
||||
revenueParticipation.setWidth("40%");
|
||||
revenueParticipation.setMaxWidth("40%");
|
||||
|
||||
@@ -53,7 +55,7 @@ public class AdminPricetableView extends VerticalLayout {
|
||||
|
||||
add(fieldsLayout);
|
||||
|
||||
Button saveButton = new Button("Speichern");
|
||||
Button saveButton = new Button(getTranslation("button.savechanges"));
|
||||
saveButton.getStyle().set("margin-top", "20px");
|
||||
saveButton.addClickListener(e -> savePriceTable());
|
||||
|
||||
@@ -73,9 +75,9 @@ public class AdminPricetableView extends VerticalLayout {
|
||||
priceTable.setRevenueParticipation(revenueParticipation.getValue());
|
||||
|
||||
priceTableRepository.save(priceTable);
|
||||
Notification.show("Preise erfolgreich gespeichert!", 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("adminpricetable.notification.saved"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Speichern: " + ex.getMessage(), 5000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("adminpricetable.notification.save.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,8 +94,13 @@ public class AdminPricetableView extends VerticalLayout {
|
||||
priceTable.getRevenueParticipation() != null ? priceTable.getRevenueParticipation() : "");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Laden der Daten: " + ex.getMessage(), 5000,
|
||||
Notification.show(getTranslation("adminpricetable.notification.load.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.pricetable");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,16 @@ import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.AppUser;
|
||||
import de.assecutor.votianlt.pages.service.AppUserService;
|
||||
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)
|
||||
@RolesAllowed({ "USER", "ADMIN" })
|
||||
public class AppUserView extends VerticalLayout {
|
||||
public class AppUserView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private final AppUserService appUserService;
|
||||
private final Grid<AppUser> appUserGrid;
|
||||
@@ -37,10 +36,10 @@ public class AppUserView extends VerticalLayout {
|
||||
header.setWidthFull();
|
||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
|
||||
H2 title = new H2("App-Nutzer");
|
||||
H2 title = new H2(getTranslation("appuser.title"));
|
||||
title.getStyle().set("margin", "0");
|
||||
|
||||
Button addButton = new Button("Neuen App-Nutzer anlegen", new Icon(VaadinIcon.PLUS));
|
||||
Button addButton = new Button(getTranslation("appuser.button.add"), new Icon(VaadinIcon.PLUS));
|
||||
addButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
addButton.addClickListener(e -> navigateToAddAppUser());
|
||||
|
||||
@@ -53,12 +52,12 @@ public class AppUserView extends VerticalLayout {
|
||||
appUserGrid.setSizeFull();
|
||||
|
||||
// Grid-Spalten konfigurieren
|
||||
appUserGrid.addColumn(AppUser::getBezeichnung).setHeader("Bezeichnung").setAutoWidth(true);
|
||||
appUserGrid.addColumn(AppUser::getVorname).setHeader("Vorname").setAutoWidth(true);
|
||||
appUserGrid.addColumn(AppUser::getNachname).setHeader("Nachname").setAutoWidth(true);
|
||||
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::getBezeichnung).setHeader(getTranslation("appuser.column.designation")).setAutoWidth(true);
|
||||
appUserGrid.addColumn(AppUser::getVorname).setHeader(getTranslation("appuser.column.firstname")).setAutoWidth(true);
|
||||
appUserGrid.addColumn(AppUser::getNachname).setHeader(getTranslation("appuser.column.lastname")).setAutoWidth(true);
|
||||
appUserGrid.addColumn(AppUser::getTelefon).setHeader(getTranslation("appuser.column.phone")).setAutoWidth(true);
|
||||
appUserGrid.addColumn(AppUser::getAppCode).setHeader(getTranslation("appuser.column.appcode")).setAutoWidth(true);
|
||||
appUserGrid.addColumn(AppUser::getEmail).setHeader(getTranslation("appuser.column.email")).setAutoWidth(true);
|
||||
|
||||
// Make grid rows clickable
|
||||
appUserGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
||||
@@ -86,4 +85,9 @@ public class AppUserView extends VerticalLayout {
|
||||
private void navigateToAddAppUser() {
|
||||
getUI().ifPresent(ui -> ui.navigate("add-app-user"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.appusers");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,16 @@ import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.pages.base.ui.view.MainLayout;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
@Route(value = "dashboard", layout = MainLayout.class)
|
||||
@PageTitle("VotianLT - Dashboard")
|
||||
@RolesAllowed({ "USER" })
|
||||
public class AuthenticatedStartView extends VerticalLayout {
|
||||
public class AuthenticatedStartView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private final SecurityService securityService;
|
||||
|
||||
@@ -59,14 +59,16 @@ public class AuthenticatedStartView extends VerticalLayout {
|
||||
heroSection.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
|
||||
// Welcome message for authenticated user
|
||||
String currentUser = securityService.getCurrentUsername();
|
||||
H1 welcomeTitle = new H1("Willkommen zurück, " + currentUser + "!");
|
||||
User currentUser = securityService.getCurrentDatabaseUser();
|
||||
String displayName = currentUser != null && currentUser.getFirstname() != null && currentUser.getName() != null
|
||||
? currentUser.getFirstname() + " " + currentUser.getName()
|
||||
: securityService.getCurrentUsername();
|
||||
H1 welcomeTitle = new H1(getTranslation("dashboard.welcome", displayName));
|
||||
welcomeTitle.getStyle().set("text-align", "center");
|
||||
welcomeTitle.getStyle().set("color", "var(--lumo-primary-text-color)");
|
||||
welcomeTitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
|
||||
|
||||
Paragraph welcomeDescription = new Paragraph(
|
||||
"Nutzen Sie die Navigation links, um neue Aufträge zu erstellen oder Ihre Verwaltung zu bearbeiten.");
|
||||
Paragraph welcomeDescription = new Paragraph(getTranslation("dashboard.description"));
|
||||
welcomeDescription.getStyle().set("text-align", "center");
|
||||
welcomeDescription.getStyle().set("max-width", "600px");
|
||||
welcomeDescription.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
||||
@@ -84,13 +86,11 @@ public class AuthenticatedStartView extends VerticalLayout {
|
||||
systemSection.getStyle().set("background-color", "var(--lumo-base-color)");
|
||||
|
||||
// Section Header
|
||||
H2 systemTitle = new H2("Das System");
|
||||
H2 systemTitle = new H2(getTranslation("dashboard.system.title"));
|
||||
systemTitle.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
systemTitle.getStyle().set("text-align", "center");
|
||||
|
||||
Paragraph systemIntro = new Paragraph(
|
||||
"Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe ist von entscheidender Bedeutung, "
|
||||
+ "dass sie sich in erster Linie auf ihr eigentliches Geschäft konzentrieren können: Kunden gewinnen und Waren von A nach B liefern.");
|
||||
Paragraph systemIntro = new Paragraph(getTranslation("dashboard.system.intro"));
|
||||
systemIntro.getStyle().set("text-align", "center");
|
||||
systemIntro.getStyle().set("max-width", "800px");
|
||||
systemIntro.getStyle().set("margin-bottom", "var(--lumo-space-xl)");
|
||||
@@ -105,12 +105,16 @@ public class AuthenticatedStartView extends VerticalLayout {
|
||||
featuresGrid.getStyle().set("width", "100%");
|
||||
|
||||
// Feature Cards
|
||||
featuresGrid.add(createFeatureCard(VaadinIcon.COG, "Einrichtungsassistent",
|
||||
"Mithilfe des Einrichtungsassistenten haben Sie die Möglichkeit, Ihr Nutzerprofil zu vervollständigen."),
|
||||
createFeatureCard(VaadinIcon.USERS, "Kunden- und Auftragsverwaltung",
|
||||
"Mit der Kunden- und Auftragsverwaltung haben Sie alle Kontaktdaten und Auftragsdetails stets im Blick."),
|
||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, "Auftragserstellung",
|
||||
"Stellen Sie mit wenigen Mausklicks Aufträge ins System ein und legen Sie fest, welcher Mitarbeiter welchen Transportauftrag abarbeiten soll."));
|
||||
featuresGrid.add(
|
||||
createFeatureCard(VaadinIcon.COG,
|
||||
getTranslation("dashboard.feature.setup.title"),
|
||||
getTranslation("dashboard.feature.setup.desc")),
|
||||
createFeatureCard(VaadinIcon.USERS,
|
||||
getTranslation("dashboard.feature.customers.title"),
|
||||
getTranslation("dashboard.feature.customers.desc")),
|
||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT,
|
||||
getTranslation("dashboard.feature.jobs.title"),
|
||||
getTranslation("dashboard.feature.jobs.desc")));
|
||||
|
||||
systemSection.add(systemTitle, systemIntro, featuresGrid);
|
||||
return systemSection;
|
||||
@@ -160,13 +164,11 @@ public class AuthenticatedStartView extends VerticalLayout {
|
||||
appSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
appSection.getStyle().set("background-color", "var(--lumo-contrast-5pct)");
|
||||
|
||||
H2 appTitle = new H2("Die App");
|
||||
H2 appTitle = new H2(getTranslation("dashboard.app.title"));
|
||||
appTitle.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
appTitle.getStyle().set("text-align", "center");
|
||||
|
||||
Paragraph appDescription = new Paragraph(
|
||||
"Mit unserer mobilen App bleiben Sie auch unterwegs immer über Ihre Aufträge informiert "
|
||||
+ "und können wichtige Aufgaben direkt vom Smartphone aus erledigen.");
|
||||
Paragraph appDescription = new Paragraph(getTranslation("dashboard.app.description"));
|
||||
appDescription.getStyle().set("text-align", "center");
|
||||
appDescription.getStyle().set("max-width", "600px");
|
||||
|
||||
@@ -188,7 +190,7 @@ public class AuthenticatedStartView extends VerticalLayout {
|
||||
footerContent.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
footerContent.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
|
||||
Paragraph copyright = new Paragraph("© 2024 VotianLT. Alle Rechte vorbehalten.");
|
||||
Paragraph copyright = new Paragraph(getTranslation("dashboard.footer.copyright"));
|
||||
copyright.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
copyright.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
copyright.getStyle().set("margin", "0");
|
||||
@@ -198,4 +200,9 @@ public class AuthenticatedStartView extends VerticalLayout {
|
||||
|
||||
return footer;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.dashboard");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.vaadin.flow.component.notification.Notification;
|
||||
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.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
@@ -44,11 +44,10 @@ import com.vaadin.flow.component.html.IFrame;
|
||||
import com.vaadin.flow.server.StreamResource;
|
||||
import com.vaadin.flow.server.VaadinSession;
|
||||
|
||||
@PageTitle("Rechnung erstellen")
|
||||
@Route(value = "create_invoice", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed({ "USER" })
|
||||
@Slf4j
|
||||
public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter<String> {
|
||||
public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter<String>, HasDynamicTitle {
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final ServiceRepository serviceRepository;
|
||||
@@ -125,14 +124,14 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
loadJob(jobId);
|
||||
} catch (Exception e) {
|
||||
log.error("Fehler beim Parsen der Job-ID: " + jobIdHex, e);
|
||||
add(new Span("Ungültige Auftrags-ID"));
|
||||
add(new Span(getTranslation("createinvoice.error.invalidid")));
|
||||
}
|
||||
}
|
||||
|
||||
public void loadJob(ObjectId jobId) {
|
||||
currentJob = jobRepository.findById(jobId).orElse(null);
|
||||
if (currentJob == null) {
|
||||
add(new Span("Auftrag nicht gefunden"));
|
||||
add(new Span(getTranslation("createinvoice.error.notfound")));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -143,7 +142,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
removeAll();
|
||||
|
||||
// Title
|
||||
H2 title = new H2("Rechnung erstellen für Auftrag " + currentJob.getJobNumber());
|
||||
H2 title = new H2(getTranslation("createinvoice.title", currentJob.getJobNumber()));
|
||||
add(title);
|
||||
|
||||
// Load previously selected services from job
|
||||
@@ -168,7 +167,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
add(summarySection);
|
||||
|
||||
// Create Invoice Button
|
||||
Button createInvoiceButton = new Button("Rechnung erstellen");
|
||||
Button createInvoiceButton = new Button(getTranslation("createinvoice.button.create"));
|
||||
createInvoiceButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
createInvoiceButton.addClickListener(e -> createInvoice());
|
||||
createInvoiceButton.getStyle().set("margin-bottom", "15px");
|
||||
@@ -181,7 +180,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
.set("border-radius", "var(--lumo-border-radius-m)").set("padding", "var(--lumo-space-m)")
|
||||
.set("margin-bottom", "var(--lumo-space-m)").set("width", "100%").set("box-sizing", "border-box");
|
||||
|
||||
H3 sectionTitle = new H3("Auftragsdetails");
|
||||
H3 sectionTitle = new H3(getTranslation("createinvoice.section.job"));
|
||||
section.add(sectionTitle);
|
||||
|
||||
// Job information
|
||||
@@ -189,11 +188,11 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
jobInfo.setSpacing(true);
|
||||
jobInfo.setWidthFull();
|
||||
|
||||
jobInfo.add(new HorizontalLayout(new Span("Auftragsnummer:"), new Span(currentJob.getJobNumber())));
|
||||
jobInfo.add(new HorizontalLayout(new Span("Kunde:"),
|
||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.jobnumber")), new Span(currentJob.getJobNumber())));
|
||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.customer")),
|
||||
new Span(extractCompanyName(currentJob.getCustomerSelection()))));
|
||||
jobInfo.add(new HorizontalLayout(new Span("Status:"), new Span(currentJob.getStatus().toString())));
|
||||
jobInfo.add(new HorizontalLayout(new Span("Preis:"), new Span(currentJob.getPrice() + " €")));
|
||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.status")), new Span(currentJob.getStatus().toString())));
|
||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.price")), new Span(currentJob.getPrice() + " €")));
|
||||
|
||||
section.add(jobInfo);
|
||||
return section;
|
||||
@@ -206,7 +205,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
.set("margin-bottom", "var(--lumo-space-m)").set("width", "100%").set("box-sizing", "border-box")
|
||||
.set("background-color", "var(--lumo-primary-color-10pct)");
|
||||
|
||||
H3 sectionTitle = new H3("Streckeninformation");
|
||||
H3 sectionTitle = new H3(getTranslation("createinvoice.section.route"));
|
||||
sectionTitle.getStyle().set("color", "var(--lumo-primary-text-color)");
|
||||
section.add(sectionTitle);
|
||||
|
||||
@@ -216,14 +215,14 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
|
||||
Double distance = currentJob.getRouteDistanceKm();
|
||||
if (distance != null) {
|
||||
routeInfo.add(new HorizontalLayout(new Span("Berechnete Entfernung:"),
|
||||
routeInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.route.distance")),
|
||||
new Span(String.format("%.1f km", distance))));
|
||||
}
|
||||
|
||||
Integer durationSeconds = currentJob.getRouteDurationSeconds();
|
||||
if (durationSeconds != null && durationSeconds > 0) {
|
||||
String formattedDuration = formatDuration(durationSeconds);
|
||||
routeInfo.add(new HorizontalLayout(new Span("Geschätzte Fahrtzeit:"),
|
||||
routeInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.route.duration")),
|
||||
new Span(formattedDuration)));
|
||||
}
|
||||
|
||||
@@ -237,7 +236,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
.set("border-radius", "var(--lumo-border-radius-m)").set("padding", "var(--lumo-space-m)")
|
||||
.set("margin-bottom", "var(--lumo-space-m)").set("width", "100%").set("box-sizing", "border-box");
|
||||
|
||||
H3 sectionTitle = new H3("Leistungen");
|
||||
H3 sectionTitle = new H3(getTranslation("createinvoice.section.services"));
|
||||
servicesSection.add(sectionTitle);
|
||||
|
||||
// Create grid with read-only rows
|
||||
@@ -251,19 +250,19 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
return row.getService().getName();
|
||||
}
|
||||
return "";
|
||||
}).setHeader("Leistung").setAutoWidth(true).setFlexGrow(2);
|
||||
}).setHeader(getTranslation("createinvoice.column.service")).setAutoWidth(true).setFlexGrow(2);
|
||||
|
||||
// Calculation basis column (read-only)
|
||||
servicesGrid.addColumn(row -> {
|
||||
if (row.getService() != null && row.getService().getCalculationBasis() != null) {
|
||||
return switch (row.getService().getCalculationBasis()) {
|
||||
case DISTANCE -> "Gefahrene Kilometer";
|
||||
case TIME -> "Zeit";
|
||||
case FLAT_RATE -> "Pauschal";
|
||||
case DISTANCE -> getTranslation("addjob.services.basis.distance");
|
||||
case TIME -> getTranslation("addjob.services.basis.time");
|
||||
case FLAT_RATE -> getTranslation("addjob.services.basis.flatrate");
|
||||
};
|
||||
}
|
||||
return "";
|
||||
}).setHeader("Berechnungsgrundlage").setAutoWidth(true).setFlexGrow(1);
|
||||
}).setHeader(getTranslation("createinvoice.column.basis")).setAutoWidth(true).setFlexGrow(1);
|
||||
|
||||
// Price column (read-only)
|
||||
servicesGrid.addColumn(row -> {
|
||||
@@ -274,7 +273,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}).setHeader("Preis").setAutoWidth(true).setFlexGrow(1).setKey("price");
|
||||
}).setHeader(getTranslation("common.price")).setAutoWidth(true).setFlexGrow(1).setKey("price");
|
||||
|
||||
servicesGrid.setItems(gridRows);
|
||||
servicesSection.add(servicesGrid);
|
||||
@@ -292,7 +291,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
.set("border-radius", "var(--lumo-border-radius-m)").set("padding", "var(--lumo-space-m)")
|
||||
.set("margin-bottom", "var(--lumo-space-m)").set("width", "100%").set("box-sizing", "border-box");
|
||||
|
||||
H3 sectionTitle = new H3("Zusammenfassung");
|
||||
H3 sectionTitle = new H3(getTranslation("createinvoice.section.summary"));
|
||||
section.add(sectionTitle);
|
||||
|
||||
// Calculate totals
|
||||
@@ -306,14 +305,14 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
summaryInfo.setWidthFull();
|
||||
|
||||
// Show only net sum, VAT sums, and total amount without individual services
|
||||
summaryInfo.add(new HorizontalLayout(new Span("Nettosumme:"),
|
||||
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.net")),
|
||||
new Span(netAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
||||
summaryInfo
|
||||
.add(new HorizontalLayout(
|
||||
new Span("Mehrwertsteuer ("
|
||||
+ vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%):"),
|
||||
new Span(getTranslation("createinvoice.summary.vat",
|
||||
vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP).toString())),
|
||||
new Span(vatAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
||||
summaryInfo.add(new HorizontalLayout(new Span("Gesamtbetrag (brutto):"),
|
||||
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.total")),
|
||||
new Span(totalAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
||||
|
||||
section.add(summaryInfo);
|
||||
@@ -399,7 +398,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
|
||||
private void createInvoice() {
|
||||
if (getSelectedServices().isEmpty()) {
|
||||
Notification.show("Bitte wählen Sie mindestens eine Leistung aus", 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.noservices"), 3000, Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -412,7 +411,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
.flatMap(auth -> userRepository.findByEmail(auth.getUsername()));
|
||||
|
||||
if (currentUserOpt.isEmpty()) {
|
||||
Notification.show("Fehler: Benutzer nicht gefunden", 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.nouser"), 3000, Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -421,16 +420,16 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
// Load invoice template from service
|
||||
Optional<InvoiceTemplate> templateOpt = invoiceTemplateService.getTemplateByUserId(currentUser.getId().toString());
|
||||
if (templateOpt.isEmpty()) {
|
||||
Notification.show("Fehler: Kein Rechnungstemplate im Profil hinterlegt", 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000, Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String templateData = templateOpt.get().getTemplateData();
|
||||
System.out.println("DEBUG CreateInvoiceView: Template data length: " + (templateData != null ? templateData.length() : 0));
|
||||
System.out.println("DEBUG CreateInvoiceView: Template data preview: " + (templateData != null ? templateData.substring(0, Math.min(200, templateData.length())) : "null"));
|
||||
|
||||
|
||||
if (templateData == null || templateData.isBlank()) {
|
||||
Notification.show("Fehler: Kein Rechnungstemplate im Profil hinterlegt", 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000, Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -443,7 +442,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.error("Fehler beim Erstellen der Rechnung", ex);
|
||||
Notification.show("Fehler beim Erstellen der Rechnung: " + ex.getMessage(), 5000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,11 +588,11 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
pdfFrame.getStyle().set("border", "none");
|
||||
|
||||
// Close button
|
||||
Button closeButton = new Button("Schließen", e -> pdfDialog.close());
|
||||
Button closeButton = new Button(getTranslation("button.close"), e -> pdfDialog.close());
|
||||
closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
// Download button
|
||||
Button downloadButton = new Button("Herunterladen", e -> {
|
||||
Button downloadButton = new Button(getTranslation("button.download"), e -> {
|
||||
getElement()
|
||||
.executeJs("const link = document.createElement('a');" +
|
||||
"link.href = 'data:application/pdf;base64," + base64Pdf + "';" +
|
||||
@@ -617,4 +616,9 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
return String.format("%d Min.", minutes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.invoice.create");
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.html.H2;
|
||||
import com.vaadin.flow.component.html.Main;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.votianlt.model.Customer;
|
||||
@@ -19,32 +19,31 @@ import java.time.Clock;
|
||||
import static com.vaadin.flow.spring.data.VaadinSpringDataHelpers.toSpringPageRequest;
|
||||
|
||||
@Route(value = "customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Kunden")
|
||||
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Kunden")
|
||||
public class CustomersView extends Main {
|
||||
public class CustomersView extends Main implements HasDynamicTitle {
|
||||
final TextField description;
|
||||
final Button createBtn;
|
||||
final Grid<Customer> todoGrid;
|
||||
|
||||
public CustomersView(CustomerService todoService, Clock clock) {
|
||||
description = new TextField();
|
||||
description.setPlaceholder("Suche");
|
||||
description.setPlaceholder(getTranslation("jobs.filter.search"));
|
||||
description.setMaxLength(255);
|
||||
description.setMinWidth("20em");
|
||||
|
||||
createBtn = new Button("Kunde anlegen", event -> addCustomer());
|
||||
createBtn = new Button(getTranslation("addcustomer.button.submit"), event -> addCustomer());
|
||||
createBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
todoGrid = new Grid<>();
|
||||
todoGrid.setItems(query -> todoService.list(toSpringPageRequest(query)).stream());
|
||||
todoGrid.addColumn(Customer::getCompanyName).setHeader("Firmenname");
|
||||
todoGrid.addColumn(Customer::getCompanyName).setHeader(getTranslation("customers.column.company"));
|
||||
todoGrid.setSizeFull();
|
||||
|
||||
setSizeFull();
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||
|
||||
H2 title = new H2("Aufträge");
|
||||
H2 title = new H2(getTranslation("customers.title"));
|
||||
add(title);
|
||||
|
||||
add(todoGrid);
|
||||
@@ -53,4 +52,9 @@ public class CustomersView extends Main {
|
||||
private void addCustomer() {
|
||||
UI.getCurrent().navigate("add_customer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.customers");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.vaadin.flow.data.binder.Binder;
|
||||
import com.vaadin.flow.data.binder.ValidationException;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.AppUser;
|
||||
import de.assecutor.votianlt.pages.service.AppUserService;
|
||||
@@ -24,23 +24,22 @@ import jakarta.annotation.security.RolesAllowed;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@PageTitle("App-Nutzer bearbeiten")
|
||||
@Route(value = "edit-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed({ "USER", "ADMIN" })
|
||||
public class EditAppUserView extends VerticalLayout implements HasUrlParameter<String> {
|
||||
public class EditAppUserView extends VerticalLayout implements HasUrlParameter<String>, HasDynamicTitle {
|
||||
|
||||
private final AppUserService appUserService;
|
||||
private AppUser appUser;
|
||||
private final Binder<AppUser> binder = new Binder<>(AppUser.class);
|
||||
|
||||
// Form fields
|
||||
private final TextField designationField = new TextField("Bezeichnung (HH H 000)");
|
||||
private final TextField firstnameField = new TextField("Vorname");
|
||||
private final TextField lastnameField = new TextField("Nachname");
|
||||
private final TextField phoneField = new TextField("Telefon (Mobil)");
|
||||
private final TextField emailField = new TextField("E-Mail-Adresse");
|
||||
private final PasswordField changePasswordField = new PasswordField("Passwort ändern");
|
||||
private final PasswordField confirmChangePasswordField = new PasswordField("Passwort ändern wiederholen");
|
||||
// Form fields - labels set in constructor
|
||||
private final TextField designationField = new TextField();
|
||||
private final TextField firstnameField = new TextField();
|
||||
private final TextField lastnameField = new TextField();
|
||||
private final TextField phoneField = new TextField();
|
||||
private final TextField emailField = new TextField();
|
||||
private final PasswordField changePasswordField = new PasswordField();
|
||||
private final PasswordField confirmChangePasswordField = new PasswordField();
|
||||
|
||||
@Autowired
|
||||
public EditAppUserView(AppUserService appUserService) {
|
||||
@@ -49,6 +48,15 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
setPadding(true);
|
||||
setSpacing(true);
|
||||
|
||||
// Set field labels via i18n
|
||||
designationField.setLabel(getTranslation("addappuser.designation"));
|
||||
firstnameField.setLabel(getTranslation("profile.firstname"));
|
||||
lastnameField.setLabel(getTranslation("profile.lastname"));
|
||||
phoneField.setLabel(getTranslation("addappuser.phone"));
|
||||
emailField.setLabel(getTranslation("profile.email"));
|
||||
changePasswordField.setLabel(getTranslation("editappuser.password.change"));
|
||||
confirmChangePasswordField.setLabel(getTranslation("editappuser.password.change.confirm"));
|
||||
|
||||
// Center content vertically
|
||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
@@ -68,10 +76,10 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
header.setSpacing(true);
|
||||
|
||||
H2 title = new H2("App-Nutzer bearbeiten");
|
||||
H2 title = new H2(getTranslation("editappuser.title"));
|
||||
title.getStyle().set("margin", "0");
|
||||
|
||||
Button backButton = new Button("Zurück", new Icon(VaadinIcon.ARROW_LEFT));
|
||||
Button backButton = new Button(getTranslation("button.back"), new Icon(VaadinIcon.ARROW_LEFT));
|
||||
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
backButton.addClickListener(e -> navigateBack());
|
||||
|
||||
@@ -101,11 +109,9 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
|
||||
// Configure password fields
|
||||
changePasswordField.setWidthFull();
|
||||
changePasswordField.setPlaceholder("Leer lassen, wenn nicht ändern");
|
||||
changePasswordField.setPlaceholder(getTranslation("editappuser.password.placeholder"));
|
||||
confirmChangePasswordField.setWidthFull();
|
||||
confirmChangePasswordField.setPlaceholder("Leer lassen, wenn nicht ändern");
|
||||
|
||||
// Configure device dropdown
|
||||
confirmChangePasswordField.setPlaceholder(getTranslation("editappuser.password.placeholder"));
|
||||
|
||||
// Add fields to form
|
||||
formLayout.add(designationField);
|
||||
@@ -122,10 +128,10 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
buttonLayout.setWidthFull();
|
||||
|
||||
Button saveButton = new Button("Speichern", e -> saveAppUser());
|
||||
Button saveButton = new Button(getTranslation("button.savechanges"), e -> saveAppUser());
|
||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
Button deleteButton = new Button("Löschen", e -> deleteAppUser());
|
||||
Button deleteButton = new Button(getTranslation("button.delete"), e -> deleteAppUser());
|
||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||
|
||||
buttonLayout.add(saveButton, deleteButton);
|
||||
@@ -158,7 +164,7 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
binder.readBean(appUser);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Notification.show("Ungültige App-Nutzer-ID", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.invalid.id"), 3000, Notification.Position.MIDDLE);
|
||||
navigateBack();
|
||||
}
|
||||
}
|
||||
@@ -185,7 +191,7 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
// Passwords match, set new password for hashing
|
||||
appUser.setPassword(newPassword);
|
||||
} else {
|
||||
Notification.show("Passwörter stimmen nicht überein", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000, Notification.Position.MIDDLE);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -194,10 +200,10 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
}
|
||||
|
||||
appUserService.updateAppUser(appUser);
|
||||
Notification.show("App-Nutzer erfolgreich gespeichert", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.saved"), 3000, Notification.Position.MIDDLE);
|
||||
navigateBack();
|
||||
} catch (ValidationException e) {
|
||||
Notification.show("Bitte überprüfen Sie die Eingaben", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.check"), 3000, Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,18 +216,18 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
boolean confirmPasswordFilled = confirmPassword != null && !confirmPassword.trim().isEmpty();
|
||||
|
||||
if (newPasswordFilled && !confirmPasswordFilled) {
|
||||
Notification.show("Bitte bestätigen Sie das neue Passwort", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.password.confirm"), 3000, Notification.Position.MIDDLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newPasswordFilled && confirmPasswordFilled) {
|
||||
Notification.show("Bitte geben Sie das neue Passwort ein", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.password.enter"), 3000, Notification.Position.MIDDLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If both are filled, they must match
|
||||
if (newPasswordFilled && confirmPasswordFilled && newPassword != null && !newPassword.equals(confirmPassword)) {
|
||||
Notification.show("Passwörter stimmen nicht überein", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000, Notification.Position.MIDDLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -231,20 +237,20 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
private void deleteAppUser() {
|
||||
// Show confirmation dialog
|
||||
com.vaadin.flow.component.dialog.Dialog confirmDialog = new com.vaadin.flow.component.dialog.Dialog();
|
||||
confirmDialog.add("Möchten Sie diesen App-Nutzer wirklich löschen?");
|
||||
confirmDialog.add(getTranslation("editappuser.dialog.delete.text"));
|
||||
|
||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||
Button confirmDeleteButton = new Button("Ja, löschen", e -> {
|
||||
Button confirmDeleteButton = new Button(getTranslation("editappuser.dialog.delete.confirm"), e -> {
|
||||
if (appUser != null && appUser.getId() != null) {
|
||||
appUserService.deleteById(appUser.getId());
|
||||
Notification.show("App-Nutzer erfolgreich gelöscht", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.deleted"), 3000, Notification.Position.MIDDLE);
|
||||
confirmDialog.close();
|
||||
navigateBack();
|
||||
}
|
||||
});
|
||||
confirmDeleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||
|
||||
Button cancelDeleteButton = new Button("Abbrechen", e -> confirmDialog.close());
|
||||
Button cancelDeleteButton = new Button(getTranslation("button.cancel"), e -> confirmDialog.close());
|
||||
|
||||
buttonLayout.add(confirmDeleteButton, cancelDeleteButton);
|
||||
buttonLayout.setSpacing(true);
|
||||
@@ -256,4 +262,9 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
private void navigateBack() {
|
||||
getUI().ifPresent(ui -> ui.navigate("app-user"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.appuser.edit");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.data.binder.Binder;
|
||||
import com.vaadin.flow.data.binder.ValidationException;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.Customer;
|
||||
import de.assecutor.votianlt.pages.service.CustomerService;
|
||||
@@ -23,28 +23,27 @@ import org.bson.types.ObjectId;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
|
||||
@PageTitle("Kunde bearbeiten")
|
||||
@Route(value = "edit-customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed({ "USER", "ADMIN" })
|
||||
public class EditCustomerView extends VerticalLayout implements HasUrlParameter<String> {
|
||||
public class EditCustomerView extends VerticalLayout implements HasUrlParameter<String>, HasDynamicTitle {
|
||||
|
||||
private final CustomerService customerService;
|
||||
private Customer customer;
|
||||
private final Binder<Customer> binder = new Binder<>(Customer.class);
|
||||
|
||||
// Form fields
|
||||
private final TextField titleField = new TextField("Titel");
|
||||
private final TextField companyNameField = new TextField("Firmenname");
|
||||
private final TextField firstnameField = new TextField("Vorname");
|
||||
private final TextField lastNameField = new TextField("Nachname");
|
||||
private final TextField telephoneField = new TextField("Telefon");
|
||||
private final TextField faxField = new TextField("Fax");
|
||||
private final EmailField mailField = new EmailField("E-Mail");
|
||||
private final TextField streetField = new TextField("Straße");
|
||||
private final TextField houseNumberField = new TextField("Hausnummer");
|
||||
private final TextField addressAdditionField = new TextField("Adresszusatz");
|
||||
private final TextField zipField = new TextField("PLZ");
|
||||
private final TextField cityField = new TextField("Stadt");
|
||||
// Form fields - labels set in constructor via setLabel()
|
||||
private final TextField titleField = new TextField();
|
||||
private final TextField companyNameField = new TextField();
|
||||
private final TextField firstnameField = new TextField();
|
||||
private final TextField lastNameField = new TextField();
|
||||
private final TextField telephoneField = new TextField();
|
||||
private final TextField faxField = new TextField();
|
||||
private final EmailField mailField = new EmailField();
|
||||
private final TextField streetField = new TextField();
|
||||
private final TextField houseNumberField = new TextField();
|
||||
private final TextField addressAdditionField = new TextField();
|
||||
private final TextField zipField = new TextField();
|
||||
private final TextField cityField = new TextField();
|
||||
|
||||
@Autowired
|
||||
public EditCustomerView(CustomerService customerService) {
|
||||
@@ -53,6 +52,20 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
setPadding(true);
|
||||
setSpacing(true);
|
||||
|
||||
// Set field labels via i18n
|
||||
titleField.setLabel(getTranslation("addjob.address.salutation"));
|
||||
companyNameField.setLabel(getTranslation("profile.company"));
|
||||
firstnameField.setLabel(getTranslation("profile.firstname"));
|
||||
lastNameField.setLabel(getTranslation("profile.lastname"));
|
||||
telephoneField.setLabel(getTranslation("profile.phone"));
|
||||
faxField.setLabel(getTranslation("profile.fax"));
|
||||
mailField.setLabel(getTranslation("profile.email"));
|
||||
streetField.setLabel(getTranslation("profile.street"));
|
||||
houseNumberField.setLabel(getTranslation("profile.housenr"));
|
||||
addressAdditionField.setLabel(getTranslation("profile.addressadd"));
|
||||
zipField.setLabel(getTranslation("profile.zip"));
|
||||
cityField.setLabel(getTranslation("profile.city"));
|
||||
|
||||
// Center content vertically
|
||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
@@ -68,7 +81,7 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
contentContainer.getStyle().set("box-shadow", "var(--lumo-box-shadow-s)");
|
||||
|
||||
// Header
|
||||
H2 header = new H2("Kunde bearbeiten");
|
||||
H2 header = new H2(getTranslation("editcustomer.title"));
|
||||
header.getStyle().set("text-align", "center");
|
||||
header.getStyle().set("margin", "0");
|
||||
contentContainer.add(header);
|
||||
@@ -98,11 +111,11 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
buttonLayout.setWidthFull();
|
||||
|
||||
Button saveButton = new Button("Speichern", e -> saveCustomer());
|
||||
Button saveButton = new Button(getTranslation("button.savechanges"), e -> saveCustomer());
|
||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
Button cancelButton = new Button("Abbrechen", e -> navigateBack());
|
||||
Button deleteButton = new Button("Löschen", e -> deleteCustomer());
|
||||
Button cancelButton = new Button(getTranslation("button.cancel"), e -> navigateBack());
|
||||
Button deleteButton = new Button(getTranslation("button.delete"), e -> deleteCustomer());
|
||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||
|
||||
buttonLayout.add(saveButton, cancelButton, deleteButton);
|
||||
@@ -138,7 +151,7 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
customer = customerService.findById(customerId);
|
||||
|
||||
if (customer == null) {
|
||||
Notification.show("Kunde nicht gefunden", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editcustomer.notification.notfound"), 3000, Notification.Position.MIDDLE);
|
||||
navigateBack();
|
||||
return;
|
||||
}
|
||||
@@ -147,7 +160,7 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
binder.readBean(customer);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Notification.show("Ungültige Kunden-ID", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editcustomer.notification.invalid.id"), 3000, Notification.Position.MIDDLE);
|
||||
navigateBack();
|
||||
}
|
||||
}
|
||||
@@ -156,29 +169,29 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
try {
|
||||
binder.writeBean(customer);
|
||||
customerService.save(customer);
|
||||
Notification.show("Kunde erfolgreich gespeichert", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editcustomer.notification.saved"), 3000, Notification.Position.MIDDLE);
|
||||
navigateBack();
|
||||
} catch (ValidationException e) {
|
||||
Notification.show("Bitte überprüfen Sie die Eingaben", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editcustomer.notification.check"), 3000, Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteCustomer() {
|
||||
// Show confirmation dialog
|
||||
Dialog confirmDialog = new Dialog();
|
||||
confirmDialog.add("Möchten Sie diesen Kunden wirklich löschen?");
|
||||
confirmDialog.add(getTranslation("editcustomer.dialog.delete.text"));
|
||||
|
||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||
Button confirmDeleteButton = new Button("Ja, löschen", e -> {
|
||||
Button confirmDeleteButton = new Button(getTranslation("editcustomer.dialog.delete.confirm"), e -> {
|
||||
if (customer != null && customer.getId() != null) {
|
||||
Notification.show("Kunde erfolgreich gelöscht", 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editcustomer.notification.deleted"), 3000, Notification.Position.MIDDLE);
|
||||
confirmDialog.close();
|
||||
navigateBack();
|
||||
}
|
||||
});
|
||||
confirmDeleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||
|
||||
Button cancelDeleteButton = new Button("Abbrechen", e -> confirmDialog.close());
|
||||
Button cancelDeleteButton = new Button(getTranslation("button.cancel"), e -> confirmDialog.close());
|
||||
|
||||
buttonLayout.add(confirmDeleteButton, cancelDeleteButton);
|
||||
buttonLayout.setSpacing(true);
|
||||
@@ -190,4 +203,9 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
private void navigateBack() {
|
||||
getUI().ifPresent(ui -> ui.navigate("customers"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.customer.edit");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,9 @@ import com.vaadin.flow.component.upload.Upload;
|
||||
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
|
||||
import com.vaadin.flow.data.binder.Binder;
|
||||
import com.vaadin.flow.data.validator.EmailValidator;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.model.Service;
|
||||
import de.assecutor.votianlt.model.UserInvoiceData;
|
||||
@@ -43,6 +44,7 @@ import de.assecutor.votianlt.repository.ServiceRepository;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.service.CustomerInvoiceService;
|
||||
import de.assecutor.votianlt.service.InvoiceTemplateService;
|
||||
import de.assecutor.votianlt.service.LanguageService;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.combobox.ComboBox;
|
||||
import com.vaadin.flow.component.textfield.NumberField;
|
||||
@@ -51,11 +53,10 @@ import com.vaadin.flow.component.dependency.JsModule;
|
||||
import com.vaadin.flow.component.ClientCallable;
|
||||
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" })
|
||||
@JsModule("./invoice-generator/profile-invoice-generator.js")
|
||||
public class EditProfileView extends HorizontalLayout {
|
||||
public class EditProfileView extends HorizontalLayout implements HasDynamicTitle {
|
||||
private final TextField prefixField;
|
||||
private final TextField ustIdField;
|
||||
private final TextField taxNumberField;
|
||||
@@ -78,14 +79,20 @@ public class EditProfileView extends HorizontalLayout {
|
||||
private final ServiceRepository serviceRepository;
|
||||
private Grid<Service> servicesGrid;
|
||||
|
||||
// Store the original language from the database
|
||||
private final Language originalLanguage;
|
||||
|
||||
public EditProfileView(UserService userService, UserInvoiceDataService userInvoiceDataService,
|
||||
CustomerInvoiceService customerInvoiceService, InvoiceTemplateService invoiceTemplateService,
|
||||
SecurityService securityService, ServiceRepository serviceRepository) {
|
||||
LanguageService languageService, SecurityService securityService, ServiceRepository serviceRepository) {
|
||||
this.userInvoiceDataService = userInvoiceDataService;
|
||||
this.customerInvoiceService = customerInvoiceService;
|
||||
this.invoiceTemplateService = invoiceTemplateService;
|
||||
this.currentUser = securityService.getCurrentDatabaseUser();
|
||||
this.serviceRepository = serviceRepository;
|
||||
|
||||
// Store the original language before any changes
|
||||
this.originalLanguage = this.currentUser != null ? this.currentUser.getLanguage() : Language.DE;
|
||||
setSizeFull();
|
||||
setPadding(true);
|
||||
setSpacing(true);
|
||||
@@ -113,53 +120,53 @@ public class EditProfileView extends HorizontalLayout {
|
||||
form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
|
||||
|
||||
// Firmenfelder
|
||||
TextField companyField = new TextField("Firma");
|
||||
companyField.addBlurListener(e -> validateField(companyField, "Firma ist ein Pflichtfeld"));
|
||||
TextField companyField = new TextField(getTranslation("profile.company"));
|
||||
companyField.addBlurListener(e -> validateField(companyField, getTranslation("profile.validation.company")));
|
||||
|
||||
TextField companyAddField = new TextField("Firmenzusatz");
|
||||
TextField companyAddField = new TextField(getTranslation("profile.companyadd"));
|
||||
|
||||
TextField firstnameField = new TextField("Vorname");
|
||||
firstnameField.addBlurListener(e -> validateField(firstnameField, "Vorname ist ein Pflichtfeld"));
|
||||
TextField firstnameField = new TextField(getTranslation("profile.firstname"));
|
||||
firstnameField.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
||||
|
||||
TextField lastnameField = new TextField("Nachname");
|
||||
lastnameField.addBlurListener(e -> validateField(lastnameField, "Nachname ist ein Pflichtfeld"));
|
||||
TextField lastnameField = new TextField(getTranslation("profile.lastname"));
|
||||
lastnameField.addBlurListener(e -> validateField(lastnameField, getTranslation("profile.validation.lastname")));
|
||||
|
||||
TextField phoneField = new TextField("Telefonnummer");
|
||||
phoneField.addBlurListener(e -> validateField(phoneField, "Telefonnummer ist ein Pflichtfeld"));
|
||||
TextField phoneField = new TextField(getTranslation("profile.phone"));
|
||||
phoneField.addBlurListener(e -> validateField(phoneField, getTranslation("profile.validation.phone")));
|
||||
|
||||
TextField faxField = new TextField("Telefon (Fax)");
|
||||
TextField mobileField = new TextField("Telefon (Mobil)");
|
||||
TextField faxField = new TextField(getTranslation("profile.fax"));
|
||||
TextField mobileField = new TextField(getTranslation("profile.mobile"));
|
||||
|
||||
EmailField emailField = new EmailField("E-Mail-Adresse (Login)*");
|
||||
EmailField emailField = new EmailField(getTranslation("profile.email.required"));
|
||||
emailField.addBlurListener(e -> validateEmailField(emailField));
|
||||
|
||||
TextField streetField = new TextField("Straße");
|
||||
streetField.addBlurListener(e -> validateField(streetField, "Straße ist ein Pflichtfeld"));
|
||||
TextField streetField = new TextField(getTranslation("profile.street"));
|
||||
streetField.addBlurListener(e -> validateField(streetField, getTranslation("profile.validation.street")));
|
||||
|
||||
TextField houseNumberField = new TextField("Hausnr");
|
||||
houseNumberField.addBlurListener(e -> validateField(houseNumberField, "Hausnummer ist ein Pflichtfeld"));
|
||||
TextField houseNumberField = new TextField(getTranslation("profile.housenr"));
|
||||
houseNumberField.addBlurListener(e -> validateField(houseNumberField, getTranslation("profile.validation.housenr")));
|
||||
|
||||
TextField addressAddField = new TextField("Adresszusatz");
|
||||
TextField addressAddField = new TextField(getTranslation("profile.addressadd"));
|
||||
|
||||
TextField zipField = new TextField("Postleitzahl");
|
||||
zipField.addBlurListener(e -> validateField(zipField, "Postleitzahl ist ein Pflichtfeld"));
|
||||
TextField zipField = new TextField(getTranslation("profile.zip"));
|
||||
zipField.addBlurListener(e -> validateField(zipField, getTranslation("profile.validation.zip")));
|
||||
|
||||
TextField cityField = new TextField("Stadt");
|
||||
cityField.addBlurListener(e -> validateField(cityField, "Stadt ist ein Pflichtfeld"));
|
||||
TextField cityField = new TextField(getTranslation("profile.city"));
|
||||
cityField.addBlurListener(e -> validateField(cityField, getTranslation("profile.validation.city")));
|
||||
|
||||
// Abweichende Rechnungsadresse
|
||||
Checkbox diffInvoiceAddress = new Checkbox("Abweichende Rechnungsadresse");
|
||||
Checkbox diffInvoiceAddress = new Checkbox(getTranslation("profile.diffinvoice"));
|
||||
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");
|
||||
TextField invCompanyField = new TextField(getTranslation("profile.company"));
|
||||
TextField invCompanyAddField = new TextField(getTranslation("profile.companyadd"));
|
||||
TextField invFirstnameField = new TextField(getTranslation("profile.firstname"));
|
||||
TextField invLastnameField = new TextField(getTranslation("profile.lastname"));
|
||||
TextField invStreetField = new TextField(getTranslation("profile.street"));
|
||||
TextField invHouseNumberField = new TextField(getTranslation("profile.housenr"));
|
||||
TextField invAddressAddField = new TextField(getTranslation("profile.addressadd"));
|
||||
TextField invZipField = new TextField(getTranslation("profile.zip"));
|
||||
TextField invCityField = new TextField(getTranslation("profile.city"));
|
||||
invCompanyField.setEnabled(false);
|
||||
invCompanyAddField.setEnabled(false);
|
||||
invFirstnameField.setEnabled(false);
|
||||
@@ -220,22 +227,22 @@ public class EditProfileView extends HorizontalLayout {
|
||||
cityField.setRequiredIndicatorVisible(true);
|
||||
|
||||
// Hauptadresse binden
|
||||
binder.forField(companyField).asRequired("Firma ist erforderlich").bind(User::getCompany, User::setCompany);
|
||||
binder.forField(companyField).asRequired(getTranslation("profile.validation.company.required")).bind(User::getCompany, User::setCompany);
|
||||
binder.forField(companyAddField).bind(User::getCompanyAddition, User::setCompanyAddition);
|
||||
binder.forField(streetField).asRequired("Straße ist erforderlich").bind(User::getStreet, User::setStreet);
|
||||
binder.forField(houseNumberField).asRequired("Hausnummer ist erforderlich").bind(User::getHouseNumber,
|
||||
binder.forField(streetField).asRequired(getTranslation("profile.validation.street.required")).bind(User::getStreet, User::setStreet);
|
||||
binder.forField(houseNumberField).asRequired(getTranslation("profile.validation.housenr.required")).bind(User::getHouseNumber,
|
||||
User::setHouseNumber);
|
||||
binder.forField(addressAddField).bind(User::getAddressAddition, User::setAddressAddition);
|
||||
binder.forField(zipField).asRequired("Postleitzahl ist erforderlich").bind(User::getZip, User::setZip);
|
||||
binder.forField(cityField).asRequired("Stadt ist erforderlich").bind(User::getCity, User::setCity);
|
||||
binder.forField(zipField).asRequired(getTranslation("profile.validation.zip.required")).bind(User::getZip, User::setZip);
|
||||
binder.forField(cityField).asRequired(getTranslation("profile.validation.city.required")).bind(User::getCity, User::setCity);
|
||||
|
||||
// Personendaten binden
|
||||
binder.forField(firstnameField).asRequired("Vorname ist erforderlich").bind(User::getFirstname,
|
||||
binder.forField(firstnameField).asRequired(getTranslation("profile.validation.firstname.required")).bind(User::getFirstname,
|
||||
User::setFirstname);
|
||||
binder.forField(lastnameField).asRequired("Nachname ist erforderlich").bind(User::getName, User::setName);
|
||||
binder.forField(phoneField).asRequired("Telefonnummer ist erforderlich").bind(User::getPhone, User::setPhone);
|
||||
binder.forField(emailField).asRequired("E-Mail ist erforderlich")
|
||||
.withValidator(new EmailValidator("Ungültige E-Mail-Adresse")).bind(User::getEmail, User::setEmail);
|
||||
binder.forField(lastnameField).asRequired(getTranslation("profile.validation.lastname.required")).bind(User::getName, User::setName);
|
||||
binder.forField(phoneField).asRequired(getTranslation("profile.validation.phone.required")).bind(User::getPhone, User::setPhone);
|
||||
binder.forField(emailField).asRequired(getTranslation("profile.validation.email.required"))
|
||||
.withValidator(new EmailValidator(getTranslation("profile.validation.email.invalid"))).bind(User::getEmail, User::setEmail);
|
||||
// Optionale Felder
|
||||
binder.forField(mobileField).bind(User::getPhone2, User::setPhone2);
|
||||
binder.forField(faxField).bind(User::getFax, User::setFax);
|
||||
@@ -272,7 +279,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
form.add(invAddressAddField, 2);
|
||||
form.add(invZipField, invCityField);
|
||||
|
||||
tabSheet.add("Stammdaten", form);
|
||||
tabSheet.add(getTranslation("profile.basicdata"), form);
|
||||
|
||||
// Karte (2. Tab)
|
||||
Span coordsLabel = new Span("53°36'25.1\"N 10°06'46.9\"E");
|
||||
@@ -287,7 +294,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
mapTab.setPadding(false);
|
||||
mapTab.setSpacing(true);
|
||||
mapTab.add(coordsLabel, mapDiv);
|
||||
tabSheet.add("Karte", mapTab);
|
||||
tabSheet.add(getTranslation("profile.map"), mapTab);
|
||||
// Dritter Tab: Rechnungserstellung
|
||||
VerticalLayout billingTab = new VerticalLayout();
|
||||
billingTab.setWidthFull();
|
||||
@@ -307,7 +314,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
pdfFrame = new IFrame();
|
||||
|
||||
// Nur die Checkbox "Rechnungslegung über votianLT"
|
||||
billingEnabled = new Checkbox("Rechnungslegung über votianLT");
|
||||
billingEnabled = new Checkbox(getTranslation("profile.billing.enabled"));
|
||||
billingEnabled.setValue(true); // Standardmäßig aktiviert
|
||||
billingTab.add(billingEnabled);
|
||||
|
||||
@@ -368,25 +375,25 @@ public class EditProfileView extends HorizontalLayout {
|
||||
actionLayout.setSpacing(true);
|
||||
actionLayout.getStyle().set("margin-top", "var(--lumo-space-s)");
|
||||
|
||||
Button clearButton = new Button("Leeren", new Icon(VaadinIcon.TRASH));
|
||||
Button clearButton = new Button(getTranslation("button.clear"), new Icon(VaadinIcon.TRASH));
|
||||
clearButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||
clearButton.addClickListener(e -> {
|
||||
getElement().executeJs("if (window.clearProfileCanvas) { window.clearProfileCanvas(); }");
|
||||
Notification.show("Canvas wurde geleert", 2000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("profile.canvas.cleared"), 2000, Notification.Position.BOTTOM_CENTER);
|
||||
});
|
||||
|
||||
Button previewPdfButton = new Button("Vorschau", new Icon(VaadinIcon.EYE));
|
||||
Button previewPdfButton = new Button(getTranslation("button.preview"), new Icon(VaadinIcon.EYE));
|
||||
previewPdfButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||
previewPdfButton.addClickListener(e -> generatePreviewPdfFromProfile());
|
||||
|
||||
Button saveTemplateButton = new Button("Template speichern", new Icon(VaadinIcon.DOWNLOAD));
|
||||
Button saveTemplateButton = new Button(getTranslation("button.savetemplate"), new Icon(VaadinIcon.DOWNLOAD));
|
||||
saveTemplateButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
saveTemplateButton.addClickListener(e -> {
|
||||
getElement().executeJs(
|
||||
"if (window.getProfileCanvasData) { return JSON.stringify(window.getProfileCanvasData()); } else { return null; }")
|
||||
.then(result -> {
|
||||
if (result == null) {
|
||||
Notification.show("Fehler: Canvas-Daten konnten nicht gelesen werden", 3000,
|
||||
Notification.show(getTranslation("profile.canvas.read.error"), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
return;
|
||||
}
|
||||
@@ -399,10 +406,10 @@ public class EditProfileView extends HorizontalLayout {
|
||||
templateData = result.toString();
|
||||
}
|
||||
invoiceTemplateService.saveTemplate(currentUser.getId().toString(), templateData);
|
||||
Notification.show("Template erfolgreich gespeichert", 3000,
|
||||
Notification.show(getTranslation("profile.template.saved"), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Speichern: " + ex.getMessage(), 5000,
|
||||
Notification.show(getTranslation("profile.save.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
});
|
||||
@@ -414,7 +421,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
// Initialen Zustand setzen (sichtbar da checkbox standardmäßig true)
|
||||
actionLayout.setVisible(true);
|
||||
|
||||
tabSheet.add("Rechnungserstellung", billingTab);
|
||||
tabSheet.add(getTranslation("profile.invoicecreation"), billingTab);
|
||||
|
||||
// Sichtbarkeit des Invoice Generators an Checkbox binden
|
||||
billingEnabled.addValueChangeListener(e -> {
|
||||
@@ -429,7 +436,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
// Initialize invoice generator when the billing tab is selected
|
||||
// Also register this view instance for JavaScript callbacks
|
||||
tabSheet.addSelectedChangeListener(e -> {
|
||||
if ("Rechnungserstellung".equals(e.getSelectedTab().getLabel())) {
|
||||
if (getTranslation("profile.invoicecreation").equals(e.getSelectedTab().getLabel())) {
|
||||
getElement().executeJs("window.invoiceGeneratorViewProfile = $0;" + "setTimeout(function() { "
|
||||
+ " if (window.initProfileInvoiceGenerator) { " + " window.initProfileInvoiceGenerator(); "
|
||||
+ " console.log('Canvas initialized, now loading template...'); "
|
||||
@@ -444,17 +451,17 @@ public class EditProfileView extends HorizontalLayout {
|
||||
switches.setPadding(false);
|
||||
switches.setSpacing(true);
|
||||
|
||||
Checkbox digitalProcess = new Checkbox("Digitale Abwicklung per App");
|
||||
Checkbox digitalProcess = new Checkbox(getTranslation("profile.settings.digitalprocess"));
|
||||
digitalProcess.setValue(currentUser.isDigitalProcessingEnabled());
|
||||
|
||||
Span digitalProcessInfo = new Span("Aktiviert die digitale Auftragsabwicklung über die mobile App");
|
||||
Span digitalProcessInfo = new Span(getTranslation("profile.settings.digitalprocess.info"));
|
||||
digitalProcessInfo.getStyle().set("font-size", "var(--lumo-font-size-s)")
|
||||
.set("color", "var(--lumo-secondary-text-color)").set("margin-left", "var(--lumo-space-xl)");
|
||||
|
||||
Checkbox locateAppUser = new Checkbox("App-Nutzer orten");
|
||||
Checkbox locateAppUser = new Checkbox(getTranslation("profile.settings.locateappuser"));
|
||||
locateAppUser.setValue(currentUser.isLocationTrackingEnabled());
|
||||
|
||||
Span locateAppUserInfo = new Span("Ermöglicht die Ortung von App-Nutzern während der Auftragsausführung");
|
||||
Span locateAppUserInfo = new Span(getTranslation("profile.settings.locateappuser.info"));
|
||||
locateAppUserInfo.getStyle().set("font-size", "var(--lumo-font-size-s)")
|
||||
.set("color", "var(--lumo-secondary-text-color)").set("margin-left", "var(--lumo-space-xl)");
|
||||
|
||||
@@ -468,7 +475,42 @@ public class EditProfileView extends HorizontalLayout {
|
||||
});
|
||||
|
||||
switches.add(digitalProcess, digitalProcessInfo, locateAppUser, locateAppUserInfo);
|
||||
tabSheet.add("Einstellungen", switches);
|
||||
tabSheet.add(getTranslation("profile.settings"), switches);
|
||||
|
||||
// Spracheinstellung unter Einstellungen
|
||||
HorizontalLayout languageLayout = new HorizontalLayout();
|
||||
languageLayout.setWidthFull();
|
||||
languageLayout.setSpacing(true);
|
||||
languageLayout.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
|
||||
// Sprache Label
|
||||
Span languageLabel = new Span(getTranslation("profile.language"));
|
||||
languageLabel.getStyle().set("font-size", "var(--lumo-font-size-m)").set("font-weight", "500");
|
||||
|
||||
// ComboBox mit Flaggen-Icons
|
||||
ComboBox<Language> languageCombo = new ComboBox<>();
|
||||
languageCombo.setWidth("200px");
|
||||
languageCombo.setItems(Language.values());
|
||||
languageCombo.setItemLabelGenerator(language -> {
|
||||
// Flaggen-Emoji für jede Sprache
|
||||
String flag = switch (language) {
|
||||
case DE -> "🇩🇪 ";
|
||||
case EN -> "🇬🇧 ";
|
||||
case FR -> "🇫🇷 ";
|
||||
case ES -> "🇪🇸 ";
|
||||
};
|
||||
return flag + language.getDisplayName();
|
||||
});
|
||||
languageCombo.setValue(currentUser.getLanguage());
|
||||
|
||||
// Store the selected language temporarily, but don't save yet
|
||||
languageCombo.addValueChangeListener(e -> {
|
||||
// Language will be saved when the user clicks save button
|
||||
currentUser.setLanguage(e.getValue());
|
||||
});
|
||||
|
||||
languageLayout.add(languageLabel, languageCombo);
|
||||
switches.add(languageLayout);
|
||||
|
||||
// Sicherheit-Tab (2FA, Passwort, Konto)
|
||||
VerticalLayout securityTab = new VerticalLayout();
|
||||
@@ -476,7 +518,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
securityTab.setSpacing(true);
|
||||
|
||||
// 2-Faktor Auth
|
||||
Checkbox twoFactor = new Checkbox("2-Faktor-Authentifizierung");
|
||||
Checkbox twoFactor = new Checkbox(getTranslation("profile.security.twofactor"));
|
||||
twoFactor.setValue(currentUser.isTwoFactorEnabled());
|
||||
twoFactor.addValueChangeListener(e -> currentUser.setTwoFactorEnabled(e.getValue()));
|
||||
Icon twoFactorInfo = VaadinIcon.QUESTION_CIRCLE_O.create();
|
||||
@@ -484,30 +526,30 @@ public class EditProfileView extends HorizontalLayout {
|
||||
HorizontalLayout twoFactorLayout = new HorizontalLayout(twoFactor, twoFactorInfo);
|
||||
twoFactorLayout.setAlignItems(Alignment.CENTER);
|
||||
|
||||
Span twoFactorDescription = new Span("Bei Aktivierung wird bei jeder Anmeldung ein Code per E-Mail gesendet");
|
||||
Span twoFactorDescription = new Span(getTranslation("profile.security.twofactor.info"));
|
||||
twoFactorDescription.getStyle().set("font-size", "var(--lumo-font-size-s)")
|
||||
.set("color", "var(--lumo-secondary-text-color)").set("margin-left", "var(--lumo-space-xl)");
|
||||
|
||||
securityTab.add(twoFactorLayout, twoFactorDescription);
|
||||
|
||||
// Passwort ändern Button
|
||||
Button changePassword = new Button("Passwort ändern");
|
||||
Button changePassword = new Button(getTranslation("button.changepassword"));
|
||||
changePassword.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
securityTab.add(changePassword);
|
||||
|
||||
// Benutzerkonto löschen Button
|
||||
Button deleteAccount = new Button("Benutzerkonto löschen");
|
||||
Button deleteAccount = new Button(getTranslation("button.deleteaccount"));
|
||||
deleteAccount.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||
securityTab.add(deleteAccount);
|
||||
|
||||
tabSheet.add("Konto", securityTab);
|
||||
tabSheet.add(getTranslation("profile.account"), securityTab);
|
||||
|
||||
// Leistungskatalog Tab
|
||||
VerticalLayout servicesTab = createServicesTab();
|
||||
tabSheet.add("Leistungskatalog", servicesTab);
|
||||
tabSheet.add(getTranslation("profile.services"), servicesTab);
|
||||
|
||||
// Profil speichern Button (unten rechts)
|
||||
Button saveProfile = new Button("Profiländerungen speichern");
|
||||
Button saveProfile = new Button(getTranslation("button.save"));
|
||||
saveProfile.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
saveProfile.addClickListener(e -> {
|
||||
// Validate all required fields first
|
||||
@@ -515,7 +557,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
emailField, streetField, houseNumberField, zipField, cityField);
|
||||
|
||||
if (!isValid) {
|
||||
Notification.show("Bitte füllen Sie alle Pflichtfelder korrekt aus", 3000,
|
||||
Notification.show(getTranslation("profile.validation.required.fill"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
return;
|
||||
}
|
||||
@@ -529,14 +571,17 @@ public class EditProfileView extends HorizontalLayout {
|
||||
binder.writeBean(currentUser);
|
||||
userService.save(currentUser);
|
||||
saveInvoiceData();
|
||||
Notification.show("Profil gespeichert", 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("profile.saved"), 3000, Notification.Position.BOTTOM_END);
|
||||
|
||||
// Always reload if billing status changed to update the sidebar
|
||||
if (oldBillingStatus != newBillingStatus) {
|
||||
// Check if language changed (compare with original language from database)
|
||||
boolean languageChanged = originalLanguage != currentUser.getLanguage();
|
||||
|
||||
// Reload if billing status changed or language changed
|
||||
if (oldBillingStatus != newBillingStatus || languageChanged) {
|
||||
UI.getCurrent().getPage().reload();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Speichern: " + ex.getMessage(), 4000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("profile.save.error", ex.getMessage()), 4000, Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -594,7 +639,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Log error or show notification
|
||||
Notification.show("Fehler bei PDF-Generierung: " + e.getMessage(), 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("profile.pdf.error", e.getMessage()), 3000, Notification.Position.BOTTOM_END);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,10 +798,10 @@ public class EditProfileView extends HorizontalLayout {
|
||||
String value = emailField.getValue();
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
emailField.setInvalid(true);
|
||||
emailField.setErrorMessage("E-Mail-Adresse ist ein Pflichtfeld");
|
||||
emailField.setErrorMessage(getTranslation("profile.validation.email.required"));
|
||||
} else if (!value.contains("@") || !value.contains(".")) {
|
||||
emailField.setInvalid(true);
|
||||
emailField.setErrorMessage("Bitte geben Sie eine gültige E-Mail-Adresse ein");
|
||||
emailField.setErrorMessage(getTranslation("profile.validation.email.invalid"));
|
||||
} else {
|
||||
emailField.setInvalid(false);
|
||||
emailField.setErrorMessage("");
|
||||
@@ -766,15 +811,15 @@ public class EditProfileView extends HorizontalLayout {
|
||||
private boolean validateAllProfileFields(TextField companyField, TextField firstnameField, TextField lastnameField,
|
||||
TextField phoneField, EmailField emailField, TextField streetField, TextField houseNumberField,
|
||||
TextField zipField, TextField cityField) {
|
||||
validateField(companyField, "Firma ist ein Pflichtfeld");
|
||||
validateField(firstnameField, "Vorname ist ein Pflichtfeld");
|
||||
validateField(lastnameField, "Nachname ist ein Pflichtfeld");
|
||||
validateField(phoneField, "Telefonnummer ist ein Pflichtfeld");
|
||||
validateField(companyField, getTranslation("profile.validation.company"));
|
||||
validateField(firstnameField, getTranslation("profile.validation.firstname"));
|
||||
validateField(lastnameField, getTranslation("profile.validation.lastname"));
|
||||
validateField(phoneField, getTranslation("profile.validation.phone"));
|
||||
validateEmailField(emailField);
|
||||
validateField(streetField, "Straße ist ein Pflichtfeld");
|
||||
validateField(houseNumberField, "Hausnummer ist ein Pflichtfeld");
|
||||
validateField(zipField, "Postleitzahl ist ein Pflichtfeld");
|
||||
validateField(cityField, "Stadt ist ein Pflichtfeld");
|
||||
validateField(streetField, getTranslation("profile.validation.street"));
|
||||
validateField(houseNumberField, getTranslation("profile.validation.housenr"));
|
||||
validateField(zipField, getTranslation("profile.validation.zip"));
|
||||
validateField(cityField, getTranslation("profile.validation.city"));
|
||||
|
||||
return !companyField.isInvalid() && !firstnameField.isInvalid() && !lastnameField.isInvalid()
|
||||
&& !phoneField.isInvalid() && !emailField.isInvalid() && !streetField.isInvalid()
|
||||
@@ -788,7 +833,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
"if (window.getProfileCanvasData) { return window.getProfileCanvasData(); } else { return null; }")
|
||||
.then(result -> {
|
||||
if (result == null) {
|
||||
Notification.show("Fehler: Canvas-Daten konnten nicht gelesen werden", 3000,
|
||||
Notification.show(getTranslation("profile.canvas.read.error"), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
return;
|
||||
}
|
||||
@@ -804,12 +849,12 @@ public class EditProfileView extends HorizontalLayout {
|
||||
currentUser);
|
||||
showPdfInDialog(pdfBytes);
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Generieren der Vorschau: " + ex.getMessage(), 3000,
|
||||
Notification.show(getTranslation("profile.pdf.preview.error", ex.getMessage()), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler: " + ex.getMessage(), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("profile.error", ex.getMessage()), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,7 +865,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
// Create dialog
|
||||
Dialog pdfDialog = new Dialog();
|
||||
pdfDialog.setHeaderTitle("PDF Vorschau");
|
||||
pdfDialog.setHeaderTitle(getTranslation("profile.pdf.preview"));
|
||||
pdfDialog.setWidth("90vw");
|
||||
pdfDialog.setHeight("90vh");
|
||||
|
||||
@@ -840,11 +885,11 @@ public class EditProfileView extends HorizontalLayout {
|
||||
pdfContainer.add(pdfFrame);
|
||||
|
||||
// Close button
|
||||
Button closeButton = new Button("Schließen", e -> pdfDialog.close());
|
||||
Button closeButton = new Button(getTranslation("button.close"), e -> pdfDialog.close());
|
||||
closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
// Download button
|
||||
Button downloadButton = new Button("Herunterladen", e -> {
|
||||
Button downloadButton = new Button(getTranslation("button.download"), e -> {
|
||||
getElement()
|
||||
.executeJs("const link = document.createElement('a');" + "link.href = 'data:application/pdf;base64,"
|
||||
+ base64Pdf + "';" + "link.download = 'vorschau.pdf';" + "link.click();");
|
||||
@@ -864,7 +909,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
panel.setHeightFull();
|
||||
|
||||
// Bereich 1: Meine Stammdaten
|
||||
Span invoiceHeader = new Span("Meine Stammdaten");
|
||||
Span invoiceHeader = new Span(getTranslation("profile.invoice.masterdata"));
|
||||
invoiceHeader.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-m)")
|
||||
.set("margin-top", "var(--lumo-space-s)");
|
||||
|
||||
@@ -876,67 +921,67 @@ public class EditProfileView extends HorizontalLayout {
|
||||
String email = safe(currentUser.getEmail());
|
||||
String phone = safe(currentUser.getPhone());
|
||||
|
||||
Div senderCompany = createVariableTemplate("Firma", VaadinIcon.OFFICE, "masterdata.company_name",
|
||||
company.isEmpty() ? "Ihre Firma" : company);
|
||||
Div senderName = createVariableTemplate("Name", VaadinIcon.USER, "masterdata.contact_name",
|
||||
fullName.trim().isEmpty() ? "Ihr Name" : fullName.trim());
|
||||
Div senderAddress = createVariableTemplate("Straße", VaadinIcon.MAP_MARKER, "masterdata.street",
|
||||
street.trim().isEmpty() ? "Ihre Straße" : street.trim());
|
||||
Div senderCity = createVariableTemplate("Ort", VaadinIcon.BUILDING, "masterdata.city",
|
||||
city.trim().isEmpty() ? "PLZ Ort" : city.trim());
|
||||
Div senderEmail = createVariableTemplate("E-Mail", VaadinIcon.ENVELOPE, "masterdata.email",
|
||||
email.isEmpty() ? "ihre@email.de" : email);
|
||||
Div senderPhone = createVariableTemplate("Telefon", VaadinIcon.PHONE, "masterdata.phone",
|
||||
phone.isEmpty() ? "Ihre Telefonnummer" : phone);
|
||||
Div senderCompany = createVariableTemplate(getTranslation("profile.company"), VaadinIcon.OFFICE, "masterdata.company_name",
|
||||
company.isEmpty() ? getTranslation("profile.invoice.placeholder.company") : company);
|
||||
Div senderName = createVariableTemplate(getTranslation("profile.invoice.name"), VaadinIcon.USER, "masterdata.contact_name",
|
||||
fullName.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.name") : fullName.trim());
|
||||
Div senderAddress = createVariableTemplate(getTranslation("profile.street"), VaadinIcon.MAP_MARKER, "masterdata.street",
|
||||
street.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.street") : street.trim());
|
||||
Div senderCity = createVariableTemplate(getTranslation("profile.invoice.city"), VaadinIcon.BUILDING, "masterdata.city",
|
||||
city.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.city") : city.trim());
|
||||
Div senderEmail = createVariableTemplate(getTranslation("profile.invoice.email"), VaadinIcon.ENVELOPE, "masterdata.email",
|
||||
email.isEmpty() ? getTranslation("profile.invoice.placeholder.email") : email);
|
||||
Div senderPhone = createVariableTemplate(getTranslation("profile.invoice.phone"), VaadinIcon.PHONE, "masterdata.phone",
|
||||
phone.isEmpty() ? getTranslation("profile.invoice.placeholder.phone") : phone);
|
||||
|
||||
// Bereich 2: Leistungen
|
||||
Span servicesHeader = new Span("Leistungen");
|
||||
Span servicesHeader = new Span(getTranslation("profile.services.label"));
|
||||
servicesHeader.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-m)")
|
||||
.set("margin-top", "var(--lumo-space-m)");
|
||||
|
||||
// Leistungen als draggable Variable
|
||||
Div servicesListBlock = createServicesVariableTemplate("Leistungen auflisten", VaadinIcon.LIST, "services.list",
|
||||
Div servicesListBlock = createServicesVariableTemplate(getTranslation("profile.invoice.services.list"), VaadinIcon.LIST, "services.list",
|
||||
"Artikel 1: 100,00 €\nArtikel 2: 50,00 €");
|
||||
Div servicesNetBlock = createServicesVariableTemplate("Nettosumme", VaadinIcon.COIN_PILES, "services.net_total",
|
||||
Div servicesNetBlock = createServicesVariableTemplate(getTranslation("profile.invoice.net"), VaadinIcon.COIN_PILES, "services.net_total",
|
||||
"150,00 €");
|
||||
Div servicesVatBlock = createServicesVariableTemplate("Umsatzsteuer", VaadinIcon.COIN_PILES,
|
||||
Div servicesVatBlock = createServicesVariableTemplate(getTranslation("profile.invoice.vat"), VaadinIcon.COIN_PILES,
|
||||
"services.vat_total", "28,50 €");
|
||||
Div servicesGrossBlock = createServicesVariableTemplate("Bruttosumme", VaadinIcon.MONEY, "services.gross_total",
|
||||
Div servicesGrossBlock = createServicesVariableTemplate(getTranslation("profile.invoice.gross"), VaadinIcon.MONEY, "services.gross_total",
|
||||
"178,50 €");
|
||||
|
||||
// Bereich 3: Kundendaten
|
||||
Span customerHeader = new Span("Kundendaten");
|
||||
Span customerHeader = new Span(getTranslation("profile.invoice.customerdata"));
|
||||
customerHeader.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-m)")
|
||||
.set("margin-top", "var(--lumo-space-m)");
|
||||
|
||||
// Kundendaten als Variablen (grün hinterlegt)
|
||||
Div customerCompany = createCustomerVariableTemplate("Kunde Firma", VaadinIcon.OFFICE, "customer.company_name",
|
||||
Div customerCompany = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.company"), VaadinIcon.OFFICE, "customer.company_name",
|
||||
"Kundenfirma GmbH");
|
||||
Div customerName = createCustomerVariableTemplate("Kunde Name", VaadinIcon.USER, "customer.contact_name",
|
||||
Div customerName = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.name"), VaadinIcon.USER, "customer.contact_name",
|
||||
"Erika Mustermann");
|
||||
Div customerAddress = createCustomerVariableTemplate("Kunde Straße", VaadinIcon.MAP_MARKER, "customer.street",
|
||||
Div customerAddress = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.street"), VaadinIcon.MAP_MARKER, "customer.street",
|
||||
"Kundenstraße 456");
|
||||
Div customerCity = createCustomerVariableTemplate("Kunde Ort", VaadinIcon.BUILDING, "customer.city",
|
||||
Div customerCity = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.city"), VaadinIcon.BUILDING, "customer.city",
|
||||
"54321 Kundenstadt");
|
||||
Div customerEmail = createCustomerVariableTemplate("Kunde E-Mail", VaadinIcon.ENVELOPE, "customer.email",
|
||||
Div customerEmail = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.email"), VaadinIcon.ENVELOPE, "customer.email",
|
||||
"kunde@beispiel.de");
|
||||
Div customerPhone = createCustomerVariableTemplate("Kunde Telefon", VaadinIcon.PHONE, "customer.phone",
|
||||
Div customerPhone = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.phone"), VaadinIcon.PHONE, "customer.phone",
|
||||
"0987 654321");
|
||||
|
||||
// Bereich 2: Freie Elemente
|
||||
Span freeHeader = new Span("Freie Elemente");
|
||||
Span freeHeader = new Span(getTranslation("profile.invoice.free.elements"));
|
||||
freeHeader.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-m)").set("margin-top",
|
||||
"var(--lumo-space-m)");
|
||||
|
||||
// Draggable Templates
|
||||
Div textBlock = createDraggableTemplate("Textfeld", VaadinIcon.TEXT_LABEL, "text");
|
||||
Div headerBlock = createDraggableTemplate("Überschrift", VaadinIcon.HEADER, "header");
|
||||
Div dateBlock = createDraggableTemplate("Datum", VaadinIcon.CALENDAR, "date");
|
||||
Div customerBlock = createDraggableTemplate("Kundeninfo", VaadinIcon.USER, "customer");
|
||||
Div companyBlock = createDraggableTemplate("Firmeninfo", VaadinIcon.WORKPLACE, "company");
|
||||
Div amountBlock = createDraggableTemplate("Betrag", VaadinIcon.COIN_PILES, "amount");
|
||||
Div lineBlock = createDraggableTemplate("Linie", VaadinIcon.LINE_V, "line");
|
||||
Div imageBlock = createDraggableTemplate("Bild", VaadinIcon.PICTURE, "image");
|
||||
Div textBlock = createDraggableTemplate(getTranslation("profile.invoice.element.text"), VaadinIcon.TEXT_LABEL, "text");
|
||||
Div headerBlock = createDraggableTemplate(getTranslation("profile.invoice.element.header"), VaadinIcon.HEADER, "header");
|
||||
Div dateBlock = createDraggableTemplate(getTranslation("profile.invoice.element.date"), VaadinIcon.CALENDAR, "date");
|
||||
Div customerBlock = createDraggableTemplate(getTranslation("profile.invoice.element.customer"), VaadinIcon.USER, "customer");
|
||||
Div companyBlock = createDraggableTemplate(getTranslation("profile.invoice.element.company"), VaadinIcon.WORKPLACE, "company");
|
||||
Div amountBlock = createDraggableTemplate(getTranslation("profile.invoice.element.amount"), VaadinIcon.COIN_PILES, "amount");
|
||||
Div lineBlock = createDraggableTemplate(getTranslation("profile.invoice.element.line"), VaadinIcon.LINE_V, "line");
|
||||
Div imageBlock = createDraggableTemplate(getTranslation("profile.invoice.element.image"), VaadinIcon.PICTURE, "image");
|
||||
|
||||
panel.add(invoiceHeader, senderCompany, senderName, senderAddress, senderCity, senderEmail, senderPhone,
|
||||
servicesHeader, servicesListBlock, servicesNetBlock, servicesVatBlock, servicesGrossBlock,
|
||||
@@ -1085,11 +1130,11 @@ public class EditProfileView extends HorizontalLayout {
|
||||
panel.setSpacing(true);
|
||||
panel.setHeightFull();
|
||||
|
||||
Span header = new Span("Eigenschaften");
|
||||
Span header = new Span(getTranslation("profile.invoice.properties"));
|
||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
||||
|
||||
Div infoText = new Div();
|
||||
infoText.setText("Klicken Sie auf ein Element im Canvas, um dessen Eigenschaften zu bearbeiten.");
|
||||
infoText.setText(getTranslation("profile.invoice.properties.info"));
|
||||
infoText.getStyle().set("color", "var(--lumo-secondary-text-color)").set("font-size",
|
||||
"var(--lumo-font-size-s)");
|
||||
|
||||
@@ -1104,15 +1149,15 @@ public class EditProfileView extends HorizontalLayout {
|
||||
getUI().ifPresent(ui -> ui.access(() -> {
|
||||
propertiesPanelProfile.removeAll();
|
||||
|
||||
Span header = new Span("Eigenschaften");
|
||||
Span header = new Span(getTranslation("profile.invoice.properties"));
|
||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
||||
|
||||
// Element Typ Anzeige
|
||||
String typeDisplay = "Typ: " + elementType;
|
||||
String typeDisplay = getTranslation("profile.invoice.type") + ": " + elementType;
|
||||
if (variable != null && !variable.isEmpty()) {
|
||||
typeDisplay += " (Variable)";
|
||||
typeDisplay += " (" + getTranslation("profile.invoice.variable") + ")";
|
||||
} else if (Boolean.TRUE.equals(isStatic)) {
|
||||
typeDisplay += " (Stammdaten)";
|
||||
typeDisplay += " (" + getTranslation("profile.basicdata") + ")";
|
||||
}
|
||||
Span typeLabel = new Span(typeDisplay);
|
||||
typeLabel.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
@@ -1121,7 +1166,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
// Variable anzeigen wenn vorhanden
|
||||
if (variable != null && !variable.isEmpty()) {
|
||||
TextField variableField = new TextField("Variable");
|
||||
TextField variableField = new TextField(getTranslation("profile.invoice.variable"));
|
||||
variableField.setValue(variable);
|
||||
variableField.setReadOnly(true);
|
||||
variableField.setWidthFull();
|
||||
@@ -1139,7 +1184,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
Upload upload = new Upload(buffer);
|
||||
upload.setAcceptedFileTypes("image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp");
|
||||
upload.setMaxFileSize(5 * 1024 * 1024); // 5 MB
|
||||
upload.setDropLabel(new Span("Bild hierher ziehen oder klicken"));
|
||||
upload.setDropLabel(new Span(getTranslation("profile.invoice.image.drop")));
|
||||
upload.setWidthFull();
|
||||
|
||||
upload.addSucceededListener(event -> {
|
||||
@@ -1154,15 +1199,15 @@ public class EditProfileView extends HorizontalLayout {
|
||||
getElement()
|
||||
.executeJs("if (window.updateProfileElementImage) { window.updateProfileElementImage('"
|
||||
+ elementId + "', $0); }", dataUrl);
|
||||
Notification.show("Bild erfolgreich hochgeladen", 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("profile.invoice.image.uploaded"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Hochladen: " + ex.getMessage(), 3000,
|
||||
Notification.show(getTranslation("profile.invoice.image.upload.error", ex.getMessage()), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
});
|
||||
|
||||
upload.addFileRejectedListener(event -> {
|
||||
Notification.show("Datei abgelehnt: " + event.getErrorMessage(), 3000,
|
||||
Notification.show(getTranslation("profile.invoice.file.rejected", event.getErrorMessage()), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
});
|
||||
|
||||
@@ -1171,13 +1216,13 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
// Text Feld (nur für Text-Elemente)
|
||||
if (!"line".equals(elementType) && !"image".equals(elementType)) {
|
||||
TextField textField = new TextField("Text");
|
||||
TextField textField = new TextField(getTranslation("profile.invoice.element.text"));
|
||||
textField.setValue(text != null ? text : "");
|
||||
textField.setWidthFull();
|
||||
// Statische Elemente können nicht editiert werden
|
||||
if (Boolean.TRUE.equals(isStatic)) {
|
||||
textField.setReadOnly(true);
|
||||
textField.setHelperText("Text kommt aus Ihren Stammdaten");
|
||||
textField.setHelperText(getTranslation("profile.invoice.text.from.masterdata"));
|
||||
} else {
|
||||
textField.addValueChangeListener(e -> {
|
||||
getElement()
|
||||
@@ -1189,7 +1234,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
}
|
||||
|
||||
// X Position
|
||||
TextField xField = new TextField("X Position");
|
||||
TextField xField = new TextField(getTranslation("profile.invoice.xposition"));
|
||||
xField.setValue(x != null ? String.valueOf(Math.round(x)) : "0");
|
||||
xField.setWidthFull();
|
||||
xField.addValueChangeListener(e -> {
|
||||
@@ -1205,7 +1250,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
propertiesPanelProfile.add(xField);
|
||||
|
||||
// Y Position
|
||||
TextField yField = new TextField("Y Position");
|
||||
TextField yField = new TextField(getTranslation("profile.invoice.yposition"));
|
||||
yField.setValue(y != null ? String.valueOf(Math.round(y)) : "0");
|
||||
yField.setWidthFull();
|
||||
yField.addValueChangeListener(e -> {
|
||||
@@ -1222,7 +1267,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
// Font Size (nur für Text-Elemente)
|
||||
if (!"line".equals(elementType) && !"image".equals(elementType)) {
|
||||
TextField fontSizeField = new TextField("Schriftgröße");
|
||||
TextField fontSizeField = new TextField(getTranslation("profile.invoice.fontsize"));
|
||||
fontSizeField.setValue(fontSize != null ? String.valueOf(fontSize) : "16");
|
||||
fontSizeField.setWidthFull();
|
||||
fontSizeField.addValueChangeListener(e -> {
|
||||
@@ -1242,7 +1287,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
colorContainer.getStyle().set("display", "flex").set("align-items", "center")
|
||||
.set("gap", "var(--lumo-space-s)").set("margin-top", "var(--lumo-space-s)");
|
||||
|
||||
Span colorLabel = new Span("Farbe");
|
||||
Span colorLabel = new Span(getTranslation("profile.invoice.color"));
|
||||
colorLabel.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
|
||||
Input colorPicker = new Input();
|
||||
@@ -1279,7 +1324,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
}
|
||||
|
||||
// Löschen Button
|
||||
Button deleteButton = new Button("Element löschen", new Icon(VaadinIcon.TRASH));
|
||||
Button deleteButton = new Button(getTranslation("profile.invoice.element.delete"), new Icon(VaadinIcon.TRASH));
|
||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||
deleteButton.setWidthFull();
|
||||
deleteButton.addClickListener(e -> {
|
||||
@@ -1299,11 +1344,11 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
propertiesPanelProfile.removeAll();
|
||||
|
||||
Span header = new Span("Eigenschaften");
|
||||
Span header = new Span(getTranslation("profile.invoice.properties"));
|
||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
||||
|
||||
Div infoText = new Div();
|
||||
infoText.setText("Klicken Sie auf ein Element im Canvas, um dessen Eigenschaften zu bearbeiten.");
|
||||
infoText.setText(getTranslation("profile.invoice.properties.info"));
|
||||
infoText.getStyle().set("color", "var(--lumo-secondary-text-color)").set("font-size",
|
||||
"var(--lumo-font-size-s)");
|
||||
|
||||
@@ -1401,12 +1446,12 @@ public class EditProfileView extends HorizontalLayout {
|
||||
servicesTab.setSpacing(true);
|
||||
|
||||
// Header
|
||||
Span header = new Span("Leistungskatalog");
|
||||
Span header = new Span(getTranslation("profile.services"));
|
||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)").set("margin-bottom",
|
||||
"var(--lumo-space-m)");
|
||||
|
||||
// Description
|
||||
Span description = new Span("Verwalten Sie hier Ihre Leistungen, die Sie Ihren Kunden anbieten.");
|
||||
Span description = new Span(getTranslation("profile.services.description"));
|
||||
description.getStyle().set("color", "var(--lumo-secondary-text-color)").set("font-size",
|
||||
"var(--lumo-font-size-s)");
|
||||
|
||||
@@ -1418,33 +1463,33 @@ public class EditProfileView extends HorizontalLayout {
|
||||
servicesGrid.setHeight("300px");
|
||||
|
||||
// Configure grid columns
|
||||
servicesGrid.addColumn(Service::getName).setHeader("Name").setSortable(true);
|
||||
servicesGrid.addColumn(Service::getName).setHeader(getTranslation("common.name")).setSortable(true);
|
||||
servicesGrid.addColumn(service -> {
|
||||
if (service.getCalculationBasis() != null) {
|
||||
return switch (service.getCalculationBasis()) {
|
||||
case DISTANCE -> "Gefahrene Kilometer";
|
||||
case TIME -> "Zeit";
|
||||
case FLAT_RATE -> "Pauschal";
|
||||
case DISTANCE -> getTranslation("profile.services.basis.distance");
|
||||
case TIME -> getTranslation("profile.services.basis.time");
|
||||
case FLAT_RATE -> getTranslation("profile.services.basis.flatrate");
|
||||
};
|
||||
}
|
||||
return "";
|
||||
}).setHeader("Berechnungsgrundlage").setSortable(true);
|
||||
}).setHeader(getTranslation("profile.services.basis")).setSortable(true);
|
||||
|
||||
servicesGrid.addColumn(service -> {
|
||||
if (service.getCalculationBasis() == Service.CalculationBasis.FLAT_RATE && service.getPrice() != null) {
|
||||
return service.getPrice().setScale(2, RoundingMode.HALF_UP) + " €";
|
||||
}
|
||||
return "Wird berechnet";
|
||||
}).setHeader("Preis").setSortable(true);
|
||||
return getTranslation("profile.services.calculated");
|
||||
}).setHeader(getTranslation("common.price")).setSortable(true);
|
||||
|
||||
servicesGrid.addColumn(service -> {
|
||||
if (service.getVatRate() != null) {
|
||||
return service.getVatRate().multiply(new BigDecimal("100")) + " %";
|
||||
}
|
||||
return "";
|
||||
}).setHeader("Mehrwertsteuersatz").setSortable(true);
|
||||
}).setHeader(getTranslation("profile.services.vatrate")).setSortable(true);
|
||||
|
||||
servicesGrid.addColumn(service -> service.isMandatory() ? "Ja" : "Nein").setHeader("Verpflichtend")
|
||||
servicesGrid.addColumn(service -> service.isMandatory() ? getTranslation("common.yes") : getTranslation("common.no")).setHeader(getTranslation("profile.services.mandatory"))
|
||||
.setSortable(true);
|
||||
|
||||
// Actions column with edit and delete buttons
|
||||
@@ -1465,12 +1510,12 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
actionsLayout.add(editButton, deleteButton);
|
||||
return actionsLayout;
|
||||
}).setHeader("Aktionen").setFlexGrow(0).setWidth("120px");
|
||||
}).setHeader(getTranslation("common.actions")).setFlexGrow(0).setWidth("120px");
|
||||
|
||||
servicesTab.add(servicesGrid);
|
||||
|
||||
// Add service button
|
||||
Button addServiceButton = new Button("Neue Leistung hinzufügen", new Icon(VaadinIcon.PLUS));
|
||||
Button addServiceButton = new Button(getTranslation("profile.services.add"), new Icon(VaadinIcon.PLUS));
|
||||
addServiceButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
addServiceButton.addClickListener(e -> openServiceDialog(null));
|
||||
|
||||
@@ -1490,7 +1535,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
List<Service> userServices = serviceRepository.findByUserId(currentUser.getId().toString());
|
||||
servicesGrid.setItems(userServices);
|
||||
} catch (Exception e) {
|
||||
Notification.show("Fehler beim Laden der Leistungen: " + e.getMessage(), 3000,
|
||||
Notification.show(getTranslation("profile.services.load.error", e.getMessage()), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
@@ -1500,7 +1545,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
*/
|
||||
private void openServiceDialog(Service service) {
|
||||
Dialog dialog = new Dialog();
|
||||
dialog.setHeaderTitle(service == null ? "Neue Leistung erstellen" : "Leistung bearbeiten");
|
||||
dialog.setHeaderTitle(service == null ? getTranslation("profile.services.dialog.create") : getTranslation("profile.services.dialog.edit"));
|
||||
dialog.setWidth("500px");
|
||||
|
||||
// Form layout
|
||||
@@ -1510,27 +1555,27 @@ public class EditProfileView extends HorizontalLayout {
|
||||
formLayout.setWidthFull();
|
||||
|
||||
// Name field
|
||||
TextField nameField = new TextField("Name");
|
||||
TextField nameField = new TextField(getTranslation("common.name"));
|
||||
nameField.setWidthFull();
|
||||
nameField.setRequired(true);
|
||||
nameField.setRequiredIndicatorVisible(true);
|
||||
|
||||
// Calculation basis combo box
|
||||
ComboBox<Service.CalculationBasis> calculationBasisCombo = new ComboBox<>("Berechnungsgrundlage");
|
||||
ComboBox<Service.CalculationBasis> calculationBasisCombo = new ComboBox<>(getTranslation("profile.services.basis"));
|
||||
calculationBasisCombo.setWidthFull();
|
||||
calculationBasisCombo.setItems(Service.CalculationBasis.values());
|
||||
calculationBasisCombo.setItemLabelGenerator(basis -> {
|
||||
return switch (basis) {
|
||||
case DISTANCE -> "Gefahrene Kilometer";
|
||||
case TIME -> "Zeit";
|
||||
case FLAT_RATE -> "Pauschal";
|
||||
case DISTANCE -> getTranslation("profile.services.basis.distance");
|
||||
case TIME -> getTranslation("profile.services.basis.time");
|
||||
case FLAT_RATE -> getTranslation("profile.services.basis.flatrate");
|
||||
};
|
||||
});
|
||||
calculationBasisCombo.setRequired(true);
|
||||
calculationBasisCombo.setRequiredIndicatorVisible(true);
|
||||
|
||||
// VAT rate field
|
||||
NumberField vatRateField = new NumberField("Mehrwertsteuersatz (%)");
|
||||
NumberField vatRateField = new NumberField(getTranslation("profile.services.vatrate.percent"));
|
||||
vatRateField.setWidthFull();
|
||||
vatRateField.setMin(0);
|
||||
vatRateField.setMax(100);
|
||||
@@ -1540,7 +1585,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
vatRateField.setRequiredIndicatorVisible(true);
|
||||
|
||||
// Mandatory checkbox
|
||||
Checkbox mandatoryCheckbox = new Checkbox("Verpflichtend");
|
||||
Checkbox mandatoryCheckbox = new Checkbox(getTranslation("profile.services.mandatory"));
|
||||
mandatoryCheckbox.setValue(false);
|
||||
|
||||
// Set values if editing existing service
|
||||
@@ -1554,7 +1599,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
}
|
||||
|
||||
// Price fields for different calculation bases
|
||||
NumberField flatRatePriceField = new NumberField("Pauschalpreis (€)");
|
||||
NumberField flatRatePriceField = new NumberField(getTranslation("profile.services.price.flatrate"));
|
||||
flatRatePriceField.setWidthFull();
|
||||
flatRatePriceField.setMin(0);
|
||||
flatRatePriceField.setStep(0.01);
|
||||
@@ -1562,7 +1607,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
flatRatePriceField.setRequired(true);
|
||||
flatRatePriceField.setRequiredIndicatorVisible(true);
|
||||
|
||||
NumberField distancePriceField = new NumberField("Preis pro Kilometer (€)");
|
||||
NumberField distancePriceField = new NumberField(getTranslation("profile.services.price.distance"));
|
||||
distancePriceField.setWidthFull();
|
||||
distancePriceField.setMin(0);
|
||||
distancePriceField.setStep(0.01);
|
||||
@@ -1570,7 +1615,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
distancePriceField.setRequired(true);
|
||||
distancePriceField.setRequiredIndicatorVisible(true);
|
||||
|
||||
NumberField timePriceField = new NumberField("Preis pro 15 Minuten (€)");
|
||||
NumberField timePriceField = new NumberField(getTranslation("profile.services.price.time"));
|
||||
timePriceField.setWidthFull();
|
||||
timePriceField.setMin(0);
|
||||
timePriceField.setStep(0.01);
|
||||
@@ -1632,10 +1677,10 @@ public class EditProfileView extends HorizontalLayout {
|
||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
|
||||
buttonLayout.setSpacing(true);
|
||||
|
||||
Button cancelButton = new Button("Abbrechen", e -> dialog.close());
|
||||
Button cancelButton = new Button(getTranslation("button.cancel"), e -> dialog.close());
|
||||
cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
Button saveButton = new Button("Speichern", e -> {
|
||||
Button saveButton = new Button(getTranslation("button.savechanges"), e -> {
|
||||
if (validateServiceForm(nameField, calculationBasisCombo, flatRatePriceField, distancePriceField,
|
||||
timePriceField, vatRateField, mandatoryCheckbox)) {
|
||||
// Get the appropriate price based on calculation basis
|
||||
@@ -1677,7 +1722,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
if (nameField.isEmpty()) {
|
||||
nameField.setInvalid(true);
|
||||
nameField.setErrorMessage("Name ist erforderlich");
|
||||
nameField.setErrorMessage(getTranslation("profile.services.validation.name"));
|
||||
isValid = false;
|
||||
} else {
|
||||
nameField.setInvalid(false);
|
||||
@@ -1685,7 +1730,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
if (calculationBasisCombo.isEmpty()) {
|
||||
calculationBasisCombo.setInvalid(true);
|
||||
calculationBasisCombo.setErrorMessage("Berechnungsgrundlage ist erforderlich");
|
||||
calculationBasisCombo.setErrorMessage(getTranslation("profile.services.validation.basis"));
|
||||
isValid = false;
|
||||
} else {
|
||||
calculationBasisCombo.setInvalid(false);
|
||||
@@ -1696,7 +1741,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
if (selectedBasis == Service.CalculationBasis.FLAT_RATE && flatRatePriceField.isVisible()) {
|
||||
if (flatRatePriceField.isEmpty() || flatRatePriceField.getValue() == null) {
|
||||
flatRatePriceField.setInvalid(true);
|
||||
flatRatePriceField.setErrorMessage("Pauschalpreis ist erforderlich");
|
||||
flatRatePriceField.setErrorMessage(getTranslation("profile.services.validation.flatrate"));
|
||||
isValid = false;
|
||||
} else {
|
||||
flatRatePriceField.setInvalid(false);
|
||||
@@ -1704,7 +1749,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
} else if (selectedBasis == Service.CalculationBasis.DISTANCE && distancePriceField.isVisible()) {
|
||||
if (distancePriceField.isEmpty() || distancePriceField.getValue() == null) {
|
||||
distancePriceField.setInvalid(true);
|
||||
distancePriceField.setErrorMessage("Preis pro Kilometer ist erforderlich");
|
||||
distancePriceField.setErrorMessage(getTranslation("profile.services.validation.distance"));
|
||||
isValid = false;
|
||||
} else {
|
||||
distancePriceField.setInvalid(false);
|
||||
@@ -1712,7 +1757,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
} else if (selectedBasis == Service.CalculationBasis.TIME && timePriceField.isVisible()) {
|
||||
if (timePriceField.isEmpty() || timePriceField.getValue() == null) {
|
||||
timePriceField.setInvalid(true);
|
||||
timePriceField.setErrorMessage("Preis pro 15 Minuten ist erforderlich");
|
||||
timePriceField.setErrorMessage(getTranslation("profile.services.validation.time"));
|
||||
isValid = false;
|
||||
} else {
|
||||
timePriceField.setInvalid(false);
|
||||
@@ -1721,7 +1766,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
if (vatRateField.isEmpty() || vatRateField.getValue() == null) {
|
||||
vatRateField.setInvalid(true);
|
||||
vatRateField.setErrorMessage("Mehrwertsteuersatz ist erforderlich");
|
||||
vatRateField.setErrorMessage(getTranslation("profile.services.validation.vatrate"));
|
||||
isValid = false;
|
||||
} else {
|
||||
vatRateField.setInvalid(false);
|
||||
@@ -1768,7 +1813,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
}
|
||||
|
||||
serviceRepository.save(service);
|
||||
Notification.show("Leistung erfolgreich gespeichert", 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("profile.services.saved"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
|
||||
// Refresh the grid by reloading services
|
||||
if (servicesGrid != null) {
|
||||
@@ -1776,7 +1821,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Notification.show("Fehler beim Speichern der Leistung: " + e.getMessage(), 5000,
|
||||
Notification.show(getTranslation("profile.services.save.error", e.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
@@ -1787,7 +1832,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
private void deleteService(Service service) {
|
||||
try {
|
||||
serviceRepository.delete(service);
|
||||
Notification.show("Leistung erfolgreich gelöscht", 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("profile.services.deleted"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
|
||||
// Refresh the grid by reloading services
|
||||
if (servicesGrid != null) {
|
||||
@@ -1795,8 +1840,13 @@ public class EditProfileView extends HorizontalLayout {
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Notification.show("Fehler beim Löschen der Leistung: " + e.getMessage(), 5000,
|
||||
Notification.show(getTranslation("profile.services.delete.error", e.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.profile.edit");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.PasswordField;
|
||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import de.assecutor.votianlt.pages.service.PasswordResetService;
|
||||
@@ -17,9 +17,8 @@ import de.assecutor.votianlt.pages.service.PasswordResetService;
|
||||
import java.util.Map;
|
||||
|
||||
@Route("forget-password")
|
||||
@PageTitle("Passwort zurücksetzen")
|
||||
@AnonymousAllowed
|
||||
public class ForgetPasswordView extends VerticalLayout implements BeforeEnterObserver {
|
||||
public class ForgetPasswordView extends VerticalLayout implements BeforeEnterObserver, HasDynamicTitle {
|
||||
|
||||
private final PasswordResetService passwordResetService;
|
||||
|
||||
@@ -91,4 +90,9 @@ public class ForgetPasswordView extends VerticalLayout implements BeforeEnterObs
|
||||
Notification.show("Token ungültig oder abgelaufen.", 3000, Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.password.forget");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,15 @@ 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.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.VaadinService;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import de.assecutor.votianlt.pages.service.PasswordResetService;
|
||||
|
||||
@Route("forgot-password-request")
|
||||
@PageTitle("Passwort zurücksetzen – E-Mail angeben")
|
||||
@AnonymousAllowed
|
||||
public class ForgotPasswordRequestView extends VerticalLayout {
|
||||
public class ForgotPasswordRequestView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
public ForgotPasswordRequestView(PasswordResetService passwordResetService) {
|
||||
|
||||
@@ -76,4 +75,9 @@ public class ForgotPasswordRequestView extends VerticalLayout {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.password.reset");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@ package de.assecutor.votianlt.pages.view;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
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 class ImprintView extends VerticalLayout implements HasDynamicTitle {
|
||||
public ImprintView() {
|
||||
setSizeFull();
|
||||
setPadding(true);
|
||||
@@ -31,8 +30,13 @@ public class ImprintView extends VerticalLayout {
|
||||
} catch (Exception e) {
|
||||
// Fallback content in case of error
|
||||
Div errorDiv = new Div();
|
||||
errorDiv.setText("Fehler beim Laden des Impressums: " + e.getMessage());
|
||||
errorDiv.setText(getTranslation("imprint.error", e.getMessage()));
|
||||
add(errorDiv);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.imprint");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,17 +21,16 @@ import elemental.json.JsonValue;
|
||||
import elemental.json.JsonType;
|
||||
import com.vaadin.flow.component.upload.Upload;
|
||||
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.pages.base.ui.view.AdminLayout;
|
||||
import de.assecutor.votianlt.service.CustomerInvoiceService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
@Route(value = "invoice-generator", layout = AdminLayout.class)
|
||||
@PageTitle("Rechnungsgenerator")
|
||||
@RolesAllowed("ADMIN")
|
||||
@JsModule("./invoice-generator/invoice-generator.js")
|
||||
public class InvoiceGeneratorView extends VerticalLayout {
|
||||
public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private final CustomerInvoiceService customerInvoiceService;
|
||||
private Div canvasContainer;
|
||||
@@ -106,18 +105,18 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
panel.getStyle().set("background-color", "var(--lumo-contrast-5pct)")
|
||||
.set("border-radius", "var(--lumo-border-radius-m)").set("overflow", "auto");
|
||||
|
||||
Span header = new Span("Textbausteine");
|
||||
Span header = new Span(getTranslation("invoicegenerator.panel.templates"));
|
||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
||||
|
||||
// Draggable Templates
|
||||
Div textBlock = createDraggableTemplate("Textfeld", VaadinIcon.TEXT_LABEL, "text");
|
||||
Div headerBlock = createDraggableTemplate("Überschrift", VaadinIcon.HEADER, "header");
|
||||
Div dateBlock = createDraggableTemplate("Datum", VaadinIcon.CALENDAR, "date");
|
||||
Div customerBlock = createDraggableTemplate("Kundeninfo", VaadinIcon.USER, "customer");
|
||||
Div companyBlock = createDraggableTemplate("Firmeninfo", VaadinIcon.OFFICE, "company");
|
||||
Div amountBlock = createDraggableTemplate("Betrag", VaadinIcon.COIN_PILES, "amount");
|
||||
Div lineBlock = createDraggableTemplate("Linie", VaadinIcon.LINE_V, "line");
|
||||
Div imageBlock = createDraggableTemplate("Bild", VaadinIcon.PICTURE, "image");
|
||||
Div textBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.text"), VaadinIcon.TEXT_LABEL, "text");
|
||||
Div headerBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.header"), VaadinIcon.HEADER, "header");
|
||||
Div dateBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.date"), VaadinIcon.CALENDAR, "date");
|
||||
Div customerBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.customerinfo"), VaadinIcon.USER, "customer");
|
||||
Div companyBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.companyinfo"), VaadinIcon.OFFICE, "company");
|
||||
Div amountBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.amount"), VaadinIcon.COIN_PILES, "amount");
|
||||
Div lineBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.line"), VaadinIcon.LINE_V, "line");
|
||||
Div imageBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.image"), VaadinIcon.PICTURE, "image");
|
||||
|
||||
panel.add(header, textBlock, headerBlock, dateBlock, customerBlock, companyBlock, amountBlock, lineBlock,
|
||||
imageBlock);
|
||||
@@ -200,12 +199,12 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
panel.getStyle().set("background-color", "var(--lumo-contrast-5pct)")
|
||||
.set("border-radius", "var(--lumo-border-radius-m)").set("overflow", "auto");
|
||||
|
||||
Span header = new Span("Eigenschaften");
|
||||
Span header = new Span(getTranslation("invoicegenerator.panel.properties"));
|
||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
||||
|
||||
// Info-Text wenn kein Element ausgewählt
|
||||
selectedElementInfo = new Div();
|
||||
selectedElementInfo.setText("Klicken Sie auf ein Element im Canvas, um dessen Eigenschaften zu bearbeiten.");
|
||||
selectedElementInfo.setText(getTranslation("invoicegenerator.properties.info"));
|
||||
selectedElementInfo.getStyle().set("color", "var(--lumo-secondary-text-color)").set("font-size",
|
||||
"var(--lumo-font-size-s)");
|
||||
|
||||
@@ -222,24 +221,24 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
layout.setSpacing(true);
|
||||
layout.setAlignItems(Alignment.CENTER);
|
||||
|
||||
Button clearButton = new Button("Canvas leeren", new Icon(VaadinIcon.TRASH));
|
||||
Button clearButton = new Button(getTranslation("invoicegenerator.button.clear"), new Icon(VaadinIcon.TRASH));
|
||||
clearButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||
clearButton.addClickListener(e -> {
|
||||
getElement().executeJs("if (window.invoiceGenerator) { window.invoiceGenerator.clearCanvas(); }");
|
||||
showNotification("Canvas wurde geleert");
|
||||
showNotification(getTranslation("invoicegenerator.notification.cleared"));
|
||||
});
|
||||
|
||||
Button previewButton = new Button("Vorschau", new Icon(VaadinIcon.EYE));
|
||||
Button previewButton = new Button(getTranslation("button.preview"), new Icon(VaadinIcon.EYE));
|
||||
previewButton.addThemeVariants(ButtonVariant.LUMO_CONTRAST);
|
||||
previewButton.addClickListener(e -> generatePreviewPdf());
|
||||
|
||||
Button saveTemplateButton = new Button("Template speichern", new Icon(VaadinIcon.DOWNLOAD));
|
||||
Button saveTemplateButton = new Button(getTranslation("button.savetemplate"), new Icon(VaadinIcon.DOWNLOAD));
|
||||
saveTemplateButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
saveTemplateButton.addClickListener(e -> {
|
||||
getElement().executeJs("if (window.invoiceGenerator) { window.invoiceGenerator.exportTemplate(); }");
|
||||
});
|
||||
|
||||
Button generatePdfButton = new Button("PDF generieren", new Icon(VaadinIcon.FILE_TEXT_O));
|
||||
Button generatePdfButton = new Button(getTranslation("invoicegenerator.button.generatepdf"), new Icon(VaadinIcon.FILE_TEXT_O));
|
||||
generatePdfButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||
generatePdfButton.addClickListener(e -> generatePdf());
|
||||
|
||||
@@ -255,14 +254,14 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
"if (window.invoiceGenerator) { return window.invoiceGenerator.getCanvasData(); } else { return null; }")
|
||||
.then(json -> {
|
||||
if (json == null) {
|
||||
showNotification("Fehler: Canvas-Daten konnten nicht gelesen werden");
|
||||
showNotification(getTranslation("invoicegenerator.notification.canvas.error"));
|
||||
return;
|
||||
}
|
||||
// Hier würde die PDF-Generierung erfolgen
|
||||
showNotification("PDF wird generiert... (Demo)");
|
||||
showNotification(getTranslation("invoicegenerator.notification.generating"));
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
showNotification("Fehler beim Generieren des PDFs: " + ex.getMessage());
|
||||
showNotification(getTranslation("invoicegenerator.notification.pdf.error", ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +271,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
"if (window.invoiceGenerator) { return window.invoiceGenerator.exportTemplateJson(); } else { return null; }")
|
||||
.then(result -> {
|
||||
if (result == null) {
|
||||
showNotification("Fehler: Canvas-Daten konnten nicht gelesen werden");
|
||||
showNotification(getTranslation("invoicegenerator.notification.canvas.error"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -290,11 +289,11 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
byte[] pdfBytes = customerInvoiceService.generatePdfFromCanvasTemplate(templateData);
|
||||
showPdfInDialog(pdfBytes);
|
||||
} catch (Exception ex) {
|
||||
showNotification("Fehler beim Generieren der Vorschau: " + ex.getMessage());
|
||||
showNotification(getTranslation("invoicegenerator.notification.preview.error", ex.getMessage()));
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
showNotification("Fehler: " + ex.getMessage());
|
||||
showNotification(getTranslation("invoicegenerator.notification.error", ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +305,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
|
||||
// Create dialog
|
||||
Dialog pdfDialog = new Dialog();
|
||||
pdfDialog.setHeaderTitle("PDF Vorschau");
|
||||
pdfDialog.setHeaderTitle(getTranslation("invoicegenerator.pdf.preview.title"));
|
||||
pdfDialog.setWidth("90vw");
|
||||
pdfDialog.setHeight("90vh");
|
||||
|
||||
@@ -329,11 +328,11 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
pdfContainer.add(pdfFrame);
|
||||
|
||||
// Close button
|
||||
Button closeButton = new Button("Schließen", e -> pdfDialog.close());
|
||||
Button closeButton = new Button(getTranslation("button.close"), e -> pdfDialog.close());
|
||||
closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
// Download button
|
||||
Button downloadButton = new Button("Herunterladen", e -> {
|
||||
Button downloadButton = new Button(getTranslation("button.download"), e -> {
|
||||
getElement()
|
||||
.executeJs("const link = document.createElement('a');" + "link.href = 'data:application/pdf;base64,"
|
||||
+ base64Pdf + "';" + "link.download = 'vorschau.pdf';" + "link.click();");
|
||||
@@ -356,11 +355,11 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
getUI().ifPresent(ui -> ui.access(() -> {
|
||||
propertiesPanel.removeAll();
|
||||
|
||||
Span header = new Span("Eigenschaften");
|
||||
Span header = new Span(getTranslation("invoicegenerator.properties.title"));
|
||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
||||
|
||||
// Element Typ Anzeige
|
||||
Span typeLabel = new Span("Typ: " + elementType);
|
||||
Span typeLabel = new Span(getTranslation("invoicegenerator.properties.type") + ": " + elementType);
|
||||
typeLabel.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
|
||||
propertiesPanel.add(header, typeLabel);
|
||||
@@ -371,7 +370,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
Upload upload = new Upload(buffer);
|
||||
upload.setAcceptedFileTypes("image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp");
|
||||
upload.setMaxFileSize(5 * 1024 * 1024); // 5 MB
|
||||
upload.setDropLabel(new Span("Bild hierher ziehen oder klicken"));
|
||||
upload.setDropLabel(new Span(getTranslation("invoicegenerator.upload.drop")));
|
||||
upload.setWidthFull();
|
||||
|
||||
upload.addSucceededListener(event -> {
|
||||
@@ -386,14 +385,14 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
getElement()
|
||||
.executeJs("if (window.invoiceGenerator) { window.invoiceGenerator.updateElementImage('"
|
||||
+ elementId + "', $0); }", dataUrl);
|
||||
showNotification("Bild erfolgreich hochgeladen");
|
||||
showNotification(getTranslation("invoicegenerator.upload.success"));
|
||||
} catch (Exception ex) {
|
||||
showNotification("Fehler beim Hochladen: " + ex.getMessage());
|
||||
showNotification(getTranslation("invoicegenerator.upload.error", ex.getMessage()));
|
||||
}
|
||||
});
|
||||
|
||||
upload.addFileRejectedListener(event -> {
|
||||
showNotification("Datei abgelehnt: " + event.getErrorMessage());
|
||||
showNotification(getTranslation("invoicegenerator.file.rejected", event.getErrorMessage()));
|
||||
});
|
||||
|
||||
propertiesPanel.add(upload);
|
||||
@@ -443,7 +442,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
|
||||
// Font Size (nur für Text-Elemente)
|
||||
if (!"line".equals(elementType) && !"image".equals(elementType)) {
|
||||
TextField fontSizeField = new TextField("Schriftgröße");
|
||||
TextField fontSizeField = new TextField(getTranslation("invoicegenerator.fontsize.label"));
|
||||
fontSizeField.setValue(fontSize != null ? String.valueOf(fontSize) : "16");
|
||||
fontSizeField.setWidthFull();
|
||||
fontSizeField.addValueChangeListener(e -> {
|
||||
@@ -459,7 +458,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
propertiesPanel.add(fontSizeField);
|
||||
|
||||
// Schriftfarbe mit Dialog
|
||||
Span colorLabel = new Span("Schriftfarbe");
|
||||
Span colorLabel = new Span(getTranslation("invoicegenerator.color.label"));
|
||||
colorLabel.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
propertiesPanel.add(colorLabel);
|
||||
|
||||
@@ -485,7 +484,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
|
||||
// Color Picker Dialog
|
||||
Dialog colorDialog = new Dialog();
|
||||
colorDialog.setHeaderTitle("Schriftfarbe wählen");
|
||||
colorDialog.setHeaderTitle(getTranslation("invoicegenerator.color.dialog.title"));
|
||||
|
||||
VerticalLayout dialogLayout = new VerticalLayout();
|
||||
dialogLayout.setSpacing(true);
|
||||
@@ -498,7 +497,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
dialogColorPicker.getStyle().set("width", "100%").set("height", "50px").set("padding", "0");
|
||||
|
||||
// Hex-Eingabe im Dialog
|
||||
TextField dialogHexField = new TextField("Hex-Farbwert");
|
||||
TextField dialogHexField = new TextField(getTranslation("invoicegenerator.color.dialog.hex"));
|
||||
dialogHexField.setValue(currentColor);
|
||||
dialogHexField.setWidthFull();
|
||||
|
||||
@@ -518,7 +517,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
colorDialog.add(dialogLayout);
|
||||
|
||||
// Dialog Buttons
|
||||
Button dialogCancelButton = new Button("Abbrechen", e -> {
|
||||
Button dialogCancelButton = new Button(getTranslation("invoicegenerator.button.cancel"), e -> {
|
||||
colorDialog.close();
|
||||
// Reset to original values
|
||||
dialogColorPicker.setValue(currentColor);
|
||||
@@ -526,7 +525,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
});
|
||||
dialogCancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
Button dialogApplyButton = new Button("Übernehmen", e -> {
|
||||
Button dialogApplyButton = new Button(getTranslation("invoicegenerator.button.apply"), e -> {
|
||||
String newColor = dialogColorPicker.getValue();
|
||||
// Update preview
|
||||
colorPreview.getStyle().set("background-color", newColor);
|
||||
@@ -535,7 +534,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
getElement().executeJs("if (window.invoiceGenerator) { window.invoiceGenerator.updateElementColor('"
|
||||
+ elementId + "', $0); }", newColor);
|
||||
colorDialog.close();
|
||||
showNotification("Farbe übernommen");
|
||||
showNotification(getTranslation("invoicegenerator.notification.color.applied"));
|
||||
});
|
||||
dialogApplyButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
@@ -558,7 +557,7 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
}
|
||||
|
||||
// Löschen Button
|
||||
Button deleteButton = new Button("Element löschen", new Icon(VaadinIcon.TRASH));
|
||||
Button deleteButton = new Button(getTranslation("invoicegenerator.button.delete"), new Icon(VaadinIcon.TRASH));
|
||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||
deleteButton.setWidthFull();
|
||||
deleteButton.addClickListener(e -> {
|
||||
@@ -590,4 +589,9 @@ public class InvoiceGeneratorView extends VerticalLayout {
|
||||
propertiesPanel.add(header, selectedElementInfo);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.invoice.generator");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.vaadin.flow.component.html.H2;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import de.assecutor.votianlt.model.invoices.SystemInvoice;
|
||||
@@ -25,10 +25,9 @@ import java.util.Locale;
|
||||
import com.vaadin.flow.server.StreamResource;
|
||||
import com.vaadin.flow.server.StreamRegistration;
|
||||
|
||||
@PageTitle("Rechnungen")
|
||||
@Route(value = "invoices", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed({ "USER", "ADMIN" })
|
||||
public class InvoicesView extends VerticalLayout {
|
||||
public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private final Grid<SystemInvoice> invoiceGrid;
|
||||
|
||||
@@ -43,15 +42,15 @@ public class InvoicesView extends VerticalLayout {
|
||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
|
||||
H2 title = new H2("Rechnungen");
|
||||
H2 title = new H2(getTranslation("invoices.title"));
|
||||
add(title);
|
||||
|
||||
invoiceGrid = new Grid<>(SystemInvoice.class, false);
|
||||
invoiceGrid.addColumn(SystemInvoice::getId).setHeader("Rechnungsnummer").setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getKunde).setHeader("Kunde").setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getDatum).setHeader("Datum").setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getBetrag).setHeader("Betrag").setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getBeschreibung).setHeader("Beschreibung").setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getId).setHeader(getTranslation("invoices.column.number")).setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getKunde).setHeader(getTranslation("invoices.column.customer")).setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getDatum).setHeader(getTranslation("invoices.column.date")).setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getBetrag).setHeader(getTranslation("invoices.column.amount")).setAutoWidth(true);
|
||||
invoiceGrid.addColumn(SystemInvoice::getBeschreibung).setHeader(getTranslation("invoices.column.description")).setAutoWidth(true);
|
||||
invoiceGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
||||
invoiceGrid.getStyle().set("cursor", "pointer");
|
||||
|
||||
@@ -89,7 +88,7 @@ public class InvoicesView extends VerticalLayout {
|
||||
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
||||
|
||||
} catch (Exception e) {
|
||||
Notification.show("Fehler beim Erstellen der PDF: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("invoices.notification.pdf.error", e.getMessage()), 5000, Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,4 +122,9 @@ public class InvoicesView extends VerticalLayout {
|
||||
|
||||
return systemInvoiceService.generateInvoicePdfFromHtml(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.invoices");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.votianlt.model.Job;
|
||||
@@ -32,10 +32,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
@Route(value = "job_history", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Job Historie")
|
||||
@RolesAllowed("USER")
|
||||
@Slf4j
|
||||
public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
public class JobHistoryView extends Main implements HasUrlParameter<String>, HasDynamicTitle {
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final JobHistoryService jobHistoryService;
|
||||
@@ -57,7 +56,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||
|
||||
add(new ViewToolbar("Job Historie"));
|
||||
add(new ViewToolbar(getTranslation("jobhistory.title")));
|
||||
|
||||
content = new VerticalLayout();
|
||||
content.setSpacing(true);
|
||||
@@ -71,7 +70,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
content.removeAll();
|
||||
|
||||
if (parameter == null || parameter.isBlank()) {
|
||||
content.add(new Span("Fehler: Keine Job-ID angegeben"));
|
||||
content.add(new Span(getTranslation("jobhistory.error.no.id")));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,13 +78,13 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
try {
|
||||
jobId = new ObjectId(parameter);
|
||||
} catch (Exception e) {
|
||||
content.add(new Span("Fehler: Ungültige Job-ID Format: " + parameter));
|
||||
content.add(new Span(getTranslation("jobhistory.error.invalid.id", parameter)));
|
||||
return;
|
||||
}
|
||||
|
||||
Job job = jobRepository.findById(jobId).orElse(null);
|
||||
if (job == null) {
|
||||
content.add(new Span("Fehler: Job mit ID " + parameter + " nicht gefunden"));
|
||||
content.add(new Span(getTranslation("jobhistory.error.not.found", parameter)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,8 +95,8 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
content.removeAll();
|
||||
|
||||
// Header mit Job-Informationen
|
||||
H2 header = new H2(
|
||||
"Job Historie - " + (job.getJobNumber() != null ? job.getJobNumber() : "Unbekannte Auftragsnummer"));
|
||||
H2 header = new H2(getTranslation("jobhistory.header",
|
||||
job.getJobNumber() != null ? job.getJobNumber() : getTranslation("jobhistory.unknown.jobnumber")));
|
||||
content.add(header);
|
||||
|
||||
// Job basic info for context
|
||||
@@ -110,12 +109,12 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
long historyCount = jobHistoryService.getJobHistoryCount(job.getId());
|
||||
|
||||
if (historyEntries.isEmpty()) {
|
||||
Span noHistory = new Span("Noch keine History-Einträge für diesen Job vorhanden.");
|
||||
Span noHistory = new Span(getTranslation("jobhistory.no.entries"));
|
||||
noHistory.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
content.add(noHistory);
|
||||
} else {
|
||||
// History section header
|
||||
H2 historyHeader = new H2("Verlauf (" + historyCount + " Einträge)");
|
||||
H2 historyHeader = new H2(getTranslation("jobhistory.count", historyCount));
|
||||
historyHeader.getStyle().set("margin-top", "var(--lumo-space-l)");
|
||||
content.add(historyHeader);
|
||||
|
||||
@@ -124,7 +123,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
content.add(timeline);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Span errorMessage = new Span("Fehler beim Laden der Job Historie: " + e.getMessage());
|
||||
Span errorMessage = new Span(getTranslation("jobhistory.error.load", e.getMessage()));
|
||||
errorMessage.getStyle().set("color", "var(--lumo-error-text-color)");
|
||||
content.add(errorMessage);
|
||||
}
|
||||
@@ -141,13 +140,13 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
infoContent.setSpacing(false);
|
||||
|
||||
if (job.getDeliveryCompany() != null) {
|
||||
infoContent.add(new Span("Kunde: " + job.getDeliveryCompany()));
|
||||
infoContent.add(new Span(getTranslation("jobhistory.info.customer", job.getDeliveryCompany())));
|
||||
}
|
||||
if (job.getCreatedAt() != null) {
|
||||
infoContent.add(new Span("Erstellt am: " + formatDateTime(job.getCreatedAt())));
|
||||
infoContent.add(new Span(getTranslation("jobhistory.info.createdat", formatDateTime(job.getCreatedAt()))));
|
||||
}
|
||||
if (job.getStatus() != null) {
|
||||
infoContent.add(new Span("Status: " + formatStatus(job.getStatus())));
|
||||
infoContent.add(new Span(getTranslation("jobhistory.info.status", formatStatus(job.getStatus()))));
|
||||
}
|
||||
|
||||
infoBox.add(infoContent);
|
||||
@@ -184,7 +183,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
Icon typeIcon = getTypeIcon(entry.getChangeType());
|
||||
typeIcon.getStyle().set("color", getTypeColor(entry.getChangeType()));
|
||||
|
||||
Span reason = new Span(entry.getReason() != null ? entry.getReason() : "Unbekannt");
|
||||
Span reason = new Span(entry.getReason() != null ? entry.getReason() : getTranslation("jobhistory.entry.unknown"));
|
||||
reason.getStyle().set("font-weight", "500");
|
||||
|
||||
Span timestamp = new Span(formatDateTime(entry.getTimestamp()));
|
||||
@@ -243,7 +242,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
|
||||
// Changed by (if available)
|
||||
if (entry.getChangedBy() != null && !entry.getChangedBy().isBlank()) {
|
||||
Span changedBy = new Span("von: " + entry.getChangedBy());
|
||||
Span changedBy = new Span(getTranslation("jobhistory.changedby", entry.getChangedBy()));
|
||||
changedBy.getStyle().set("color", "var(--lumo-secondary-text-color)")
|
||||
.set("font-size", "var(--lumo-font-size-xs)").set("margin-top", "var(--lumo-space-xs)")
|
||||
.set("display", "block");
|
||||
@@ -303,17 +302,17 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
|
||||
private String formatStatus(de.assecutor.votianlt.model.JobStatus status) {
|
||||
if (status == null)
|
||||
return "Unbekannt";
|
||||
return getTranslation("jobhistory.entry.unknown");
|
||||
|
||||
return switch (status) {
|
||||
case CREATED -> "Erstellt";
|
||||
case IN_PROGRESS -> "In Bearbeitung";
|
||||
case CREATED -> getTranslation("jobstatus.CREATED");
|
||||
case IN_PROGRESS -> getTranslation("jobstatus.IN_PROGRESS");
|
||||
case PICKUP_SCHEDULED -> "Abholung geplant";
|
||||
case PICKED_UP -> "Abgeholt";
|
||||
case IN_TRANSIT -> "Unterwegs";
|
||||
case DELIVERED -> "Zugestellt";
|
||||
case COMPLETED -> "Abgeschlossen";
|
||||
case CANCELLED -> "Storniert";
|
||||
case COMPLETED -> getTranslation("jobstatus.COMPLETED");
|
||||
case CANCELLED -> getTranslation("jobstatus.CANCELLED");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -549,6 +548,11 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
||||
"<div style=\"width: " + width + "; height: " + height + ";\">" + responsiveSvg + "</div>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.job.history");
|
||||
}
|
||||
|
||||
private void showEnlargedSignature(String svgContent) {
|
||||
Dialog signatureDialog = new Dialog();
|
||||
signatureDialog.setWidth("60vw");
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.vaadin.flow.component.icon.Icon;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.votianlt.model.CargoItem;
|
||||
@@ -64,10 +64,9 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Route(value = "job_summary", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Zusammenfassung")
|
||||
@RolesAllowed("USER")
|
||||
@Slf4j
|
||||
public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
public class JobSummaryView extends Main implements HasUrlParameter<String>, HasDynamicTitle {
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final CargoItemRepository cargoItemRepository;
|
||||
@@ -145,12 +144,12 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
}
|
||||
|
||||
// Create Send Message Button for toolbar
|
||||
Button sendMessageButton = new Button("Nachricht senden");
|
||||
Button sendMessageButton = new Button(getTranslation("jobsummary.button.sendmessage"));
|
||||
sendMessageButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
sendMessageButton.addClickListener(e -> {
|
||||
// Check if job has an app user assigned
|
||||
if (job.getAppUser() == null || job.getAppUser().isBlank()) {
|
||||
Notification.show("Diesem Auftrag ist kein App-Nutzer zugeordnet", 3000, Notification.Position.MIDDLE)
|
||||
Notification.show(getTranslation("jobsummary.notification.noappuser"), 3000, Notification.Position.MIDDLE)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
return;
|
||||
}
|
||||
@@ -165,7 +164,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
});
|
||||
|
||||
// Create Job History Button for toolbar
|
||||
Button jobHistoryButton = new Button("Job Historie");
|
||||
Button jobHistoryButton = new Button(getTranslation("jobsummary.button.jobhistory"));
|
||||
jobHistoryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
jobHistoryButton.addClickListener(e -> {
|
||||
getUI().ifPresent(ui -> ui.navigate("job_history/" + job.getId().toHexString()));
|
||||
@@ -190,7 +189,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
topRow.setSpacing(true);
|
||||
|
||||
VerticalLayout pickupBox = borderedBox();
|
||||
pickupBox.add(new H3("Abholung " + formatDateWithTime(job.getPickupDate(), job.getPickupTime())));
|
||||
pickupBox.add(new H3(getTranslation("jobsummary.section.pickup") + " " + formatDateWithTime(job.getPickupDate(), job.getPickupTime())));
|
||||
pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany())));
|
||||
pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation()) + (job.getPickupSalutation() != null ? " " : "")
|
||||
+ valueOrEmpty(job.getPickupFirstName()) + (job.getPickupFirstName() != null ? " " : "")
|
||||
@@ -199,7 +198,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
pickupBox.add(new Span(concatZipCity(job.getPickupZip(), job.getPickupCity())));
|
||||
|
||||
VerticalLayout deliveryBox = borderedBox();
|
||||
deliveryBox.add(new H3("Lieferung " + formatDateWithTime(job.getDeliveryDate(), job.getDeliveryTime())));
|
||||
deliveryBox.add(new H3(getTranslation("jobsummary.section.delivery") + " " + formatDateWithTime(job.getDeliveryDate(), job.getDeliveryTime())));
|
||||
deliveryBox.add(new Span(valueOrEmpty(job.getDeliveryCompany())));
|
||||
deliveryBox.add(new Span(valueOrEmpty(job.getDeliverySalutation())
|
||||
+ (job.getDeliverySalutation() != null ? " " : "") + valueOrEmpty(job.getDeliveryFirstName())
|
||||
@@ -214,7 +213,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
|
||||
// Aufgaben
|
||||
VerticalLayout tasksBox = borderedBox();
|
||||
tasksBox.add(new H3("Zu quittierende Aufgaben"));
|
||||
tasksBox.add(new H3(getTranslation("jobsummary.section.tasks")));
|
||||
|
||||
// Ensure consistent spacing and width for task cards
|
||||
tasksBox.setSpacing(false);
|
||||
@@ -224,7 +223,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
taskCards.clear();
|
||||
|
||||
if (tasks == null || tasks.isEmpty()) {
|
||||
tasksBox.add(new Span("Keine Aufgaben"));
|
||||
tasksBox.add(new Span(getTranslation("jobsummary.tasks.none")));
|
||||
} else {
|
||||
for (BaseTask task : tasks) {
|
||||
if (task != null) {
|
||||
@@ -246,9 +245,9 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
midRow.setSpacing(true);
|
||||
|
||||
VerticalLayout cargoBox = borderedBox();
|
||||
cargoBox.add(new H3("Zu transportierende Fracht"));
|
||||
cargoBox.add(new H3(getTranslation("jobsummary.section.cargo")));
|
||||
if (cargoItems == null || cargoItems.isEmpty()) {
|
||||
cargoBox.add(new Span("Keine Frachtangaben"));
|
||||
cargoBox.add(new Span(getTranslation("jobsummary.cargo.none")));
|
||||
} else {
|
||||
for (CargoItem ci : cargoItems) {
|
||||
if (ci == null)
|
||||
@@ -265,22 +264,22 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
}
|
||||
|
||||
VerticalLayout infoBox = borderedBox();
|
||||
infoBox.add(new H3("Weitere Informationen"));
|
||||
infoBox.add(new H3(getTranslation("jobsummary.section.info")));
|
||||
|
||||
// Preis basierend auf den hinterlegten Leistungen berechnen
|
||||
PriceCalculationResult priceResult = calculatePriceFromServices(job);
|
||||
infoBox.add(new Span("Netto: " + formatPrice(priceResult.netAmount())));
|
||||
infoBox.add(new Span("USt: " + formatPrice(priceResult.vatAmount())));
|
||||
infoBox.add(new Span("Gesamt: " + formatPrice(priceResult.totalAmount())));
|
||||
infoBox.add(new Span(getTranslation("jobsummary.info.netto") + ": " + formatPrice(priceResult.netAmount())));
|
||||
infoBox.add(new Span(getTranslation("jobsummary.info.ust") + ": " + formatPrice(priceResult.vatAmount())));
|
||||
infoBox.add(new Span(getTranslation("jobsummary.info.gesamt") + ": " + formatPrice(priceResult.totalAmount())));
|
||||
|
||||
if (job.getRemark() != null && !job.getRemark().isBlank()) {
|
||||
infoBox.add(new Span("Bemerkung: " + job.getRemark()));
|
||||
infoBox.add(new Span(getTranslation("jobsummary.info.bemerkung") + ": " + job.getRemark()));
|
||||
}
|
||||
if (job.isDigitalProcessing()) {
|
||||
infoBox.add(new Span("Digitale Abwicklung per App: aktiviert"));
|
||||
infoBox.add(new Span(getTranslation("jobsummary.info.digital")));
|
||||
}
|
||||
if (job.getAppUser() != null && !job.getAppUser().isBlank()) {
|
||||
infoBox.add(new Span("App-Nutzer: " + resolveAppUserName(job.getAppUser())));
|
||||
infoBox.add(new Span(getTranslation("jobsummary.info.appuser") + ": " + resolveAppUserName(job.getAppUser())));
|
||||
}
|
||||
|
||||
cargoBox.setWidth("50%");
|
||||
@@ -299,15 +298,15 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
buttonRow.setJustifyContentMode(HorizontalLayout.JustifyContentMode.CENTER);
|
||||
buttonRow.getStyle().set("margin-top", "var(--lumo-space-l)");
|
||||
|
||||
Button completeButton = new Button("Auftrag manuell abschließen", new Icon(VaadinIcon.CHECK_CIRCLE));
|
||||
Button completeButton = new Button(getTranslation("jobsummary.button.complete"), new Icon(VaadinIcon.CHECK_CIRCLE));
|
||||
completeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||
completeButton.addClickListener(e -> {
|
||||
ConfirmDialog dialog = new ConfirmDialog();
|
||||
dialog.setHeader("Auftrag abschließen");
|
||||
dialog.setText("Möchten Sie den Auftrag " + job.getJobNumber() + " manuell abschließen?");
|
||||
dialog.setHeader(getTranslation("jobsummary.dialog.complete.title"));
|
||||
dialog.setText(getTranslation("jobsummary.dialog.complete.text", job.getJobNumber()));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setCancelText("Abbrechen");
|
||||
dialog.setConfirmText("Abschließen");
|
||||
dialog.setCancelText(getTranslation("jobsummary.dialog.complete.cancel"));
|
||||
dialog.setConfirmText(getTranslation("jobsummary.dialog.complete.confirm"));
|
||||
dialog.setConfirmButtonTheme("primary");
|
||||
dialog.addConfirmListener(ev -> {
|
||||
try {
|
||||
@@ -317,14 +316,14 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
jobRepository.save(job);
|
||||
jobHistoryService.logStatusChange(job, oldStatus, JobStatus.COMPLETED, "Manuell");
|
||||
Notification
|
||||
.show("Auftrag " + job.getJobNumber() + " wurde abgeschlossen.", 3000,
|
||||
.show(getTranslation("jobsummary.notification.completed", job.getJobNumber()), 3000,
|
||||
Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
// Re-render the page
|
||||
getUI().ifPresent(ui -> ui.navigate("job_summary/" + job.getId().toHexString()));
|
||||
} catch (Exception ex) {
|
||||
Notification
|
||||
.show("Fehler beim Abschließen: " + ex.getMessage(), 5000,
|
||||
.show(getTranslation("jobsummary.notification.complete.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
}
|
||||
@@ -666,6 +665,11 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
||||
return s.replace("\\", "\\\\").replace("'", "\\'").replace("\n", " ").replace("\r", " ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.job.summary");
|
||||
}
|
||||
|
||||
private void showTaskDetailsDialog(BaseTask task) {
|
||||
Dialog dialog = new Dialog();
|
||||
dialog.setWidth("500px");
|
||||
|
||||
@@ -15,7 +15,7 @@ import com.vaadin.flow.router.AfterNavigationEvent;
|
||||
import com.vaadin.flow.router.AfterNavigationObserver;
|
||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import de.assecutor.votianlt.security.totp.TwoFactorService;
|
||||
@@ -33,13 +33,12 @@ import org.springframework.core.env.Environment;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@Route("login")
|
||||
@PageTitle("Bei VotianLT anmelden")
|
||||
@AnonymousAllowed
|
||||
public class LoginView extends VerticalLayout implements BeforeEnterObserver, AfterNavigationObserver {
|
||||
public class LoginView extends VerticalLayout implements BeforeEnterObserver, AfterNavigationObserver, HasDynamicTitle {
|
||||
|
||||
private final LoginForm loginForm = new LoginForm();
|
||||
private final TextField twoFaField = new TextField("2FA Code");
|
||||
private final Button verify2faButton = new Button("Code prüfen");
|
||||
private final TextField twoFaField = new TextField(getTranslation("login.2fa.title"));
|
||||
private final Button verify2faButton = new Button(getTranslation("login.2fa.button"));
|
||||
private final Div flashBox = new Div();
|
||||
|
||||
@Autowired
|
||||
@@ -76,7 +75,7 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
||||
twoFaField.setVisible(false);
|
||||
twoFaField.setMaxLength(6);
|
||||
twoFaField.setPattern("[0-9]{6}");
|
||||
twoFaField.setHelperText("Bitte 6-stelligen Code aus der E-Mail eingeben");
|
||||
twoFaField.setHelperText(getTranslation("login.2fa.helper"));
|
||||
verify2faButton.setVisible(false);
|
||||
verify2faButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
verify2faButton.addClickListener(e -> handleVerify2fa());
|
||||
@@ -84,10 +83,10 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
||||
loginForm.setForgotPasswordButtonVisible(true);
|
||||
loginForm.addForgotPasswordListener(e -> UI.getCurrent().navigate(ForgotPasswordRequestView.class));
|
||||
|
||||
H1 title = new H1("VotianLT");
|
||||
H1 title = new H1(getTranslation("login.votianlt"));
|
||||
title.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
|
||||
Button registerButton = new Button("Noch kein Konto? Registrieren", e -> UI.getCurrent().navigate("register"));
|
||||
Button registerButton = new Button(getTranslation("login.register"), e -> UI.getCurrent().navigate("register"));
|
||||
registerButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
// Version display - will be set in @PostConstruct
|
||||
@@ -147,7 +146,7 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
||||
twoFaField.setVisible(true);
|
||||
verify2faButton.setVisible(true);
|
||||
twoFactorService.initiateTwoFactorFor(username);
|
||||
Notification.show("2FA-Code per E-Mail gesendet.", 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("login.2fa.sent"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
} else {
|
||||
// 2FA deaktiviert: Direkt anmelden
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
@@ -173,19 +172,19 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
||||
|
||||
private void handleVerify2fa() {
|
||||
if (pendingAuth == null) {
|
||||
Notification.show("Bitte zuerst Benutzername und Passwort eingeben.", 3000,
|
||||
Notification.show(getTranslation("login.2fa.no.credentials"), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
return;
|
||||
}
|
||||
String username = pendingAuth.getName();
|
||||
String code = twoFaField.getValue();
|
||||
if (code == null || !code.matches("[0-9]{6}")) {
|
||||
Notification.show("Ungültiger Code.", 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("login.2fa.invalid.code"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
return;
|
||||
}
|
||||
boolean ok = twoFactorService.verifyTwoFactorCode(username, code);
|
||||
if (!ok) {
|
||||
Notification.show("Falscher oder abgelaufener Code.", 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("login.2fa.wrong.code"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
return;
|
||||
}
|
||||
// 2FA korrekt: Benutzer nun anmelden
|
||||
@@ -234,4 +233,9 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.login");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.vaadin.flow.component.upload.UploadI18N;
|
||||
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
|
||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.router.RouteParameters;
|
||||
import com.vaadin.flow.shared.Registration;
|
||||
@@ -67,10 +67,9 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Route(value = "message-details/:clientId/:conversationId", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Nachrichtenverlauf")
|
||||
@RolesAllowed("USER")
|
||||
@Slf4j
|
||||
public class MessageDetailsView extends Main implements BeforeEnterObserver {
|
||||
public class MessageDetailsView extends Main implements BeforeEnterObserver, HasDynamicTitle {
|
||||
|
||||
private final AppUserService appUserService;
|
||||
private final MessageService messageService;
|
||||
@@ -291,7 +290,7 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
|
||||
|
||||
AtomicReference<String> base64Ref = new AtomicReference<>();
|
||||
|
||||
Button confirmButton = new Button("Senden");
|
||||
Button confirmButton = new Button(getTranslation("messagedetails.button.send"));
|
||||
confirmButton.addThemeVariants(com.vaadin.flow.component.button.ButtonVariant.LUMO_PRIMARY);
|
||||
confirmButton.setEnabled(false);
|
||||
|
||||
@@ -1011,4 +1010,9 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.message.history");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import com.vaadin.flow.component.notification.NotificationVariant;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.dto.ClientMessageSummary;
|
||||
import de.assecutor.votianlt.model.AppUser;
|
||||
@@ -38,11 +38,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import com.vaadin.flow.shared.Registration;
|
||||
|
||||
@Route(value = "messages", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Nachrichten")
|
||||
@Menu(order = 1, icon = "vaadin:envelope", title = "Nachrichten")
|
||||
@RolesAllowed("USER")
|
||||
@Slf4j
|
||||
public class MessagesView extends Main {
|
||||
public class MessagesView extends Main implements HasDynamicTitle {
|
||||
|
||||
private static final int POLL_INTERVAL_MS = 5000;
|
||||
|
||||
@@ -85,7 +84,7 @@ public class MessagesView extends Main {
|
||||
}
|
||||
|
||||
private HorizontalLayout createHeaderLayout() {
|
||||
H2 title = new H2("Nachrichten");
|
||||
H2 title = new H2(getTranslation("messages.title"));
|
||||
HorizontalLayout layout = new HorizontalLayout(title);
|
||||
layout.setWidthFull();
|
||||
layout.setAlignItems(com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment.CENTER);
|
||||
@@ -105,15 +104,15 @@ public class MessagesView extends Main {
|
||||
span.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
}
|
||||
return span;
|
||||
})).setHeader("Status").setWidth("80px").setFlexGrow(0);
|
||||
})).setHeader(getTranslation("messages.column.status")).setWidth("80px").setFlexGrow(0);
|
||||
|
||||
grid.addColumn(ClientMessageSummary::getClientName).setHeader("Client").setAutoWidth(true);
|
||||
grid.addColumn(ClientMessageSummary::getClientEmail).setHeader("E-Mail").setAutoWidth(true);
|
||||
grid.addColumn(ClientMessageSummary::getClientName).setHeader(getTranslation("messages.column.client")).setAutoWidth(true);
|
||||
grid.addColumn(ClientMessageSummary::getClientEmail).setHeader(getTranslation("messages.column.email")).setAutoWidth(true);
|
||||
|
||||
grid.addColumn(new ComponentRenderer<>(summary -> {
|
||||
Span span = new Span(String.valueOf(summary.getTotalMessages()));
|
||||
return span;
|
||||
})).setHeader("Nachrichten").setWidth("120px").setFlexGrow(0);
|
||||
})).setHeader(getTranslation("messages.column.total")).setWidth("120px").setFlexGrow(0);
|
||||
|
||||
grid.addColumn(new ComponentRenderer<>(summary -> {
|
||||
if (summary.getUnreadCount() > 0) {
|
||||
@@ -123,11 +122,11 @@ public class MessagesView extends Main {
|
||||
return span;
|
||||
}
|
||||
return new Span("0");
|
||||
})).setHeader("Ungelesen").setWidth("100px").setFlexGrow(0);
|
||||
})).setHeader(getTranslation("messages.column.unread")).setWidth("100px").setFlexGrow(0);
|
||||
|
||||
grid.addColumn(summary -> summary.getLastMessageDate() != null
|
||||
? DateTimeFormatUtil.formatDateTime(summary.getLastMessageDate())
|
||||
: "-").setHeader("Letzte Nachricht").setAutoWidth(true);
|
||||
: "-").setHeader(getTranslation("messages.column.lastmessage")).setAutoWidth(true);
|
||||
|
||||
grid.addColumn(new ComponentRenderer<>(summary -> {
|
||||
String preview = summary.getLastMessagePreview();
|
||||
@@ -135,7 +134,7 @@ public class MessagesView extends Main {
|
||||
preview = preview.substring(0, 47) + "...";
|
||||
}
|
||||
return new Span(preview != null ? preview : "-");
|
||||
})).setHeader("Vorschau").setAutoWidth(true);
|
||||
})).setHeader(getTranslation("messages.column.preview")).setAutoWidth(true);
|
||||
|
||||
// Add click listener to navigate to UserMessagesView
|
||||
grid.addItemClickListener(event -> {
|
||||
@@ -168,7 +167,7 @@ public class MessagesView extends Main {
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading client summaries: {}", e.getMessage(), e);
|
||||
Notification.show("Fehler beim Laden der Nachrichten", 3000, Notification.Position.MIDDLE)
|
||||
Notification.show(getTranslation("messages.notification.error"), 3000, Notification.Position.MIDDLE)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
} finally {
|
||||
loading.set(false);
|
||||
@@ -252,14 +251,15 @@ public class MessagesView extends Main {
|
||||
|
||||
private String resolvePreview(Message message) {
|
||||
if (message == null) {
|
||||
return "(kein Inhalt)";
|
||||
return getTranslation("messages.preview.empty");
|
||||
}
|
||||
|
||||
if (message.getContentType() == MessageContentType.IMAGE) {
|
||||
return "[Bildnachricht]";
|
||||
return getTranslation("messages.preview.image");
|
||||
}
|
||||
|
||||
return Optional.ofNullable(message.getContent()).filter(content -> !content.isBlank()).orElse("(kein Inhalt)");
|
||||
return Optional.ofNullable(message.getContent()).filter(content -> !content.isBlank())
|
||||
.orElse(getTranslation("messages.preview.empty"));
|
||||
}
|
||||
|
||||
private String resolveParticipantKey(Message message) {
|
||||
@@ -384,7 +384,8 @@ public class MessagesView extends Main {
|
||||
+ "} catch(e) { console.warn('Notification sound failed:', e); }");
|
||||
|
||||
// Show notification
|
||||
Notification notification = Notification.show("Neue Nachricht von " + senderName + ": " + preview, 4000,
|
||||
Notification notification = Notification.show(
|
||||
getTranslation("messages.notification.new", senderName, preview), 4000,
|
||||
Notification.Position.TOP_END);
|
||||
notification.addThemeVariants(NotificationVariant.LUMO_PRIMARY);
|
||||
|
||||
@@ -394,11 +395,16 @@ public class MessagesView extends Main {
|
||||
|
||||
private String resolveSenderName(String clientId) {
|
||||
if (clientId == null || clientId.isBlank()) {
|
||||
return "Unbekannt";
|
||||
return getTranslation("messages.sender.unknown");
|
||||
}
|
||||
List<AppUser> appUsers = cachedAppUsers != null ? cachedAppUsers : List.of();
|
||||
return appUsers.stream().filter(user -> clientId.equals(user.getIdAsString())
|
||||
|| clientId.equals(user.getEmail()) || clientId.equals(user.getAppCode())).findFirst()
|
||||
.map(this::buildClientName).orElse(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.messages");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.vaadin.flow.component.select.Select;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||
import com.vaadin.flow.data.value.ValueChangeMode;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
||||
import de.assecutor.votianlt.pages.base.ui.view.MainLayout;
|
||||
@@ -40,10 +40,9 @@ import java.util.Locale;
|
||||
* Modernisierte Optik: Responsive Karten, Lumo-Theme-Varianten, Status-Badges,
|
||||
* Suche und leere Zustandsanzeige.
|
||||
*/
|
||||
@PageTitle("Meine Rechnungen")
|
||||
@Route(value = "my-invoices", layout = MainLayout.class)
|
||||
@RolesAllowed("USER")
|
||||
public class MyInvoicesView extends Main {
|
||||
public class MyInvoicesView extends Main implements HasDynamicTitle {
|
||||
|
||||
private final Grid<MyInvoiceRow> grid = new Grid<>(MyInvoiceRow.class, false);
|
||||
private final List<MyInvoiceRow> allRows = new ArrayList<>(); // zunächst leer
|
||||
@@ -60,7 +59,7 @@ public class MyInvoicesView extends Main {
|
||||
getStyle().set("padding", "var(--lumo-space-m)");
|
||||
|
||||
// Toolbar / Titel
|
||||
add(new ViewToolbar("Meine Rechnungen"));
|
||||
add(new ViewToolbar(getTranslation("myinvoices.title")));
|
||||
|
||||
// Kartenbereich oben
|
||||
add(createTopCards());
|
||||
@@ -79,18 +78,19 @@ public class MyInvoicesView extends Main {
|
||||
.set("gap", "var(--lumo-space-m)");
|
||||
|
||||
// Karte: Offene Rechnungen
|
||||
Paragraph hint = new Paragraph("Momentan sind keine neuen Rechnungen für Sie im System gespeichert.");
|
||||
Paragraph hint = new Paragraph(getTranslation("myinvoices.hint.noopen"));
|
||||
hint.getStyle().set("color", "var(--lumo-success-text-color)");
|
||||
Div openInvoicesCard = createCard("Offene Rechnungen", hint);
|
||||
Div openInvoicesCard = createCard(getTranslation("myinvoices.card.open"), hint);
|
||||
|
||||
// Karte: Bankverbindung
|
||||
VerticalLayout bankData = new VerticalLayout();
|
||||
bankData.setPadding(false);
|
||||
bankData.setSpacing(false);
|
||||
bankData.add(labeledValue("Kreditinstitut", "Hamburger Sparkasse"),
|
||||
labeledValue("Begünstigter", "Assecutor Data Service GmbH"),
|
||||
labeledValue("IBAN", "DE67200505501217139888"), labeledValue("Verwendungszweck", "vlt-00000610"));
|
||||
Div bankCard = createCard("Bankverbindung", bankData);
|
||||
bankData.add(labeledValue(getTranslation("myinvoices.bank.institute"), "Hamburger Sparkasse"),
|
||||
labeledValue(getTranslation("myinvoices.bank.beneficiary"), "Assecutor Data Service GmbH"),
|
||||
labeledValue(getTranslation("myinvoices.bank.iban"), "DE67200505501217139888"),
|
||||
labeledValue(getTranslation("myinvoices.bank.reference"), "vlt-00000610"));
|
||||
Div bankCard = createCard(getTranslation("myinvoices.card.bank"), bankData);
|
||||
|
||||
container.add(openInvoicesCard, bankCard);
|
||||
return container;
|
||||
@@ -104,19 +104,19 @@ public class MyInvoicesView extends Main {
|
||||
styleCard(card);
|
||||
|
||||
// Kopfzeile
|
||||
H3 title = new H3("Rechnungen");
|
||||
H3 title = new H3(getTranslation("myinvoices.section.title"));
|
||||
title.getStyle().set("margin", "0");
|
||||
|
||||
// Steuerleiste: Seitengröße + Suche
|
||||
Select<Integer> pageSize = new Select<>();
|
||||
pageSize.setItems(10, 25, 50);
|
||||
pageSize.setLabel("Einträge anzeigen");
|
||||
pageSize.setLabel(getTranslation("myinvoices.filter.pagesize"));
|
||||
pageSize.setValue(10);
|
||||
pageSize.setWidth("160px");
|
||||
|
||||
TextField search = new TextField();
|
||||
search.setLabel("Suchen");
|
||||
search.setPlaceholder("Rechnungsnr., Datum, Betrag...");
|
||||
search.setLabel(getTranslation("myinvoices.filter.search"));
|
||||
search.setPlaceholder(getTranslation("myinvoices.filter.search.placeholder"));
|
||||
search.setClearButtonVisible(true);
|
||||
search.setValueChangeMode(ValueChangeMode.EAGER);
|
||||
search.setWidth("300px");
|
||||
@@ -137,12 +137,12 @@ public class MyInvoicesView extends Main {
|
||||
grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES, GridVariant.LUMO_COMPACT,
|
||||
GridVariant.LUMO_WRAP_CELL_CONTENT, GridVariant.LUMO_COLUMN_BORDERS);
|
||||
grid.setWidthFull();
|
||||
grid.addColumn(new ComponentRenderer<>(row -> statusBadge(row.status()))).setHeader("Status").setAutoWidth(true)
|
||||
grid.addColumn(new ComponentRenderer<>(row -> statusBadge(row.status()))).setHeader(getTranslation("myinvoices.column.status")).setAutoWidth(true)
|
||||
.setFlexGrow(0);
|
||||
grid.addColumn(MyInvoicesView::formatInvoiceNumber).setHeader("Rechnungsnummer").setAutoWidth(true);
|
||||
grid.addColumn(row -> DateTimeFormatUtil.formatDate(row.date())).setHeader("Datum").setAutoWidth(true)
|
||||
grid.addColumn(MyInvoicesView::formatInvoiceNumber).setHeader(getTranslation("myinvoices.column.number")).setAutoWidth(true);
|
||||
grid.addColumn(row -> DateTimeFormatUtil.formatDate(row.date())).setHeader(getTranslation("myinvoices.column.date")).setAutoWidth(true)
|
||||
.setFlexGrow(0);
|
||||
grid.addColumn(row -> CURRENCY_FMT.format(row.amount())).setHeader("Betrag").setAutoWidth(true)
|
||||
grid.addColumn(row -> CURRENCY_FMT.format(row.amount())).setHeader(getTranslation("myinvoices.column.amount")).setAutoWidth(true)
|
||||
.setTextAlign(ColumnTextAlign.END).setFlexGrow(0);
|
||||
grid.setAllRowsVisible(true);
|
||||
grid.setItems(allRows); // zunächst leer
|
||||
@@ -155,8 +155,8 @@ public class MyInvoicesView extends Main {
|
||||
|
||||
// Leerer Zustand
|
||||
emptyState.removeAll();
|
||||
H4 emptyTitle = new H4("Keine Rechnungen vorhanden");
|
||||
Paragraph emptyDesc = new Paragraph("Sobald Rechnungen vorliegen, erscheinen sie hier.");
|
||||
H4 emptyTitle = new H4(getTranslation("myinvoices.empty.title"));
|
||||
Paragraph emptyDesc = new Paragraph(getTranslation("myinvoices.empty.desc"));
|
||||
emptyState.add(emptyTitle, emptyDesc);
|
||||
emptyState.getStyle().set("text-align", "center").set("color", "var(--lumo-secondary-text-color)")
|
||||
.set("padding", "var(--lumo-space-m)").set("border", "1px dashed var(--lumo-contrast-20pct)")
|
||||
@@ -167,8 +167,8 @@ public class MyInvoicesView extends Main {
|
||||
search.addValueChangeListener(e -> applyFilter(e.getValue()));
|
||||
|
||||
// Paginierungs-Buttons (vorerst ohne Funktion, als Platzhalter)
|
||||
Button prev = new Button("Zurück", VaadinIcon.ANGLE_LEFT.create());
|
||||
Button next = new Button("Nächste", VaadinIcon.ANGLE_RIGHT.create());
|
||||
Button prev = new Button(getTranslation("myinvoices.button.prev"), VaadinIcon.ANGLE_LEFT.create());
|
||||
Button next = new Button(getTranslation("myinvoices.button.next"), VaadinIcon.ANGLE_RIGHT.create());
|
||||
prev.setEnabled(false);
|
||||
next.setEnabled(false);
|
||||
HorizontalLayout pager = new HorizontalLayout(prev, next);
|
||||
@@ -298,4 +298,9 @@ public class MyInvoicesView extends Main {
|
||||
|
||||
return systemInvoiceService.generateInvoicePdfFromHtml(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.myinvoices");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
||||
import com.vaadin.flow.component.textfield.PasswordField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.VaadinSession;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
@@ -23,9 +23,8 @@ import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Route("register")
|
||||
@PageTitle("Bei VotianLT registrieren")
|
||||
@AnonymousAllowed
|
||||
public class RegisterView extends VerticalLayout {
|
||||
public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
private final UserService userService;
|
||||
private final EmailService emailService;
|
||||
|
||||
@@ -82,12 +81,12 @@ public class RegisterView extends VerticalLayout {
|
||||
container.getStyle().set("box-shadow", "var(--lumo-box-shadow-s)");
|
||||
|
||||
// Titel
|
||||
H1 title = new H1("Registrierung");
|
||||
H1 title = new H1(getTranslation("register.title"));
|
||||
title.getStyle().set("text-align", "center");
|
||||
title.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
title.getStyle().set("margin-top", "0");
|
||||
|
||||
H2 subtitle = new H2("Erstellen Sie Ihr VotianLT-Konto");
|
||||
H2 subtitle = new H2(getTranslation("register.subtitle"));
|
||||
subtitle.getStyle().set("text-align", "center");
|
||||
subtitle.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
subtitle.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
||||
@@ -95,52 +94,52 @@ public class RegisterView extends VerticalLayout {
|
||||
subtitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
|
||||
|
||||
// Formularfelder
|
||||
emailField = new TextField("E-Mail-Adresse");
|
||||
emailField = new TextField(getTranslation("register.email"));
|
||||
emailField.setWidthFull();
|
||||
emailField.setRequired(true);
|
||||
emailField.setPlaceholder("");
|
||||
|
||||
passwordField = new PasswordField("Passwort");
|
||||
passwordField = new PasswordField(getTranslation("register.password"));
|
||||
passwordField.setWidthFull();
|
||||
passwordField.setRequired(true);
|
||||
passwordField.setPlaceholder("Mindestens 6 Zeichen");
|
||||
passwordField.setPlaceholder(getTranslation("register.password.placeholder"));
|
||||
|
||||
confirmPasswordField = new PasswordField("Passwort bestätigen");
|
||||
confirmPasswordField = new PasswordField(getTranslation("register.password.confirm"));
|
||||
confirmPasswordField.setWidthFull();
|
||||
confirmPasswordField.setRequired(true);
|
||||
confirmPasswordField.setPlaceholder("Passwort wiederholen");
|
||||
confirmPasswordField.setPlaceholder(getTranslation("register.password.confirm.placeholder"));
|
||||
|
||||
// Pflichtfelder aus EditProfileView
|
||||
firstNameField = new TextField("Vorname");
|
||||
firstNameField = new TextField(getTranslation("register.firstname"));
|
||||
firstNameField.setWidthFull();
|
||||
firstNameField.setRequired(true);
|
||||
lastNameField = new TextField("Nachname");
|
||||
lastNameField = new TextField(getTranslation("register.lastname"));
|
||||
lastNameField.setWidthFull();
|
||||
lastNameField.setRequired(true);
|
||||
phoneField = new TextField("Telefonnummer");
|
||||
phoneField = new TextField(getTranslation("register.phone"));
|
||||
phoneField.setWidthFull();
|
||||
phoneField.setRequired(true);
|
||||
companyField = new TextField("Firma");
|
||||
companyField = new TextField(getTranslation("register.company"));
|
||||
companyField.setWidthFull();
|
||||
companyField.setRequired(true);
|
||||
streetField = new TextField("Straße");
|
||||
streetField = new TextField(getTranslation("register.street"));
|
||||
streetField.setWidthFull();
|
||||
streetField.setRequired(true);
|
||||
houseNumberField = new TextField("Hausnr");
|
||||
houseNumberField = new TextField(getTranslation("register.housenr"));
|
||||
houseNumberField.setWidthFull();
|
||||
houseNumberField.setRequired(true);
|
||||
zipField = new TextField("Postleitzahl");
|
||||
zipField = new TextField(getTranslation("register.zip"));
|
||||
zipField.setWidthFull();
|
||||
zipField.setRequired(true);
|
||||
cityField = new TextField("Stadt");
|
||||
cityField = new TextField(getTranslation("register.city"));
|
||||
cityField.setWidthFull();
|
||||
cityField.setRequired(true);
|
||||
|
||||
codeField = new TextField("Bestätigungscode (6 Ziffern)");
|
||||
codeField = new TextField(getTranslation("register.code.label"));
|
||||
codeField.setWidthFull();
|
||||
codeField.setMaxLength(6);
|
||||
codeField.setPattern("\\d{6}");
|
||||
codeField.setPlaceholder("z. B. 123456");
|
||||
codeField.setPlaceholder(getTranslation("register.code.placeholder"));
|
||||
codeField.setVisible(false);
|
||||
codeField.addValueChangeListener(e -> {
|
||||
String v = e.getValue();
|
||||
@@ -150,22 +149,22 @@ public class RegisterView extends VerticalLayout {
|
||||
});
|
||||
|
||||
// Buttons
|
||||
submitButton = new Button("Registrieren", event -> onStartRegistration());
|
||||
submitButton = new Button(getTranslation("register.button.submit"), event -> onStartRegistration());
|
||||
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_LARGE);
|
||||
submitButton.setWidthFull();
|
||||
|
||||
verifyButton = new Button("Code prüfen und registrieren", event -> onVerifyCode());
|
||||
verifyButton = new Button(getTranslation("register.button.verify"), event -> onVerifyCode());
|
||||
verifyButton.addThemeVariants(ButtonVariant.LUMO_SUCCESS);
|
||||
verifyButton.setWidthFull();
|
||||
verifyButton.setVisible(false);
|
||||
|
||||
resendButton = new Button("Code erneut senden", event -> onResendCode());
|
||||
resendButton = new Button(getTranslation("register.button.resend"), event -> onResendCode());
|
||||
resendButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
resendButton.setWidthFull();
|
||||
resendButton.setVisible(false);
|
||||
|
||||
// Zurück-Link
|
||||
Button backButton = new Button("Zurück zur Startseite", event -> getUI().ifPresent(ui -> ui.navigate("")));
|
||||
Button backButton = new Button(getTranslation("register.button.back"), event -> getUI().ifPresent(ui -> ui.navigate("")));
|
||||
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
backButton.setWidthFull();
|
||||
|
||||
@@ -418,4 +417,9 @@ public class RegisterView extends VerticalLayout {
|
||||
int num = random.nextInt(1_000_000); // 0..999999
|
||||
return String.format("%06d", num);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.register");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.vaadin.flow.component.icon.Icon;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.Customer;
|
||||
import de.assecutor.votianlt.pages.service.CustomerService;
|
||||
@@ -15,10 +15,9 @@ import de.assecutor.votianlt.security.SecurityService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@PageTitle("Kunden")
|
||||
@Route(value = "customers", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed({ "USER", "ADMIN" })
|
||||
public class ShowCustomersView extends VerticalLayout {
|
||||
public class ShowCustomersView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private final CustomerService customerService;
|
||||
private final SecurityService securityService;
|
||||
@@ -35,8 +34,8 @@ public class ShowCustomersView extends VerticalLayout {
|
||||
// Header with title and add button
|
||||
HorizontalLayout header = new HorizontalLayout();
|
||||
header.setWidthFull();
|
||||
header.add(new H2("Kunden"));
|
||||
Button addCustomerButton = new Button("Kunde hinzufügen", new Icon(VaadinIcon.PLUS));
|
||||
header.add(new H2(getTranslation("customers.title")));
|
||||
Button addCustomerButton = new Button(getTranslation("customers.button.add"), new Icon(VaadinIcon.PLUS));
|
||||
header.add(addCustomerButton);
|
||||
header.setJustifyContentMode(JustifyContentMode.BETWEEN);
|
||||
header.setAlignItems(Alignment.CENTER);
|
||||
@@ -44,23 +43,23 @@ public class ShowCustomersView extends VerticalLayout {
|
||||
|
||||
// Add hint text
|
||||
var hintText = new com.vaadin.flow.component.html.Paragraph(
|
||||
"Klicken Sie auf einen Eintrag, um ihn zu bearbeiten.");
|
||||
getTranslation("customers.hint.click"));
|
||||
hintText.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
hintText.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
add(hintText);
|
||||
|
||||
// Configure grid columns
|
||||
grid.addColumn(Customer::getCompanyName).setHeader("Firma").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(Customer::getCompanyName).setHeader(getTranslation("customers.column.company")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(customer -> (customer.getFirstname() != null ? customer.getFirstname() : "") + " "
|
||||
+ (customer.getLastName() != null ? customer.getLastName() : "")).setHeader("Name").setAutoWidth(true)
|
||||
+ (customer.getLastName() != null ? customer.getLastName() : "")).setHeader(getTranslation("customers.column.name")).setAutoWidth(true)
|
||||
.setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(Customer::getMail).setHeader("E-Mail").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(Customer::getTelephone).setHeader("Telefon").setAutoWidth(true).setSortable(true);
|
||||
grid.addColumn(Customer::getMail).setHeader(getTranslation("customers.column.email")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(Customer::getTelephone).setHeader(getTranslation("customers.column.phone")).setAutoWidth(true).setSortable(true);
|
||||
grid.addColumn(customer -> (customer.getStreet() != null ? customer.getStreet() : "") + " "
|
||||
+ (customer.getHouseNumber() != null ? customer.getHouseNumber() : "")).setHeader("Straße")
|
||||
+ (customer.getHouseNumber() != null ? customer.getHouseNumber() : "")).setHeader(getTranslation("customers.column.street"))
|
||||
.setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(customer -> (customer.getZip() != null ? customer.getZip() : "") + " "
|
||||
+ (customer.getCity() != null ? customer.getCity() : "")).setHeader("Ort").setAutoWidth(true)
|
||||
+ (customer.getCity() != null ? customer.getCity() : "")).setHeader(getTranslation("customers.column.city")).setAutoWidth(true)
|
||||
.setFlexGrow(1).setSortable(true);
|
||||
|
||||
grid.setMultiSort(true);
|
||||
@@ -93,4 +92,9 @@ public class ShowCustomersView extends VerticalLayout {
|
||||
.filter(c -> c.getCreatedBy() != null && c.getCreatedBy().equals(currentUserId)).toList();
|
||||
grid.setItems(ownCustomers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.show.customers");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ 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.server.StreamResource;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.Job;
|
||||
import de.assecutor.votianlt.model.JobStatus;
|
||||
@@ -33,16 +33,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
@PageTitle("Aufträge")
|
||||
@Route(value = "jobs", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed({ "USER" })
|
||||
@Slf4j
|
||||
public class ShowJobsView extends VerticalLayout {
|
||||
public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private final DatePicker startDate = new DatePicker("Startdatum");
|
||||
private final DatePicker endDate = new DatePicker("Enddatum");
|
||||
private final TextField searchField = new TextField("Auftragsnummer suchen");
|
||||
private final ComboBox<String> statusFilter = new ComboBox<>("Status");
|
||||
private final DatePicker startDate = new DatePicker();
|
||||
private final DatePicker endDate = new DatePicker();
|
||||
private final TextField searchField = new TextField();
|
||||
private final ComboBox<String> statusFilter = new ComboBox<>();
|
||||
private final JobRepository jobRepository;
|
||||
private final JobHistoryService jobHistoryService;
|
||||
private final SecurityService securityService;
|
||||
@@ -63,28 +62,33 @@ public class ShowJobsView extends VerticalLayout {
|
||||
setPadding(true);
|
||||
setSpacing(true);
|
||||
|
||||
H2 title = new H2("Aufträge");
|
||||
startDate.setLabel(getTranslation("jobs.filter.startdate"));
|
||||
endDate.setLabel(getTranslation("jobs.filter.enddate"));
|
||||
searchField.setLabel(getTranslation("jobs.filter.search"));
|
||||
statusFilter.setLabel(getTranslation("jobs.filter.status"));
|
||||
|
||||
H2 title = new H2(getTranslation("nav.jobs"));
|
||||
add(title);
|
||||
|
||||
// Configure status filter
|
||||
statusFilter.setItems("Alle", "Offen", "Erledigt");
|
||||
statusFilter.setValue("Offen");
|
||||
statusFilter.setItems(getTranslation("jobs.status.all"), getTranslation("jobs.status.open"), getTranslation("jobs.status.done"));
|
||||
statusFilter.setValue(getTranslation("jobs.status.open"));
|
||||
statusFilter.setWidth("150px");
|
||||
|
||||
// Configure search field
|
||||
searchField.setPlaceholder("Auftragsnummer eingeben...");
|
||||
searchField.setPlaceholder(getTranslation("jobs.filter.search.placeholder"));
|
||||
searchField.setClearButtonVisible(true);
|
||||
searchField.setWidth("200px");
|
||||
|
||||
// Filterleiste mit Export-Button am rechten Rand
|
||||
Button applyFilter = new Button("Anwenden");
|
||||
Button applyFilter = new Button(getTranslation("jobs.filter.apply"));
|
||||
HorizontalLayout leftFilters = new HorizontalLayout(startDate, endDate, searchField, statusFilter, applyFilter);
|
||||
leftFilters.setAlignItems(Alignment.END);
|
||||
|
||||
HorizontalLayout filterBar = new HorizontalLayout();
|
||||
filterBar.setWidthFull();
|
||||
filterBar.add(leftFilters);
|
||||
Button exportButton = new Button("CSV Export");
|
||||
Button exportButton = new Button(getTranslation("jobs.button.csvexport"));
|
||||
filterBar.add(exportButton);
|
||||
filterBar.setJustifyContentMode(JustifyContentMode.BETWEEN);
|
||||
filterBar.setAlignItems(Alignment.END);
|
||||
@@ -104,12 +108,12 @@ public class ShowJobsView extends VerticalLayout {
|
||||
endDate.addValueChangeListener(e -> loadData());
|
||||
|
||||
// Configure grid columns: Auftraggeber, Auftragsnummer, Auftragsdatum, Zielort
|
||||
grid.addColumn(job -> extractCompanyName(job.getCustomerSelection())).setHeader("Auftraggeber")
|
||||
grid.addColumn(job -> extractCompanyName(job.getCustomerSelection())).setHeader(getTranslation("jobs.column.customer"))
|
||||
.setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(Job::getJobNumber).setHeader("Auftragsnummer").setAutoWidth(true).setSortable(true);
|
||||
grid.addColumn(job -> DateTimeFormatUtil.formatDateTime(job.getCreatedAt())).setHeader("Auftragsdatum")
|
||||
grid.addColumn(Job::getJobNumber).setHeader(getTranslation("jobs.column.jobnumber")).setAutoWidth(true).setSortable(true);
|
||||
grid.addColumn(job -> DateTimeFormatUtil.formatDateTime(job.getCreatedAt())).setHeader(getTranslation("jobs.column.jobdate"))
|
||||
.setAutoWidth(true).setSortable(true);
|
||||
grid.addColumn(Job::getDeliveryCity).setHeader("Zielort").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(Job::getDeliveryCity).setHeader(getTranslation("jobs.column.destination")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
|
||||
// Action column: manual completion for jobs without digital processing
|
||||
grid.addComponentColumn(job -> {
|
||||
@@ -117,7 +121,7 @@ public class ShowJobsView extends VerticalLayout {
|
||||
&& job.getStatus() != JobStatus.CANCELLED) {
|
||||
Button completeBtn = new Button(new Icon(VaadinIcon.CHECK_CIRCLE));
|
||||
completeBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SUCCESS);
|
||||
completeBtn.setTooltipText("Auftrag manuell abschließen");
|
||||
completeBtn.setTooltipText(getTranslation("jobs.tooltip.complete"));
|
||||
completeBtn.addClickListener(e -> {
|
||||
e.getSource().getElement().getNode(); // prevent row click
|
||||
showCompleteJobDialog(job);
|
||||
@@ -132,7 +136,7 @@ public class ShowJobsView extends VerticalLayout {
|
||||
if (job.getStatus() == JobStatus.COMPLETED) {
|
||||
Button invoiceBtn = new Button(new Icon(VaadinIcon.DOLLAR));
|
||||
invoiceBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SUCCESS);
|
||||
invoiceBtn.setTooltipText("Rechnung erstellen");
|
||||
invoiceBtn.setTooltipText(getTranslation("jobs.tooltip.createinvoice"));
|
||||
invoiceBtn.addClickListener(e -> {
|
||||
e.getSource().getElement().getNode(); // prevent row click
|
||||
getUI().ifPresent(ui -> ui.navigate("create_invoice/" + job.getId().toHexString()));
|
||||
@@ -150,7 +154,7 @@ public class ShowJobsView extends VerticalLayout {
|
||||
}
|
||||
Button deleteBtn = new Button(new Icon(VaadinIcon.TRASH));
|
||||
deleteBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ERROR);
|
||||
deleteBtn.setTooltipText("Auftrag löschen");
|
||||
deleteBtn.setTooltipText(getTranslation("jobs.tooltip.delete"));
|
||||
deleteBtn.addClickListener(e -> {
|
||||
e.getSource().getElement().getNode(); // prevent row click
|
||||
showDeleteJobDialog(job);
|
||||
@@ -180,11 +184,11 @@ public class ShowJobsView extends VerticalLayout {
|
||||
|
||||
private void showCompleteJobDialog(Job job) {
|
||||
ConfirmDialog dialog = new ConfirmDialog();
|
||||
dialog.setHeader("Auftrag abschließen");
|
||||
dialog.setText("Möchten Sie den Auftrag " + job.getJobNumber() + " manuell abschließen?");
|
||||
dialog.setHeader(getTranslation("jobs.dialog.complete.title"));
|
||||
dialog.setText(getTranslation("jobs.dialog.complete.text", job.getJobNumber()));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setCancelText("Abbrechen");
|
||||
dialog.setConfirmText("Abschließen");
|
||||
dialog.setCancelText(getTranslation("button.cancel"));
|
||||
dialog.setConfirmText(getTranslation("jobs.dialog.complete.confirm"));
|
||||
dialog.setConfirmButtonTheme("primary");
|
||||
dialog.addConfirmListener(e -> {
|
||||
try {
|
||||
@@ -193,11 +197,11 @@ public class ShowJobsView extends VerticalLayout {
|
||||
job.setUpdatedAt(LocalDateTime.now());
|
||||
jobRepository.save(job);
|
||||
jobHistoryService.logStatusChange(job, oldStatus, JobStatus.COMPLETED, "Manuell");
|
||||
Notification.show("Auftrag " + job.getJobNumber() + " wurde abgeschlossen.", 3000,
|
||||
Notification.show(getTranslation("jobs.notification.completed", job.getJobNumber()), 3000,
|
||||
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
loadData();
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Abschließen: " + ex.getMessage(), 5000, Notification.Position.BOTTOM_END)
|
||||
Notification.show(getTranslation("jobs.notification.complete.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
}
|
||||
});
|
||||
@@ -206,12 +210,11 @@ public class ShowJobsView extends VerticalLayout {
|
||||
|
||||
private void showDeleteJobDialog(Job job) {
|
||||
ConfirmDialog dialog = new ConfirmDialog();
|
||||
dialog.setHeader("Auftrag löschen");
|
||||
dialog.setText("Möchten Sie den Auftrag " + job.getJobNumber()
|
||||
+ " wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.");
|
||||
dialog.setHeader(getTranslation("jobs.dialog.delete.title"));
|
||||
dialog.setText(getTranslation("jobs.dialog.delete.text", job.getJobNumber()));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setCancelText("Abbrechen");
|
||||
dialog.setConfirmText("Löschen");
|
||||
dialog.setCancelText(getTranslation("button.cancel"));
|
||||
dialog.setConfirmText(getTranslation("button.delete"));
|
||||
dialog.setConfirmButtonTheme("error primary");
|
||||
dialog.addConfirmListener(e -> {
|
||||
try {
|
||||
@@ -219,11 +222,11 @@ public class ShowJobsView extends VerticalLayout {
|
||||
notifyClientJobDeleted(job);
|
||||
|
||||
jobRepository.delete(job);
|
||||
Notification.show("Auftrag " + job.getJobNumber() + " wurde gelöscht.", 3000,
|
||||
Notification.show(getTranslation("jobs.notification.deleted", job.getJobNumber()), 3000,
|
||||
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
loadData();
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Löschen: " + ex.getMessage(), 5000, Notification.Position.BOTTOM_END)
|
||||
Notification.show(getTranslation("jobs.notification.delete.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
}
|
||||
});
|
||||
@@ -264,9 +267,9 @@ public class ShowJobsView extends VerticalLayout {
|
||||
String selectedStatus = statusFilter.getValue();
|
||||
java.util.List<JobStatus> statusList;
|
||||
|
||||
if ("Erledigt".equals(selectedStatus)) {
|
||||
if (getTranslation("jobs.status.done").equals(selectedStatus)) {
|
||||
statusList = java.util.List.of(JobStatus.DELIVERED, JobStatus.COMPLETED, JobStatus.CANCELLED);
|
||||
} else if ("Offen".equals(selectedStatus)) {
|
||||
} else if (getTranslation("jobs.status.open").equals(selectedStatus)) {
|
||||
statusList = java.util.List.of(JobStatus.CREATED, JobStatus.IN_PROGRESS, JobStatus.PICKUP_SCHEDULED,
|
||||
JobStatus.PICKED_UP, JobStatus.IN_TRANSIT);
|
||||
} else { // "Alle"
|
||||
@@ -311,7 +314,10 @@ public class ShowJobsView extends VerticalLayout {
|
||||
private String generateCsv(java.util.List<Job> jobs) {
|
||||
StringBuilder csv = new StringBuilder();
|
||||
// CSV Header
|
||||
csv.append("Auftraggeber,Auftragsnummer,Auftragsdatum,Zielort\n");
|
||||
csv.append(getTranslation("csv.header.customer")).append(",")
|
||||
.append(getTranslation("csv.header.jobnumber")).append(",")
|
||||
.append(getTranslation("csv.header.jobdate")).append(",")
|
||||
.append(getTranslation("csv.header.destination")).append("\n");
|
||||
|
||||
// CSV Data
|
||||
for (Job job : jobs) {
|
||||
@@ -344,4 +350,9 @@ public class ShowJobsView extends VerticalLayout {
|
||||
}
|
||||
return customerSelection.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.jobs");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
@@ -20,9 +20,8 @@ import de.assecutor.votianlt.security.SecurityService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
@Route("")
|
||||
@PageTitle("VotianLT - Willkommen")
|
||||
@AnonymousAllowed
|
||||
public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
||||
public class StartView extends VerticalLayout implements BeforeEnterObserver, HasDynamicTitle {
|
||||
|
||||
private final SecurityService securityService;
|
||||
private final String appVersion;
|
||||
@@ -89,10 +88,10 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
||||
navButtons.setSpacing(true);
|
||||
navButtons.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
|
||||
Button loginBtn = new Button("Anmelden", event -> login());
|
||||
Button loginBtn = new Button(getTranslation("start.button.login"), event -> login());
|
||||
loginBtn.addThemeVariants(ButtonVariant.LUMO_CONTRAST);
|
||||
|
||||
Button registerBtn = new Button("Registrieren", event -> register());
|
||||
Button registerBtn = new Button(getTranslation("start.button.register"), event -> register());
|
||||
registerBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
navButtons.add(loginBtn, registerBtn);
|
||||
@@ -181,7 +180,7 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
||||
heroIcon.setSize("120px");
|
||||
heroIcon.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
|
||||
H1 heroTitle = new H1("VotianLT - Ihr digitaler Transportpartner");
|
||||
H1 heroTitle = new H1(getTranslation("start.title"));
|
||||
heroTitle.getStyle().set("text-align", "center");
|
||||
heroTitle.getStyle().set("color", "var(--lumo-primary-text-color)");
|
||||
heroTitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
|
||||
@@ -193,7 +192,7 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
||||
heroDescription.getStyle().set("max-width", "600px");
|
||||
heroDescription.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
||||
|
||||
Button ctaButton = new Button("Jetzt kostenlos testen", event -> register());
|
||||
Button ctaButton = new Button(getTranslation("cta.freetest"), event -> register());
|
||||
ctaButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_LARGE);
|
||||
|
||||
heroSection.add(heroIcon, heroTitle, heroDescription, ctaButton);
|
||||
@@ -344,4 +343,9 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
||||
private void login() {
|
||||
UI.getCurrent().navigate("login");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.welcome");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.Scroller;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.ai.service.AiStatisticsService;
|
||||
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
||||
@@ -27,12 +27,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@PageTitle("KI-Statistiken")
|
||||
@Route(value = "statistics", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed({ "USER", "ADMIN" })
|
||||
@JavaScript("https://cdn.jsdelivr.net/npm/chart.js")
|
||||
@Slf4j
|
||||
public class StatisticsView extends VerticalLayout {
|
||||
public class StatisticsView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private final AiStatisticsService aiStatisticsService;
|
||||
private final VerticalLayout chatContainer;
|
||||
@@ -43,7 +42,7 @@ public class StatisticsView extends VerticalLayout {
|
||||
|
||||
// Prompt Field initialisieren
|
||||
this.promptField = new TextField();
|
||||
this.promptField.setPlaceholder("Stelle eine Frage zu deinen Statistiken...");
|
||||
this.promptField.setPlaceholder(getTranslation("statistics.prompt.placeholder"));
|
||||
this.promptField.setWidthFull();
|
||||
this.promptField.setClearButtonVisible(true);
|
||||
this.promptField.addKeyPressListener(Key.ENTER, e -> sendPrompt());
|
||||
@@ -87,10 +86,10 @@ public class StatisticsView extends VerticalLayout {
|
||||
Icon aiIcon = VaadinIcon.MAGIC.create();
|
||||
aiIcon.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
|
||||
H2 title = new H2("KI-Statistik-Assistent");
|
||||
H2 title = new H2(getTranslation("statistics.title"));
|
||||
title.getStyle().set("margin", "0").set("font-size", "var(--lumo-font-size-xl)");
|
||||
|
||||
Span subtitle = new Span("Frage mich zu Aufträgen, Umsätzen und Statistiken");
|
||||
Span subtitle = new Span(getTranslation("statistics.subtitle"));
|
||||
subtitle.getStyle().set("color", "var(--lumo-secondary-text-color)").set("font-size", "var(--lumo-font-size-s)")
|
||||
.set("margin-left", "var(--lumo-space-m)");
|
||||
|
||||
@@ -113,11 +112,12 @@ public class StatisticsView extends VerticalLayout {
|
||||
sendButton.getStyle().set("min-width", "50px");
|
||||
|
||||
// Quick Action Buttons
|
||||
Button jobCountBtn = createQuickActionButton("Aufträge zählen",
|
||||
"Wie viele Aufträge gibt es insgesamt und nach Status?");
|
||||
Button revenueBtn = createQuickActionButton("Umsatz", "Zeige mir den Umsatz pro Kunde.");
|
||||
Button trendBtn = createQuickActionButton("Monatstrend",
|
||||
"Zeige mir den Monatstrend der Aufträge für dieses Jahr.");
|
||||
Button jobCountBtn = createQuickActionButton(getTranslation("statistics.quick.jobcount"),
|
||||
getTranslation("statistics.quick.jobcount.prompt"));
|
||||
Button revenueBtn = createQuickActionButton(getTranslation("statistics.quick.revenue"),
|
||||
getTranslation("statistics.quick.revenue.prompt"));
|
||||
Button trendBtn = createQuickActionButton(getTranslation("statistics.quick.trend"),
|
||||
getTranslation("statistics.quick.trend.prompt"));
|
||||
|
||||
HorizontalLayout quickActions = new HorizontalLayout(jobCountBtn, revenueBtn, trendBtn);
|
||||
quickActions.setSpacing(true);
|
||||
@@ -179,7 +179,7 @@ public class StatisticsView extends VerticalLayout {
|
||||
log.error("Error processing AI request", e);
|
||||
ui.access(() -> {
|
||||
chatContainer.remove(loadingMessage);
|
||||
addErrorMessage("Entschuldigung, es gab einen Fehler bei der Verarbeitung: " + e.getMessage());
|
||||
addErrorMessage(getTranslation("statistics.error", e.getMessage()));
|
||||
scrollToBottom();
|
||||
});
|
||||
}
|
||||
@@ -234,7 +234,7 @@ public class StatisticsView extends VerticalLayout {
|
||||
aiIcon.setSize("16px");
|
||||
aiIcon.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
|
||||
Span aiLabel = new Span("KI-Assistent");
|
||||
Span aiLabel = new Span(getTranslation("statistics.ai.label"));
|
||||
aiLabel.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-s)");
|
||||
|
||||
header.add(aiIcon, aiLabel);
|
||||
@@ -245,7 +245,7 @@ public class StatisticsView extends VerticalLayout {
|
||||
textDiv.getStyle().set("margin-top", "var(--lumo-space-s)");
|
||||
String responseText = response.textResponse();
|
||||
if (responseText == null || responseText.isBlank()) {
|
||||
responseText = "*Die Statistikdaten wurden erfolgreich abgerufen und werden im Diagramm angezeigt.*";
|
||||
responseText = getTranslation("statistics.data.fetched");
|
||||
}
|
||||
textDiv.getElement().setProperty("innerHTML", formatMarkdown(responseText));
|
||||
bubble.add(textDiv);
|
||||
@@ -307,7 +307,7 @@ public class StatisticsView extends VerticalLayout {
|
||||
.set("padding", "var(--lumo-space-s) var(--lumo-space-m)")
|
||||
.set("border-radius", "var(--lumo-border-radius-l)");
|
||||
|
||||
Span dots = new Span("Analysiere...");
|
||||
Span dots = new Span(getTranslation("statistics.loading"));
|
||||
dots.getStyle().set("color", "var(--lumo-secondary-text-color)").set("font-style", "italic");
|
||||
|
||||
bubble.add(dots);
|
||||
@@ -650,4 +650,9 @@ public class StatisticsView extends VerticalLayout {
|
||||
super.onAttach(attachEvent);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.statistics");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ 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.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.AppUser;
|
||||
import de.assecutor.votianlt.model.Message;
|
||||
@@ -36,10 +36,9 @@ import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Route(value = "user-messages", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Nachrichten")
|
||||
@RolesAllowed("USER")
|
||||
@Slf4j
|
||||
public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
public class UserMessagesView extends Main implements HasUrlParameter<String>, HasDynamicTitle {
|
||||
|
||||
private final AppUserService appUserService;
|
||||
private final MessageService messageService;
|
||||
@@ -79,7 +78,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
}
|
||||
|
||||
String clientName = client != null ? client.getVorname() + " " + client.getNachname()
|
||||
: Optional.ofNullable(participantKey).orElse("Unbekannter Teilnehmer");
|
||||
: Optional.ofNullable(participantKey).orElse(getTranslation("usermessages.unknown.participant"));
|
||||
|
||||
HorizontalLayout headerLayout = createHeaderLayout(clientName);
|
||||
contentLayout.add(headerLayout);
|
||||
@@ -96,10 +95,10 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
}
|
||||
|
||||
private HorizontalLayout createHeaderLayout(String clientName) {
|
||||
Button backButton = new Button("Zurück", VaadinIcon.ARROW_LEFT.create());
|
||||
Button backButton = new Button(getTranslation("button.back"), VaadinIcon.ARROW_LEFT.create());
|
||||
backButton.addClickListener(e -> UI.getCurrent().navigate("messages"));
|
||||
|
||||
H2 title = new H2("Nachrichten mit " + clientName);
|
||||
H2 title = new H2(getTranslation("usermessages.title.with", clientName));
|
||||
|
||||
HorizontalLayout layout = new HorizontalLayout(backButton, title);
|
||||
layout.setWidthFull();
|
||||
@@ -118,7 +117,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
section.setWidthFull();
|
||||
section.getStyle().set("margin-right", "20px");
|
||||
|
||||
H3 title = new H3("Allgemeine Nachrichten");
|
||||
H3 title = new H3(getTranslation("usermessages.general.title"));
|
||||
section.add(title);
|
||||
|
||||
List<Message> sortedMessages = new ArrayList<>();
|
||||
@@ -136,7 +135,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
LocalDateTime lastMessageTime = latest != null ? latest.getCreatedAt() : null;
|
||||
String preview = resolvePreview(latest);
|
||||
|
||||
section.add(createMessageCard("Allgemeine Unterhaltung", preview, lastMessageTime, messageCount, unreadCount,
|
||||
section.add(createMessageCard(getTranslation("usermessages.general.conversation"), preview, lastMessageTime, messageCount, unreadCount,
|
||||
"general"));
|
||||
|
||||
return section;
|
||||
@@ -151,11 +150,11 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
section.setWidthFull();
|
||||
section.getStyle().set("margin-right", "20px");
|
||||
|
||||
H3 title = new H3("Nachrichten zu Aufträgen");
|
||||
H3 title = new H3(getTranslation("usermessages.job.title"));
|
||||
section.add(title);
|
||||
|
||||
if (jobMessages == null || jobMessages.isEmpty()) {
|
||||
section.add(new Span("Keine auftragsbezogenen Nachrichten vorhanden."));
|
||||
section.add(new Span(getTranslation("usermessages.no.job.messages")));
|
||||
return section;
|
||||
}
|
||||
|
||||
@@ -168,7 +167,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
int unreadCount = (int) messages.stream()
|
||||
.filter(message -> message.getOrigin() == MessageOrigin.CLIENT && !message.isRead()).count();
|
||||
|
||||
String conversationTitle = "Auftrag " + jobKey;
|
||||
String conversationTitle = getTranslation("usermessages.job.conversation", jobKey);
|
||||
section.add(createMessageCard(conversationTitle, resolvePreview(latest), latest.getCreatedAt(),
|
||||
messages.size(), unreadCount, "job-" + sanitizeConversationId(jobKey)));
|
||||
});
|
||||
@@ -182,7 +181,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
}
|
||||
|
||||
if (message.getContentType() == MessageContentType.IMAGE) {
|
||||
return "[Bildnachricht]";
|
||||
return getTranslation("messages.preview.image");
|
||||
}
|
||||
|
||||
return Optional.ofNullable(message.getContent()).map(String::trim).orElse("");
|
||||
@@ -235,7 +234,8 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
|
||||
// Preview text
|
||||
Span preview = new Span(
|
||||
Optional.ofNullable(lastMessagePreview).filter(s -> !s.isBlank()).orElse("(kein Inhalt)"));
|
||||
Optional.ofNullable(lastMessagePreview).filter(s -> !s.isBlank())
|
||||
.orElse(getTranslation("usermessages.preview.empty")));
|
||||
preview.getStyle().set("color", "#666666");
|
||||
preview.getStyle().set("font-size", "14px");
|
||||
|
||||
@@ -247,7 +247,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
timeSpan.getStyle().set("color", "#999999");
|
||||
timeSpan.getStyle().set("font-size", "12px");
|
||||
|
||||
Span countSpan = new Span(messageCount + " Nachrichten");
|
||||
Span countSpan = new Span(getTranslation("usermessages.message.count", messageCount));
|
||||
countSpan.getStyle().set("color", "#999999");
|
||||
countSpan.getStyle().set("font-size", "12px");
|
||||
|
||||
@@ -270,7 +270,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
|
||||
private String resolveJobKey(Message message) {
|
||||
if (message == null) {
|
||||
return "Unbekannt";
|
||||
return getTranslation("usermessages.unknown");
|
||||
}
|
||||
String jobNumber = Optional.ofNullable(message.getJobNumber()).filter(s -> !s.isBlank()).orElse(null);
|
||||
if (jobNumber != null) {
|
||||
@@ -280,7 +280,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
if (jobId != null && !jobId.isBlank()) {
|
||||
return jobId;
|
||||
}
|
||||
return "Unbekannt";
|
||||
return getTranslation("usermessages.unknown");
|
||||
}
|
||||
|
||||
private String sanitizeConversationId(String value) {
|
||||
@@ -289,4 +289,9 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
}
|
||||
return value.replaceAll("[^a-zA-Z0-9_-]", "_");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.user.messages");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.vaadin.flow.component.html.Main;
|
||||
import com.vaadin.flow.component.html.Paragraph;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
||||
@@ -13,25 +13,24 @@ import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
@Route(value = "verwaltung", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@PageTitle("Verwaltung")
|
||||
@Menu(order = 5, icon = "vaadin:cogs", title = "Verwaltung")
|
||||
@RolesAllowed("USER")
|
||||
public class VerwaltungView extends Main {
|
||||
public class VerwaltungView extends Main implements HasDynamicTitle {
|
||||
|
||||
public VerwaltungView() {
|
||||
setSizeFull();
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||
|
||||
add(new ViewToolbar("Verwaltung"));
|
||||
add(new ViewToolbar(getTranslation("verwaltung.title")));
|
||||
|
||||
// Content
|
||||
VerticalLayout content = new VerticalLayout();
|
||||
|
||||
H1 title = new H1("Verwaltung");
|
||||
H1 title = new H1(getTranslation("verwaltung.title"));
|
||||
title.getStyle().set("color", "var(--lumo-primary-color)");
|
||||
|
||||
Paragraph description = new Paragraph("Willkommen im Verwaltungsbereich. Wählen Sie eine Option aus dem Menü.");
|
||||
Paragraph description = new Paragraph(getTranslation("verwaltung.description"));
|
||||
description.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
|
||||
content.add(title, description);
|
||||
@@ -41,4 +40,9 @@ public class VerwaltungView extends Main {
|
||||
|
||||
add(content);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.verwaltung");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.assecutor.votianlt.service;
|
||||
|
||||
import com.vaadin.flow.component.UI;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.repository.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Service
|
||||
public class LanguageService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
public LanguageService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
public void updateUserLanguage(User user, Language language) {
|
||||
user.setLanguage(language);
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
public Language getUserLanguage(User user) {
|
||||
return user.getLanguage();
|
||||
}
|
||||
|
||||
public String getTranslation(String key, Language language) {
|
||||
try {
|
||||
Locale locale;
|
||||
switch (language) {
|
||||
case DE:
|
||||
locale = Locale.GERMAN;
|
||||
break;
|
||||
case EN:
|
||||
locale = Locale.ENGLISH;
|
||||
break;
|
||||
case FR:
|
||||
locale = Locale.FRENCH;
|
||||
break;
|
||||
case ES:
|
||||
locale = new Locale("es", "ES");
|
||||
break;
|
||||
default:
|
||||
locale = Locale.GERMAN;
|
||||
}
|
||||
|
||||
ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
|
||||
return bundle.getString(key);
|
||||
} catch (Exception e) {
|
||||
// Fallback to key itself if translation not found
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public String getTranslation(String key, User user) {
|
||||
return getTranslation(key, user.getLanguage());
|
||||
}
|
||||
|
||||
public void applyLocaleForUser(User user) {
|
||||
if (user != null && user.getLanguage() != null) {
|
||||
// Get the UI instance and set the locale
|
||||
UI ui = UI.getCurrent();
|
||||
if (ui != null) {
|
||||
Locale locale = getLocaleFromLanguage(user.getLanguage());
|
||||
ui.setLocale(locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Locale getLocaleFromLanguage(Language language) {
|
||||
return switch (language) {
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> new Locale("es", "ES");
|
||||
default -> Locale.GERMAN;
|
||||
};
|
||||
}
|
||||
|
||||
public Language getCurrentUserLanguage(User user) {
|
||||
return user != null ? user.getLanguage() : Language.DE;
|
||||
}
|
||||
}
|
||||
876
src/main/resources/messages.properties
Normal file
876
src/main/resources/messages.properties
Normal file
@@ -0,0 +1,876 @@
|
||||
# Navigation and Main Layout
|
||||
nav.jobs=Aufträge
|
||||
nav.customers=Kunden
|
||||
nav.appusers=App-Nutzer
|
||||
nav.statistics=Statistiken
|
||||
nav.invoices=Rechnungen
|
||||
nav.messages=Nachrichten
|
||||
nav.profile=Mein Profil
|
||||
nav.myinvoices=Meine Rechnungen
|
||||
nav.imprint=Impressum
|
||||
nav.management=Verwaltung
|
||||
nav.users=Benutzer
|
||||
nav.showprofile=Profil anzeigen
|
||||
nav.settings=Einstellungen
|
||||
nav.logout=Abmelden
|
||||
|
||||
# Profile View
|
||||
profile.title=Profil bearbeiten
|
||||
profile.language=Sprache
|
||||
profile.company=Firma
|
||||
profile.companyadd=Firmenzusatz
|
||||
profile.firstname=Vorname
|
||||
profile.lastname=Nachname
|
||||
profile.phone=Telefonnummer
|
||||
profile.fax=Telefon (Fax)
|
||||
profile.mobile=Telefon (Mobil)
|
||||
profile.email=E-Mail-Adresse (Login)*
|
||||
profile.street=Straße
|
||||
profile.housenr=Hausnr
|
||||
profile.addressadd=Adresszusatz
|
||||
profile.zip=Postleitzahl
|
||||
profile.city=Stadt
|
||||
profile.diffinvoice=Abweichende Rechnungsadresse
|
||||
profile.basicdata=Stammdaten
|
||||
profile.map=Karte
|
||||
profile.invoicecreation=Rechnungserstellung
|
||||
profile.settings=Einstellungen
|
||||
profile.account=Konto
|
||||
profile.security=Sicherheit
|
||||
profile.services=Leistungskatalog
|
||||
profile.saved=Profil gespeichert
|
||||
profile.save.error=Fehler beim Speichern: {0}
|
||||
profile.validation.required.fill=Bitte füllen Sie alle Pflichtfelder korrekt aus
|
||||
|
||||
# Profile Settings
|
||||
settings.digitalprocessing=Digitale Abwicklung per App
|
||||
settings.digitalprocessinginfo=Aktiviert die digitale Auftragsabwicklung über die mobile App
|
||||
settings.locationtracking=App-Nutzer orten
|
||||
settings.locationtrackinginfo=Ermöglicht die Ortung von App-Nutzern während der Auftragsausführung
|
||||
settings.twofactor=2-Faktor-Authentifizierung
|
||||
settings.twofactorinfo=Bei Aktivierung wird bei jeder Anmeldung ein Code per E-Mail gesendet
|
||||
|
||||
# Profile Billing
|
||||
profile.billing.enabled=Rechnungslegung über votianLT
|
||||
|
||||
# Profile Validation
|
||||
profile.validation.company=Firma ist ein Pflichtfeld
|
||||
profile.validation.firstname=Vorname ist ein Pflichtfeld
|
||||
profile.validation.lastname=Nachname ist ein Pflichtfeld
|
||||
profile.validation.phone=Telefonnummer ist ein Pflichtfeld
|
||||
profile.validation.street=Straße ist ein Pflichtfeld
|
||||
profile.validation.housenr=Hausnummer ist ein Pflichtfeld
|
||||
profile.validation.zip=Postleitzahl ist ein Pflichtfeld
|
||||
profile.validation.city=Stadt ist ein Pflichtfeld
|
||||
profile.validation.email.required=E-Mail-Adresse ist ein Pflichtfeld
|
||||
profile.validation.email.invalid=Bitte geben Sie eine gültige E-Mail-Adresse ein
|
||||
profile.validation.company.required=Firma ist erforderlich
|
||||
profile.validation.street.required=Straße ist erforderlich
|
||||
profile.validation.housenr.required=Hausnummer ist erforderlich
|
||||
profile.validation.zip.required=Postleitzahl ist erforderlich
|
||||
profile.validation.city.required=Stadt ist erforderlich
|
||||
profile.validation.firstname.required=Vorname ist erforderlich
|
||||
profile.validation.lastname.required=Nachname ist erforderlich
|
||||
profile.validation.phone.required=Telefonnummer ist erforderlich
|
||||
|
||||
# Profile Invoice
|
||||
profile.invoice.masterdata=Meine Stammdaten
|
||||
profile.invoice.name=Name
|
||||
profile.invoice.city=Ort
|
||||
profile.invoice.email=E-Mail
|
||||
profile.invoice.phone=Telefon
|
||||
profile.invoice.placeholder.company=Ihre Firma
|
||||
profile.invoice.placeholder.name=Ihr Name
|
||||
profile.invoice.placeholder.street=Ihre Straße
|
||||
profile.invoice.placeholder.city=PLZ Ort
|
||||
profile.invoice.placeholder.email=ihre@email.de
|
||||
profile.invoice.placeholder.phone=Ihre Telefonnummer
|
||||
profile.invoice.services.list=Leistungen auflisten
|
||||
profile.invoice.net=Nettosumme
|
||||
profile.invoice.vat=Umsatzsteuer
|
||||
profile.invoice.gross=Bruttosumme
|
||||
profile.invoice.customerdata=Kundendaten
|
||||
profile.invoice.customer.company=Kunde Firma
|
||||
profile.invoice.customer.name=Kunde Name
|
||||
profile.invoice.customer.street=Kunde Straße
|
||||
profile.invoice.customer.city=Kunde Ort
|
||||
profile.invoice.customer.email=Kunde E-Mail
|
||||
profile.invoice.customer.phone=Kunde Telefon
|
||||
profile.invoice.free.elements=Freie Elemente
|
||||
profile.invoice.element.text=Textfeld
|
||||
profile.invoice.element.header=Überschrift
|
||||
profile.invoice.element.date=Datum
|
||||
profile.invoice.element.customer=Kundeninfo
|
||||
profile.invoice.element.company=Firmeninfo
|
||||
profile.invoice.element.amount=Betrag
|
||||
profile.invoice.element.line=Linie
|
||||
profile.invoice.element.image=Bild
|
||||
profile.invoice.properties=Eigenschaften
|
||||
profile.invoice.properties.info=Klicken Sie auf ein Element im Canvas, um dessen Eigenschaften zu bearbeiten.
|
||||
profile.invoice.type=Typ
|
||||
profile.invoice.variable=Variable
|
||||
profile.invoice.xposition=X Position
|
||||
profile.invoice.yposition=Y Position
|
||||
profile.invoice.fontsize=Schriftgröße
|
||||
profile.invoice.color=Farbe
|
||||
profile.invoice.element.delete=Element löschen
|
||||
profile.invoice.image=Bild hochladen
|
||||
profile.invoice.image.drop=Bild hierher ziehen oder klicken
|
||||
profile.invoice.image.uploaded=Bild erfolgreich hochgeladen
|
||||
profile.invoice.image.upload.error=Fehler beim Hochladen: {0}
|
||||
profile.invoice.file.rejected=Datei abgelehnt: {0}
|
||||
profile.invoice.text.from.masterdata=Text kommt aus Ihren Stammdaten
|
||||
profile.invoice.canvas.cleared=Canvas wurde geleert
|
||||
profile.invoice.canvas.read.error=Fehler: Canvas-Daten konnten nicht gelesen werden
|
||||
profile.invoice.template.saved=Template erfolgreich gespeichert
|
||||
profile.invoice.pdf.error=Fehler bei PDF-Generierung: {0}
|
||||
profile.invoice.pdf.preview=Vorschau
|
||||
profile.invoice.pdf.preview.error=Fehler beim Generieren der Vorschau: {0}
|
||||
|
||||
# Profile Services
|
||||
profile.services.label=Leistungen
|
||||
profile.services.description=Verwalten Sie hier Ihre Leistungen, die Sie Ihren Kunden anbieten.
|
||||
profile.services.add=Neue Leistung hinzufügen
|
||||
profile.services.load.error=Fehler beim Laden der Leistungen: {0}
|
||||
profile.services.saved=Leistung erfolgreich gespeichert
|
||||
profile.services.save.error=Fehler beim Speichern der Leistung: {0}
|
||||
profile.services.deleted=Leistung erfolgreich gelöscht
|
||||
profile.services.delete.error=Fehler beim Löschen der Leistung: {0}
|
||||
profile.services.dialog.create=Neue Leistung erstellen
|
||||
profile.services.dialog.edit=Leistung bearbeiten
|
||||
profile.services.basis=Berechnungsgrundlage
|
||||
profile.services.basis.distance=Gefahrene Kilometer
|
||||
profile.services.basis.time=Zeit
|
||||
profile.services.basis.flatrate=Pauschal
|
||||
profile.services.vatrate=Mehrwertsteuersatz (%)
|
||||
profile.services.vatrate.percent=Mehrwertsteuersatz (%)
|
||||
profile.services.price.flatrate=Pauschalpreis (€)
|
||||
profile.services.price.distance=Preis pro Kilometer (€)
|
||||
profile.services.price.time=Preis pro 15 Minuten (€)
|
||||
profile.services.mandatory=Verpflichtend
|
||||
profile.services.calculated=Wird berechnet
|
||||
profile.services.validation.name=Name ist erforderlich
|
||||
profile.services.validation.basis=Berechnungsgrundlage ist erforderlich
|
||||
profile.services.validation.flatrate=Pauschalpreis ist erforderlich
|
||||
profile.services.validation.distance=Preis pro Kilometer ist erforderlich
|
||||
profile.services.validation.time=Preis pro 15 Minuten ist erforderlich
|
||||
profile.services.validation.vatrate=Mehrwertsteuersatz ist erforderlich
|
||||
profile.services.savechanges=Leistung speichern
|
||||
|
||||
# Buttons
|
||||
button.save=Profiländerungen speichern
|
||||
button.savechanges=Speichern
|
||||
button.clear=Leeren
|
||||
button.preview=Vorschau
|
||||
button.savetemplate=Template speichern
|
||||
button.changepassword=Passwort ändern
|
||||
button.deleteaccount=Benutzerkonto löschen
|
||||
button.add=Neu
|
||||
button.edit=Bearbeiten
|
||||
button.delete=Löschen
|
||||
button.cancel=Abbrechen
|
||||
button.close=Schließen
|
||||
button.download=Herunterladen
|
||||
button.back=Zurück
|
||||
|
||||
# Common
|
||||
common.name=Name
|
||||
common.yes=Ja
|
||||
common.no=Nein
|
||||
common.total=Gesamt
|
||||
common.price=Preis
|
||||
common.service=Leistung
|
||||
common.customer=Kunde
|
||||
common.actions=Aktionen
|
||||
common.loading=Laden...
|
||||
common.error=Fehler
|
||||
common.success=Erfolg
|
||||
common.required=Pflichtfeld
|
||||
|
||||
# Validation
|
||||
validation.required=Feld ist erforderlich
|
||||
validation.email=Ungültige E-Mail-Adresse
|
||||
validation.error=Fehler bei der Validierung
|
||||
|
||||
# Notifications
|
||||
notification.saved=Profil gespeichert
|
||||
notification.error=Fehler beim Speichern
|
||||
notification.languagechanged=Sprache geändert
|
||||
|
||||
# Login
|
||||
login.title=Anmelden
|
||||
login.username=Benutzername
|
||||
login.password=Passwort
|
||||
login.login=Anmelden
|
||||
login.forgotpassword=Passwort vergessen?
|
||||
login.rememberme=Angemeldet bleiben
|
||||
login.register=Registrieren
|
||||
login.2fa.helper=6-stelliger Code
|
||||
login.2fa.sent=Code wurde per E-Mail gesendet
|
||||
login.2fa.no.credentials=Keine Anmeldedaten vorhanden
|
||||
login.2fa.invalid.code=Ungültiger Code
|
||||
login.2fa.wrong.code=Falscher Code
|
||||
|
||||
# Error Messages
|
||||
error.loading=Fehler beim Laden
|
||||
error.saving=Fehler beim Speichern
|
||||
error.validation=Validierungsfehler
|
||||
|
||||
# Page Titles
|
||||
page.title.dashboard=VotianLT - Dashboard
|
||||
page.title.appuser.create=Neuen App-Nutzer anlegen
|
||||
page.title.messages=Nachrichten
|
||||
page.title.register=Bei VotianLT registrieren
|
||||
page.title.customers=Kunden
|
||||
page.title.customer.edit=Kunde bearbeiten
|
||||
page.title.verwaltung=Verwaltung
|
||||
page.title.company.create=Neue Firma anlegen
|
||||
page.title.imprint=Impressum
|
||||
page.title.profile.edit=Profil bearbeiten
|
||||
page.title.admin.dashboard=Admin Dashboard
|
||||
page.title.invoice.create=Rechnung erstellen
|
||||
page.title.customer.create=Neuen Kunden anlegen
|
||||
page.title.login=Bei VotianLT anmelden
|
||||
page.title.jobs=Aufträge
|
||||
page.title.appuser.edit=App-Nutzer bearbeiten
|
||||
page.title.statistics=KI-Statistiken
|
||||
page.title.password.forget=Passwort zurücksetzen
|
||||
page.title.invoices=Rechnungen
|
||||
page.title.appusers=App-Nutzer
|
||||
page.title.job.history=Job Historie
|
||||
page.title.message.history=Nachrichtenverlauf
|
||||
page.title.myinvoices=Meine Rechnungen
|
||||
page.title.job.create=Neuen Auftrag anlegen
|
||||
page.title.job.summary=Zusammenfassung
|
||||
page.title.pricetable=Preis-Tabelle
|
||||
page.title.invoice.generator=Rechnungsgenerator
|
||||
page.title.welcome=VotianLT - Willkommen
|
||||
page.title.password.reset=Passwort zurücksetzen – E-Mail angeben
|
||||
page.title.add.appuser=Neuen App-Nutzer anlegen
|
||||
page.title.user.messages=Nachrichten
|
||||
page.title.edit.customer=Kunde bearbeiten
|
||||
page.title.show.customers=Kunden
|
||||
page.title.add.company=Neue Firma anlegen
|
||||
page.title.create.invoice=Rechnung erstellen
|
||||
page.title.add.customer=Neuen Kunden anlegen
|
||||
page.title.edit.appuser=App-Nutzer bearbeiten
|
||||
page.title.forget.password=Passwort zurücksetzen
|
||||
page.title.job.history=Job Historie
|
||||
page.title.admin.pricetable=Preis-Tabelle
|
||||
page.title.invoice.generator=Rechnungsgenerator
|
||||
page.title.job.summary=Zusammenfassung
|
||||
page.title.add.job=Neuen Auftrag anlegen
|
||||
|
||||
# Dashboard
|
||||
dashboard.welcome=Willkommen, {0}!
|
||||
dashboard.footer.copyright=© 2024 VotianLT. Alle Rechte vorbehalten.
|
||||
dashboard.description=Hier können Sie Ihre Aufträge verwalten, Kunden organisieren und alle wichtigen Funktionen von VotianLT nutzen.
|
||||
dashboard.system.title=Systemübersicht
|
||||
dashboard.system.intro=Verwalten Sie Ihre Geschäftsprozesse effizient mit den folgenden Funktionen
|
||||
dashboard.feature.setup.title=Einrichtung
|
||||
dashboard.feature.setup.desc=Konfigurieren Sie Ihre Systemeinstellungen und Stammdaten
|
||||
dashboard.feature.customers.title=Kunden
|
||||
dashboard.feature.customers.desc=Verwalten Sie Ihre Kundenbeziehungen und Kontakte
|
||||
dashboard.feature.jobs.title=Aufträge
|
||||
dashboard.feature.jobs.desc=Erstellen und verwalten Sie Aufträge effizient
|
||||
dashboard.app.title=Mobile App
|
||||
dashboard.app.description=Nutzen Sie die VotianLT App für unterwegs und bleiben Sie immer verbunden
|
||||
|
||||
# Add App User
|
||||
addappuser.title=Neuen App-Nutzer anlegen
|
||||
addappuser.designation=Bezeichnung
|
||||
addappuser.phone=Telefon (Mobil)
|
||||
addappuser.password=Passwort
|
||||
addappuser.password.confirm=Passwort bestätigen
|
||||
addappuser.button.submit=App-Nutzer anlegen
|
||||
addappuser.validation.designation=Bezeichnung ist erforderlich
|
||||
addappuser.validation.phone=Telefonnummer ist erforderlich
|
||||
addappuser.validation.password.required=Passwort ist erforderlich
|
||||
addappuser.validation.password.min=Passwort muss mindestens 6 Zeichen haben
|
||||
addappuser.validation.password.confirm=Passwortbestätigung ist erforderlich
|
||||
addappuser.validation.password.mismatch=Passwörter stimmen nicht überein
|
||||
addappuser.validation.email.required=E-Mail ist erforderlich
|
||||
addappuser.validation.email.invalid=Ungültige E-Mail-Adresse
|
||||
addappuser.notification.validation=Bitte füllen Sie alle Pflichtfelder aus
|
||||
addappuser.notification.success=App-Nutzer erfolgreich angelegt
|
||||
addappuser.notification.check=Bitte überprüfen Sie Ihre Eingaben
|
||||
addappuser.notification.email.duplicate=Diese E-Mail-Adresse wird bereits verwendet
|
||||
addappuser.notification.error=Fehler: {0}
|
||||
addappuser.placeholder.designation=(HH H 000)
|
||||
|
||||
# Edit App User
|
||||
editappuser.title=App-Nutzer bearbeiten
|
||||
editappuser.password.change=Neues Passwort
|
||||
editappuser.password.change.confirm=Neues Passwort bestätigen
|
||||
editappuser.password.placeholder=Leer lassen, um Passwort nicht zu ändern
|
||||
editappuser.notification.invalid.id=Ungültige App-Nutzer-ID
|
||||
editappuser.notification.password.mismatch=Passwörter stimmen nicht überein
|
||||
editappuser.notification.saved=App-Nutzer erfolgreich gespeichert
|
||||
editappuser.notification.check=Bitte überprüfen Sie Ihre Eingaben
|
||||
editappuser.notification.password.confirm=Bitte bestätigen Sie das neue Passwort
|
||||
editappuser.notification.password.enter=Bitte geben Sie ein neues Passwort ein
|
||||
editappuser.notification.deleted=App-Nutzer erfolgreich gelöscht
|
||||
editappuser.dialog.delete.text=Möchten Sie diesen App-Nutzer wirklich löschen?
|
||||
editappuser.dialog.delete.confirm=Löschen
|
||||
|
||||
# Customers
|
||||
customers.title=Kunden
|
||||
customers.button.add=Neuen Kunden hinzufügen
|
||||
customers.hint.click=Klicken Sie auf einen Kunden, um Details zu sehen
|
||||
customers.column.company=Firma
|
||||
customers.column.name=Name
|
||||
customers.column.email=E-Mail
|
||||
customers.column.phone=Telefon
|
||||
customers.column.street=Straße
|
||||
customers.column.city=Ort
|
||||
|
||||
# Edit Customer
|
||||
editcustomer.title=Kunde bearbeiten
|
||||
editcustomer.notification.notfound=Kunde nicht gefunden
|
||||
editcustomer.notification.invalid.id=Ungültige Kunden-ID
|
||||
editcustomer.notification.saved=Kunde erfolgreich gespeichert
|
||||
editcustomer.notification.check=Bitte überprüfen Sie Ihre Eingaben
|
||||
editcustomer.notification.deleted=Kunde erfolgreich gelöscht
|
||||
editcustomer.dialog.delete.text=Möchten Sie diesen Kunden wirklich löschen?
|
||||
editcustomer.dialog.delete.confirm=Löschen
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Neuen Kunden anlegen
|
||||
addcustomer.button.submit=Kunden anlegen
|
||||
addcustomer.notification.validation=Bitte füllen Sie alle Pflichtfelder aus
|
||||
addcustomer.notification.success=Kunde erfolgreich angelegt
|
||||
addcustomer.notification.check=Bitte überprüfen Sie Ihre Eingaben
|
||||
addcustomer.notification.error=Fehler: {0}
|
||||
addcustomer.validation.required=Dieses Feld ist erforderlich
|
||||
|
||||
# Add Company
|
||||
addcompany.title=Neue Firma anlegen
|
||||
addcompany.button.submit=Firma anlegen
|
||||
|
||||
# Verwaltung
|
||||
verwaltung.title=Verwaltung
|
||||
verwaltung.description=Verwalten Sie hier Ihre Firmen, Kunden und Systemeinstellungen
|
||||
|
||||
# User Messages
|
||||
usermessages.title.with=Nachrichten mit {0}
|
||||
usermessages.general.title=Allgemeine Konversationen
|
||||
usermessages.general.conversation=Allgemeine Konversation
|
||||
usermessages.job.title=Auftragsbezogene Nachrichten
|
||||
usermessages.job.conversation=Auftrag {0}
|
||||
usermessages.no.job.messages=Keine auftragsbezogenen Nachrichten
|
||||
usermessages.preview.empty=Keine Vorschau verfügbar
|
||||
usermessages.message.count={0} Nachrichten
|
||||
usermessages.unknown=Unbekannt
|
||||
usermessages.unknown.participant=Unbekannter Teilnehmer
|
||||
|
||||
# Admin Dashboard
|
||||
admindashboard.title=Admin Dashboard
|
||||
admindashboard.loading=Statistiken werden geladen...
|
||||
admindashboard.error=Fehler beim Laden: {0}
|
||||
admindashboard.section.overview=Übersicht
|
||||
admindashboard.section.jobs=Aufträge
|
||||
admindashboard.section.tasks=Aufgaben
|
||||
admindashboard.section.users=Benutzeraktivitäten
|
||||
admindashboard.section.health=Systemstatus
|
||||
admindashboard.stat.totaljobs=Gesamtaufträge
|
||||
admindashboard.stat.users=Benutzer
|
||||
admindashboard.stat.appusers=App-Nutzer
|
||||
admindashboard.stat.lastupdated=Zuletzt aktualisiert
|
||||
admindashboard.stat.openjobs=Offene Aufträge
|
||||
admindashboard.stat.inprogress=In Bearbeitung
|
||||
admindashboard.stat.completed=Abgeschlossen
|
||||
admindashboard.stat.cargo=Frachtstücke
|
||||
admindashboard.stat.status.info=Status
|
||||
admindashboard.stat.status.unavailable=Nicht verfügbar
|
||||
admindashboard.stat.totaltasks=Gesamtaufgaben
|
||||
admindashboard.stat.completedtasks=Erledigt
|
||||
admindashboard.stat.pendingtasks=Ausstehend
|
||||
admindashboard.stat.successrate=Erfolgsrate
|
||||
admindashboard.stat.photos=Fotos
|
||||
admindashboard.stat.barcodes=Barcodes
|
||||
admindashboard.stat.signatures=Unterschriften
|
||||
admindashboard.stat.comments=Kommentare
|
||||
admindashboard.stat.database=Datenbank
|
||||
admindashboard.stat.database.connected=Verbunden
|
||||
admindashboard.stat.database.error=Fehler
|
||||
admindashboard.stat.websocket=WebSocket
|
||||
admindashboard.stat.websocket.active=Aktiv
|
||||
admindashboard.stat.app=Anwendung
|
||||
admindashboard.stat.app.running=Läuft
|
||||
admindashboard.stat.memory=Speicher
|
||||
|
||||
# Messages
|
||||
messages.title=Nachrichten
|
||||
messages.column.status=Status
|
||||
messages.column.client=Kunde
|
||||
messages.column.email=E-Mail
|
||||
messages.column.total=Gesamt
|
||||
messages.column.unread=Ungelesen
|
||||
messages.column.lastmessage=Letzte Nachricht
|
||||
messages.column.preview=Vorschau
|
||||
messages.notification.error=Fehler beim Laden der Nachrichten
|
||||
messages.preview.image=Bild
|
||||
messages.preview.empty=Keine Vorschau
|
||||
messages.sender.unknown=Unbekannter Absender
|
||||
|
||||
# Add Job
|
||||
addjob.title=Neuen Auftrag anlegen
|
||||
addjob.customer.label=Kunde
|
||||
addjob.customer.placeholder=Kunde auswählen
|
||||
addjob.customer.unnamed=Unbenannter Kunde
|
||||
addjob.button.clearfields=Felder leeren
|
||||
addjob.button.submit=Auftrag anlegen
|
||||
addjob.address.salutation=Anrede
|
||||
addjob.address.salutation.placeholder=Anrede wählen
|
||||
addjob.salutation.mr=Herr
|
||||
addjob.salutation.ms=Frau
|
||||
addjob.salutation.other=Divers
|
||||
addjob.address.company.placeholder=Firma eingeben
|
||||
addjob.address.street.placeholder=Straße eingeben
|
||||
addjob.address.housenumber=Hausnummer
|
||||
addjob.address.addition.placeholder=Adresszusatz
|
||||
addjob.address.city=Ort
|
||||
addjob.address.city.placeholder.pickup=Ort (Abholung)
|
||||
addjob.address.city.placeholder.delivery=Ort (Lieferung)
|
||||
addjob.address.delivery.street.placeholder=Straße (Lieferung)
|
||||
addjob.address.delivery.addition.placeholder=Adresszusatz (Lieferung)
|
||||
addjob.address.save=Adresse speichern
|
||||
addjob.section.pickup=Abholung
|
||||
addjob.section.delivery=Lieferung
|
||||
addjob.tab.addresses=Auftraggeber & Adressen
|
||||
addjob.tab.appointments=Termine & Verarbeitung
|
||||
addjob.tab.cargo=Fracht
|
||||
addjob.tab.tasks=Aufgaben
|
||||
addjob.tab.price=Preis & Abschluss
|
||||
addjob.appointment.date=Datum
|
||||
addjob.appointment.time=Uhrzeit
|
||||
addjob.appointment.pickup=Abholtermin
|
||||
addjob.appointment.delivery=Liefertermin
|
||||
addjob.settings.digitalprocess=Digitale Abwicklung per App
|
||||
addjob.appuser.label=App-Nutzer
|
||||
addjob.appuser.placeholder=App-Nutzer auswählen
|
||||
addjob.cargo.description=Beschreibung
|
||||
addjob.cargo.description.placeholder=Beschreibung eingeben
|
||||
addjob.cargo.quantity=Anzahl
|
||||
addjob.cargo.weight=Gewicht
|
||||
addjob.cargo.length=Länge
|
||||
addjob.cargo.width=Breite
|
||||
addjob.cargo.height=Höhe
|
||||
addjob.cargo.europalette=Europalette
|
||||
addjob.cargo.disposablepalette=Einwegpalette
|
||||
addjob.cargo.dusseldorfpalette=Düsseldorfer Palette
|
||||
addjob.cargo.gridboxpalette=Gitterboxpalette
|
||||
addjob.cargo.gridcart=Gitterwagen
|
||||
addjob.cargo.parcel=Paket
|
||||
addjob.cargo.add=Fracht hinzufügen
|
||||
addjob.tasks.title=Aufgaben
|
||||
addjob.tasks.template.placeholder=Template auswählen
|
||||
addjob.tasks.template.save.tooltip=Als Template speichern
|
||||
addjob.tasks.template.save.title=Template speichern
|
||||
addjob.tasks.template.name=Template-Name
|
||||
addjob.tasks.template.name.placeholder=Name eingeben
|
||||
addjob.tasks.template.name.required=Name ist erforderlich
|
||||
addjob.tasks.template.saved=Template "{0}" gespeichert
|
||||
addjob.tasks.template.save.error=Fehler beim Speichern: {0}
|
||||
addjob.tasks.template.dialog.error=Fehler beim Öffnen des Dialogs: {0}
|
||||
addjob.tasks.template.no.tasks=Keine Aufgaben zum Speichern
|
||||
addjob.tasks.template.load.title=Template laden
|
||||
addjob.tasks.template.load.text=Möchten Sie das Template "{0}" laden? Diese Aktion ersetzt alle aktuellen Aufgaben.
|
||||
addjob.tasks.template.load.confirm=Laden
|
||||
addjob.tasks.template.loaded=Template "{0}" geladen
|
||||
addjob.tasks.template.load.error=Fehler beim Laden: {0}
|
||||
addjob.tasks.template.load.templates.error=Fehler beim Laden der Templates: {0}
|
||||
addjob.tasks.add=Aufgabe hinzufügen
|
||||
addjob.tasks.tasktype=Aufgabentyp
|
||||
addjob.tasks.tasktype.placeholder=Typ wählen
|
||||
addjob.tasks.description=Beschreibung
|
||||
addjob.tasks.description.placeholder=Beschreibung eingeben
|
||||
addjob.tasks.buttontext=Button-Text
|
||||
addjob.tasks.buttontext.placeholder=Text eingeben
|
||||
addjob.tasks.remark=Bemerkung
|
||||
addjob.tasks.remark.placeholder=Bemerkung eingeben
|
||||
addjob.tasks.photo.min=Min. Fotos
|
||||
addjob.tasks.photo.max=Max. Fotos
|
||||
addjob.tasks.barcode.min=Min. Barcodes
|
||||
addjob.tasks.barcode.max=Max. Barcodes
|
||||
addjob.tasks.signature.noconfig=Keine Konfiguration erforderlich
|
||||
addjob.tasks.todolist.title=To-Do Liste
|
||||
addjob.tasks.todolist.item.placeholder=To-Do eingeben
|
||||
addjob.tasks.todolist.add=To-Do hinzufügen
|
||||
addjob.tasks.comment.label=Kommentar
|
||||
addjob.tasks.comment.placeholder=Kommentar eingeben
|
||||
addjob.tasks.comment.required=Kommentar erforderlich
|
||||
addjob.services.title=Leistungen
|
||||
addjob.services.add=Leistung hinzufügen
|
||||
addjob.services.calculation=Berechnung
|
||||
addjob.services.basis.distance=Gefahrene Kilometer
|
||||
addjob.services.basis.time=Zeit
|
||||
addjob.services.basis.flatrate=Pauschal
|
||||
addjob.services.vat=Mehrwertsteuer
|
||||
addjob.services.route.missing=Route fehlt
|
||||
addjob.services.dialog.title=Leistung auswählen
|
||||
addjob.services.dialog.placeholder=Leistung wählen
|
||||
addjob.services.dialog.add=Hinzufügen
|
||||
addjob.summary.title=Zusammenfassung
|
||||
addjob.summary.net=Netto
|
||||
addjob.summary.vat=Mehrwertsteuer
|
||||
addjob.summary.gross=Brutto
|
||||
addjob.route.title=Route
|
||||
addjob.route.distance=Entfernung
|
||||
addjob.route.distance.km=Entfernung (km)
|
||||
addjob.route.distance.placeholder=z.B. 150.5
|
||||
addjob.route.duration=Dauer
|
||||
addjob.route.duration.min=Dauer (Min.)
|
||||
addjob.route.duration.placeholder=z.B. 120
|
||||
addjob.route.manual.title=Manuelle Streckeneingabe
|
||||
addjob.route.manual.hint=Geben Sie die Entfernung und Dauer manuell ein, wenn keine Route berechnet wurde
|
||||
addjob.notification.success=Auftrag {0} erfolgreich angelegt
|
||||
addjob.notification.cleared=Alle Felder wurden geleert
|
||||
addjob.notification.draft.restored=Entwurf wiederhergestellt
|
||||
addjob.validation.required.fields=Bitte füllen Sie alle Pflichtfelder aus
|
||||
addjob.validation.appuser.required=Bitte wählen Sie einen App-Nutzer aus
|
||||
addjob.validation.cargo.required=Bitte geben Sie mindestens eine Fracht an
|
||||
addjob.validation.pickupdate.future=Abholdatum muss heute oder in der Zukunft liegen
|
||||
addjob.validation.deliverydate.future=Lieferdatum muss heute oder in der Zukunft liegen
|
||||
addjob.validation.dialog.title=Adressvalidierung
|
||||
addjob.validation.dialog.loading=Adressen werden validiert...
|
||||
addjob.validation.dialog.back=Zurück
|
||||
addjob.validation.dialog.continue=Weiter
|
||||
addjob.validation.dialog.continue.anyway=Trotzdem weiter
|
||||
addjob.validation.pickup.address=Abholadresse
|
||||
addjob.validation.delivery.address=Lieferadresse
|
||||
addjob.validation.route=Route
|
||||
|
||||
# Job Summary
|
||||
jobsummary.title=Zusammenfassung
|
||||
jobsummary.error.noid=Keine Job-ID angegeben
|
||||
jobsummary.error.invalidid=Ungültige Job-ID Format: {0}
|
||||
jobsummary.error.notfound=Job mit ID {0} nicht gefunden
|
||||
jobsummary.button.sendmessage=Nachricht senden
|
||||
jobsummary.button.jobhistory=Job Historie
|
||||
jobsummary.button.complete=Auftrag manuell abschließen
|
||||
jobsummary.dialog.complete.title=Auftrag abschließen
|
||||
jobsummary.dialog.complete.text=Möchten Sie den Auftrag {0} manuell abschließen?
|
||||
jobsummary.dialog.complete.cancel=Abbrechen
|
||||
jobsummary.dialog.complete.confirm=Abschließen
|
||||
jobsummary.notification.completed=Auftrag {0} wurde abgeschlossen
|
||||
jobsummary.notification.complete.error=Fehler beim Abschließen: {0}
|
||||
jobsummary.notification.noappuser=Diesem Auftrag ist kein App-Nutzer zugeordnet
|
||||
jobsummary.section.pickup=Abholung
|
||||
jobsummary.section.delivery=Lieferung
|
||||
jobsummary.section.tasks=Zu quittierende Aufgaben
|
||||
jobsummary.section.cargo=Zu transportierende Fracht
|
||||
jobsummary.section.info=Weitere Informationen
|
||||
jobsummary.tasks.none=Keine Aufgaben
|
||||
jobsummary.cargo.none=Keine Frachtangaben
|
||||
jobsummary.info.netto=Netto
|
||||
jobsummary.info.ust=USt
|
||||
jobsummary.info.gesamt=Gesamt
|
||||
jobsummary.info.bemerkung=Bemerkung
|
||||
jobsummary.info.digital=Digitale Abwicklung per App: aktiviert
|
||||
jobsummary.info.appuser=App-Nutzer
|
||||
jobsummary.task.status.abgeschlossen=Abgeschlossen
|
||||
jobsummary.task.status.offen=Offen
|
||||
jobsummary.task.typ=Typ
|
||||
jobsummary.task.completedAt=Abgeschlossen am
|
||||
jobsummary.task.completedBy=Abgeschlossen von
|
||||
jobsummary.task.todo.items=To-Do Items
|
||||
jobsummary.task.photo.info=Fotos
|
||||
jobsummary.task.photo.minmax=Mindestens {0} Fotos erforderlich
|
||||
jobsummary.task.photo.maxonly=Maximal {0} Fotos erlaubt
|
||||
jobsummary.task.photo.taken=Aufgenommene Fotos ({0})
|
||||
jobsummary.task.button.text=Button-Text
|
||||
jobsummary.button.schliessen=Schließen
|
||||
|
||||
# Jobs
|
||||
jobs.title=Aufträge
|
||||
jobs.filter.search=Suchen
|
||||
jobs.filter.search.placeholder=Suche nach Auftragsnummer...
|
||||
jobs.filter.startdate=Startdatum
|
||||
jobs.filter.enddate=Enddatum
|
||||
jobs.filter.status=Status
|
||||
jobs.filter.apply=Filter anwenden
|
||||
jobs.status.all=Alle
|
||||
jobs.status.open=Offen
|
||||
jobs.status.done=Erledigt
|
||||
jobs.notification.completed=Auftrag {0} wurde abgeschlossen
|
||||
jobs.column.status=Status
|
||||
jobs.column.customer=Kunde
|
||||
jobs.column.jobnumber=Auftragsnummer
|
||||
jobs.column.jobdate=Auftragsdatum
|
||||
jobs.column.destination=Zielort
|
||||
jobs.historie.manuell=Manuell
|
||||
jobs.button.csvexport=CSV Export
|
||||
jobs.tooltip.complete=Auftrag abschließen
|
||||
jobs.tooltip.createinvoice=Rechnung erstellen
|
||||
jobs.tooltip.delete=Auftrag löschen
|
||||
jobs.dialog.complete.title=Auftrag abschließen
|
||||
jobs.dialog.complete.text=Möchten Sie den Auftrag {0} manuell abschließen?
|
||||
jobs.dialog.complete.confirm=Abschließen
|
||||
jobs.dialog.delete.title=Auftrag löschen
|
||||
jobs.dialog.delete.text=Möchten Sie den Auftrag {0} wirklich löschen?
|
||||
jobs.notification.completed=Auftrag {0} wurde abgeschlossen
|
||||
jobs.notification.complete.error=Fehler beim Abschließen: {0}
|
||||
jobs.notification.deleted=Auftrag {0} wurde gelöscht
|
||||
jobs.notification.delete.error=Fehler beim Löschen: {0}
|
||||
|
||||
# Create Invoice
|
||||
createinvoice.error.invalidid=Ungültige Job-ID
|
||||
createinvoice.error.notfound=Job nicht gefunden
|
||||
createinvoice.button.create=Rechnung erstellen
|
||||
createinvoice.section.job=Auftragsdetails
|
||||
createinvoice.section.route=Streckeninfo
|
||||
createinvoice.section.services=Leistungen
|
||||
createinvoice.section.summary=Zusammenfassung
|
||||
createinvoice.field.jobnumber=Auftragsnummer
|
||||
createinvoice.field.customer=Kunde
|
||||
createinvoice.field.status=Status
|
||||
createinvoice.field.price=Preis
|
||||
createinvoice.route.distance=Entfernung
|
||||
createinvoice.route.duration=Fahrtzeit
|
||||
createinvoice.column.service=Leistung
|
||||
createinvoice.column.basis=Berechnungsbasis
|
||||
createinvoice.summary.net=Nettosumme
|
||||
createinvoice.summary.total=Gesamtsumme
|
||||
createinvoice.notification.noservices=Bitte wählen Sie mindestens eine Leistung aus
|
||||
createinvoice.notification.nouser=Benutzer nicht gefunden
|
||||
createinvoice.notification.notemplate=Kein Rechnungstemplate gefunden
|
||||
createinvoice.notification.error=Fehler beim Erstellen der Rechnung: {0}
|
||||
|
||||
# Invoices
|
||||
invoices.title=Rechnungen
|
||||
invoices.column.number=Nummer
|
||||
invoices.column.customer=Kunde
|
||||
invoices.column.date=Datum
|
||||
invoices.column.amount=Betrag
|
||||
invoices.column.description=Beschreibung
|
||||
|
||||
# My Invoices
|
||||
myinvoices.title=Meine Rechnungen
|
||||
myinvoices.hint.noopen=Sie haben keine offenen Rechnungen. Alle Rechnungen sind beglichen.
|
||||
myinvoices.bank.institute=Bank
|
||||
myinvoices.bank.beneficiary=Empfänger
|
||||
myinvoices.bank.iban=IBAN
|
||||
myinvoices.recipient.name=Kunde
|
||||
myinvoices.recipient.department=
|
||||
myinvoices.item.description=Position: {0}
|
||||
|
||||
# App User
|
||||
appuser.title=App-Nutzer
|
||||
appuser.button.add=App-Nutzer hinzufügen
|
||||
appuser.column.designation=Bezeichnung
|
||||
appuser.column.firstname=Vorname
|
||||
appuser.column.lastname=Nachname
|
||||
appuser.column.phone=Telefon
|
||||
appuser.column.appcode=App-Code
|
||||
appuser.column.email=E-Mail
|
||||
|
||||
# Statistics
|
||||
statistics.title=KI-Statistiken
|
||||
statistics.subtitle=Stellen Sie Fragen zu Ihren Aufträgen und Kunden
|
||||
statistics.prompt.placeholder=Frage eingeben...
|
||||
statistics.quick.jobcount=Anzahl Aufträge
|
||||
statistics.quick.jobcount.prompt=Wie viele Aufträge habe ich aktuell?
|
||||
statistics.quick.revenue=Umsatz
|
||||
statistics.quick.revenue.prompt=Wie hoch ist mein Umsatz diesen Monat?
|
||||
statistics.quick.trend=Trends
|
||||
statistics.quick.trend.prompt=Zeige mir Trends in den letzten 3 Monaten
|
||||
statistics.ai.label=KI-Antwort
|
||||
statistics.data.fetched=Daten wurden abgerufen
|
||||
statistics.loading=Berechne...
|
||||
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=In Bearbeitung
|
||||
jobstatus.COMPLETED=Abgeschlossen
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Bestätigung
|
||||
tasktype.SIGNATURE=Unterschrift
|
||||
tasktype.TODOLIST=To-Do Liste
|
||||
tasktype.PHOTO=Foto
|
||||
tasktype.BARCODE=Barcode
|
||||
tasktype.COMMENT=Kommentar
|
||||
|
||||
# Password Reset
|
||||
passwordreset.title=Passwort zurücksetzen
|
||||
passwordreset.newpassword=Neues Passwort
|
||||
passwordreset.confirmpassword=Passwort bestätigen
|
||||
passwordreset.button.submit=Passwort speichern
|
||||
passwordreset.button.cancel=Abbrechen
|
||||
passwordreset.button.send=E-Mail senden
|
||||
passwordreset.notification.enterpassword=Bitte geben Sie ein neues Passwort ein
|
||||
passwordreset.notification.mismatch=Die Passwörter stimmen nicht überein
|
||||
passwordreset.notification.success=Passwort wurde erfolgreich geändert
|
||||
passwordreset.notification.invalidtoken=Token ungültig oder abgelaufen
|
||||
passwordreset.notification.entermail=Bitte E-Mail eingeben
|
||||
passwordreset.notification.sent=Falls die E-Mail existiert, wurde ein Link versendet
|
||||
passwordreset.notification.wait=Bitte warten Sie {0} Sekunden, bevor Sie den Code erneut senden
|
||||
|
||||
# Email
|
||||
email.2fa.subject=Ihr VotianLT Bestätigungscode
|
||||
email.2fa.body=Ihr Bestätigungscode lautet: {0}\n\nDieser Code ist 10 Minuten gültig.\nWenn Sie diese Registrierung nicht angefragt haben, ignorieren Sie diese E-Mail.
|
||||
|
||||
# Register
|
||||
register.title=Registrierung
|
||||
register.subtitle=Erstellen Sie Ihr VotianLT-Konto
|
||||
register.email=E-Mail-Adresse
|
||||
register.password=Passwort
|
||||
register.password.placeholder=Mindestens 6 Zeichen
|
||||
register.password.confirm=Passwort bestätigen
|
||||
register.password.confirm.placeholder=Passwort wiederholen
|
||||
register.firstname=Vorname
|
||||
register.lastname=Nachname
|
||||
register.phone=Telefonnummer
|
||||
register.company=Firma
|
||||
register.street=Straße
|
||||
register.housenr=Hausnr
|
||||
register.zip=Postleitzahl
|
||||
register.city=Stadt
|
||||
register.code.label=Bestätigungscode (6 Ziffern)
|
||||
register.code.placeholder=z. B. 123456
|
||||
register.button.submit=Registrieren
|
||||
register.button.verify=Code prüfen und registrieren
|
||||
register.button.resend=Code erneut senden
|
||||
register.button.back=Zurück zur Startseite
|
||||
register.notification.email.required=Bitte geben Sie eine E-Mail-Adresse ein
|
||||
register.notification.email.invalid=Bitte geben Sie eine gültige E-Mail-Adresse ein
|
||||
register.notification.email.duplicate=Ein Benutzer mit dieser E-Mail-Adresse existiert bereits
|
||||
register.notification.password.required=Bitte geben Sie ein Passwort ein
|
||||
register.notification.password.min=Das Passwort muss mindestens 6 Zeichen lang sein
|
||||
register.notification.password.mismatch=Die Passwörter stimmen nicht überein
|
||||
register.notification.firstname.required=Bitte geben Sie Ihren Vornamen ein
|
||||
register.notification.lastname.required=Bitte geben Sie Ihren Nachnamen ein
|
||||
register.notification.phone.required=Bitte geben Sie Ihre Telefonnummer ein
|
||||
register.notification.company.required=Bitte geben Sie den Firmennamen ein
|
||||
register.notification.street.required=Bitte geben Sie die Straße ein
|
||||
register.notification.housenr.required=Bitte geben Sie die Hausnummer ein
|
||||
register.notification.zip.required=Bitte geben Sie die Postleitzahl ein
|
||||
register.notification.city.required=Bitte geben Sie die Stadt ein
|
||||
register.notification.code.sent=Ein Bestätigungscode wurde an {0} gesendet
|
||||
register.notification.code.emailerror=Fehler beim Senden der E-Mail: {0}
|
||||
register.notification.code.expired=Der Code ist abgelaufen. Bitte senden Sie einen neuen Code.
|
||||
register.notification.code.invalid=Der eingegebene Code ist ungültig
|
||||
register.notification.code.startfirst=Bitte starten Sie zuerst die Registrierung
|
||||
register.notification.code.required=Bitte geben Sie den 6-stelligen Code ein
|
||||
register.notification.success=Registrierung erfolgreich. Bitte melden Sie sich an.
|
||||
register.notification.failed=Registrierung fehlgeschlagen: {0}
|
||||
|
||||
# Start Page
|
||||
start.title=VotianLT - Ihr digitaler Transportpartner
|
||||
start.button.login=Anmelden
|
||||
start.button.register=Registrieren
|
||||
start.button.createorder=Auftragserstellung
|
||||
start.button.notifications=Benachrichtigungen
|
||||
start.button.nonotifications=Keine neuen Benachrichtigungen
|
||||
start.system.title=Das System
|
||||
start.feature.setup.title=Einrichtungsassistent
|
||||
start.feature.setup.desc=Mithilfe des Einrichtungsassistenten haben Sie die Möglichkeit, Ihr Nutzerprofil zu vervollständigen.
|
||||
start.feature.customers.title=Kunden- und Auftragsverwaltung
|
||||
start.feature.customers.desc=Mit der Kunden- und Auftragsverwaltung haben Sie alle Kontaktdaten und Auftragsdetails stets im Blick.
|
||||
start.feature.jobs.title=Auftragserstellung
|
||||
start.feature.jobs.desc=Stellen Sie mit wenigen Mausklicks Aufträge ins System ein und legen Sie fest, welcher Mitarbeiter welchen Transportauftrag abarbeiten soll.
|
||||
start.app.title=Die App
|
||||
start.app.description=Jeder Auftrag kann optional über die votianLT-App abgearbeitet werden – ganz ohne "Zettelwirtschaft". So gelangen alle relevanten Auftragsinformationen direkt auf das Smartphone des Fahrers.
|
||||
start.imprint.title=Impressum
|
||||
start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-Mail: ahoi@assecutor.de
|
||||
start.slogan=Betreiben Sie Ihr Geschäft smart … mit votianLT!
|
||||
start.version=Version
|
||||
|
||||
# Login View
|
||||
login.2fa.title=2FA Code
|
||||
login.2fa.button=Code prüfen
|
||||
login.votianlt=VotianLT
|
||||
login.version=Version
|
||||
|
||||
# Message Details
|
||||
messagedetails.button.send=Senden
|
||||
messagedetails.placeholder=Nachricht eingeben...
|
||||
messagedetails.noimage=(kein Bildinhalt)
|
||||
messagedetails.imageerror=(Bild konnte nicht geladen werden)
|
||||
|
||||
# Invoice Generator
|
||||
invoicegenerator.properties.title=Eigenschaften
|
||||
invoicegenerator.properties.type=Typ
|
||||
invoicegenerator.fontsize.label=Schriftgröße
|
||||
invoicegenerator.color.label=Schriftfarbe
|
||||
invoicegenerator.color.dialog.title=Schriftfarbe wählen
|
||||
invoicegenerator.color.dialog.hex=Hex-Farbwert
|
||||
invoicegenerator.button.cancel=Abbrechen
|
||||
invoicegenerator.button.apply=Übernehmen
|
||||
invoicegenerator.button.delete=Element löschen
|
||||
invoicegenerator.notification.color.applied=Farbe übernommen
|
||||
invoicegenerator.upload.drop=Bild hierher ziehen oder klicken
|
||||
invoicegenerator.upload.success=Bild erfolgreich hochgeladen
|
||||
invoicegenerator.upload.error=Fehler beim Hochladen: {0}
|
||||
invoicegenerator.file.rejected=Datei abgelehnt: {0}
|
||||
invoicegenerator.properties.select.info=Klicken Sie auf ein Element im Canvas, um dessen Eigenschaften zu bearbeiten.
|
||||
|
||||
# CSV Export
|
||||
csv.header.customer=Auftraggeber
|
||||
csv.header.jobnumber=Auftragsnummer
|
||||
csv.header.jobdate=Auftragsdatum
|
||||
csv.header.destination=Zielort
|
||||
csv.filename=jobs.csv
|
||||
|
||||
# DatePicker I18n
|
||||
datepicker.month.januar=Januar
|
||||
datepicker.month.februar=Februar
|
||||
datepicker.month.märz=März
|
||||
datepicker.month.april=April
|
||||
datepicker.month.mai=Mai
|
||||
datepicker.month.juni=Juni
|
||||
datepicker.month.juli=Juli
|
||||
datepicker.month.august=August
|
||||
datepicker.month.september=September
|
||||
datepicker.month.oktober=Oktober
|
||||
datepicker.month.november=November
|
||||
datepicker.month.dezember=Dezember
|
||||
datepicker.weekday.sonntag=Sonntag
|
||||
datepicker.weekday.montag=Montag
|
||||
datepicker.weekday.dienstag=Dienstag
|
||||
datepicker.weekday.mittwoch=Mittwoch
|
||||
datepicker.weekday.donnerstag=Donnerstag
|
||||
datepicker.weekday.freitag=Freitag
|
||||
datepicker.weekday.samstag=Samstag
|
||||
datepicker.weekdayshort.so=So
|
||||
datepicker.weekdayshort.mo=Mo
|
||||
datepicker.weekdayshort.di=Di
|
||||
datepicker.weekdayshort.mi=Mi
|
||||
datepicker.weekdayshort.do=Do
|
||||
datepicker.weekdayshort.fr=Fr
|
||||
datepicker.weekdayshort.sa=Sa
|
||||
|
||||
# Job History
|
||||
jobhistory.status.pickupscheduled=Abholung geplant
|
||||
jobhistory.status.pickedup=Abgeholt
|
||||
jobhistory.status.intransit=Unterwegs
|
||||
jobhistory.status.delivered=Zugestellt
|
||||
jobhistory.image.alt=Vergrößertes Foto
|
||||
|
||||
# Version
|
||||
version.label=Version
|
||||
|
||||
# Management Combo
|
||||
management.placeholder=Verwaltung
|
||||
management.customers=Kunden
|
||||
management.jobs=Aufträge
|
||||
management.companies=Firmen
|
||||
|
||||
# User Menu
|
||||
usermenu.profile=Profil anzeigen
|
||||
usermenu.settings=Einstellungen
|
||||
usermenu.logout=Abmelden
|
||||
|
||||
# CTA Button
|
||||
cta.freetest=Jetzt kostenlos testen
|
||||
|
||||
# Miscellaneous
|
||||
misc.toggle.hide=Ausblenden
|
||||
misc.toggle.show=Einblenden
|
||||
misc.nodata=Keine Daten vorhanden
|
||||
misc.loading=Daten werden geladen...
|
||||
misc.error=Fehler aufgetreten
|
||||
misc.retry=Erneut versuchen
|
||||
876
src/main/resources/messages_en.properties
Normal file
876
src/main/resources/messages_en.properties
Normal file
@@ -0,0 +1,876 @@
|
||||
# Navigation and Main Layout
|
||||
nav.jobs=Jobs
|
||||
nav.customers=Customers
|
||||
nav.appusers=App Users
|
||||
nav.statistics=Statistics
|
||||
nav.invoices=Invoices
|
||||
nav.messages=Messages
|
||||
nav.profile=My Profile
|
||||
nav.myinvoices=My Invoices
|
||||
nav.imprint=Imprint
|
||||
nav.management=Management
|
||||
nav.users=Users
|
||||
nav.showprofile=Show Profile
|
||||
nav.settings=Settings
|
||||
nav.logout=Logout
|
||||
|
||||
# Profile View
|
||||
profile.title=Edit Profile
|
||||
profile.language=Language
|
||||
profile.company=Company
|
||||
profile.companyadd=Company Addition
|
||||
profile.firstname=First Name
|
||||
profile.lastname=Last Name
|
||||
profile.phone=Phone Number
|
||||
profile.fax=Fax
|
||||
profile.mobile=Mobile
|
||||
profile.email=Email Address (Login)*
|
||||
profile.street=Street
|
||||
profile.housenr=House No.
|
||||
profile.addressadd=Address Addition
|
||||
profile.zip=Zip Code
|
||||
profile.city=City
|
||||
profile.diffinvoice=Different Invoice Address
|
||||
profile.basicdata=Basic Data
|
||||
profile.map=Map
|
||||
profile.invoicecreation=Invoice Creation
|
||||
profile.settings=Settings
|
||||
profile.account=Account
|
||||
profile.security=Security
|
||||
profile.services=Service Catalog
|
||||
profile.saved=Profile saved
|
||||
profile.save.error=Error saving: {0}
|
||||
profile.validation.required.fill=Please fill in all required fields correctly
|
||||
|
||||
# Profile Settings
|
||||
settings.digitalprocessing=Digital Processing via App
|
||||
settings.digitalprocessinginfo=Enables digital order processing through the mobile app
|
||||
settings.locationtracking=Track App Users
|
||||
settings.locationtrackinginfo=Allows tracking of app users during order execution
|
||||
settings.twofactor=Two-Factor Authentication
|
||||
settings.twofactorinfo=When enabled, a code will be sent via email for each login
|
||||
|
||||
# Profile Billing
|
||||
profile.billing.enabled=Billing via votianLT
|
||||
|
||||
# Profile Validation
|
||||
profile.validation.company=Company is a required field
|
||||
profile.validation.firstname=First name is a required field
|
||||
profile.validation.lastname=Last name is a required field
|
||||
profile.validation.phone=Phone number is a required field
|
||||
profile.validation.street=Street is a required field
|
||||
profile.validation.housenr=House number is a required field
|
||||
profile.validation.zip=Zip code is a required field
|
||||
profile.validation.city=City is a required field
|
||||
profile.validation.email.required=Email address is a required field
|
||||
profile.validation.email.invalid=Please enter a valid email address
|
||||
profile.validation.company.required=Company is required
|
||||
profile.validation.street.required=Street is required
|
||||
profile.validation.housenr.required=House number is required
|
||||
profile.validation.zip.required=Zip code is required
|
||||
profile.validation.city.required=City is required
|
||||
profile.validation.firstname.required=First name is required
|
||||
profile.validation.lastname.required=Last name is required
|
||||
profile.validation.phone.required=Phone number is required
|
||||
|
||||
# Profile Invoice
|
||||
profile.invoice.masterdata=My Data
|
||||
profile.invoice.name=Name
|
||||
profile.invoice.city=City
|
||||
profile.invoice.email=Email
|
||||
profile.invoice.phone=Phone
|
||||
profile.invoice.placeholder.company=Your Company
|
||||
profile.invoice.placeholder.name=Your Name
|
||||
profile.invoice.placeholder.street=Your Street
|
||||
profile.invoice.placeholder.city=ZIP City
|
||||
profile.invoice.placeholder.email=your@email.com
|
||||
profile.invoice.placeholder.phone=Your Phone Number
|
||||
profile.invoice.services.list=List Services
|
||||
profile.invoice.net=Net Total
|
||||
profile.invoice.vat=VAT
|
||||
profile.invoice.gross=Gross Total
|
||||
profile.invoice.customerdata=Customer Data
|
||||
profile.invoice.customer.company=Customer Company
|
||||
profile.invoice.customer.name=Customer Name
|
||||
profile.invoice.customer.street=Customer Street
|
||||
profile.invoice.customer.city=Customer City
|
||||
profile.invoice.customer.email=Customer Email
|
||||
profile.invoice.customer.phone=Customer Phone
|
||||
profile.invoice.free.elements=Free Elements
|
||||
profile.invoice.element.text=Text Field
|
||||
profile.invoice.element.header=Header
|
||||
profile.invoice.element.date=Date
|
||||
profile.invoice.element.customer=Customer Info
|
||||
profile.invoice.element.company=Company Info
|
||||
profile.invoice.element.amount=Amount
|
||||
profile.invoice.element.line=Line
|
||||
profile.invoice.element.image=Image
|
||||
profile.invoice.properties=Properties
|
||||
profile.invoice.properties.info=Click on an element in the canvas to edit its properties
|
||||
profile.invoice.type=Type
|
||||
profile.invoice.variable=Variable
|
||||
profile.invoice.xposition=X Position
|
||||
profile.invoice.yposition=Y Position
|
||||
profile.invoice.fontsize=Font Size
|
||||
profile.invoice.color=Color
|
||||
profile.invoice.element.delete=Delete Element
|
||||
profile.invoice.image=Upload Image
|
||||
profile.invoice.image.drop=Drag image here or click
|
||||
profile.invoice.image.uploaded=Image uploaded successfully
|
||||
profile.invoice.image.upload.error=Error uploading: {0}
|
||||
profile.invoice.file.rejected=File rejected: {0}
|
||||
profile.invoice.text.from.masterdata=Text comes from your master data
|
||||
profile.invoice.canvas.cleared=Canvas cleared
|
||||
profile.invoice.canvas.read.error=Error: Could not read canvas data
|
||||
profile.invoice.template.saved=Template saved successfully
|
||||
profile.invoice.pdf.error=Error generating PDF: {0}
|
||||
profile.invoice.pdf.preview=Preview
|
||||
profile.invoice.pdf.preview.error=Error generating preview: {0}
|
||||
|
||||
# Profile Services
|
||||
profile.services.label=Services
|
||||
profile.services.description=Manage your services that you offer to your customers
|
||||
profile.services.add=Add New Service
|
||||
profile.services.load.error=Error loading services: {0}
|
||||
profile.services.saved=Service saved successfully
|
||||
profile.services.save.error=Error saving service: {0}
|
||||
profile.services.deleted=Service deleted successfully
|
||||
profile.services.delete.error=Error deleting service: {0}
|
||||
profile.services.dialog.create=Create New Service
|
||||
profile.services.dialog.edit=Edit Service
|
||||
profile.services.basis=Calculation Basis
|
||||
profile.services.basis.distance=Distance (km)
|
||||
profile.services.basis.time=Time
|
||||
profile.services.basis.flatrate=Flat Rate
|
||||
profile.services.vatrate=VAT Rate (%)
|
||||
profile.services.vatrate.percent=VAT Rate (%)
|
||||
profile.services.price.flatrate=Flat Rate Price (€)
|
||||
profile.services.price.distance=Price per Kilometer (€)
|
||||
profile.services.price.time=Price per 15 Minutes (€)
|
||||
profile.services.mandatory=Mandatory
|
||||
profile.services.calculated=Calculated
|
||||
profile.services.validation.name=Name is required
|
||||
profile.services.validation.basis=Calculation basis is required
|
||||
profile.services.validation.flatrate=Flat rate price is required
|
||||
profile.services.validation.distance=Price per kilometer is required
|
||||
profile.services.validation.time=Price per 15 minutes is required
|
||||
profile.services.validation.vatrate=VAT rate is required
|
||||
profile.services.savechanges=Save Service
|
||||
|
||||
# Buttons
|
||||
button.save=Save Profile Changes
|
||||
button.savechanges=Save
|
||||
button.clear=Clear
|
||||
button.preview=Preview
|
||||
button.savetemplate=Save Template
|
||||
button.changepassword=Change Password
|
||||
button.deleteaccount=Delete Account
|
||||
button.add=New
|
||||
button.edit=Edit
|
||||
button.delete=Delete
|
||||
button.cancel=Cancel
|
||||
button.close=Close
|
||||
button.download=Download
|
||||
button.back=Back
|
||||
|
||||
# Common
|
||||
common.name=Name
|
||||
common.yes=Yes
|
||||
common.no=No
|
||||
common.total=Total
|
||||
common.price=Price
|
||||
common.service=Service
|
||||
common.customer=Customer
|
||||
common.actions=Actions
|
||||
common.loading=Loading...
|
||||
common.error=Error
|
||||
common.success=Success
|
||||
common.required=Required
|
||||
|
||||
# Validation
|
||||
validation.required=Field is required
|
||||
validation.email=Invalid email address
|
||||
validation.error=Validation error
|
||||
|
||||
# Notifications
|
||||
notification.saved=Profile saved
|
||||
notification.error=Error saving
|
||||
notification.languagechanged=Language changed
|
||||
|
||||
# Login
|
||||
login.title=Login
|
||||
login.username=Username
|
||||
login.password=Password
|
||||
login.login=Login
|
||||
login.forgotpassword=Forgot password?
|
||||
login.rememberme=Remember me
|
||||
login.register=Register
|
||||
login.2fa.helper=6-digit code
|
||||
login.2fa.sent=Code sent via email
|
||||
login.2fa.no.credentials=No credentials available
|
||||
login.2fa.invalid.code=Invalid code
|
||||
login.2fa.wrong.code=Wrong code
|
||||
|
||||
# Error Messages
|
||||
error.loading=Error loading
|
||||
error.saving=Error saving
|
||||
error.validation=Validation error
|
||||
|
||||
# Page Titles
|
||||
page.title.dashboard=VotianLT - Dashboard
|
||||
page.title.appuser.create=Create New App User
|
||||
page.title.messages=Messages
|
||||
page.title.register=Register with VotianLT
|
||||
page.title.customers=Customers
|
||||
page.title.customer.edit=Edit Customer
|
||||
page.title.verwaltung=Management
|
||||
page.title.company.create=Create New Company
|
||||
page.title.imprint=Imprint
|
||||
page.title.profile.edit=Edit Profile
|
||||
page.title.admin.dashboard=Admin Dashboard
|
||||
page.title.invoice.create=Create Invoice
|
||||
page.title.customer.create=Create New Customer
|
||||
page.title.login=Login to VotianLT
|
||||
page.title.jobs=Jobs
|
||||
page.title.appuser.edit=Edit App User
|
||||
page.title.statistics=AI Statistics
|
||||
page.title.password.forget=Reset Password
|
||||
page.title.invoices=Invoices
|
||||
page.title.appusers=App Users
|
||||
page.title.job.history=Job History
|
||||
page.title.message.history=Message History
|
||||
page.title.myinvoices=My Invoices
|
||||
page.title.job.create=Create New Job
|
||||
page.title.job.summary=Summary
|
||||
page.title.pricetable=Price Table
|
||||
page.title.invoice.generator=Invoice Generator
|
||||
page.title.welcome=VotianLT - Welcome
|
||||
page.title.password.reset=Reset Password - Enter Email
|
||||
page.title.add.appuser=Create New App User
|
||||
page.title.user.messages=Messages
|
||||
page.title.edit.customer=Edit Customer
|
||||
page.title.show.customers=Customers
|
||||
page.title.add.company=Create New Company
|
||||
page.title.create.invoice=Create Invoice
|
||||
page.title.add.customer=Create New Customer
|
||||
page.title.edit.appuser=Edit App User
|
||||
page.title.forget.password=Reset Password
|
||||
page.title.job.history=Job History
|
||||
page.title.admin.pricetable=Price Table
|
||||
page.title.invoice.generator=Invoice Generator
|
||||
page.title.job.summary=Summary
|
||||
page.title.add.job=Create New Job
|
||||
|
||||
# Dashboard
|
||||
dashboard.welcome=Welcome, {0}!
|
||||
dashboard.footer.copyright=© 2024 VotianLT. All rights reserved.
|
||||
dashboard.description=Here you can manage your jobs, organize customers and use all important features of VotianLT.
|
||||
dashboard.system.title=System Overview
|
||||
dashboard.system.intro=Manage your business processes efficiently with the following features
|
||||
dashboard.feature.setup.title=Setup
|
||||
dashboard.feature.setup.desc=Configure your system settings and master data
|
||||
dashboard.feature.customers.title=Customers
|
||||
dashboard.feature.customers.desc=Manage your customer relationships and contacts
|
||||
dashboard.feature.jobs.title=Jobs
|
||||
dashboard.feature.jobs.desc=Create and manage jobs efficiently
|
||||
dashboard.app.title=Mobile App
|
||||
dashboard.app.description=Use the VotianLT app on the go and stay connected
|
||||
|
||||
# Add App User
|
||||
addappuser.title=Create New App User
|
||||
addappuser.designation=Designation
|
||||
addappuser.phone=Phone (Mobile)
|
||||
addappuser.password=Password
|
||||
addappuser.password.confirm=Confirm Password
|
||||
addappuser.button.submit=Create App User
|
||||
addappuser.validation.designation=Designation is required
|
||||
addappuser.validation.phone=Phone number is required
|
||||
addappuser.validation.password.required=Password is required
|
||||
addappuser.validation.password.min=Password must be at least 6 characters
|
||||
addappuser.validation.password.confirm=Password confirmation is required
|
||||
addappuser.validation.password.mismatch=Passwords do not match
|
||||
addappuser.validation.email.required=Email is required
|
||||
addappuser.validation.email.invalid=Invalid email address
|
||||
addappuser.notification.validation=Please fill in all required fields
|
||||
addappuser.notification.success=App user created successfully
|
||||
addappuser.notification.check=Please check your input
|
||||
addappuser.notification.email.duplicate=This email address is already in use
|
||||
addappuser.notification.error=Error: {0}
|
||||
addappuser.placeholder.designation=(HH H 000)
|
||||
|
||||
# Edit App User
|
||||
editappuser.title=Edit App User
|
||||
editappuser.password.change=New Password
|
||||
editappuser.password.change.confirm=Confirm New Password
|
||||
editappuser.password.placeholder=Leave empty to keep current password
|
||||
editappuser.notification.invalid.id=Invalid app user ID
|
||||
editappuser.notification.password.mismatch=Passwords do not match
|
||||
editappuser.notification.saved=App user saved successfully
|
||||
editappuser.notification.check=Please check your input
|
||||
editappuser.notification.password.confirm=Please confirm the new password
|
||||
editappuser.notification.password.enter=Please enter a new password
|
||||
editappuser.notification.deleted=App user deleted successfully
|
||||
editappuser.dialog.delete.text=Do you really want to delete this app user?
|
||||
editappuser.dialog.delete.confirm=Delete
|
||||
|
||||
# Customers
|
||||
customers.title=Customers
|
||||
customers.button.add=Add New Customer
|
||||
customers.hint.click=Click on a customer to view details
|
||||
customers.column.company=Company
|
||||
customers.column.name=Name
|
||||
customers.column.email=Email
|
||||
customers.column.phone=Phone
|
||||
customers.column.street=Street
|
||||
customers.column.city=City
|
||||
|
||||
# Edit Customer
|
||||
editcustomer.title=Edit Customer
|
||||
editcustomer.notification.notfound=Customer not found
|
||||
editcustomer.notification.invalid.id=Invalid customer ID
|
||||
editcustomer.notification.saved=Customer saved successfully
|
||||
editcustomer.notification.check=Please check your input
|
||||
editcustomer.notification.deleted=Customer deleted successfully
|
||||
editcustomer.dialog.delete.text=Do you really want to delete this customer?
|
||||
editcustomer.dialog.delete.confirm=Delete
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Create New Customer
|
||||
addcustomer.button.submit=Create Customer
|
||||
addcustomer.notification.validation=Please fill in all required fields
|
||||
addcustomer.notification.success=Customer created successfully
|
||||
addcustomer.notification.check=Please check your input
|
||||
addcustomer.notification.error=Error: {0}
|
||||
addcustomer.validation.required=This field is required
|
||||
|
||||
# Add Company
|
||||
addcompany.title=Create New Company
|
||||
addcompany.button.submit=Create Company
|
||||
|
||||
# Verwaltung
|
||||
verwaltung.title=Management
|
||||
verwaltung.description=Manage your companies, customers and system settings here
|
||||
|
||||
# User Messages
|
||||
usermessages.title.with=Messages with {0}
|
||||
usermessages.general.title=General Conversations
|
||||
usermessages.general.conversation=General Conversation
|
||||
usermessages.job.title=Job-related Messages
|
||||
usermessages.job.conversation=Job {0}
|
||||
usermessages.no.job.messages=No job-related messages
|
||||
usermessages.preview.empty=No preview available
|
||||
usermessages.message.count={0} Messages
|
||||
usermessages.unknown=Unknown
|
||||
usermessages.unknown.participant=Unknown Participant
|
||||
|
||||
# Admin Dashboard
|
||||
admindashboard.title=Admin Dashboard
|
||||
admindashboard.loading=Loading statistics...
|
||||
admindashboard.error=Error loading: {0}
|
||||
admindashboard.section.overview=Overview
|
||||
admindashboard.section.jobs=Jobs
|
||||
admindashboard.section.tasks=Tasks
|
||||
admindashboard.section.users=User Activities
|
||||
admindashboard.section.health=System Status
|
||||
admindashboard.stat.totaljobs=Total Jobs
|
||||
admindashboard.stat.users=Users
|
||||
admindashboard.stat.appusers=App Users
|
||||
admindashboard.stat.lastupdated=Last Updated
|
||||
admindashboard.stat.openjobs=Open Jobs
|
||||
admindashboard.stat.inprogress=In Progress
|
||||
admindashboard.stat.completed=Completed
|
||||
admindashboard.stat.cargo=Cargo Items
|
||||
admindashboard.stat.status.info=Status
|
||||
admindashboard.stat.status.unavailable=Not Available
|
||||
admindashboard.stat.totaltasks=Total Tasks
|
||||
admindashboard.stat.completedtasks=Completed
|
||||
admindashboard.stat.pendingtasks=Pending
|
||||
admindashboard.stat.successrate=Success Rate
|
||||
admindashboard.stat.photos=Photos
|
||||
admindashboard.stat.barcodes=Barcodes
|
||||
admindashboard.stat.signatures=Signatures
|
||||
admindashboard.stat.comments=Comments
|
||||
admindashboard.stat.database=Database
|
||||
admindashboard.stat.database.connected=Connected
|
||||
admindashboard.stat.database.error=Error
|
||||
admindashboard.stat.websocket=WebSocket
|
||||
admindashboard.stat.websocket.active=Active
|
||||
admindashboard.stat.app=Application
|
||||
admindashboard.stat.app.running=Running
|
||||
admindashboard.stat.memory=Memory
|
||||
|
||||
# Messages
|
||||
messages.title=Messages
|
||||
messages.column.status=Status
|
||||
messages.column.client=Client
|
||||
messages.column.email=Email
|
||||
messages.column.total=Total
|
||||
messages.column.unread=Unread
|
||||
messages.column.lastmessage=Last Message
|
||||
messages.column.preview=Preview
|
||||
messages.notification.error=Error loading messages
|
||||
messages.preview.image=Image
|
||||
messages.preview.empty=No preview
|
||||
messages.sender.unknown=Unknown sender
|
||||
|
||||
# Add Job
|
||||
addjob.title=Create New Job
|
||||
addjob.customer.label=Customer
|
||||
addjob.customer.placeholder=Select customer
|
||||
addjob.customer.unnamed=Unnamed Customer
|
||||
addjob.button.clearfields=Clear Fields
|
||||
addjob.button.submit=Create Job
|
||||
addjob.address.salutation=Salutation
|
||||
addjob.address.salutation.placeholder=Select salutation
|
||||
addjob.salutation.mr=Mr
|
||||
addjob.salutation.ms=Ms
|
||||
addjob.salutation.other=Other
|
||||
addjob.address.company.placeholder=Enter company
|
||||
addjob.address.street.placeholder=Enter street
|
||||
addjob.address.housenumber=House Number
|
||||
addjob.address.addition.placeholder=Address addition
|
||||
addjob.address.city=City
|
||||
addjob.address.city.placeholder.pickup=City (Pickup)
|
||||
addjob.address.city.placeholder.delivery=City (Delivery)
|
||||
addjob.address.delivery.street.placeholder=Street (Delivery)
|
||||
addjob.address.delivery.addition.placeholder=Address addition (Delivery)
|
||||
addjob.address.save=Save Address
|
||||
addjob.section.pickup=Pickup
|
||||
addjob.section.delivery=Delivery
|
||||
addjob.tab.addresses=Customer & Addresses
|
||||
addjob.tab.appointments=Appointments & Processing
|
||||
addjob.tab.cargo=Cargo
|
||||
addjob.tab.tasks=Tasks
|
||||
addjob.tab.price=Price & Submit
|
||||
addjob.appointment.date=Date
|
||||
addjob.appointment.time=Time
|
||||
addjob.appointment.pickup=Pickup Appointment
|
||||
addjob.appointment.delivery=Delivery Appointment
|
||||
addjob.settings.digitalprocess=Digital Processing via App
|
||||
addjob.appuser.label=App User
|
||||
addjob.appuser.placeholder=Select app user
|
||||
addjob.cargo.description=Description
|
||||
addjob.cargo.description.placeholder=Enter description
|
||||
addjob.cargo.quantity=Quantity
|
||||
addjob.cargo.weight=Weight
|
||||
addjob.cargo.length=Length
|
||||
addjob.cargo.width=Width
|
||||
addjob.cargo.height=Height
|
||||
addjob.cargo.europalette=Euro Pallet
|
||||
addjob.cargo.disposablepalette=Disposable Pallet
|
||||
addjob.cargo.dusseldorfpalette=Düsseldorf Pallet
|
||||
addjob.cargo.gridboxpalette=Grid Box Pallet
|
||||
addjob.cargo.gridcart=Grid Cart
|
||||
addjob.cargo.parcel=Parcel
|
||||
addjob.cargo.add=Add Cargo
|
||||
addjob.tasks.title=Tasks
|
||||
addjob.tasks.template.placeholder=Select template
|
||||
addjob.tasks.template.save.tooltip=Save as template
|
||||
addjob.tasks.template.save.title=Save Template
|
||||
addjob.tasks.template.name=Template Name
|
||||
addjob.tasks.template.name.placeholder=Enter name
|
||||
addjob.tasks.template.name.required=Name is required
|
||||
addjob.tasks.template.saved=Template "{0}" saved
|
||||
addjob.tasks.template.save.error=Error saving: {0}
|
||||
addjob.tasks.template.dialog.error=Error opening dialog: {0}
|
||||
addjob.tasks.template.no.tasks=No tasks to save
|
||||
addjob.tasks.template.load.title=Load Template
|
||||
addjob.tasks.template.load.text=Do you want to load template "{0}"? This will replace all current tasks.
|
||||
addjob.tasks.template.load.confirm=Load
|
||||
addjob.tasks.template.loaded=Template "{0}" loaded
|
||||
addjob.tasks.template.load.error=Error loading: {0}
|
||||
addjob.tasks.template.load.templates.error=Error loading templates: {0}
|
||||
addjob.tasks.add=Add Task
|
||||
addjob.tasks.tasktype=Task Type
|
||||
addjob.tasks.tasktype.placeholder=Select type
|
||||
addjob.tasks.description=Description
|
||||
addjob.tasks.description.placeholder=Enter description
|
||||
addjob.tasks.buttontext=Button Text
|
||||
addjob.tasks.buttontext.placeholder=Enter text
|
||||
addjob.tasks.remark=Remark
|
||||
addjob.tasks.remark.placeholder=Enter remark
|
||||
addjob.tasks.photo.min=Min. Photos
|
||||
addjob.tasks.photo.max=Max. Photos
|
||||
addjob.tasks.barcode.min=Min. Barcodes
|
||||
addjob.tasks.barcode.max=Max. Barcodes
|
||||
addjob.tasks.signature.noconfig=No configuration required
|
||||
addjob.tasks.todolist.title=To-Do List
|
||||
addjob.tasks.todolist.item.placeholder=Enter to-do
|
||||
addjob.tasks.todolist.add=Add To-Do
|
||||
addjob.tasks.comment.label=Comment
|
||||
addjob.tasks.comment.placeholder=Enter comment
|
||||
addjob.tasks.comment.required=Comment required
|
||||
addjob.services.title=Services
|
||||
addjob.services.add=Add Service
|
||||
addjob.services.calculation=Calculation
|
||||
addjob.services.basis.distance=Distance (km)
|
||||
addjob.services.basis.time=Time
|
||||
addjob.services.basis.flatrate=Flat Rate
|
||||
addjob.services.vat=VAT
|
||||
addjob.services.route.missing=Route missing
|
||||
addjob.services.dialog.title=Select Service
|
||||
addjob.services.dialog.placeholder=Select service
|
||||
addjob.services.dialog.add=Add
|
||||
addjob.summary.title=Summary
|
||||
addjob.summary.net=Net
|
||||
addjob.summary.vat=VAT
|
||||
addjob.summary.gross=Gross
|
||||
addjob.route.title=Route
|
||||
addjob.route.distance=Distance
|
||||
addjob.route.distance.km=Distance (km)
|
||||
addjob.route.distance.placeholder=e.g. 150.5
|
||||
addjob.route.duration=Duration
|
||||
addjob.route.duration.min=Duration (Min.)
|
||||
addjob.route.duration.placeholder=e.g. 120
|
||||
addjob.route.manual.title=Manual Route Entry
|
||||
addjob.route.manual.hint=Enter distance and duration manually if no route was calculated
|
||||
addjob.notification.success=Job {0} created successfully
|
||||
addjob.notification.cleared=All fields cleared
|
||||
addjob.notification.draft.restored=Draft restored
|
||||
addjob.validation.required.fields=Please fill in all required fields
|
||||
addjob.validation.appuser.required=Please select an app user
|
||||
addjob.validation.cargo.required=Please enter at least one cargo item
|
||||
addjob.validation.pickupdate.future=Pickup date must be today or in the future
|
||||
addjob.validation.deliverydate.future=Delivery date must be today or in the future
|
||||
addjob.validation.dialog.title=Address Validation
|
||||
addjob.validation.dialog.loading=Validating addresses...
|
||||
addjob.validation.dialog.back=Back
|
||||
addjob.validation.dialog.continue=Continue
|
||||
addjob.validation.dialog.continue.anyway=Continue anyway
|
||||
addjob.validation.pickup.address=Pickup Address
|
||||
addjob.validation.delivery.address=Delivery Address
|
||||
addjob.validation.route=Route
|
||||
|
||||
# Job Summary
|
||||
jobsummary.title=Summary
|
||||
jobsummary.error.noid=No job ID provided
|
||||
jobsummary.error.invalidid=Invalid job ID format: {0}
|
||||
jobsummary.error.notfound=Job with ID {0} not found
|
||||
jobsummary.button.sendmessage=Send Message
|
||||
jobsummary.button.jobhistory=Job History
|
||||
jobsummary.button.complete=Complete Job Manually
|
||||
jobsummary.dialog.complete.title=Complete Job
|
||||
jobsummary.dialog.complete.text=Do you want to manually complete job {0}?
|
||||
jobsummary.dialog.complete.cancel=Cancel
|
||||
jobsummary.dialog.complete.confirm=Complete
|
||||
jobsummary.notification.completed=Job {0} completed
|
||||
jobsummary.notification.complete.error=Error completing job: {0}
|
||||
jobsummary.notification.noappuser=No app user assigned to this job
|
||||
jobsummary.section.pickup=Pickup
|
||||
jobsummary.section.delivery=Delivery
|
||||
jobsummary.section.tasks=Tasks to Confirm
|
||||
jobsummary.section.cargo=Cargo to Transport
|
||||
jobsummary.section.info=Additional Information
|
||||
jobsummary.tasks.none=No tasks
|
||||
jobsummary.cargo.none=No cargo information
|
||||
jobsummary.info.netto=Net
|
||||
jobsummary.info.ust=VAT
|
||||
jobsummary.info.gesamt=Total
|
||||
jobsummary.info.bemerkung=Remark
|
||||
jobsummary.info.digital=Digital Processing via App: enabled
|
||||
jobsummary.info.appuser=App User
|
||||
jobsummary.task.status.abgeschlossen=Completed
|
||||
jobsummary.task.status.offen=Open
|
||||
jobsummary.task.typ=Type
|
||||
jobsummary.task.completedAt=Completed at
|
||||
jobsummary.task.completedBy=Completed by
|
||||
jobsummary.task.todo.items=To-Do Items
|
||||
jobsummary.task.photo.info=Photos
|
||||
jobsummary.task.photo.minmax=At least {0} photos required
|
||||
jobsummary.task.photo.maxonly=Maximum {0} photos allowed
|
||||
jobsummary.task.photo.taken=Photos taken ({0})
|
||||
jobsummary.task.button.text=Button Text
|
||||
jobsummary.button.schliessen=Close
|
||||
|
||||
# Jobs
|
||||
jobs.title=Jobs
|
||||
jobs.filter.search=Search
|
||||
jobs.filter.search.placeholder=Search by job number...
|
||||
jobs.filter.startdate=Start Date
|
||||
jobs.filter.enddate=End Date
|
||||
jobs.filter.status=Status
|
||||
jobs.filter.apply=Apply Filter
|
||||
jobs.status.all=All
|
||||
jobs.status.open=Open
|
||||
jobs.status.done=Done
|
||||
jobs.notification.completed=Job {0} completed
|
||||
jobs.column.status=Status
|
||||
jobs.column.customer=Customer
|
||||
jobs.column.jobnumber=Job Number
|
||||
jobs.column.jobdate=Job Date
|
||||
jobs.column.destination=Destination
|
||||
jobs.historie.manuell=Manual
|
||||
jobs.button.csvexport=CSV Export
|
||||
jobs.tooltip.complete=Complete Job
|
||||
jobs.tooltip.createinvoice=Create Invoice
|
||||
jobs.tooltip.delete=Delete Job
|
||||
jobs.dialog.complete.title=Complete Job
|
||||
jobs.dialog.complete.text=Do you want to manually complete job {0}?
|
||||
jobs.dialog.complete.confirm=Complete
|
||||
jobs.dialog.delete.title=Delete Job
|
||||
jobs.dialog.delete.text=Do you really want to delete job {0}?
|
||||
jobs.notification.completed=Job {0} completed
|
||||
jobs.notification.complete.error=Error completing job: {0}
|
||||
jobs.notification.deleted=Job {0} deleted
|
||||
jobs.notification.delete.error=Error deleting job: {0}
|
||||
|
||||
# Create Invoice
|
||||
createinvoice.error.invalidid=Invalid Job ID
|
||||
createinvoice.error.notfound=Job not found
|
||||
createinvoice.button.create=Create Invoice
|
||||
createinvoice.section.job=Job Details
|
||||
createinvoice.section.route=Route Info
|
||||
createinvoice.section.services=Services
|
||||
createinvoice.section.summary=Summary
|
||||
createinvoice.field.jobnumber=Job Number
|
||||
createinvoice.field.customer=Customer
|
||||
createinvoice.field.status=Status
|
||||
createinvoice.field.price=Price
|
||||
createinvoice.route.distance=Distance
|
||||
createinvoice.route.duration=Duration
|
||||
createinvoice.column.service=Service
|
||||
createinvoice.column.basis=Calculation Basis
|
||||
createinvoice.summary.net=Net Total
|
||||
createinvoice.summary.total=Total Amount
|
||||
createinvoice.notification.noservices=Please select at least one service
|
||||
createinvoice.notification.nouser=User not found
|
||||
createinvoice.notification.notemplate=No invoice template found
|
||||
createinvoice.notification.error=Error creating invoice: {0}
|
||||
|
||||
# Invoices
|
||||
invoices.title=Invoices
|
||||
invoices.column.number=Number
|
||||
invoices.column.customer=Customer
|
||||
invoices.column.date=Date
|
||||
invoices.column.amount=Amount
|
||||
invoices.column.description=Description
|
||||
|
||||
# My Invoices
|
||||
myinvoices.title=My Invoices
|
||||
myinvoices.hint.noopen=You have no open invoices. All invoices are settled.
|
||||
myinvoices.bank.institute=Bank
|
||||
myinvoices.bank.beneficiary=Beneficiary
|
||||
myinvoices.bank.iban=IBAN
|
||||
myinvoices.recipient.name=Customer
|
||||
myinvoices.recipient.department=
|
||||
myinvoices.item.description=Item: {0}
|
||||
|
||||
# App User
|
||||
appuser.title=App Users
|
||||
appuser.button.add=Add App User
|
||||
appuser.column.designation=Designation
|
||||
appuser.column.firstname=First Name
|
||||
appuser.column.lastname=Last Name
|
||||
appuser.column.phone=Phone
|
||||
appuser.column.appcode=App Code
|
||||
appuser.column.email=Email
|
||||
|
||||
# Statistics
|
||||
statistics.title=AI Statistics
|
||||
statistics.subtitle=Ask questions about your jobs and customers
|
||||
statistics.prompt.placeholder=Enter question...
|
||||
statistics.quick.jobcount=Number of Jobs
|
||||
statistics.quick.jobcount.prompt=How many jobs do I currently have?
|
||||
statistics.quick.revenue=Revenue
|
||||
statistics.quick.revenue.prompt=What is my revenue this month?
|
||||
statistics.quick.trend=Trends
|
||||
statistics.quick.trend.prompt=Show me trends in the last 3 months
|
||||
statistics.ai.label=AI Response
|
||||
statistics.data.fetched=Data fetched
|
||||
statistics.loading=Calculating...
|
||||
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=In Progress
|
||||
jobstatus.COMPLETED=Completed
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Confirmation
|
||||
tasktype.SIGNATURE=Signature
|
||||
tasktype.TODOLIST=To-Do List
|
||||
tasktype.PHOTO=Photo
|
||||
tasktype.BARCODE=Barcode
|
||||
tasktype.COMMENT=Comment
|
||||
|
||||
# Password Reset
|
||||
passwordreset.title=Reset Password
|
||||
passwordreset.newpassword=New Password
|
||||
passwordreset.confirmpassword=Confirm Password
|
||||
passwordreset.button.submit=Save Password
|
||||
passwordreset.button.cancel=Cancel
|
||||
passwordreset.button.send=Send Email
|
||||
passwordreset.notification.enterpassword=Please enter a new password
|
||||
passwordreset.notification.mismatch=Passwords do not match
|
||||
passwordreset.notification.success=Password changed successfully
|
||||
passwordreset.notification.invalidtoken=Token invalid or expired
|
||||
passwordreset.notification.entermail=Please enter email
|
||||
passwordreset.notification.sent=If the email exists, a link has been sent
|
||||
passwordreset.notification.wait=Please wait {0} seconds before sending the code again
|
||||
|
||||
# Email
|
||||
email.2fa.subject=Your VotianLT Verification Code
|
||||
email.2fa.body=Your verification code is: {0}\n\nThis code is valid for 10 minutes.\nIf you did not request this registration, please ignore this email.
|
||||
|
||||
# Register
|
||||
register.title=Registration
|
||||
register.subtitle=Create your VotianLT account
|
||||
register.email=Email Address
|
||||
register.password=Password
|
||||
register.password.placeholder=At least 6 characters
|
||||
register.password.confirm=Confirm Password
|
||||
register.password.confirm.placeholder=Repeat password
|
||||
register.firstname=First Name
|
||||
register.lastname=Last Name
|
||||
register.phone=Phone Number
|
||||
register.company=Company
|
||||
register.street=Street
|
||||
register.housenr=House No.
|
||||
register.zip=Zip Code
|
||||
register.city=City
|
||||
register.code.label=Verification Code (6 digits)
|
||||
register.code.placeholder=e.g. 123456
|
||||
register.button.submit=Register
|
||||
register.button.verify=Verify Code and Register
|
||||
register.button.resend=Resend Code
|
||||
register.button.back=Back to Start Page
|
||||
register.notification.email.required=Please enter an email address
|
||||
register.notification.email.invalid=Please enter a valid email address
|
||||
register.notification.email.duplicate=A user with this email address already exists
|
||||
register.notification.password.required=Please enter a password
|
||||
register.notification.password.min=Password must be at least 6 characters long
|
||||
register.notification.password.mismatch=Passwords do not match
|
||||
register.notification.firstname.required=Please enter your first name
|
||||
register.notification.lastname.required=Please enter your last name
|
||||
register.notification.phone.required=Please enter your phone number
|
||||
register.notification.company.required=Please enter the company name
|
||||
register.notification.street.required=Please enter the street
|
||||
register.notification.housenr.required=Please enter the house number
|
||||
register.notification.zip.required=Please enter the zip code
|
||||
register.notification.city.required=Please enter the city
|
||||
register.notification.code.sent=A verification code has been sent to {0}
|
||||
register.notification.code.emailerror=Error sending email: {0}
|
||||
register.notification.code.expired=The code has expired. Please request a new code.
|
||||
register.notification.code.invalid=The entered code is invalid
|
||||
register.notification.code.startfirst=Please start the registration first
|
||||
register.notification.code.required=Please enter the 6-digit code
|
||||
register.notification.success=Registration successful. Please log in.
|
||||
register.notification.failed=Registration failed: {0}
|
||||
|
||||
# Start Page
|
||||
start.title=VotianLT - Your Digital Transport Partner
|
||||
start.button.login=Login
|
||||
start.button.register=Register
|
||||
start.button.createorder=Create Order
|
||||
start.button.notifications=Notifications
|
||||
start.button.nonotifications=No new notifications
|
||||
start.system.title=The System
|
||||
start.feature.setup.title=Setup Assistant
|
||||
start.feature.setup.desc=Use the setup assistant to complete your user profile.
|
||||
start.feature.customers.title=Customer and Job Management
|
||||
start.feature.customers.desc=With customer and job management, you always have all contact details and job details in view.
|
||||
start.feature.jobs.title=Job Creation
|
||||
start.feature.jobs.desc=Create jobs in the system with just a few clicks and determine which employee should process which transport job.
|
||||
start.app.title=The App
|
||||
start.app.description=Every job can optionally be processed via the votianLT app - completely without "paperwork". All relevant job information goes directly to the driver's smartphone.
|
||||
start.imprint.title=Imprint
|
||||
start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Phone: +49 40 18 123 771 0
|
||||
start.imprint.email=Email: ahoi@assecutor.de
|
||||
start.slogan=Run your business smart … with votianLT!
|
||||
start.version=Version
|
||||
|
||||
# Login View
|
||||
login.2fa.title=2FA Code
|
||||
login.2fa.button=Verify Code
|
||||
login.votianlt=VotianLT
|
||||
login.version=Version
|
||||
|
||||
# Message Details
|
||||
messagedetails.button.send=Send
|
||||
messagedetails.placeholder=Enter message...
|
||||
messagedetails.noimage=(no image content)
|
||||
messagedetails.imageerror=(image could not be loaded)
|
||||
|
||||
# Invoice Generator
|
||||
invoicegenerator.properties.title=Properties
|
||||
invoicegenerator.properties.type=Type
|
||||
invoicegenerator.fontsize.label=Font Size
|
||||
invoicegenerator.color.label=Text Color
|
||||
invoicegenerator.color.dialog.title=Choose Text Color
|
||||
invoicegenerator.color.dialog.hex=Hex Color Value
|
||||
invoicegenerator.button.cancel=Cancel
|
||||
invoicegenerator.button.apply=Apply
|
||||
invoicegenerator.button.delete=Delete Element
|
||||
invoicegenerator.notification.color.applied=Color applied
|
||||
invoicegenerator.upload.drop=Drag image here or click
|
||||
invoicegenerator.upload.success=Image uploaded successfully
|
||||
invoicegenerator.upload.error=Error uploading: {0}
|
||||
invoicegenerator.file.rejected=File rejected: {0}
|
||||
invoicegenerator.properties.select.info=Click on an element in the canvas to edit its properties
|
||||
|
||||
# CSV Export
|
||||
csv.header.customer=Customer
|
||||
csv.header.jobnumber=Job Number
|
||||
csv.header.jobdate=Job Date
|
||||
csv.header.destination=Destination
|
||||
csv.filename=jobs.csv
|
||||
|
||||
# DatePicker I18n
|
||||
datepicker.month.januar=January
|
||||
datepicker.month.februar=February
|
||||
datepicker.month.märz=March
|
||||
datepicker.month.april=April
|
||||
datepicker.month.mai=May
|
||||
datepicker.month.juni=June
|
||||
datepicker.month.juli=July
|
||||
datepicker.month.august=August
|
||||
datepicker.month.september=September
|
||||
datepicker.month.oktober=October
|
||||
datepicker.month.november=November
|
||||
datepicker.month.dezember=December
|
||||
datepicker.weekday.sonntag=Sunday
|
||||
datepicker.weekday.montag=Monday
|
||||
datepicker.weekday.dienstag=Tuesday
|
||||
datepicker.weekday.mittwoch=Wednesday
|
||||
datepicker.weekday.donnerstag=Thursday
|
||||
datepicker.weekday.freitag=Friday
|
||||
datepicker.weekday.samstag=Saturday
|
||||
datepicker.weekdayshort.so=Su
|
||||
datepicker.weekdayshort.mo=Mo
|
||||
datepicker.weekdayshort.di=Tu
|
||||
datepicker.weekdayshort.mi=We
|
||||
datepicker.weekdayshort.do=Th
|
||||
datepicker.weekdayshort.fr=Fr
|
||||
datepicker.weekdayshort.sa=Sa
|
||||
|
||||
# Job History
|
||||
jobhistory.status.pickupscheduled=Pickup Scheduled
|
||||
jobhistory.status.pickedup=Picked Up
|
||||
jobhistory.status.intransit=In Transit
|
||||
jobhistory.status.delivered=Delivered
|
||||
jobhistory.image.alt=Enlarged Photo
|
||||
|
||||
# Version
|
||||
version.label=Version
|
||||
|
||||
# Management Combo
|
||||
management.placeholder=Management
|
||||
management.customers=Customers
|
||||
management.jobs=Jobs
|
||||
management.companies=Companies
|
||||
|
||||
# User Menu
|
||||
usermenu.profile=Show Profile
|
||||
usermenu.settings=Settings
|
||||
usermenu.logout=Logout
|
||||
|
||||
# CTA Button
|
||||
cta.freetest=Try for free now
|
||||
|
||||
# Miscellaneous
|
||||
misc.toggle.hide=Hide
|
||||
misc.toggle.show=Show
|
||||
misc.nodata=No data available
|
||||
misc.loading=Loading data...
|
||||
misc.error=Error occurred
|
||||
misc.retry=Retry
|
||||
Reference in New Issue
Block a user