Erweiterungen
This commit is contained in:
@@ -9,8 +9,8 @@ const COOKIE_MAX_AGE_DAYS = 365; // Cookie gültig für 1 Jahr
|
||||
* @param languageCode - The language code (e.g., 'de', 'en', 'fr', 'es')
|
||||
*/
|
||||
export function setLanguageCookie(languageCode: string): void {
|
||||
const maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60; // Convert days to seconds
|
||||
document.cookie = `${LANGUAGE_COOKIE_NAME}=${languageCode};path=/;max-age=${maxAge};SameSite=Lax`;
|
||||
const maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60; // Convert days to seconds
|
||||
document.cookie = `${LANGUAGE_COOKIE_NAME}=${languageCode};path=/;max-age=${maxAge};SameSite=Lax`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,39 +18,39 @@ export function setLanguageCookie(languageCode: string): void {
|
||||
* @returns The language code or null if not found
|
||||
*/
|
||||
export function getLanguageCookie(): string | null {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (const cookie of cookies) {
|
||||
const [name, value] = cookie.trim().split('=');
|
||||
if (name === LANGUAGE_COOKIE_NAME) {
|
||||
return decodeURIComponent(value);
|
||||
}
|
||||
const cookies = document.cookie.split(';');
|
||||
for (const cookie of cookies) {
|
||||
const [name, value] = cookie.trim().split('=');
|
||||
if (name === LANGUAGE_COOKIE_NAME) {
|
||||
return decodeURIComponent(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the language cookie
|
||||
*/
|
||||
export function clearLanguageCookie(): void {
|
||||
document.cookie = `${LANGUAGE_COOKIE_NAME}=;path=/;max-age=0;SameSite=Lax`;
|
||||
document.cookie = `${LANGUAGE_COOKIE_NAME}=;path=/;max-age=0;SameSite=Lax`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps Language enum values to locale strings
|
||||
*/
|
||||
export const languageToLocale: Record<string, string> = {
|
||||
'DE': 'de',
|
||||
'EN': 'en',
|
||||
'FR': 'fr',
|
||||
'ES': 'es'
|
||||
DE: 'de',
|
||||
EN: 'en',
|
||||
FR: 'fr',
|
||||
ES: 'es',
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps locale strings to Language enum values
|
||||
*/
|
||||
export const localeToLanguage: Record<string, string> = {
|
||||
'de': 'DE',
|
||||
'en': 'EN',
|
||||
'fr': 'FR',
|
||||
'es': 'ES'
|
||||
de: 'DE',
|
||||
en: 'EN',
|
||||
fr: 'FR',
|
||||
es: 'ES',
|
||||
};
|
||||
@@ -16,12 +16,13 @@ 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.
|
||||
* constructed. Registered via {@code UIInitListener} →
|
||||
* {@code BeforeEnterListener}, which fires prior to the router creating the
|
||||
* layout component tree.
|
||||
*
|
||||
* For authenticated users: Uses the language preference from the user profile.
|
||||
* For anonymous users: Uses the language from the 'votianlt.language' cookie
|
||||
* or falls back to the browser's preferred locale.
|
||||
* For anonymous users: Uses the language from the 'votianlt.language' cookie or
|
||||
* falls back to the browser's preferred locale.
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@@ -126,26 +127,26 @@ public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
||||
// Check if the browser locale is supported
|
||||
String language = locale.getLanguage().toLowerCase();
|
||||
return switch (language) {
|
||||
case "de" -> Locale.GERMAN;
|
||||
case "en" -> Locale.ENGLISH;
|
||||
case "fr" -> Locale.FRENCH;
|
||||
case "es" -> Locale.of("es", "ES");
|
||||
default -> null; // Return null to use Vaadin's default
|
||||
case "de" -> Locale.GERMAN;
|
||||
case "en" -> Locale.ENGLISH;
|
||||
case "fr" -> Locale.FRENCH;
|
||||
case "es" -> Locale.of("es", "ES");
|
||||
default -> null; // Return null to use Vaadin's default
|
||||
};
|
||||
}
|
||||
|
||||
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");
|
||||
case TR -> Locale.of("tr", "TR");
|
||||
case PL -> Locale.of("pl", "PL");
|
||||
case RU -> Locale.of("ru", "RU");
|
||||
case EE -> Locale.of("et", "EE");
|
||||
case LV -> Locale.of("lv", "LV");
|
||||
case LT -> Locale.of("lt", "LT");
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> Locale.of("es", "ES");
|
||||
case TR -> Locale.of("tr", "TR");
|
||||
case PL -> Locale.of("pl", "PL");
|
||||
case RU -> Locale.of("ru", "RU");
|
||||
case EE -> Locale.of("et", "EE");
|
||||
case LV -> Locale.of("lv", "LV");
|
||||
case LT -> Locale.of("lt", "LT");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,17 +156,17 @@ public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
||||
}
|
||||
|
||||
return switch (languageCode.toUpperCase()) {
|
||||
case "DE" -> Locale.GERMAN;
|
||||
case "EN" -> Locale.ENGLISH;
|
||||
case "FR" -> Locale.FRENCH;
|
||||
case "ES" -> Locale.of("es", "ES");
|
||||
case "TR" -> Locale.of("tr", "TR");
|
||||
case "PL" -> Locale.of("pl", "PL");
|
||||
case "RU" -> Locale.of("ru", "RU");
|
||||
case "EE" -> Locale.of("et", "EE");
|
||||
case "LV" -> Locale.of("lv", "LV");
|
||||
case "LT" -> Locale.of("lt", "LT");
|
||||
default -> null;
|
||||
case "DE" -> Locale.GERMAN;
|
||||
case "EN" -> Locale.ENGLISH;
|
||||
case "FR" -> Locale.FRENCH;
|
||||
case "ES" -> Locale.of("es", "ES");
|
||||
case "TR" -> Locale.of("tr", "TR");
|
||||
case "PL" -> Locale.of("pl", "PL");
|
||||
case "RU" -> Locale.of("ru", "RU");
|
||||
case "EE" -> Locale.of("et", "EE");
|
||||
case "LV" -> Locale.of("lv", "LV");
|
||||
case "LT" -> Locale.of("lt", "LT");
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.vaadin.flow.i18n.I18NProvider;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -22,21 +23,20 @@ public class TranslationProvider implements I18NProvider {
|
||||
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
|
||||
// Map Estonian "et" to "ee" file, Latvian "lv" to "lv", Lithuanian "lt" to "lt"
|
||||
String language = locale.getLanguage();
|
||||
String country = locale.getCountry();
|
||||
|
||||
// Create a locale that matches our file naming convention
|
||||
Locale mappedLocale = switch (language) {
|
||||
case "et" -> new Locale("ee"); // Estonian -> messages_ee.properties
|
||||
case "lv" -> new Locale("lv"); // Latvian -> messages_lv.properties
|
||||
case "lt" -> new Locale("lt"); // Lithuanian -> messages_lt.properties
|
||||
case "ru" -> new Locale("ru"); // Russian -> messages_ru.properties
|
||||
case "pl" -> new Locale("pl"); // Polish -> messages_pl.properties
|
||||
case "tr" -> new Locale("tr"); // Turkish -> messages_tr.properties
|
||||
case "es" -> new Locale("es"); // Spanish -> messages_es.properties
|
||||
case "fr" -> new Locale("fr"); // French -> messages_fr.properties
|
||||
case "en" -> new Locale("en"); // English -> messages_en.properties
|
||||
case "de" -> new Locale("de"); // German -> messages.properties (default)
|
||||
default -> locale;
|
||||
case "et" -> Locale.of("ee"); // Estonian -> messages_ee.properties
|
||||
case "lv" -> Locale.of("lv"); // Latvian -> messages_lv.properties
|
||||
case "lt" -> Locale.of("lt"); // Lithuanian -> messages_lt.properties
|
||||
case "ru" -> Locale.of("ru"); // Russian -> messages_ru.properties
|
||||
case "pl" -> Locale.of("pl"); // Polish -> messages_pl.properties
|
||||
case "tr" -> Locale.of("tr"); // Turkish -> messages_tr.properties
|
||||
case "es" -> Locale.of("es"); // Spanish -> messages_es.properties
|
||||
case "fr" -> Locale.of("fr"); // French -> messages_fr.properties
|
||||
case "en" -> Locale.of("en"); // English -> messages_en.properties
|
||||
case "de" -> Locale.of("de"); // German -> messages.properties (default)
|
||||
default -> locale;
|
||||
};
|
||||
|
||||
return super.getCandidateLocales(baseName, mappedLocale);
|
||||
@@ -45,18 +45,9 @@ public class TranslationProvider implements I18NProvider {
|
||||
|
||||
@Override
|
||||
public List<Locale> getProvidedLocales() {
|
||||
return Collections.unmodifiableList(Arrays.asList(
|
||||
Locale.GERMAN,
|
||||
Locale.ENGLISH,
|
||||
Locale.FRENCH,
|
||||
Locale.of("es", "ES"),
|
||||
Locale.of("tr", "TR"),
|
||||
Locale.of("pl", "PL"),
|
||||
Locale.of("ru", "RU"),
|
||||
Locale.of("et", "EE"),
|
||||
Locale.of("lv", "LV"),
|
||||
Locale.of("lt", "LT")
|
||||
));
|
||||
return Collections.unmodifiableList(Arrays.asList(Locale.GERMAN, Locale.ENGLISH, Locale.FRENCH,
|
||||
Locale.of("es", "ES"), Locale.of("tr", "TR"), Locale.of("pl", "PL"), Locale.of("ru", "RU"),
|
||||
Locale.of("et", "EE"), Locale.of("lv", "LV"), Locale.of("lt", "LT")));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,7 +61,7 @@ public class TranslationProvider implements I18NProvider {
|
||||
String value = bundle.getString(key);
|
||||
|
||||
if (params.length > 0) {
|
||||
value = String.format(value, params);
|
||||
value = MessageFormat.format(value, params);
|
||||
}
|
||||
|
||||
return value;
|
||||
@@ -81,16 +72,16 @@ public class TranslationProvider implements I18NProvider {
|
||||
|
||||
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");
|
||||
case TR -> Locale.of("tr", "TR");
|
||||
case PL -> Locale.of("pl", "PL");
|
||||
case RU -> Locale.of("ru", "RU");
|
||||
case EE -> Locale.of("et", "EE");
|
||||
case LV -> Locale.of("lv", "LV");
|
||||
case LT -> Locale.of("lt", "LT");
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> Locale.of("es", "ES");
|
||||
case TR -> Locale.of("tr", "TR");
|
||||
case PL -> Locale.of("pl", "PL");
|
||||
case RU -> Locale.of("ru", "RU");
|
||||
case EE -> Locale.of("et", "EE");
|
||||
case LV -> Locale.of("lv", "LV");
|
||||
case LT -> Locale.of("lt", "LT");
|
||||
};
|
||||
return getTranslation(key, locale);
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ public enum TaskType {
|
||||
|
||||
// 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";
|
||||
case CONFIRMATION -> "Bestätigung";
|
||||
case SIGNATURE -> "Unterschrift";
|
||||
case TODOLIST -> "To-Do Liste";
|
||||
case PHOTO -> "Foto";
|
||||
case BARCODE -> "Barcode";
|
||||
case COMMENT -> "Kommentar";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +111,10 @@ public final class MainLayout extends AppLayout {
|
||||
TreeData<MenuTreeItem> treeData = new TreeData<>();
|
||||
|
||||
// Root nodes
|
||||
MenuTreeItem auftragserstellungItem = new MenuTreeItem(getTranslation("nav.job.create"), "add_job", VaadinIcon.PLUS_CIRCLE);
|
||||
MenuTreeItem nachrichtenItem = new MenuTreeItem(getTranslation("nav.messages"), "messages", VaadinIcon.ENVELOPE);
|
||||
MenuTreeItem auftragserstellungItem = new MenuTreeItem(getTranslation("nav.job.create"), "add_job",
|
||||
VaadinIcon.PLUS_CIRCLE);
|
||||
MenuTreeItem nachrichtenItem = new MenuTreeItem(getTranslation("nav.messages"), "messages",
|
||||
VaadinIcon.ENVELOPE);
|
||||
MenuTreeItem verwaltungItem = new MenuTreeItem(getTranslation("nav.management"), null, VaadinIcon.COG);
|
||||
MenuTreeItem benutzerItem = new MenuTreeItem(getTranslation("nav.users"), null, VaadinIcon.USER);
|
||||
|
||||
@@ -126,19 +128,26 @@ public final class MainLayout extends AppLayout {
|
||||
treeData.addItem(null, benutzerItem);
|
||||
|
||||
// Add children to "Verwaltung"
|
||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS));
|
||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.appusers"), "app-user", VaadinIcon.USERS));
|
||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.statistics"), "statistics", VaadinIcon.BAR_CHART));
|
||||
treeData.addItem(verwaltungItem,
|
||||
new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS));
|
||||
treeData.addItem(verwaltungItem,
|
||||
new MenuTreeItem(getTranslation("nav.appusers"), "app-user", VaadinIcon.USERS));
|
||||
treeData.addItem(verwaltungItem,
|
||||
new MenuTreeItem(getTranslation("nav.statistics"), "statistics", VaadinIcon.BAR_CHART));
|
||||
|
||||
// Add invoices only if billing is enabled
|
||||
if (isBillingEnabledForCurrentUser()) {
|
||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.invoices"), "invoices", VaadinIcon.FILE_TEXT));
|
||||
treeData.addItem(verwaltungItem,
|
||||
new MenuTreeItem(getTranslation("nav.invoices"), "invoices", VaadinIcon.FILE_TEXT));
|
||||
}
|
||||
|
||||
// Add children to "Benutzer"
|
||||
treeData.addItem(benutzerItem, new MenuTreeItem(getTranslation("nav.profile"), "edit-profile", VaadinIcon.USER));
|
||||
treeData.addItem(benutzerItem, new MenuTreeItem(getTranslation("nav.myinvoices"), "my-invoices", VaadinIcon.FILE_TEXT));
|
||||
treeData.addItem(benutzerItem, new MenuTreeItem(getTranslation("nav.imprint"), "impressum", VaadinIcon.INFO_CIRCLE));
|
||||
treeData.addItem(benutzerItem,
|
||||
new MenuTreeItem(getTranslation("nav.profile"), "edit-profile", VaadinIcon.USER));
|
||||
treeData.addItem(benutzerItem,
|
||||
new MenuTreeItem(getTranslation("nav.myinvoices"), "my-invoices", VaadinIcon.FILE_TEXT));
|
||||
treeData.addItem(benutzerItem,
|
||||
new MenuTreeItem(getTranslation("nav.imprint"), "impressum", VaadinIcon.INFO_CIRCLE));
|
||||
|
||||
// Create Tree
|
||||
tree = new TreeGrid<>();
|
||||
@@ -223,20 +232,14 @@ public final class MainLayout extends AppLayout {
|
||||
TreeData<MenuTreeItem> treeData = dataProvider.getTreeData();
|
||||
|
||||
// Find and update the messages item with new badge count
|
||||
treeData.getChildren(null).stream()
|
||||
.filter(item -> "messages".equals(item.path()))
|
||||
.findFirst()
|
||||
.ifPresent(oldItem -> {
|
||||
MenuTreeItem newItem = new MenuTreeItem(
|
||||
getTranslation("nav.messages"),
|
||||
"messages",
|
||||
VaadinIcon.ENVELOPE,
|
||||
unreadCount
|
||||
);
|
||||
messagesTreeItem = newItem;
|
||||
// Refresh to show updated badge
|
||||
dataProvider.refreshAll();
|
||||
});
|
||||
treeData.getChildren(null).stream().filter(item -> "messages".equals(item.path())).findFirst()
|
||||
.ifPresent(oldItem -> {
|
||||
MenuTreeItem newItem = new MenuTreeItem(getTranslation("nav.messages"), "messages",
|
||||
VaadinIcon.ENVELOPE, unreadCount);
|
||||
messagesTreeItem = newItem;
|
||||
// Refresh to show updated badge
|
||||
dataProvider.refreshAll();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,8 +252,10 @@ public final class MainLayout extends AppLayout {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
MenuTreeItem that = (MenuTreeItem) o;
|
||||
return Objects.equals(label, that.label) && Objects.equals(path, that.path);
|
||||
}
|
||||
@@ -307,7 +312,8 @@ public final class MainLayout extends AppLayout {
|
||||
userMenuItem.add(userNameSpan);
|
||||
|
||||
// Profil anzeigen mit Navigation
|
||||
userMenuItem.getSubMenu().addItem(getTranslation("nav.showprofile"), e -> UI.getCurrent().navigate(EditProfileView.class));
|
||||
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());
|
||||
|
||||
@@ -359,10 +365,10 @@ 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.
|
||||
* 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 {
|
||||
@@ -386,11 +392,11 @@ public final class MainLayout extends AppLayout {
|
||||
|
||||
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");
|
||||
default -> Locale.GERMAN;
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> Locale.of("es", "ES");
|
||||
default -> Locale.GERMAN;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -90,11 +90,13 @@ public class AddAppUserView extends VerticalLayout implements HasDynamicTitle {
|
||||
designationField.setPlaceholder("(HH H 000)");
|
||||
designationField.setWidthFull();
|
||||
designationField.setRequiredIndicatorVisible(true);
|
||||
designationField.addBlurListener(e -> validateField(designationField, getTranslation("addappuser.validation.designation")));
|
||||
designationField.addBlurListener(
|
||||
e -> validateField(designationField, getTranslation("addappuser.validation.designation")));
|
||||
|
||||
firstnameField.setWidthFull();
|
||||
firstnameField.setRequiredIndicatorVisible(true);
|
||||
firstnameField.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
||||
firstnameField
|
||||
.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
||||
|
||||
lastnameField.setWidthFull();
|
||||
lastnameField.setRequiredIndicatorVisible(true);
|
||||
@@ -162,8 +164,8 @@ public class AddAppUserView extends VerticalLayout implements HasDynamicTitle {
|
||||
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(getTranslation("addappuser.validation.password.required")).bind(AppUser::getPassword,
|
||||
AppUser::setPassword);
|
||||
binder.forField(passwordField).asRequired(getTranslation("addappuser.validation.password.required"))
|
||||
.bind(AppUser::getPassword, AppUser::setPassword);
|
||||
|
||||
// Confirm password field validation
|
||||
binder.forField(confirmPasswordField).asRequired(getTranslation("addappuser.validation.password.confirm"))
|
||||
@@ -201,13 +203,13 @@ public class AddAppUserView extends VerticalLayout implements HasDynamicTitle {
|
||||
} catch (org.springframework.dao.DuplicateKeyException e) {
|
||||
// Handle duplicate email error
|
||||
if (e.getMessage().contains("email")) {
|
||||
Notification.show(getTranslation("addappuser.notification.email.duplicate"), 5000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addappuser.notification.email.duplicate"), 5000,
|
||||
Notification.Position.MIDDLE);
|
||||
emailField.focus();
|
||||
emailField.setInvalid(true);
|
||||
emailField.setErrorMessage(getTranslation("addappuser.notification.email.duplicate"));
|
||||
} else {
|
||||
Notification.show(getTranslation("addappuser.notification.check"), 5000,
|
||||
Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addappuser.notification.check"), 5000, Notification.Position.MIDDLE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Notification.show(getTranslation("addappuser.notification.error", e.getMessage()), 5000,
|
||||
|
||||
@@ -54,7 +54,8 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
||||
|
||||
// Anrede (Dropdown)
|
||||
title = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||
title.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
||||
title.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"),
|
||||
getTranslation("addjob.salutation.other"));
|
||||
title.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
||||
title.setWidthFull();
|
||||
|
||||
@@ -160,16 +161,16 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
||||
}
|
||||
|
||||
private void configureBinder() {
|
||||
binder.forField(companyName).asRequired(getTranslation("profile.validation.company.required")).bind(Customer::getCompanyName,
|
||||
Customer::setCompanyName);
|
||||
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(getTranslation("profile.validation.firstname.required")).bind(Customer::getFirstname,
|
||||
Customer::setFirstname);
|
||||
binder.forField(firstName).asRequired(getTranslation("profile.validation.firstname.required"))
|
||||
.bind(Customer::getFirstname, Customer::setFirstname);
|
||||
|
||||
binder.forField(lastName).asRequired(getTranslation("profile.validation.lastname.required")).bind(Customer::getLastName,
|
||||
Customer::setLastName);
|
||||
binder.forField(lastName).asRequired(getTranslation("profile.validation.lastname.required"))
|
||||
.bind(Customer::getLastName, Customer::setLastName);
|
||||
|
||||
binder.forField(telephone).asRequired(getTranslation("profile.validation.phone")).bind(Customer::getTelephone,
|
||||
Customer::setTelephone);
|
||||
@@ -180,16 +181,19 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
||||
.withValidator(email -> email.contains("@"), getTranslation("profile.validation.email.invalid"))
|
||||
.bind(Customer::getMail, Customer::setMail);
|
||||
|
||||
binder.forField(street).asRequired(getTranslation("profile.validation.street.required")).bind(Customer::getStreet, Customer::setStreet);
|
||||
binder.forField(street).asRequired(getTranslation("profile.validation.street.required"))
|
||||
.bind(Customer::getStreet, Customer::setStreet);
|
||||
|
||||
binder.forField(houseNumber).asRequired(getTranslation("profile.validation.housenr.required")).bind(Customer::getHouseNumber,
|
||||
Customer::setHouseNumber);
|
||||
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(getTranslation("profile.validation.zip.required")).bind(Customer::getZip, Customer::setZip);
|
||||
binder.forField(zip).asRequired(getTranslation("profile.validation.zip.required")).bind(Customer::getZip,
|
||||
Customer::setZip);
|
||||
|
||||
binder.forField(city).asRequired(getTranslation("profile.validation.city.required")).bind(Customer::getCity, Customer::setCity);
|
||||
binder.forField(city).asRequired(getTranslation("profile.validation.city.required")).bind(Customer::getCity,
|
||||
Customer::setCity);
|
||||
}
|
||||
|
||||
private void setTestData() {
|
||||
@@ -201,7 +205,8 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
||||
boolean isValid = validateAllFields();
|
||||
|
||||
if (!isValid) {
|
||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.validation"), 3000,
|
||||
com.vaadin.flow.component.notification.Notification.show(
|
||||
getTranslation("addcustomer.notification.validation"), 3000,
|
||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||
return;
|
||||
}
|
||||
@@ -212,16 +217,17 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
||||
|
||||
addCustomerService.addCustomer(customer);
|
||||
|
||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.success"), 3000,
|
||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||
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(getTranslation("addcustomer.notification.check"), 3000,
|
||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||
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(getTranslation("addcustomer.notification.error", 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
pickupCompany.setAllowCustomValue(true);
|
||||
setupCompanyAutocomplete(pickupCompany, true); // true für Pickup
|
||||
pickupSalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||
pickupSalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
||||
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"));
|
||||
@@ -353,7 +354,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
deliveryCompany.setAllowCustomValue(true);
|
||||
setupCompanyAutocomplete(deliveryCompany, false); // false für Delivery
|
||||
deliverySalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||
deliverySalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
||||
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"));
|
||||
@@ -678,7 +680,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
manualInputRow.setWidthFull();
|
||||
manualInputRow.setSpacing(true);
|
||||
|
||||
manualDistanceInput = new com.vaadin.flow.component.textfield.NumberField(getTranslation("addjob.route.distance.km"));
|
||||
manualDistanceInput = new com.vaadin.flow.component.textfield.NumberField(
|
||||
getTranslation("addjob.route.distance.km"));
|
||||
manualDistanceInput.setWidthFull();
|
||||
manualDistanceInput.setPlaceholder(getTranslation("addjob.route.distance.placeholder"));
|
||||
manualDistanceInput.setMin(0);
|
||||
@@ -692,7 +695,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
}
|
||||
});
|
||||
|
||||
manualDurationInput = new com.vaadin.flow.component.textfield.IntegerField(getTranslation("addjob.route.duration.min"));
|
||||
manualDurationInput = new com.vaadin.flow.component.textfield.IntegerField(
|
||||
getTranslation("addjob.route.duration.min"));
|
||||
manualDurationInput.setWidthFull();
|
||||
manualDurationInput.setPlaceholder(getTranslation("addjob.route.duration.placeholder"));
|
||||
manualDurationInput.setMin(0);
|
||||
@@ -740,7 +744,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
}
|
||||
// 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 (" + getTranslation("addjob.services.route.missing") + ")";
|
||||
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) + " €"
|
||||
@@ -937,7 +942,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
|
||||
// Dauer in 15-Minuten-Einheiten umrechnen (aufrunden)
|
||||
int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
|
||||
if (durationSeconds % 900 > 0) units++; // Aufrunden
|
||||
if (durationSeconds % 900 > 0)
|
||||
units++; // Aufrunden
|
||||
return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
@@ -1245,8 +1251,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
boolean digital = Boolean.TRUE.equals(digitalProcessing.getValue());
|
||||
boolean hasUser = selectedUserId != null && !selectedUserId.trim().isEmpty();
|
||||
return !digital || hasUser;
|
||||
}, getTranslation("addjob.validation.appuser.required"))
|
||||
.bind(Job::getAppUser, Job::setAppUser);
|
||||
}, getTranslation("addjob.validation.appuser.required")).bind(Job::getAppUser, Job::setAppUser);
|
||||
|
||||
// Toggle required indicator and visibility for App-Nutzer based on
|
||||
// digitalProcessing
|
||||
@@ -1525,14 +1530,15 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
// Manuelle Eingabe verwenden
|
||||
job.setRouteDistanceKm(manualDistanceInput.getValue());
|
||||
if (manualDurationInput != null && manualDurationInput.getValue() != null) {
|
||||
job.setRouteDurationSeconds(manualDurationInput.getValue() * 60); // Minuten in Sekunden umrechnen
|
||||
job.setRouteDurationSeconds(manualDurationInput.getValue() * 60); // Minuten in Sekunden
|
||||
// umrechnen
|
||||
}
|
||||
}
|
||||
// Additional validation: If digital processing is enabled, app user must be
|
||||
// selected
|
||||
if (digitalProcessing.getValue() && appUser.getValue() == null) {
|
||||
Notification errorNotification = Notification.show(
|
||||
getTranslation("addjob.validation.appuser.required"));
|
||||
Notification errorNotification = Notification
|
||||
.show(getTranslation("addjob.validation.appuser.required"));
|
||||
errorNotification.setDuration(5000);
|
||||
return;
|
||||
}
|
||||
@@ -1600,8 +1606,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
getUI().ifPresent(ui -> ui.navigate(JobSummaryView.class, savedJob.getId().toHexString()));
|
||||
} else {
|
||||
// Validation failed, show error message
|
||||
Notification errorNotification = Notification
|
||||
.show(getTranslation("addjob.validation.required.fields"));
|
||||
Notification errorNotification = Notification.show(getTranslation("addjob.validation.required.fields"));
|
||||
errorNotification.setDuration(5000);
|
||||
}
|
||||
|
||||
@@ -1612,7 +1617,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
cargoError.setVisible(false);
|
||||
if (cargoAreaContainer != null)
|
||||
cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
|
||||
Notification errorNotification = Notification.show(getTranslation("addjob.notification.error", e.getMessage()));
|
||||
Notification errorNotification = Notification
|
||||
.show(getTranslation("addjob.notification.error", e.getMessage()));
|
||||
errorNotification.setDuration(5000);
|
||||
}
|
||||
}
|
||||
@@ -1659,8 +1665,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
loadJobIntoForm(draft);
|
||||
|
||||
// Benutzer informieren
|
||||
Notification notification = Notification
|
||||
.show(getTranslation("addjob.notification.draft.restored"));
|
||||
Notification notification = Notification.show(getTranslation("addjob.notification.draft.restored"));
|
||||
notification.setDuration(4000);
|
||||
}
|
||||
}
|
||||
@@ -1712,7 +1717,9 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
row.setAlignItems(FlexComponent.Alignment.END);
|
||||
|
||||
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.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(getTranslation("addjob.cargo.description.placeholder"));
|
||||
desc.setWidth("40%");
|
||||
@@ -2510,7 +2517,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
try {
|
||||
// Check if there are any tasks to save
|
||||
if (tasksState.isEmpty()) {
|
||||
Notification.show(getTranslation("addjob.tasks.template.no.tasks"), 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("addjob.tasks.template.no.tasks"), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2577,7 +2585,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error opening save template dialog", e);
|
||||
Notification.show(getTranslation("addjob.tasks.template.dialog.error", e.getMessage()), 4000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addjob.tasks.template.dialog.error", e.getMessage()), 4000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2644,7 +2653,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
templateComboBox.setItems(templates);
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading templates", e);
|
||||
Notification.show(getTranslation("addjob.tasks.template.load.templates.error", e.getMessage()), 4000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("addjob.tasks.template.load.templates.error", e.getMessage()), 4000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3075,10 +3085,12 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
// Abholadresse anzeigen
|
||||
if (pickupResult != null) {
|
||||
if (pickupResult.isValid()) {
|
||||
pickupResultLabel.setText("✓ " + getTranslation("addjob.validation.pickup.address") + ": " + pickupResult.getFormattedAddress());
|
||||
pickupResultLabel.setText("✓ " + getTranslation("addjob.validation.pickup.address") + ": "
|
||||
+ pickupResult.getFormattedAddress());
|
||||
pickupResultLabel.getStyle().set("color", "var(--lumo-success-text-color)");
|
||||
} else {
|
||||
pickupResultLabel.setText("⚠ " + getTranslation("addjob.validation.pickup.address") + ": " + 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 +3102,12 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
// Lieferadresse anzeigen
|
||||
if (deliveryResult != null) {
|
||||
if (deliveryResult.isValid()) {
|
||||
deliveryResultLabel.setText("✓ " + getTranslation("addjob.validation.delivery.address") + ": " + deliveryResult.getFormattedAddress());
|
||||
deliveryResultLabel.setText("✓ " + getTranslation("addjob.validation.delivery.address") + ": "
|
||||
+ deliveryResult.getFormattedAddress());
|
||||
deliveryResultLabel.getStyle().set("color", "var(--lumo-success-text-color)");
|
||||
} else {
|
||||
deliveryResultLabel.setText("⚠ " + getTranslation("addjob.validation.delivery.address") + ": " + 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 +3118,10 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
|
||||
// Route anzeigen, wenn beide Adressen gültig sind
|
||||
if (bothAddressesValid && routeCalculationResult != null && routeCalculationResult.isValid()) {
|
||||
routeResultLabel.setText("🚛 " + getTranslation("addjob.validation.route") + ": " + String.format("%.1f km", routeCalculationResult.getDistanceKm())
|
||||
+ " (" + getTranslation("addjob.route.duration") + ": " + 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 {
|
||||
@@ -3234,9 +3250,11 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die berechnete oder manuell eingegebene Entfernung zurück (für Preisberechnungen).
|
||||
* Gibt die berechnete oder manuell eingegebene Entfernung zurück (für
|
||||
* Preisberechnungen).
|
||||
*
|
||||
* @return Entfernung in km oder 0.0, wenn keine Berechnung oder Eingabe vorhanden ist
|
||||
* @return Entfernung in km oder 0.0, wenn keine Berechnung oder Eingabe
|
||||
* vorhanden ist
|
||||
*/
|
||||
public double getRouteDistanceKm() {
|
||||
if (routeCalculationResult != null && routeCalculationResult.isValid()) {
|
||||
|
||||
@@ -148,19 +148,23 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
||||
|
||||
// Total jobs card
|
||||
long totalJobs = jobRepository.count();
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.totaljobs"), 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(getTranslation("admindashboard.stat.users"), 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(getTranslation("admindashboard.stat.appusers"), 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(getTranslation("admindashboard.stat.lastupdated"), currentTime, VaadinIcon.CLOCK, "gray"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.lastupdated"), currentTime, VaadinIcon.CLOCK,
|
||||
"gray"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
@@ -185,17 +189,22 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
||||
long inProgressJobs = jobRepository.countByStatus(JobStatus.IN_PROGRESS);
|
||||
long completedJobs = jobRepository.countByStatus(JobStatus.COMPLETED);
|
||||
|
||||
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"));
|
||||
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(getTranslation("admindashboard.stat.cargo"), 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(getTranslation("admindashboard.stat.status.info"), getTranslation("admindashboard.stat.status.unavailable"), VaadinIcon.WARNING, "red"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.status.info"),
|
||||
getTranslation("admindashboard.stat.status.unavailable"), VaadinIcon.WARNING, "red"));
|
||||
}
|
||||
|
||||
section.add(title, cards);
|
||||
@@ -217,20 +226,23 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
||||
|
||||
// Total tasks
|
||||
long totalTasks = taskRepository.count();
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.totaltasks"), 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(getTranslation("admindashboard.stat.completedtasks"), 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(getTranslation("admindashboard.stat.pendingtasks"), 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(getTranslation("admindashboard.stat.successrate"), String.format("%.1f%%", completionRate), VaadinIcon.TRENDING_UP,
|
||||
"purple"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.successrate"),
|
||||
String.format("%.1f%%", completionRate), VaadinIcon.TRENDING_UP, "purple"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
@@ -251,16 +263,20 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
||||
|
||||
// Content statistics
|
||||
long totalPhotos = photoRepository.count();
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.photos"), 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(getTranslation("admindashboard.stat.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(getTranslation("admindashboard.stat.signatures"), 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(getTranslation("admindashboard.stat.comments"), 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;
|
||||
@@ -282,16 +298,20 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
||||
// Database connection status
|
||||
try {
|
||||
userRepository.count(); // Test database connection
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.database"), getTranslation("admindashboard.stat.database.connected"), VaadinIcon.DATABASE, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.database"),
|
||||
getTranslation("admindashboard.stat.database.connected"), VaadinIcon.DATABASE, "green"));
|
||||
} catch (Exception e) {
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.database"), getTranslation("admindashboard.stat.database.error"), VaadinIcon.DATABASE, "red"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.database"),
|
||||
getTranslation("admindashboard.stat.database.error"), VaadinIcon.DATABASE, "red"));
|
||||
}
|
||||
|
||||
// Messaging status
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.websocket"), getTranslation("admindashboard.stat.websocket.active"), VaadinIcon.CONNECT, "green"));
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.websocket"),
|
||||
getTranslation("admindashboard.stat.websocket.active"), VaadinIcon.CONNECT, "green"));
|
||||
|
||||
// System uptime (placeholder)
|
||||
cards.add(createStatCard(getTranslation("admindashboard.stat.app"), getTranslation("admindashboard.stat.app.running"), 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();
|
||||
|
||||
@@ -75,9 +75,11 @@ public class AdminPricetableView extends VerticalLayout implements HasDynamicTit
|
||||
priceTable.setRevenueParticipation(revenueParticipation.getValue());
|
||||
|
||||
priceTableRepository.save(priceTable);
|
||||
Notification.show(getTranslation("adminpricetable.notification.saved"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("adminpricetable.notification.saved"), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
} catch (Exception ex) {
|
||||
Notification.show(getTranslation("adminpricetable.notification.save.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("adminpricetable.notification.save.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,11 +52,15 @@ public class AppUserView extends VerticalLayout implements HasDynamicTitle {
|
||||
appUserGrid.setSizeFull();
|
||||
|
||||
// Grid-Spalten konfigurieren
|
||||
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::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::getAppCode).setHeader(getTranslation("appuser.column.appcode"))
|
||||
.setAutoWidth(true);
|
||||
appUserGrid.addColumn(AppUser::getEmail).setHeader(getTranslation("appuser.column.email")).setAutoWidth(true);
|
||||
|
||||
// Make grid rows clickable
|
||||
|
||||
@@ -106,14 +106,11 @@ public class AuthenticatedStartView extends VerticalLayout implements HasDynamic
|
||||
|
||||
// Feature Cards
|
||||
featuresGrid.add(
|
||||
createFeatureCard(VaadinIcon.COG,
|
||||
getTranslation("dashboard.feature.setup.title"),
|
||||
createFeatureCard(VaadinIcon.COG, getTranslation("dashboard.feature.setup.title"),
|
||||
getTranslation("dashboard.feature.setup.desc")),
|
||||
createFeatureCard(VaadinIcon.USERS,
|
||||
getTranslation("dashboard.feature.customers.title"),
|
||||
createFeatureCard(VaadinIcon.USERS, getTranslation("dashboard.feature.customers.title"),
|
||||
getTranslation("dashboard.feature.customers.desc")),
|
||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT,
|
||||
getTranslation("dashboard.feature.jobs.title"),
|
||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, getTranslation("dashboard.feature.jobs.title"),
|
||||
getTranslation("dashboard.feature.jobs.desc")));
|
||||
|
||||
systemSection.add(systemTitle, systemIntro, featuresGrid);
|
||||
|
||||
@@ -188,11 +188,14 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
jobInfo.setSpacing(true);
|
||||
jobInfo.setWidthFull();
|
||||
|
||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.jobnumber")), new Span(currentJob.getJobNumber())));
|
||||
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(getTranslation("createinvoice.field.status")), new Span(currentJob.getStatus().toString())));
|
||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.price")), 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;
|
||||
@@ -307,11 +310,10 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
// Show only net sum, VAT sums, and total amount without individual services
|
||||
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.net")),
|
||||
new Span(netAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
||||
summaryInfo
|
||||
.add(new HorizontalLayout(
|
||||
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(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(getTranslation("createinvoice.summary.total")),
|
||||
new Span(totalAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
||||
|
||||
@@ -398,7 +400,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
|
||||
private void createInvoice() {
|
||||
if (getSelectedServices().isEmpty()) {
|
||||
Notification.show(getTranslation("createinvoice.notification.noservices"), 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.noservices"), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -411,38 +414,47 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
.flatMap(auth -> userRepository.findByEmail(auth.getUsername()));
|
||||
|
||||
if (currentUserOpt.isEmpty()) {
|
||||
Notification.show(getTranslation("createinvoice.notification.nouser"), 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.nouser"), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
currentUser = currentUserOpt.get();
|
||||
|
||||
// Load invoice template from service
|
||||
Optional<InvoiceTemplate> templateOpt = invoiceTemplateService.getTemplateByUserId(currentUser.getId().toString());
|
||||
Optional<InvoiceTemplate> templateOpt = invoiceTemplateService
|
||||
.getTemplateByUserId(currentUser.getId().toString());
|
||||
if (templateOpt.isEmpty()) {
|
||||
Notification.show(getTranslation("createinvoice.notification.notemplate"), 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"));
|
||||
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(getTranslation("createinvoice.notification.notemplate"), 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate PDF with template and actual job data
|
||||
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser);
|
||||
System.out.println("DEBUG CreateInvoiceView: PDF bytes generated: " + (pdfBytes != null ? pdfBytes.length : 0));
|
||||
System.out.println(
|
||||
"DEBUG CreateInvoiceView: PDF bytes generated: " + (pdfBytes != null ? pdfBytes.length : 0));
|
||||
|
||||
// Show PDF in dialog
|
||||
showPdfInDialog(pdfBytes, "Rechnung " + currentJob.getJobNumber());
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.error("Fehler beim Erstellen der Rechnung", ex);
|
||||
Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,10 +518,14 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
// Invoice data
|
||||
variables.put("invoice.number", currentJob.getJobNumber() + "-" + System.currentTimeMillis());
|
||||
variables.put("invoice.date", java.time.LocalDate.now().toString());
|
||||
variables.put("invoice.net_total", netAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||
variables.put("invoice.vat_total", vatAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||
variables.put("invoice.gross_total", totalAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||
variables.put("invoice.vat_rate", vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
|
||||
variables.put("invoice.net_total",
|
||||
netAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||
variables.put("invoice.vat_total",
|
||||
vatAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||
variables.put("invoice.gross_total",
|
||||
totalAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||
variables.put("invoice.vat_rate",
|
||||
vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
|
||||
|
||||
// Job data
|
||||
if (currentJob.getRouteDistanceKm() != null) {
|
||||
@@ -534,7 +550,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
|
||||
// VAT rate
|
||||
if (service.getVatRate() != null) {
|
||||
serviceData.put("vatRate", service.getVatRate().multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
|
||||
serviceData.put("vatRate",
|
||||
service.getVatRate().multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
|
||||
} else {
|
||||
serviceData.put("vatRate", "19%");
|
||||
}
|
||||
@@ -593,11 +610,9 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
|
||||
// Download button
|
||||
Button downloadButton = new Button(getTranslation("button.download"), e -> {
|
||||
getElement()
|
||||
.executeJs("const link = document.createElement('a');" +
|
||||
"link.href = 'data:application/pdf;base64," + base64Pdf + "';" +
|
||||
"link.download = '" + title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf';" +
|
||||
"link.click();");
|
||||
getElement().executeJs("const link = document.createElement('a');"
|
||||
+ "link.href = 'data:application/pdf;base64," + base64Pdf + "';" + "link.download = '"
|
||||
+ title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf';" + "link.click();");
|
||||
});
|
||||
downloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
|
||||
@@ -164,7 +164,8 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
binder.readBean(appUser);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Notification.show(getTranslation("editappuser.notification.invalid.id"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.invalid.id"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
navigateBack();
|
||||
}
|
||||
}
|
||||
@@ -191,7 +192,8 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
// Passwords match, set new password for hashing
|
||||
appUser.setPassword(newPassword);
|
||||
} else {
|
||||
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -216,18 +218,21 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
boolean confirmPasswordFilled = confirmPassword != null && !confirmPassword.trim().isEmpty();
|
||||
|
||||
if (newPasswordFilled && !confirmPasswordFilled) {
|
||||
Notification.show(getTranslation("editappuser.notification.password.confirm"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.password.confirm"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newPasswordFilled && confirmPasswordFilled) {
|
||||
Notification.show(getTranslation("editappuser.notification.password.enter"), 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(getTranslation("editappuser.notification.password.mismatch"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -243,7 +248,8 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
||||
Button confirmDeleteButton = new Button(getTranslation("editappuser.dialog.delete.confirm"), e -> {
|
||||
if (appUser != null && appUser.getId() != null) {
|
||||
appUserService.deleteById(appUser.getId());
|
||||
Notification.show(getTranslation("editappuser.notification.deleted"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editappuser.notification.deleted"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
confirmDialog.close();
|
||||
navigateBack();
|
||||
}
|
||||
|
||||
@@ -151,7 +151,8 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
customer = customerService.findById(customerId);
|
||||
|
||||
if (customer == null) {
|
||||
Notification.show(getTranslation("editcustomer.notification.notfound"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editcustomer.notification.notfound"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
navigateBack();
|
||||
return;
|
||||
}
|
||||
@@ -160,7 +161,8 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
binder.readBean(customer);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Notification.show(getTranslation("editcustomer.notification.invalid.id"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editcustomer.notification.invalid.id"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
navigateBack();
|
||||
}
|
||||
}
|
||||
@@ -184,7 +186,8 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||
Button confirmDeleteButton = new Button(getTranslation("editcustomer.dialog.delete.confirm"), e -> {
|
||||
if (customer != null && customer.getId() != null) {
|
||||
Notification.show(getTranslation("editcustomer.notification.deleted"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("editcustomer.notification.deleted"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
confirmDialog.close();
|
||||
navigateBack();
|
||||
}
|
||||
|
||||
@@ -126,7 +126,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
TextField companyAddField = new TextField(getTranslation("profile.companyadd"));
|
||||
|
||||
TextField firstnameField = new TextField(getTranslation("profile.firstname"));
|
||||
firstnameField.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
||||
firstnameField
|
||||
.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
||||
|
||||
TextField lastnameField = new TextField(getTranslation("profile.lastname"));
|
||||
lastnameField.addBlurListener(e -> validateField(lastnameField, getTranslation("profile.validation.lastname")));
|
||||
@@ -144,7 +145,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
streetField.addBlurListener(e -> validateField(streetField, getTranslation("profile.validation.street")));
|
||||
|
||||
TextField houseNumberField = new TextField(getTranslation("profile.housenr"));
|
||||
houseNumberField.addBlurListener(e -> validateField(houseNumberField, getTranslation("profile.validation.housenr")));
|
||||
houseNumberField
|
||||
.addBlurListener(e -> validateField(houseNumberField, getTranslation("profile.validation.housenr")));
|
||||
|
||||
TextField addressAddField = new TextField(getTranslation("profile.addressadd"));
|
||||
|
||||
@@ -227,22 +229,29 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
cityField.setRequiredIndicatorVisible(true);
|
||||
|
||||
// Hauptadresse binden
|
||||
binder.forField(companyField).asRequired(getTranslation("profile.validation.company.required")).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(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(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(getTranslation("profile.validation.zip.required")).bind(User::getZip, User::setZip);
|
||||
binder.forField(cityField).asRequired(getTranslation("profile.validation.city.required")).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(getTranslation("profile.validation.firstname.required")).bind(User::getFirstname,
|
||||
User::setFirstname);
|
||||
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(firstnameField).asRequired(getTranslation("profile.validation.firstname.required"))
|
||||
.bind(User::getFirstname, User::setFirstname);
|
||||
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);
|
||||
.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);
|
||||
@@ -494,16 +503,16 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
languageCombo.setItemLabelGenerator(language -> {
|
||||
// Flaggen-Emoji für jede Sprache
|
||||
String flag = switch (language) {
|
||||
case DE -> "🇩🇪 ";
|
||||
case EN -> "🇬🇧 ";
|
||||
case FR -> "🇫🇷 ";
|
||||
case ES -> "🇪🇸 ";
|
||||
case TR -> "🇹🇷 ";
|
||||
case PL -> "🇵🇱 ";
|
||||
case RU -> "🇷🇺 ";
|
||||
case EE -> "🇪🇪 ";
|
||||
case LV -> "🇱🇻 ";
|
||||
case LT -> "🇱🇹 ";
|
||||
case DE -> "🇩🇪 ";
|
||||
case EN -> "🇬🇧 ";
|
||||
case FR -> "🇫🇷 ";
|
||||
case ES -> "🇪🇸 ";
|
||||
case TR -> "🇹🇷 ";
|
||||
case PL -> "🇵🇱 ";
|
||||
case RU -> "🇷🇺 ";
|
||||
case EE -> "🇪🇪 ";
|
||||
case LV -> "🇱🇻 ";
|
||||
case LT -> "🇱🇹 ";
|
||||
};
|
||||
return flag + language.getDisplayName();
|
||||
});
|
||||
@@ -590,7 +599,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
UI.getCurrent().getPage().reload();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Notification.show(getTranslation("profile.save.error", ex.getMessage()), 4000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("profile.save.error", ex.getMessage()), 4000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -648,7 +658,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Log error or show notification
|
||||
Notification.show(getTranslation("profile.pdf.error", e.getMessage()), 3000, Notification.Position.BOTTOM_END);
|
||||
Notification.show(getTranslation("profile.pdf.error", e.getMessage()), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,7 +874,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Notification.show(getTranslation("profile.error", ex.getMessage()), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("profile.error", ex.getMessage()), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,18 +942,22 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
String email = safe(currentUser.getEmail());
|
||||
String phone = safe(currentUser.getPhone());
|
||||
|
||||
Div senderCompany = createVariableTemplate(getTranslation("profile.company"), VaadinIcon.OFFICE, "masterdata.company_name",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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);
|
||||
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(getTranslation("profile.services.label"));
|
||||
@@ -949,14 +965,14 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
.set("margin-top", "var(--lumo-space-m)");
|
||||
|
||||
// Leistungen als draggable Variable
|
||||
Div servicesListBlock = createServicesVariableTemplate(getTranslation("profile.invoice.services.list"), VaadinIcon.LIST, "services.list",
|
||||
"Artikel 1: 100,00 €\nArtikel 2: 50,00 €");
|
||||
Div servicesNetBlock = createServicesVariableTemplate(getTranslation("profile.invoice.net"), VaadinIcon.COIN_PILES, "services.net_total",
|
||||
"150,00 €");
|
||||
Div servicesVatBlock = createServicesVariableTemplate(getTranslation("profile.invoice.vat"), VaadinIcon.COIN_PILES,
|
||||
"services.vat_total", "28,50 €");
|
||||
Div servicesGrossBlock = createServicesVariableTemplate(getTranslation("profile.invoice.gross"), VaadinIcon.MONEY, "services.gross_total",
|
||||
"178,50 €");
|
||||
Div servicesListBlock = createServicesVariableTemplate(getTranslation("profile.invoice.services.list"),
|
||||
VaadinIcon.LIST, "services.list", "Artikel 1: 100,00 €\nArtikel 2: 50,00 €");
|
||||
Div servicesNetBlock = createServicesVariableTemplate(getTranslation("profile.invoice.net"),
|
||||
VaadinIcon.COIN_PILES, "services.net_total", "150,00 €");
|
||||
Div servicesVatBlock = createServicesVariableTemplate(getTranslation("profile.invoice.vat"),
|
||||
VaadinIcon.COIN_PILES, "services.vat_total", "28,50 €");
|
||||
Div servicesGrossBlock = createServicesVariableTemplate(getTranslation("profile.invoice.gross"),
|
||||
VaadinIcon.MONEY, "services.gross_total", "178,50 €");
|
||||
|
||||
// Bereich 3: Kundendaten
|
||||
Span customerHeader = new Span(getTranslation("profile.invoice.customerdata"));
|
||||
@@ -964,18 +980,18 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
.set("margin-top", "var(--lumo-space-m)");
|
||||
|
||||
// Kundendaten als Variablen (grün hinterlegt)
|
||||
Div customerCompany = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.company"), VaadinIcon.OFFICE, "customer.company_name",
|
||||
"Kundenfirma GmbH");
|
||||
Div customerName = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.name"), VaadinIcon.USER, "customer.contact_name",
|
||||
"Erika Mustermann");
|
||||
Div customerAddress = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.street"), VaadinIcon.MAP_MARKER, "customer.street",
|
||||
"Kundenstraße 456");
|
||||
Div customerCity = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.city"), VaadinIcon.BUILDING, "customer.city",
|
||||
"54321 Kundenstadt");
|
||||
Div customerEmail = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.email"), VaadinIcon.ENVELOPE, "customer.email",
|
||||
"kunde@beispiel.de");
|
||||
Div customerPhone = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.phone"), VaadinIcon.PHONE, "customer.phone",
|
||||
"0987 654321");
|
||||
Div customerCompany = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.company"),
|
||||
VaadinIcon.OFFICE, "customer.company_name", "Kundenfirma GmbH");
|
||||
Div customerName = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.name"),
|
||||
VaadinIcon.USER, "customer.contact_name", "Erika Mustermann");
|
||||
Div customerAddress = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.street"),
|
||||
VaadinIcon.MAP_MARKER, "customer.street", "Kundenstraße 456");
|
||||
Div customerCity = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.city"),
|
||||
VaadinIcon.BUILDING, "customer.city", "54321 Kundenstadt");
|
||||
Div customerEmail = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.email"),
|
||||
VaadinIcon.ENVELOPE, "customer.email", "kunde@beispiel.de");
|
||||
Div customerPhone = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.phone"),
|
||||
VaadinIcon.PHONE, "customer.phone", "0987 654321");
|
||||
|
||||
// Bereich 2: Freie Elemente
|
||||
Span freeHeader = new Span(getTranslation("profile.invoice.free.elements"));
|
||||
@@ -983,14 +999,22 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
"var(--lumo-space-m)");
|
||||
|
||||
// Draggable Templates
|
||||
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");
|
||||
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,
|
||||
@@ -1208,7 +1232,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
getElement()
|
||||
.executeJs("if (window.updateProfileElementImage) { window.updateProfileElementImage('"
|
||||
+ elementId + "', $0); }", dataUrl);
|
||||
Notification.show(getTranslation("profile.invoice.image.uploaded"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("profile.invoice.image.uploaded"), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
} catch (Exception ex) {
|
||||
Notification.show(getTranslation("profile.invoice.image.upload.error", ex.getMessage()), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
@@ -1333,7 +1358,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
}
|
||||
|
||||
// Löschen Button
|
||||
Button deleteButton = new Button(getTranslation("profile.invoice.element.delete"), 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 -> {
|
||||
@@ -1498,8 +1524,10 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
return "";
|
||||
}).setHeader(getTranslation("profile.services.vatrate")).setSortable(true);
|
||||
|
||||
servicesGrid.addColumn(service -> service.isMandatory() ? getTranslation("common.yes") : getTranslation("common.no")).setHeader(getTranslation("profile.services.mandatory"))
|
||||
.setSortable(true);
|
||||
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
|
||||
servicesGrid.addComponentColumn(service -> {
|
||||
@@ -1554,7 +1582,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
*/
|
||||
private void openServiceDialog(Service service) {
|
||||
Dialog dialog = new Dialog();
|
||||
dialog.setHeaderTitle(service == null ? getTranslation("profile.services.dialog.create") : getTranslation("profile.services.dialog.edit"));
|
||||
dialog.setHeaderTitle(service == null ? getTranslation("profile.services.dialog.create")
|
||||
: getTranslation("profile.services.dialog.edit"));
|
||||
dialog.setWidth("500px");
|
||||
|
||||
// Form layout
|
||||
@@ -1570,7 +1599,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
nameField.setRequiredIndicatorVisible(true);
|
||||
|
||||
// Calculation basis combo box
|
||||
ComboBox<Service.CalculationBasis> calculationBasisCombo = new ComboBox<>(getTranslation("profile.services.basis"));
|
||||
ComboBox<Service.CalculationBasis> calculationBasisCombo = new ComboBox<>(
|
||||
getTranslation("profile.services.basis"));
|
||||
calculationBasisCombo.setWidthFull();
|
||||
calculationBasisCombo.setItems(Service.CalculationBasis.values());
|
||||
calculationBasisCombo.setItemLabelGenerator(basis -> {
|
||||
@@ -1855,31 +1885,30 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the language cookie via JavaScript so the selected language
|
||||
* is available even before the user logs in (e.g., on the login page).
|
||||
* Sets the language cookie via JavaScript so the selected language is available
|
||||
* even before the user logs in (e.g., on the login page).
|
||||
*
|
||||
* @param language the selected language
|
||||
* @param language
|
||||
* the selected language
|
||||
*/
|
||||
private void setLanguageCookie(Language language) {
|
||||
String languageCode = switch (language) {
|
||||
case DE -> "DE";
|
||||
case EN -> "EN";
|
||||
case FR -> "FR";
|
||||
case ES -> "ES";
|
||||
case TR -> "TR";
|
||||
case PL -> "PL";
|
||||
case RU -> "RU";
|
||||
case EE -> "EE";
|
||||
case LV -> "LV";
|
||||
case LT -> "LT";
|
||||
case DE -> "DE";
|
||||
case EN -> "EN";
|
||||
case FR -> "FR";
|
||||
case ES -> "ES";
|
||||
case TR -> "TR";
|
||||
case PL -> "PL";
|
||||
case RU -> "RU";
|
||||
case EE -> "EE";
|
||||
case LV -> "LV";
|
||||
case LT -> "LT";
|
||||
};
|
||||
|
||||
// Execute JavaScript to set the cookie
|
||||
UI.getCurrent().getPage().executeJs(
|
||||
"const maxAge = 365 * 24 * 60 * 60;" +
|
||||
"document.cookie = 'votianlt.language=' + $0 + ';path=/;max-age=' + maxAge + ';SameSite=Lax';",
|
||||
languageCode
|
||||
);
|
||||
UI.getCurrent().getPage().executeJs("const maxAge = 365 * 24 * 60 * 60;"
|
||||
+ "document.cookie = 'votianlt.language=' + $0 + ';path=/;max-age=' + maxAge + ';SameSite=Lax';",
|
||||
languageCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -109,14 +109,22 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
||||
|
||||
// Draggable Templates
|
||||
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");
|
||||
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);
|
||||
@@ -238,7 +246,8 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
||||
getElement().executeJs("if (window.invoiceGenerator) { window.invoiceGenerator.exportTemplate(); }");
|
||||
});
|
||||
|
||||
Button generatePdfButton = new Button(getTranslation("invoicegenerator.button.generatepdf"), 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());
|
||||
|
||||
@@ -289,7 +298,8 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
||||
byte[] pdfBytes = customerInvoiceService.generatePdfFromCanvasTemplate(templateData);
|
||||
showPdfInDialog(pdfBytes);
|
||||
} catch (Exception ex) {
|
||||
showNotification(getTranslation("invoicegenerator.notification.preview.error", ex.getMessage()));
|
||||
showNotification(
|
||||
getTranslation("invoicegenerator.notification.preview.error", ex.getMessage()));
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
@@ -557,7 +567,8 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
||||
}
|
||||
|
||||
// Löschen Button
|
||||
Button deleteButton = new Button(getTranslation("invoicegenerator.button.delete"), 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 -> {
|
||||
|
||||
@@ -46,11 +46,16 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
||||
add(title);
|
||||
|
||||
invoiceGrid = new Grid<>(SystemInvoice.class, false);
|
||||
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.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");
|
||||
|
||||
@@ -88,7 +93,8 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
||||
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
||||
|
||||
} catch (Exception e) {
|
||||
Notification.show(getTranslation("invoices.notification.pdf.error", e.getMessage()), 5000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("invoices.notification.pdf.error", e.getMessage()), 5000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,8 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
||||
Icon typeIcon = getTypeIcon(entry.getChangeType());
|
||||
typeIcon.getStyle().set("color", getTypeColor(entry.getChangeType()));
|
||||
|
||||
Span reason = new Span(entry.getReason() != null ? entry.getReason() : getTranslation("jobhistory.entry.unknown"));
|
||||
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()));
|
||||
|
||||
@@ -149,7 +149,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
sendMessageButton.addClickListener(e -> {
|
||||
// Check if job has an app user assigned
|
||||
if (job.getAppUser() == null || job.getAppUser().isBlank()) {
|
||||
Notification.show(getTranslation("jobsummary.notification.noappuser"), 3000, Notification.Position.MIDDLE)
|
||||
Notification
|
||||
.show(getTranslation("jobsummary.notification.noappuser"), 3000, Notification.Position.MIDDLE)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +190,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
topRow.setSpacing(true);
|
||||
|
||||
VerticalLayout pickupBox = borderedBox();
|
||||
pickupBox.add(new H3(getTranslation("jobsummary.section.pickup") + " " + 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 ? " " : "")
|
||||
@@ -198,7 +200,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
pickupBox.add(new Span(concatZipCity(job.getPickupZip(), job.getPickupCity())));
|
||||
|
||||
VerticalLayout deliveryBox = borderedBox();
|
||||
deliveryBox.add(new H3(getTranslation("jobsummary.section.delivery") + " " + 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())
|
||||
@@ -279,7 +282,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
infoBox.add(new Span(getTranslation("jobsummary.info.digital")));
|
||||
}
|
||||
if (job.getAppUser() != null && !job.getAppUser().isBlank()) {
|
||||
infoBox.add(new Span(getTranslation("jobsummary.info.appuser") + ": " + resolveAppUserName(job.getAppUser())));
|
||||
infoBox.add(
|
||||
new Span(getTranslation("jobsummary.info.appuser") + ": " + resolveAppUserName(job.getAppUser())));
|
||||
}
|
||||
|
||||
cargoBox.setWidth("50%");
|
||||
@@ -298,7 +302,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
buttonRow.setJustifyContentMode(HorizontalLayout.JustifyContentMode.CENTER);
|
||||
buttonRow.getStyle().set("margin-top", "var(--lumo-space-l)");
|
||||
|
||||
Button completeButton = new Button(getTranslation("jobsummary.button.complete"), 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();
|
||||
@@ -475,14 +480,15 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
Integer savedDuration = job.getRouteDurationSeconds();
|
||||
boolean hasSavedRouteData = savedDistance != null && savedDuration != null;
|
||||
|
||||
String js = buildMapJs(origin, destination, hasPosition, position, appUserId, shouldUpdate,
|
||||
hasSavedRouteData, savedDistance != null ? savedDistance : 0.0, savedDuration != null ? savedDuration : 0);
|
||||
String js = buildMapJs(origin, destination, hasPosition, position, appUserId, shouldUpdate, hasSavedRouteData,
|
||||
savedDistance != null ? savedDistance : 0.0, savedDuration != null ? savedDuration : 0);
|
||||
|
||||
map.getElement().executeJs(js, map.getElement(), routeInfo.getElement());
|
||||
}
|
||||
|
||||
private String buildMapJs(String origin, String destination, boolean hasPosition, LocationPosition position,
|
||||
String appUserId, boolean shouldUpdate, boolean hasSavedRouteData, double savedDistance, int savedDuration) {
|
||||
String appUserId, boolean shouldUpdate, boolean hasSavedRouteData, double savedDistance,
|
||||
int savedDuration) {
|
||||
String apiKey = getGoogleMapsApiKey();
|
||||
// Explizit mit Punkt als Dezimaltrennzeichen formatieren
|
||||
String lat = hasPosition ? String.format(java.util.Locale.US, "%.6f", position.getLatitude()) : "0";
|
||||
@@ -493,9 +499,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
// Gespeicherte Dauer formatieren
|
||||
int hours = savedDuration / 3600;
|
||||
int minutes = (savedDuration % 3600) / 60;
|
||||
String savedDurationText = hours > 0
|
||||
? String.format("%d Std. %d Min.", hours, minutes)
|
||||
: String.format("%d Min.", minutes);
|
||||
String savedDurationText = hours > 0 ? String.format("%d Std. %d Min.", hours, minutes)
|
||||
: String.format("%d Min.", minutes);
|
||||
|
||||
return """
|
||||
(function(){
|
||||
@@ -1213,7 +1218,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
|
||||
for (String serviceId : serviceIds) {
|
||||
Service service = serviceRepository.findById(serviceId).orElse(null);
|
||||
if (service == null) continue;
|
||||
if (service == null)
|
||||
continue;
|
||||
|
||||
BigDecimal price = calculateServicePrice(service, routeDistance, durationSeconds);
|
||||
BigDecimal vatRate = service.getVatRate() != null ? service.getVatRate() : new BigDecimal("0.19");
|
||||
@@ -1227,7 +1233,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Preis für eine einzelne Leistung basierend auf ihrer Berechnungsgrundlage.
|
||||
* Berechnet den Preis für eine einzelne Leistung basierend auf ihrer
|
||||
* Berechnungsgrundlage.
|
||||
*/
|
||||
private BigDecimal calculateServicePrice(Service service, Double routeDistance, Integer durationSeconds) {
|
||||
if (service.getCalculationBasis() == null) {
|
||||
@@ -1248,7 +1255,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
|
||||
// Dauer in 15-Minuten-Einheiten umrechnen
|
||||
int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
|
||||
if (durationSeconds % 900 > 0) units++; // Aufrunden
|
||||
if (durationSeconds % 900 > 0)
|
||||
units++; // Aufrunden
|
||||
return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
|
||||
@@ -184,8 +184,7 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
||||
|
||||
private void handleVerify2fa() {
|
||||
if (pendingAuth == null) {
|
||||
Notification.show(getTranslation("login.2fa.no.credentials"), 3000,
|
||||
Notification.Position.BOTTOM_CENTER);
|
||||
Notification.show(getTranslation("login.2fa.no.credentials"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||
return;
|
||||
}
|
||||
String username = pendingAuth.getName();
|
||||
|
||||
@@ -106,8 +106,10 @@ public class MessagesView extends Main implements HasDynamicTitle {
|
||||
return span;
|
||||
})).setHeader(getTranslation("messages.column.status")).setWidth("80px").setFlexGrow(0);
|
||||
|
||||
grid.addColumn(ClientMessageSummary::getClientName).setHeader(getTranslation("messages.column.client")).setAutoWidth(true);
|
||||
grid.addColumn(ClientMessageSummary::getClientEmail).setHeader(getTranslation("messages.column.email")).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()));
|
||||
|
||||
@@ -137,13 +137,14 @@ public class MyInvoicesView extends Main implements HasDynamicTitle {
|
||||
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(getTranslation("myinvoices.column.status")).setAutoWidth(true)
|
||||
.setFlexGrow(0);
|
||||
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(getTranslation("myinvoices.column.amount")).setAutoWidth(true)
|
||||
.setTextAlign(ColumnTextAlign.END).setFlexGrow(0);
|
||||
grid.addColumn(new ComponentRenderer<>(row -> statusBadge(row.status())))
|
||||
.setHeader(getTranslation("myinvoices.column.status")).setAutoWidth(true).setFlexGrow(0);
|
||||
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(getTranslation("myinvoices.column.amount"))
|
||||
.setAutoWidth(true).setTextAlign(ColumnTextAlign.END).setFlexGrow(0);
|
||||
grid.setAllRowsVisible(true);
|
||||
grid.setItems(allRows); // zunächst leer
|
||||
grid.addItemClickListener(event -> {
|
||||
|
||||
@@ -164,7 +164,8 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
resendButton.setVisible(false);
|
||||
|
||||
// Zurück-Link
|
||||
Button backButton = new Button(getTranslation("register.button.back"), 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();
|
||||
|
||||
@@ -223,12 +224,14 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
// Validierung
|
||||
if (email.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.email.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.email.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
emailField.focus();
|
||||
return;
|
||||
}
|
||||
if (!email.contains("@") || !email.contains(".")) {
|
||||
Notification.show(getTranslation("register.notification.email.invalid"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.email.invalid"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
emailField.focus();
|
||||
return;
|
||||
}
|
||||
@@ -238,7 +241,8 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
return;
|
||||
}
|
||||
if (password.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.password.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.password.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
passwordField.focus();
|
||||
return;
|
||||
}
|
||||
@@ -248,7 +252,8 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
return;
|
||||
}
|
||||
if (!password.equals(confirmPassword)) {
|
||||
Notification.show(getTranslation("register.notification.password.mismatch"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.password.mismatch"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
confirmPasswordField.focus();
|
||||
return;
|
||||
}
|
||||
@@ -256,37 +261,43 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
// Weitere Pflichtfelder prüfen (aus Edit-Profile)
|
||||
var firstName = firstNameField.getValue() != null ? firstNameField.getValue().trim() : "";
|
||||
if (firstName.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.firstname.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.firstname.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
firstNameField.focus();
|
||||
return;
|
||||
}
|
||||
var lastName = lastNameField.getValue() != null ? lastNameField.getValue().trim() : "";
|
||||
if (lastName.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.lastname.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.lastname.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
lastNameField.focus();
|
||||
return;
|
||||
}
|
||||
var phone = phoneField.getValue() != null ? phoneField.getValue().trim() : "";
|
||||
if (phone.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.phone.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.phone.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
phoneField.focus();
|
||||
return;
|
||||
}
|
||||
var company = companyField.getValue() != null ? companyField.getValue().trim() : "";
|
||||
if (company.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.company.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.company.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
companyField.focus();
|
||||
return;
|
||||
}
|
||||
var street = streetField.getValue() != null ? streetField.getValue().trim() : "";
|
||||
if (street.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.street.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.street.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
streetField.focus();
|
||||
return;
|
||||
}
|
||||
var houseNo = houseNumberField.getValue() != null ? houseNumberField.getValue().trim() : "";
|
||||
if (houseNo.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.housenr.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.housenr.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
houseNumberField.focus();
|
||||
return;
|
||||
}
|
||||
@@ -298,7 +309,8 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
}
|
||||
var city = cityField.getValue() != null ? cityField.getValue().trim() : "";
|
||||
if (city.isEmpty()) {
|
||||
Notification.show(getTranslation("register.notification.city.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.city.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
cityField.focus();
|
||||
return;
|
||||
}
|
||||
@@ -351,23 +363,25 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
Notification.Position.MIDDLE);
|
||||
} catch (Exception e) {
|
||||
awaitingVerification = false;
|
||||
Notification.show(getTranslation("register.notification.code.emailerror", e.getMessage()), 5000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.code.emailerror", e.getMessage()), 5000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onVerifyCode() {
|
||||
if (!awaitingVerification) {
|
||||
Notification.show(getTranslation("register.notification.code.startfirst"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.code.startfirst"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
return;
|
||||
}
|
||||
String entered = codeField.getValue() != null ? codeField.getValue().trim() : "";
|
||||
if (!entered.matches("\\d{6}")) {
|
||||
Notification.show(getTranslation("register.notification.code.required"), 3000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.code.required"), 3000,
|
||||
Notification.Position.MIDDLE);
|
||||
return;
|
||||
}
|
||||
if (codeExpiresAt == null || LocalDateTime.now().isAfter(codeExpiresAt)) {
|
||||
Notification.show(getTranslation("register.notification.code.expired"), 4000,
|
||||
Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.code.expired"), 4000, Notification.Position.MIDDLE);
|
||||
return;
|
||||
}
|
||||
if (!entered.equals(pendingCode)) {
|
||||
@@ -397,11 +411,11 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
||||
user.setZip(zip);
|
||||
user.setCity(city);
|
||||
userService.save(user);
|
||||
VaadinSession.getCurrent().setAttribute("flashMessage",
|
||||
getTranslation("register.notification.success"));
|
||||
VaadinSession.getCurrent().setAttribute("flashMessage", getTranslation("register.notification.success"));
|
||||
getUI().ifPresent(ui -> ui.navigate("login"));
|
||||
} catch (RuntimeException e) {
|
||||
Notification.show(getTranslation("register.notification.failed", e.getMessage()), 5000, Notification.Position.MIDDLE);
|
||||
Notification.show(getTranslation("register.notification.failed", e.getMessage()), 5000,
|
||||
Notification.Position.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,25 +42,28 @@ public class ShowCustomersView extends VerticalLayout implements HasDynamicTitle
|
||||
add(header);
|
||||
|
||||
// Add hint text
|
||||
var hintText = new com.vaadin.flow.component.html.Paragraph(
|
||||
getTranslation("customers.hint.click"));
|
||||
var hintText = new com.vaadin.flow.component.html.Paragraph(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(getTranslation("customers.column.company")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(customer -> (customer.getFirstname() != null ? customer.getFirstname() : "") + " "
|
||||
+ (customer.getLastName() != null ? customer.getLastName() : "")).setHeader(getTranslation("customers.column.name")).setAutoWidth(true)
|
||||
.setFlexGrow(1).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(getTranslation("customers.column.street"))
|
||||
grid.addColumn(Customer::getCompanyName).setHeader(getTranslation("customers.column.company"))
|
||||
.setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(customer -> (customer.getZip() != null ? customer.getZip() : "") + " "
|
||||
+ (customer.getCity() != null ? customer.getCity() : "")).setHeader(getTranslation("customers.column.city")).setAutoWidth(true)
|
||||
grid.addColumn(customer -> (customer.getFirstname() != null ? customer.getFirstname() : "") + " "
|
||||
+ (customer.getLastName() != null ? customer.getLastName() : ""))
|
||||
.setHeader(getTranslation("customers.column.name")).setAutoWidth(true).setFlexGrow(1).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(getTranslation("customers.column.street")).setAutoWidth(true).setFlexGrow(1)
|
||||
.setSortable(true);
|
||||
grid.addColumn(customer -> (customer.getZip() != null ? customer.getZip() : "") + " "
|
||||
+ (customer.getCity() != null ? customer.getCity() : ""))
|
||||
.setHeader(getTranslation("customers.column.city")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
|
||||
grid.setMultiSort(true);
|
||||
grid.setSizeFull();
|
||||
|
||||
@@ -71,7 +71,8 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
add(title);
|
||||
|
||||
// Configure status filter
|
||||
statusFilter.setItems(getTranslation("jobs.status.all"), getTranslation("jobs.status.open"), getTranslation("jobs.status.done"));
|
||||
statusFilter.setItems(getTranslation("jobs.status.all"), getTranslation("jobs.status.open"),
|
||||
getTranslation("jobs.status.done"));
|
||||
statusFilter.setValue(getTranslation("jobs.status.open"));
|
||||
statusFilter.setWidth("150px");
|
||||
|
||||
@@ -108,12 +109,14 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
endDate.addValueChangeListener(e -> loadData());
|
||||
|
||||
// Configure grid columns: Auftraggeber, Auftragsnummer, Auftragsdatum, Zielort
|
||||
grid.addColumn(job -> extractCompanyName(job.getCustomerSelection())).setHeader(getTranslation("jobs.column.customer"))
|
||||
.setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
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(getTranslation("jobs.column.destination")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
grid.addColumn(job -> extractCompanyName(job.getCustomerSelection()))
|
||||
.setHeader(getTranslation("jobs.column.customer")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||
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(getTranslation("jobs.column.destination")).setAutoWidth(true)
|
||||
.setFlexGrow(1).setSortable(true);
|
||||
|
||||
// Action column: manual completion for jobs without digital processing
|
||||
grid.addComponentColumn(job -> {
|
||||
@@ -201,8 +204,8 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
loadData();
|
||||
} catch (Exception ex) {
|
||||
Notification.show(getTranslation("jobs.notification.complete.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
Notification.show(getTranslation("jobs.notification.complete.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
}
|
||||
});
|
||||
dialog.open();
|
||||
@@ -226,8 +229,8 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
loadData();
|
||||
} catch (Exception ex) {
|
||||
Notification.show(getTranslation("jobs.notification.delete.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
Notification.show(getTranslation("jobs.notification.delete.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
}
|
||||
});
|
||||
dialog.open();
|
||||
@@ -314,10 +317,9 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
private String generateCsv(java.util.List<Job> jobs) {
|
||||
StringBuilder csv = new StringBuilder();
|
||||
// CSV Header
|
||||
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.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) {
|
||||
|
||||
@@ -107,7 +107,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
||||
}
|
||||
|
||||
private Button createLanguageSelector() {
|
||||
// Aktuelle Sprache aus der UI-Locale ermitteln (wird vom LocaleVaadinInitListener gesetzt)
|
||||
// Aktuelle Sprache aus der UI-Locale ermitteln (wird vom
|
||||
// LocaleVaadinInitListener gesetzt)
|
||||
String currentLang = getCurrentLanguageFromLocale();
|
||||
String flag = getFlagForLanguage(currentLang);
|
||||
|
||||
@@ -134,7 +135,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
||||
}
|
||||
|
||||
private String getCurrentLanguageFromLocale() {
|
||||
// Aktuelle UI-Locale verwenden (wird vom LocaleVaadinInitListener aus Cookie/Browser gesetzt)
|
||||
// Aktuelle UI-Locale verwenden (wird vom LocaleVaadinInitListener aus
|
||||
// Cookie/Browser gesetzt)
|
||||
Locale locale = UI.getCurrent().getLocale();
|
||||
if (locale == null) {
|
||||
return "DE";
|
||||
@@ -142,16 +144,16 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
||||
|
||||
String language = locale.getLanguage();
|
||||
return switch (language) {
|
||||
case "en" -> "EN";
|
||||
case "fr" -> "FR";
|
||||
case "es" -> "ES";
|
||||
case "tr" -> "TR";
|
||||
case "pl" -> "PL";
|
||||
case "ru" -> "RU";
|
||||
case "et" -> "EE"; // Estnisch
|
||||
case "lv" -> "LV"; // Lettisch
|
||||
case "lt" -> "LT"; // Litauisch
|
||||
default -> "DE"; // German as default
|
||||
case "en" -> "EN";
|
||||
case "fr" -> "FR";
|
||||
case "es" -> "ES";
|
||||
case "tr" -> "TR";
|
||||
case "pl" -> "PL";
|
||||
case "ru" -> "RU";
|
||||
case "et" -> "EE"; // Estnisch
|
||||
case "lv" -> "LV"; // Lettisch
|
||||
case "lt" -> "LT"; // Litauisch
|
||||
default -> "DE"; // German as default
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,26 +162,24 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
||||
return "🇩🇪";
|
||||
}
|
||||
return switch (languageCode.toUpperCase()) {
|
||||
case "DE" -> "🇩🇪";
|
||||
case "EN" -> "🇬🇧";
|
||||
case "FR" -> "🇫🇷";
|
||||
case "ES" -> "🇪🇸";
|
||||
case "TR" -> "🇹🇷";
|
||||
case "PL" -> "🇵🇱";
|
||||
case "RU" -> "🇷🇺";
|
||||
case "EE" -> "🇪🇪";
|
||||
case "LV" -> "🇱🇻";
|
||||
case "LT" -> "🇱🇹";
|
||||
default -> "🇩🇪";
|
||||
case "DE" -> "🇩🇪";
|
||||
case "EN" -> "🇬🇧";
|
||||
case "FR" -> "🇫🇷";
|
||||
case "ES" -> "🇪🇸";
|
||||
case "TR" -> "🇹🇷";
|
||||
case "PL" -> "🇵🇱";
|
||||
case "RU" -> "🇷🇺";
|
||||
case "EE" -> "🇪🇪";
|
||||
case "LV" -> "🇱🇻";
|
||||
case "LT" -> "🇱🇹";
|
||||
default -> "🇩🇪";
|
||||
};
|
||||
}
|
||||
|
||||
private void setLanguageCookie(String languageCode) {
|
||||
UI.getCurrent().getPage().executeJs(
|
||||
"const maxAge = 365 * 24 * 60 * 60;" +
|
||||
"document.cookie = 'votianlt.language=' + $0 + ';path=/;max-age=' + maxAge + ';SameSite=Lax';",
|
||||
languageCode
|
||||
);
|
||||
UI.getCurrent().getPage().executeJs("const maxAge = 365 * 24 * 60 * 60;"
|
||||
+ "document.cookie = 'votianlt.language=' + $0 + ';path=/;max-age=' + maxAge + ';SameSite=Lax';",
|
||||
languageCode);
|
||||
}
|
||||
|
||||
private Component createAuthenticatedNavigation() {
|
||||
@@ -307,8 +307,9 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
||||
featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.STRETCH);
|
||||
|
||||
// Feature Cards
|
||||
featuresGrid.add(createFeatureCard(VaadinIcon.COG, getTranslation("start.feature.setup.title"),
|
||||
getTranslation("start.feature.setup.desc")),
|
||||
featuresGrid.add(
|
||||
createFeatureCard(VaadinIcon.COG, getTranslation("start.feature.setup.title"),
|
||||
getTranslation("start.feature.setup.desc")),
|
||||
createFeatureCard(VaadinIcon.USERS, getTranslation("start.feature.customers.title"),
|
||||
getTranslation("start.feature.customers.desc")),
|
||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, getTranslation("start.feature.jobs.title"),
|
||||
@@ -389,8 +390,10 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
||||
companyInfo.setPadding(false);
|
||||
companyInfo.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
|
||||
companyInfo.add(new Paragraph(getTranslation("start.imprint.company")), new Paragraph(getTranslation("start.imprint.address")),
|
||||
new Paragraph(getTranslation("start.imprint.phone")), new Paragraph(getTranslation("start.imprint.email")));
|
||||
companyInfo.add(new Paragraph(getTranslation("start.imprint.company")),
|
||||
new Paragraph(getTranslation("start.imprint.address")),
|
||||
new Paragraph(getTranslation("start.imprint.phone")),
|
||||
new Paragraph(getTranslation("start.imprint.email")));
|
||||
|
||||
// Call to Action
|
||||
Paragraph ctaText = new Paragraph(getTranslation("start.cta.text"));
|
||||
|
||||
@@ -135,8 +135,8 @@ public class UserMessagesView extends Main implements HasUrlParameter<String>, H
|
||||
LocalDateTime lastMessageTime = latest != null ? latest.getCreatedAt() : null;
|
||||
String preview = resolvePreview(latest);
|
||||
|
||||
section.add(createMessageCard(getTranslation("usermessages.general.conversation"), preview, lastMessageTime, messageCount, unreadCount,
|
||||
"general"));
|
||||
section.add(createMessageCard(getTranslation("usermessages.general.conversation"), preview, lastMessageTime,
|
||||
messageCount, unreadCount, "general"));
|
||||
|
||||
return section;
|
||||
}
|
||||
@@ -233,9 +233,8 @@ public class UserMessagesView extends Main implements HasUrlParameter<String>, H
|
||||
titleRow.expand(titleSpan);
|
||||
|
||||
// Preview text
|
||||
Span preview = new Span(
|
||||
Optional.ofNullable(lastMessagePreview).filter(s -> !s.isBlank())
|
||||
.orElse(getTranslation("usermessages.preview.empty")));
|
||||
Span preview = new Span(Optional.ofNullable(lastMessagePreview).filter(s -> !s.isBlank())
|
||||
.orElse(getTranslation("usermessages.preview.empty")));
|
||||
preview.getStyle().set("color", "#666666");
|
||||
preview.getStyle().set("font-size", "14px");
|
||||
|
||||
|
||||
@@ -459,11 +459,8 @@ public class CustomerInvoiceService {
|
||||
StringBuilder html = new StringBuilder();
|
||||
|
||||
// Sample data for preview (will be replaced with actual job data later)
|
||||
String[][] sampleData = {
|
||||
{"Umzugsleistung inkl. Verpackung", "19%", "450,00 €"},
|
||||
{"Entsorgung Möbel", "19%", "85,00 €"},
|
||||
{"Montage/De-Montage", "19%", "120,00 €"}
|
||||
};
|
||||
String[][] sampleData = { { "Umzugsleistung inkl. Verpackung", "19%", "450,00 €" },
|
||||
{ "Entsorgung Möbel", "19%", "85,00 €" }, { "Montage/De-Montage", "19%", "120,00 €" } };
|
||||
|
||||
// Calculate totals
|
||||
double netTotal = 655.00;
|
||||
@@ -478,18 +475,25 @@ public class CustomerInvoiceService {
|
||||
|
||||
// Header row
|
||||
html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
|
||||
html.append("<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
||||
html.append(
|
||||
"<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
||||
html.append(
|
||||
"<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
||||
html.append(
|
||||
"<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Data rows
|
||||
for (int i = 0; i < sampleData.length; i++) {
|
||||
String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
|
||||
html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
|
||||
html.append("<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>").append(sampleData[i][0]).append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][1]).append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][2]).append("</td>");
|
||||
html.append(
|
||||
"<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>")
|
||||
.append(sampleData[i][0]).append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][1])
|
||||
.append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][2])
|
||||
.append("</td>");
|
||||
html.append("</tr>");
|
||||
}
|
||||
|
||||
@@ -505,21 +509,26 @@ public class CustomerInvoiceService {
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", netTotal)).append("</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
|
||||
.append(String.format(java.util.Locale.GERMANY, "%,.2f €", netTotal)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Umsatzsteuer - label in col 2, value in col 3
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. 19% USt:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", vatTotal)).append("</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
|
||||
.append(String.format(java.util.Locale.GERMANY, "%,.2f €", vatTotal)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Gesamtsumme - label in col 2, value in col 3
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", grossTotal)).append("</td>");
|
||||
html.append(
|
||||
"<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
||||
html.append(
|
||||
"<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>")
|
||||
.append(String.format(java.util.Locale.GERMANY, "%,.2f €", grossTotal)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
html.append("</table>");
|
||||
@@ -530,8 +539,9 @@ public class CustomerInvoiceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a PDF preview from canvas template data with pre-built variables map.
|
||||
* This version accepts a pre-built variables map instead of building it from the User object.
|
||||
* Generate a PDF preview from canvas template data with pre-built variables
|
||||
* map. This version accepts a pre-built variables map instead of building it
|
||||
* from the User object.
|
||||
*/
|
||||
public byte[] generatePdfFromCanvasTemplateWithData(String jsonTemplateData,
|
||||
java.util.Map<String, String> variables, de.assecutor.votianlt.model.User user) throws Exception {
|
||||
@@ -542,7 +552,8 @@ public class CustomerInvoiceService {
|
||||
// Parse the JSON template data - JSON is stored as a string in the database
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
|
||||
// The template data is stored as a JSON string, so we need to parse it as a string first
|
||||
// The template data is stored as a JSON string, so we need to parse it as a
|
||||
// string first
|
||||
String jsonContent = mapper.readValue(jsonTemplateData, String.class);
|
||||
com.fasterxml.jackson.databind.JsonNode rootNode = mapper.readTree(jsonContent);
|
||||
|
||||
@@ -597,9 +608,11 @@ public class CustomerInvoiceService {
|
||||
String variable = element.has("variable") ? element.get("variable").asText(null) : null;
|
||||
String text = "";
|
||||
|
||||
System.out.println("DEBUG: Processing element " + elementCount + ", type=" + type + ", variable=" + variable);
|
||||
System.out.println(
|
||||
"DEBUG: Processing element " + elementCount + ", type=" + type + ", variable=" + variable);
|
||||
|
||||
// Use percentage values if available, otherwise fall back to legacy pixel values
|
||||
// Use percentage values if available, otherwise fall back to legacy pixel
|
||||
// values
|
||||
double xPercent, yPercent, widthPercent, heightPercent;
|
||||
if (element.has("xPercent")) {
|
||||
xPercent = element.get("xPercent").asDouble(0);
|
||||
@@ -671,7 +684,8 @@ public class CustomerInvoiceService {
|
||||
System.out.println("DEBUG: Replaced variable " + variable + " with: " + text);
|
||||
} else if (variable != null) {
|
||||
// Variable exists but no value provided - use placeholder
|
||||
text = "[" + variable.replace("masterdata.", "").replace("customer.", "").replace("invoice.", "").replace("job.", "") + "]";
|
||||
text = "[" + variable.replace("masterdata.", "").replace("customer.", "").replace("invoice.", "")
|
||||
.replace("job.", "") + "]";
|
||||
System.out.println("DEBUG: No value for variable " + variable + ", using placeholder");
|
||||
} else {
|
||||
System.out.println("DEBUG: Using static text: " + text);
|
||||
@@ -744,8 +758,8 @@ public class CustomerInvoiceService {
|
||||
if (servicesJson != null && !servicesJson.isEmpty() && !servicesJson.equals("[]")) {
|
||||
try {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
com.fasterxml.jackson.core.type.TypeReference<java.util.List<java.util.Map<String, String>>> typeRef =
|
||||
new com.fasterxml.jackson.core.type.TypeReference<>() {};
|
||||
com.fasterxml.jackson.core.type.TypeReference<java.util.List<java.util.Map<String, String>>> typeRef = new com.fasterxml.jackson.core.type.TypeReference<>() {
|
||||
};
|
||||
servicesData = mapper.readValue(servicesJson, typeRef);
|
||||
} catch (Exception e) {
|
||||
System.err.println("DEBUG: Failed to parse services JSON: " + e.getMessage());
|
||||
@@ -760,16 +774,20 @@ public class CustomerInvoiceService {
|
||||
|
||||
// Header row
|
||||
html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
|
||||
html.append("<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
||||
html.append(
|
||||
"<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
||||
html.append(
|
||||
"<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
||||
html.append(
|
||||
"<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Data rows - use actual service data from the job
|
||||
if (servicesData.isEmpty()) {
|
||||
// Fallback: show a single row with no data
|
||||
html.append("<tr style='border-bottom:1px solid #eeeeee;'>");
|
||||
html.append("<td colspan='3' style='text-align:center;padding:4px 8px;white-space:nowrap;'>Keine Leistungen vorhanden</td>");
|
||||
html.append(
|
||||
"<td colspan='3' style='text-align:center;padding:4px 8px;white-space:nowrap;'>Keine Leistungen vorhanden</td>");
|
||||
html.append("</tr>");
|
||||
} else {
|
||||
for (int i = 0; i < servicesData.size(); i++) {
|
||||
@@ -780,9 +798,13 @@ public class CustomerInvoiceService {
|
||||
|
||||
String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
|
||||
html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
|
||||
html.append("<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>").append(escapeHtml(name)).append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(serviceVatRate).append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(netAmount).append(" €</td>");
|
||||
html.append(
|
||||
"<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>")
|
||||
.append(escapeHtml(name)).append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(serviceVatRate)
|
||||
.append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(netAmount)
|
||||
.append(" €</td>");
|
||||
html.append("</tr>");
|
||||
}
|
||||
}
|
||||
@@ -800,21 +822,27 @@ public class CustomerInvoiceService {
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(netTotal).append("</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
|
||||
.append(netTotal).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Umsatzsteuer
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. ").append(vatPercent).append("% USt:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(vatTotal).append("</td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. ")
|
||||
.append(vatPercent).append("% USt:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
|
||||
.append(vatTotal).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Gesamtsumme
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>").append(grossTotal).append("</td>");
|
||||
html.append(
|
||||
"<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
||||
html.append(
|
||||
"<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>")
|
||||
.append(grossTotal).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
html.append("</table>");
|
||||
@@ -831,10 +859,7 @@ public class CustomerInvoiceService {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
return input.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'");
|
||||
return input.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """)
|
||||
.replace("'", "'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,20 +33,20 @@ public class LanguageService {
|
||||
try {
|
||||
Locale locale;
|
||||
switch (language) {
|
||||
case DE:
|
||||
locale = Locale.GERMAN;
|
||||
break;
|
||||
case EN:
|
||||
locale = Locale.ENGLISH;
|
||||
break;
|
||||
case FR:
|
||||
locale = Locale.FRENCH;
|
||||
break;
|
||||
case DE:
|
||||
locale = Locale.GERMAN;
|
||||
break;
|
||||
case EN:
|
||||
locale = Locale.ENGLISH;
|
||||
break;
|
||||
case FR:
|
||||
locale = Locale.FRENCH;
|
||||
break;
|
||||
case ES:
|
||||
locale = Locale.of("es", "ES");
|
||||
break;
|
||||
default:
|
||||
locale = Locale.GERMAN;
|
||||
default:
|
||||
locale = Locale.GERMAN;
|
||||
}
|
||||
|
||||
ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
|
||||
@@ -74,11 +74,11 @@ public class LanguageService {
|
||||
|
||||
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");
|
||||
default -> Locale.GERMAN;
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> Locale.of("es", "ES");
|
||||
default -> Locale.GERMAN;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user