Erweiterungen
This commit is contained in:
1 parent
00811cdc36
commit
049deab12b
6 files changed
+154
-115
No files matched your search
@@ -7,21 +7,21 @@ import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.applayout.AppLayout;
|
||||
import com.vaadin.flow.component.avatar.Avatar;
|
||||
import com.vaadin.flow.component.avatar.AvatarVariant;
|
||||
import com.vaadin.flow.component.details.Details;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.icon.Icon;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.menubar.MenuBar;
|
||||
import com.vaadin.flow.component.menubar.MenuBarVariant;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.Scroller;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.sidenav.SideNav;
|
||||
import com.vaadin.flow.component.sidenav.SideNavItem;
|
||||
import com.vaadin.flow.component.treegrid.TreeGrid;
|
||||
import com.vaadin.flow.data.provider.hierarchy.TreeData;
|
||||
import com.vaadin.flow.data.provider.hierarchy.TreeDataProvider;
|
||||
import com.vaadin.flow.router.Layout;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import com.vaadin.flow.server.menu.MenuConfiguration;
|
||||
import com.vaadin.flow.server.menu.MenuEntry;
|
||||
import com.vaadin.flow.shared.Registration;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.model.UserInvoiceData;
|
||||
@@ -29,7 +29,6 @@ import de.assecutor.votianlt.pages.service.AppUserService;
|
||||
import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
|
||||
import de.assecutor.votianlt.pages.view.EditProfileView;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import de.assecutor.votianlt.config.TranslationProvider;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.service.LanguageService;
|
||||
import de.assecutor.votianlt.service.MessageBadgeUpdateService;
|
||||
@@ -40,6 +39,7 @@ import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
@AnonymousAllowed
|
||||
@Slf4j
|
||||
@@ -55,9 +55,9 @@ public final class MainLayout extends AppLayout {
|
||||
private Div headerRef;
|
||||
private Scroller navRef;
|
||||
private Component userMenuRef;
|
||||
private Span messagesBadge; // Reference to the messages badge for dynamic updates
|
||||
private SideNavItem messagesNavItem; // Reference to the messages nav item
|
||||
private Registration badgeUpdateRegistration; // Track badge update listener registration
|
||||
private TreeGrid<MenuTreeItem> tree;
|
||||
private MenuTreeItem messagesTreeItem;
|
||||
private Registration badgeUpdateRegistration;
|
||||
|
||||
public MainLayout(SecurityService securityService, UserInvoiceDataService userInvoiceDataService,
|
||||
MessageService messageService, MessageBadgeUpdateService messageBadgeUpdateService,
|
||||
@@ -107,117 +107,157 @@ public final class MainLayout extends AppLayout {
|
||||
}
|
||||
|
||||
private Component createSideNav() {
|
||||
var nav = new SideNav();
|
||||
nav.addClassNames(Margin.Horizontal.MEDIUM);
|
||||
|
||||
MenuConfiguration.getMenuEntries().forEach(entry -> {
|
||||
// Skip "Verwaltung" entry as we'll handle it separately with Details component
|
||||
if (!"Verwaltung".equals(entry.title())) {
|
||||
SideNavItem item = createSideNavItem(entry);
|
||||
nav.addItem(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Create Details component for "Verwaltung" with collapsible list
|
||||
Details verwaltungDetails = new Details();
|
||||
verwaltungDetails.setSummaryText(getTranslation("nav.management"));
|
||||
verwaltungDetails.addClassNames(Margin.Horizontal.MEDIUM, FontSize.MEDIUM, FontWeight.MEDIUM, "#000000");
|
||||
|
||||
// Create collapsible content with navigation items
|
||||
VerticalLayout verwaltungContent = new VerticalLayout();
|
||||
verwaltungContent.setPadding(false);
|
||||
verwaltungContent.setSpacing(true);
|
||||
|
||||
// Create navigation items for the collapsible list
|
||||
SideNavItem jobs = new SideNavItem(getTranslation("nav.jobs"), "jobs", new Icon(VaadinIcon.LIST));
|
||||
SideNavItem customers = new SideNavItem(getTranslation("nav.customers"), "customers", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem appUsers = new SideNavItem(getTranslation("nav.appusers"), "app-user", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem statistics = new SideNavItem(getTranslation("nav.statistics"), "statistics", new Icon(VaadinIcon.BAR_CHART));
|
||||
|
||||
verwaltungContent.add(jobs, customers, appUsers, statistics);
|
||||
|
||||
// Only show invoices menu if billing is enabled for the current user
|
||||
// Create tree data with hierarchical menu structure
|
||||
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 verwaltungItem = new MenuTreeItem(getTranslation("nav.management"), null, VaadinIcon.COG);
|
||||
MenuTreeItem benutzerItem = new MenuTreeItem(getTranslation("nav.users"), null, VaadinIcon.USER);
|
||||
|
||||
// Store reference to messages item for badge updates
|
||||
messagesTreeItem = nachrichtenItem;
|
||||
|
||||
// Add root items
|
||||
treeData.addItem(null, auftragserstellungItem);
|
||||
treeData.addItem(null, nachrichtenItem);
|
||||
treeData.addItem(null, verwaltungItem);
|
||||
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));
|
||||
|
||||
// Add invoices only if billing is enabled
|
||||
if (isBillingEnabledForCurrentUser()) {
|
||||
SideNavItem invoices = new SideNavItem(getTranslation("nav.invoices"), "invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||
verwaltungContent.add(invoices);
|
||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.invoices"), "invoices", VaadinIcon.FILE_TEXT));
|
||||
}
|
||||
|
||||
verwaltungContent.add(statistics);
|
||||
verwaltungDetails.add(verwaltungContent);
|
||||
|
||||
// Create Details component for "Verwaltung" with collapsible list
|
||||
Details userDetails = new Details();
|
||||
userDetails.setSummaryText(getTranslation("nav.users"));
|
||||
userDetails.addClassNames(Margin.Horizontal.MEDIUM, FontSize.MEDIUM, FontWeight.MEDIUM, TextColor.BODY);
|
||||
|
||||
// Create collapsible content with navigation items
|
||||
VerticalLayout userContent = new VerticalLayout();
|
||||
userContent.setPadding(false);
|
||||
userContent.setSpacing(true);
|
||||
|
||||
// Create navigation items for the collapsible list
|
||||
SideNavItem profile = new SideNavItem(getTranslation("nav.profile"), "edit-profile", new Icon(VaadinIcon.USER));
|
||||
SideNavItem myInvoices = new SideNavItem(getTranslation("nav.myinvoices"), "my-invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||
SideNavItem imprint = new SideNavItem(getTranslation("nav.imprint"), "impressum", new Icon(VaadinIcon.INFO_CIRCLE));
|
||||
|
||||
userContent.add(profile, myInvoices, imprint);
|
||||
userDetails.add(userContent);
|
||||
|
||||
// Create a vertical layout to hold both regular menu items and collapsible
|
||||
// sections
|
||||
|
||||
// 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));
|
||||
|
||||
// Create Tree
|
||||
tree = new TreeGrid<>();
|
||||
tree.setDataProvider(new TreeDataProvider<>(treeData));
|
||||
tree.addClassNames(Margin.Horizontal.MEDIUM);
|
||||
|
||||
// Custom item renderer to show icon and label with badge
|
||||
tree.addComponentHierarchyColumn(item -> {
|
||||
HorizontalLayout row = new HorizontalLayout();
|
||||
row.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
row.setSpacing(true);
|
||||
row.setPadding(false);
|
||||
row.setMargin(false);
|
||||
row.setWidthFull();
|
||||
|
||||
// Icon
|
||||
if (item.icon() != null) {
|
||||
Icon icon = item.icon().create();
|
||||
icon.setSize("var(--lumo-icon-size-s)");
|
||||
icon.getStyle().set("min-width", "var(--lumo-icon-size-s)");
|
||||
row.add(icon);
|
||||
}
|
||||
|
||||
// Label
|
||||
Span label = new Span(item.label());
|
||||
label.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||
row.add(label);
|
||||
row.setFlexGrow(1, label);
|
||||
|
||||
// Badge for messages
|
||||
if (item == messagesTreeItem && item.badgeCount() > 0) {
|
||||
Span badge = new Span(String.valueOf(item.badgeCount()));
|
||||
badge.getElement().getThemeList().add("badge");
|
||||
badge.getStyle().set("background-color", "var(--lumo-primary-color)");
|
||||
badge.getStyle().set("color", "#ffffff");
|
||||
badge.getStyle().set("border-radius", "12px");
|
||||
badge.getStyle().set("padding", "2px 8px");
|
||||
badge.getStyle().set("font-size", "11px");
|
||||
badge.getStyle().set("font-weight", "bold");
|
||||
badge.getStyle().set("min-width", "18px");
|
||||
badge.getStyle().set("text-align", "center");
|
||||
badge.getStyle().set("margin-left", "auto");
|
||||
row.add(badge);
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
// Handle selection/navigation
|
||||
tree.addSelectionListener(event -> {
|
||||
event.getFirstSelectedItem().ifPresent(item -> {
|
||||
if (item.path() != null && !item.path().isEmpty()) {
|
||||
UI.getCurrent().navigate(item.path());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Expand management and user nodes by default
|
||||
tree.expand(verwaltungItem, benutzerItem);
|
||||
|
||||
// Create container
|
||||
VerticalLayout navContainer = new VerticalLayout();
|
||||
navContainer.setPadding(false);
|
||||
navContainer.setSpacing(false);
|
||||
navContainer.add(nav, verwaltungDetails, userDetails);
|
||||
|
||||
navContainer.add(tree);
|
||||
|
||||
return navContainer;
|
||||
}
|
||||
|
||||
private SideNavItem createSideNavItem(MenuEntry menuEntry) {
|
||||
SideNavItem item;
|
||||
if (menuEntry.icon() != null) {
|
||||
item = new SideNavItem(menuEntry.title(), menuEntry.path(), new Icon(menuEntry.icon()));
|
||||
} else {
|
||||
item = new SideNavItem(menuEntry.title(), menuEntry.path());
|
||||
}
|
||||
if ("Nachrichten".equals(menuEntry.title())) {
|
||||
messagesNavItem = item;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the messages badge with the current unread count
|
||||
*/
|
||||
private void updateMessagesBadge() {
|
||||
if (messagesNavItem == null) {
|
||||
if (tree == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
long unreadCount = resolveUnreadMessageCount();
|
||||
|
||||
if (unreadCount > 0) {
|
||||
if (messagesBadge == null) {
|
||||
messagesBadge = new Span(String.valueOf(unreadCount));
|
||||
messagesBadge.getElement().getThemeList().add("badge");
|
||||
messagesBadge.getStyle().set("background-color", "var(--lumo-primary-color)");
|
||||
messagesBadge.getStyle().set("color", "#ffffff");
|
||||
messagesBadge.getStyle().set("border-radius", "12px");
|
||||
messagesBadge.getStyle().set("padding", "2px 8px");
|
||||
messagesBadge.getStyle().set("font-size", "12px");
|
||||
messagesBadge.getStyle().set("font-weight", "bold");
|
||||
messagesBadge.getStyle().set("min-width", "20px");
|
||||
messagesBadge.getStyle().set("text-align", "center");
|
||||
messagesNavItem.setSuffixComponent(messagesBadge);
|
||||
} else {
|
||||
messagesBadge.setText(String.valueOf(unreadCount));
|
||||
messagesBadge.setVisible(true);
|
||||
}
|
||||
} else {
|
||||
if (messagesBadge != null) {
|
||||
messagesNavItem.setSuffixComponent(null);
|
||||
messagesBadge = null;
|
||||
}
|
||||
|
||||
// Get current data provider and update the messages item
|
||||
TreeDataProvider<MenuTreeItem> dataProvider = (TreeDataProvider<MenuTreeItem>) tree.getDataProvider();
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Record representing a menu item in the tree
|
||||
*/
|
||||
private record MenuTreeItem(String label, String path, VaadinIcon icon, long badgeCount) {
|
||||
MenuTreeItem(String label, String path, VaadinIcon icon) {
|
||||
this(label, path, icon, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(label, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +389,7 @@ public final class MainLayout extends AppLayout {
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> new Locale("es", "ES");
|
||||
case ES -> Locale.of("es", "ES");
|
||||
default -> Locale.GERMAN;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.component.textfield.IntegerField;
|
||||
import com.vaadin.flow.component.textfield.NumberField;
|
||||
import com.vaadin.flow.data.binder.Binder;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.textfield.TextArea;
|
||||
import com.vaadin.flow.component.tabs.TabSheet;
|
||||
@@ -69,7 +68,6 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Route(value = "add_job", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Auftragserstellung")
|
||||
@RolesAllowed("USER")
|
||||
@Slf4j
|
||||
public class AddJobView extends Main implements HasDynamicTitle {
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.itextpdf.html2pdf.HtmlConverter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.time.LocalDate;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
@@ -42,9 +42,9 @@ public class LanguageService {
|
||||
case FR:
|
||||
locale = Locale.FRENCH;
|
||||
break;
|
||||
case ES:
|
||||
locale = new Locale("es", "ES");
|
||||
break;
|
||||
case ES:
|
||||
locale = Locale.of("es", "ES");
|
||||
break;
|
||||
default:
|
||||
locale = Locale.GERMAN;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class LanguageService {
|
||||
case DE -> Locale.GERMAN;
|
||||
case EN -> Locale.ENGLISH;
|
||||
case FR -> Locale.FRENCH;
|
||||
case ES -> new Locale("es", "ES");
|
||||
case ES -> Locale.of("es", "ES");
|
||||
default -> Locale.GERMAN;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Navigation and Main Layout
|
||||
nav.jobs=Aufträge
|
||||
nav.job.create=Auftragserstellung
|
||||
nav.customers=Kunden
|
||||
nav.appusers=App-Nutzer
|
||||
nav.statistics=Statistiken
|
||||
nav.invoices=Rechnungen
|
||||
nav.messages=Nachrichten
|
||||
nav.profile=Mein Profil
|
||||
nav.myinvoices=Meine Rechnungen
|
||||
nav.myinvoices=Rechnungen
|
||||
nav.imprint=Impressum
|
||||
nav.management=Verwaltung
|
||||
nav.users=Benutzer
|
||||
@@ -239,7 +240,7 @@ page.title.invoices=Rechnungen
|
||||
page.title.appusers=App-Nutzer
|
||||
page.title.job.history=Job Historie
|
||||
page.title.message.history=Nachrichtenverlauf
|
||||
page.title.myinvoices=Meine Rechnungen
|
||||
page.title.myinvoices=Rechnungen
|
||||
page.title.job.create=Neuen Auftrag anlegen
|
||||
page.title.job.summary=Zusammenfassung
|
||||
page.title.pricetable=Preis-Tabelle
|
||||
@@ -646,7 +647,7 @@ invoices.column.amount=Betrag
|
||||
invoices.column.description=Beschreibung
|
||||
|
||||
# My Invoices
|
||||
myinvoices.title=Meine Rechnungen
|
||||
myinvoices.title=Rechnungen
|
||||
myinvoices.hint.noopen=Sie haben keine offenen Rechnungen. Alle Rechnungen sind beglichen.
|
||||
myinvoices.bank.institute=Bank
|
||||
myinvoices.bank.beneficiary=Empfänger
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Navigation and Main Layout
|
||||
nav.jobs=Jobs
|
||||
nav.job.create=Create New Job
|
||||
nav.customers=Customers
|
||||
nav.appusers=App Users
|
||||
nav.statistics=Statistics
|
||||
|
||||
Reference in new issue
Block a user