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