feat: erweiterte Chat-Funktionalität, UI-Verbesserungen und Lokalisierungsupdates
- Chat: Nachrichten-Status (read/unread), WebSocket-Verbesserungen - App: Login-Optimierung, Job-Übersicht verbessert, neue Übersetzungen - Backend: Dialog-Styling, Invoice-Generator, Job-Verwaltung erweitert - Mehrsprachigkeit: Neue Übersetzungen für DE, EN, ES, ET, FR, LT, LV, PL, RU, TR
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<revision>0.9.14</revision>
|
||||
<revision>0.9.15</revision>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
|
||||
@@ -8,8 +8,8 @@ import org.bson.types.ObjectId;
|
||||
* Normalized payload for chat messages sent by mobile clients via WebSocket.
|
||||
* receiver = AppUser ID (clientId) extracted from topic
|
||||
*/
|
||||
public record ChatMessageInboundPayload(String receiver, String content, MessageContentType contentType, ObjectId jobId,
|
||||
String jobNumber) {
|
||||
public record ChatMessageInboundPayload(String receiver, String content, MessageContentType contentType,
|
||||
String messageId, ObjectId jobId, String jobNumber) {
|
||||
|
||||
public ChatMessageInboundPayload {
|
||||
contentType = contentType != null ? contentType : MessageContentType.TEXT;
|
||||
@@ -23,10 +23,11 @@ public record ChatMessageInboundPayload(String receiver, String content, Message
|
||||
String receiver = extractRequiredString(payload, "receiver");
|
||||
String content = extractRequiredString(payload, "content");
|
||||
MessageContentType contentType = extractContentType(payload.get("contentType"));
|
||||
String messageId = extractOptionalString(payload.get("messageId"));
|
||||
ObjectId jobId = extractObjectId(payload.get("jobId"), "jobId");
|
||||
String jobNumber = extractOptionalString(payload.get("jobNumber"));
|
||||
|
||||
return new ChatMessageInboundPayload(receiver, content, contentType, jobId, jobNumber);
|
||||
return new ChatMessageInboundPayload(receiver, content, contentType, messageId, jobId, jobNumber);
|
||||
}
|
||||
|
||||
public boolean hasJobContext() {
|
||||
|
||||
@@ -42,6 +42,13 @@ public class Message {
|
||||
@Field("receiver")
|
||||
private String receiver;
|
||||
|
||||
/**
|
||||
* Optional stable client-side ID used for idempotent retries from the mobile
|
||||
* app.
|
||||
*/
|
||||
@Field("client_message_id")
|
||||
private String clientMessageId;
|
||||
|
||||
/**
|
||||
* Timestamp when the message was created
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||
import com.vaadin.flow.component.combobox.ComboBox;
|
||||
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.H3;
|
||||
@@ -441,25 +440,21 @@ public class DeliveryStationDialog extends Dialog {
|
||||
close();
|
||||
} else {
|
||||
// Adresse nicht gefunden: Benutzer fragen
|
||||
ConfirmDialog confirmDialog = new ConfirmDialog();
|
||||
confirmDialog.setHeader(
|
||||
translationHelper.getTranslation("addjob.validation.address.not.found.title"));
|
||||
confirmDialog.setText(
|
||||
translationHelper.getTranslation("addjob.validation.address.not.found.message"));
|
||||
confirmDialog.setConfirmText(
|
||||
translationHelper.getTranslation("addjob.validation.address.save.anyway"));
|
||||
confirmDialog.setConfirmButtonTheme("primary");
|
||||
confirmDialog.setCancelable(true);
|
||||
confirmDialog.setCancelText(
|
||||
translationHelper.getTranslation("addjob.validation.address.correct"));
|
||||
confirmDialog.addConfirmListener(ev -> {
|
||||
data.setAddressValidatedByGoogle(false);
|
||||
data.setAddressValidationResult(validationResult);
|
||||
if (saveListener != null) {
|
||||
saveListener.onSave(data);
|
||||
}
|
||||
close();
|
||||
});
|
||||
Dialog confirmDialog = DialogStylingHelper.createConfirmationDialog(
|
||||
translationHelper.getTranslation("addjob.validation.address.not.found.title"),
|
||||
translationHelper.getTranslation("addjob.validation.address.not.found.message"),
|
||||
"560px",
|
||||
translationHelper.getTranslation("addjob.validation.address.correct"),
|
||||
translationHelper.getTranslation("addjob.validation.address.save.anyway"),
|
||||
() -> {
|
||||
data.setAddressValidatedByGoogle(false);
|
||||
data.setAddressValidationResult(validationResult);
|
||||
if (saveListener != null) {
|
||||
saveListener.onSave(data);
|
||||
}
|
||||
close();
|
||||
},
|
||||
ButtonVariant.LUMO_PRIMARY);
|
||||
confirmDialog.open();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package de.assecutor.votianlt.pages.base.ui.component;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
|
||||
@@ -20,6 +23,33 @@ public final class DialogStylingHelper {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static Dialog createConfirmationDialog(String title, String message, String width, String cancelText,
|
||||
String confirmText, Runnable onConfirm, ButtonVariant... confirmVariants) {
|
||||
Dialog dialog = createStyledDialog(title, width);
|
||||
dialog.setCloseOnEsc(true);
|
||||
dialog.setCloseOnOutsideClick(true);
|
||||
|
||||
VerticalLayout dialogContent = createContentLayout("320px");
|
||||
if (message != null && !message.isBlank()) {
|
||||
dialogContent.add(new Span(message));
|
||||
}
|
||||
|
||||
Button cancelButton = new Button(cancelText, event -> dialog.close());
|
||||
cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
Button confirmButton = new Button(confirmText, event -> {
|
||||
dialog.close();
|
||||
onConfirm.run();
|
||||
});
|
||||
if (confirmVariants != null && confirmVariants.length > 0) {
|
||||
confirmButton.addThemeVariants(confirmVariants);
|
||||
}
|
||||
|
||||
dialog.add(wrapContent(dialogContent));
|
||||
dialog.getFooter().add(cancelButton, confirmButton);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static void apply(Dialog dialog, String title, String width) {
|
||||
if (title != null && !title.isBlank()) {
|
||||
dialog.setHeaderTitle(title);
|
||||
@@ -32,16 +62,40 @@ public final class DialogStylingHelper {
|
||||
}
|
||||
|
||||
public static Component wrapContent(Component content) {
|
||||
return wrapContent(content, false);
|
||||
}
|
||||
|
||||
public static Component wrapContent(Component content, boolean fillHeight) {
|
||||
Div frame = new Div();
|
||||
frame.getStyle().set("border", "10px solid transparent");
|
||||
frame.getStyle().set("border-radius", "0");
|
||||
frame.getStyle().set("box-sizing", "border-box");
|
||||
if (fillHeight) {
|
||||
frame.getStyle().set("display", "flex");
|
||||
frame.getStyle().set("flex-direction", "column");
|
||||
frame.getStyle().set("height", "100%");
|
||||
frame.getStyle().set("min-height", "0");
|
||||
frame.getStyle().set("flex", "1");
|
||||
}
|
||||
frame.setWidthFull();
|
||||
|
||||
Div whiteCard = new Div();
|
||||
whiteCard.getStyle().set("background", "white");
|
||||
whiteCard.getStyle().set("border-radius", "24px");
|
||||
whiteCard.getStyle().set("overflow", "auto");
|
||||
if (fillHeight) {
|
||||
whiteCard.getStyle().set("display", "flex");
|
||||
whiteCard.getStyle().set("flex-direction", "column");
|
||||
whiteCard.getStyle().set("height", "100%");
|
||||
whiteCard.getStyle().set("min-height", "0");
|
||||
whiteCard.getStyle().set("flex", "1");
|
||||
whiteCard.getStyle().set("overflow", "hidden");
|
||||
content.getElement().getStyle().set("width", "100%");
|
||||
content.getElement().getStyle().set("height", "100%");
|
||||
content.getElement().getStyle().set("min-height", "0");
|
||||
content.getElement().getStyle().set("flex", "1");
|
||||
} else {
|
||||
whiteCard.getStyle().set("overflow", "auto");
|
||||
}
|
||||
whiteCard.setWidthFull();
|
||||
whiteCard.add(content);
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.vaadin.flow.component.textfield.NumberField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.component.timepicker.TimePicker;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.progressbar.ProgressBar;
|
||||
import de.assecutor.votianlt.model.AddressValidationResult;
|
||||
@@ -577,25 +576,21 @@ public class PickupStationDialog extends Dialog {
|
||||
close();
|
||||
} else {
|
||||
// Adresse nicht gefunden: Benutzer fragen
|
||||
ConfirmDialog confirmDialog = new ConfirmDialog();
|
||||
confirmDialog.setHeader(
|
||||
translationHelper.getTranslation("addjob.validation.address.not.found.title"));
|
||||
confirmDialog.setText(
|
||||
translationHelper.getTranslation("addjob.validation.address.not.found.message"));
|
||||
confirmDialog.setConfirmText(
|
||||
translationHelper.getTranslation("addjob.validation.address.save.anyway"));
|
||||
confirmDialog.setConfirmButtonTheme("primary");
|
||||
confirmDialog.setCancelable(true);
|
||||
confirmDialog.setCancelText(
|
||||
translationHelper.getTranslation("addjob.validation.address.correct"));
|
||||
confirmDialog.addConfirmListener(ev -> {
|
||||
data.setAddressValidatedByGoogle(false);
|
||||
data.setAddressValidationResult(validationResult);
|
||||
if (saveListener != null) {
|
||||
saveListener.onSave(data);
|
||||
}
|
||||
close();
|
||||
});
|
||||
Dialog confirmDialog = DialogStylingHelper.createConfirmationDialog(
|
||||
translationHelper.getTranslation("addjob.validation.address.not.found.title"),
|
||||
translationHelper.getTranslation("addjob.validation.address.not.found.message"),
|
||||
"560px",
|
||||
translationHelper.getTranslation("addjob.validation.address.correct"),
|
||||
translationHelper.getTranslation("addjob.validation.address.save.anyway"),
|
||||
() -> {
|
||||
data.setAddressValidatedByGoogle(false);
|
||||
data.setAddressValidationResult(validationResult);
|
||||
if (saveListener != null) {
|
||||
saveListener.onSave(data);
|
||||
}
|
||||
close();
|
||||
},
|
||||
ButtonVariant.LUMO_PRIMARY);
|
||||
confirmDialog.open();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.vaadin.flow.component.sidenav.SideNav;
|
||||
import com.vaadin.flow.component.sidenav.SideNavItem;
|
||||
import com.vaadin.flow.router.Layout;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
|
||||
import de.assecutor.votianlt.pages.view.EditProfileView;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
|
||||
@@ -136,7 +137,7 @@ public final class AdminLayout extends AppLayout {
|
||||
// Profile display with navigation
|
||||
userMenuItem.getSubMenu().addItem("Profil anzeigen", e -> UI.getCurrent().navigate(EditProfileView.class));
|
||||
userMenuItem.getSubMenu().addItem("Admin-Einstellungen");
|
||||
userMenuItem.getSubMenu().addItem("Abmelden", e -> securityService.logout());
|
||||
userMenuItem.getSubMenu().addItem("Abmelden", e -> openLogoutConfirmDialog());
|
||||
|
||||
// Update function for username and avatar
|
||||
Runnable updateUserInfo = () -> {
|
||||
@@ -151,4 +152,17 @@ public final class AdminLayout extends AppLayout {
|
||||
|
||||
return userMenu;
|
||||
}
|
||||
|
||||
private void openLogoutConfirmDialog() {
|
||||
var dialog = DialogStylingHelper.createConfirmationDialog(
|
||||
getTranslation("logout.confirm.title"),
|
||||
getTranslation("logout.confirm.message"),
|
||||
"460px",
|
||||
getTranslation("button.cancel"),
|
||||
getTranslation("nav.logout"),
|
||||
securityService::logout,
|
||||
com.vaadin.flow.component.button.ButtonVariant.LUMO_PRIMARY,
|
||||
com.vaadin.flow.component.button.ButtonVariant.LUMO_ERROR);
|
||||
dialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import com.vaadin.flow.shared.Registration;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
|
||||
import de.assecutor.votianlt.pages.service.AppUserService;
|
||||
import de.assecutor.votianlt.pages.view.EditProfileView;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
@@ -327,8 +328,7 @@ public final class MainLayout extends AppLayout {
|
||||
// Profil anzeigen mit Navigation
|
||||
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());
|
||||
userMenuItem.getSubMenu().addItem(getTranslation("nav.logout"), e -> openLogoutConfirmDialog());
|
||||
|
||||
// Update-Funktion für Benutzername und Avatar
|
||||
Runnable updateUserInfo = () -> {
|
||||
@@ -344,6 +344,19 @@ public final class MainLayout extends AppLayout {
|
||||
return userMenu;
|
||||
}
|
||||
|
||||
private void openLogoutConfirmDialog() {
|
||||
var dialog = DialogStylingHelper.createConfirmationDialog(
|
||||
getTranslation("logout.confirm.title"),
|
||||
getTranslation("logout.confirm.message"),
|
||||
"460px",
|
||||
getTranslation("button.cancel"),
|
||||
getTranslation("nav.logout"),
|
||||
securityService::logout,
|
||||
com.vaadin.flow.component.button.ButtonVariant.LUMO_PRIMARY,
|
||||
com.vaadin.flow.component.button.ButtonVariant.LUMO_ERROR);
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttach(AttachEvent attachEvent) {
|
||||
super.onAttach(attachEvent);
|
||||
|
||||
@@ -249,7 +249,7 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
||||
String value = mail.getValue();
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
mail.setInvalid(true);
|
||||
mail.setErrorMessage(getTranslation("profile.email.required"));
|
||||
mail.setErrorMessage(getTranslation("profile.validation.email.required"));
|
||||
} else if (!value.contains("@")) {
|
||||
mail.setInvalid(true);
|
||||
mail.setErrorMessage(getTranslation("profile.validation.email.invalid"));
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||
import com.vaadin.flow.component.combobox.ComboBox;
|
||||
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.UI;
|
||||
|
||||
@@ -756,86 +755,89 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
if (idx < 0)
|
||||
return;
|
||||
|
||||
ConfirmDialog dialog = new ConfirmDialog();
|
||||
dialog.setHeader(getTranslation("addjob.station.remove.confirm", idx + 1));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setCancelText(getTranslation("dialog.cancel"));
|
||||
dialog.setConfirmText(getTranslation("dialog.confirm"));
|
||||
dialog.addConfirmListener(e -> {
|
||||
int removeIdx = deliveryStationTilesList.indexOf(tile);
|
||||
if (removeIdx < 0)
|
||||
return;
|
||||
Dialog dialog = DialogStylingHelper.createConfirmationDialog(
|
||||
getTranslation("addjob.station.remove.confirm", idx + 1),
|
||||
null,
|
||||
"460px",
|
||||
getTranslation("dialog.cancel"),
|
||||
getTranslation("dialog.confirm"),
|
||||
() -> {
|
||||
int removeIdx = deliveryStationTilesList.indexOf(tile);
|
||||
if (removeIdx < 0)
|
||||
return;
|
||||
|
||||
deliveryStationTilesList.remove(removeIdx);
|
||||
deliveryStationsState.remove(removeIdx);
|
||||
deliveryStationsSaveAddress.remove(removeIdx);
|
||||
deliveryStationsMailState.remove(removeIdx);
|
||||
deliveryStationsValidatedByGoogle.remove(removeIdx);
|
||||
deliveryStationTasksState.remove(removeIdx);
|
||||
Div removedSlot = deliveryStationSlotList.remove(removeIdx);
|
||||
deliveryStationDistanceChips.remove(removeIdx);
|
||||
pickupToDeliveryRouteResults.remove(removeIdx);
|
||||
// Re-index tasks state for remaining stations
|
||||
Map<Integer, List<BaseTask>> reindexed = new HashMap<>();
|
||||
for (Map.Entry<Integer, List<BaseTask>> entry : deliveryStationTasksState.entrySet()) {
|
||||
int oldIdx = entry.getKey();
|
||||
int newIdx = oldIdx > removeIdx ? oldIdx - 1 : oldIdx;
|
||||
reindexed.put(newIdx, entry.getValue());
|
||||
}
|
||||
deliveryStationTasksState.clear();
|
||||
deliveryStationTasksState.putAll(reindexed);
|
||||
deliveryStationTilesList.remove(removeIdx);
|
||||
deliveryStationsState.remove(removeIdx);
|
||||
deliveryStationsSaveAddress.remove(removeIdx);
|
||||
deliveryStationsMailState.remove(removeIdx);
|
||||
deliveryStationsValidatedByGoogle.remove(removeIdx);
|
||||
deliveryStationTasksState.remove(removeIdx);
|
||||
Div removedSlot = deliveryStationSlotList.remove(removeIdx);
|
||||
deliveryStationDistanceChips.remove(removeIdx);
|
||||
pickupToDeliveryRouteResults.remove(removeIdx);
|
||||
// Re-index tasks state for remaining stations
|
||||
Map<Integer, List<BaseTask>> reindexed = new HashMap<>();
|
||||
for (Map.Entry<Integer, List<BaseTask>> entry : deliveryStationTasksState.entrySet()) {
|
||||
int oldIdx = entry.getKey();
|
||||
int newIdx = oldIdx > removeIdx ? oldIdx - 1 : oldIdx;
|
||||
reindexed.put(newIdx, entry.getValue());
|
||||
}
|
||||
deliveryStationTasksState.clear();
|
||||
deliveryStationTasksState.putAll(reindexed);
|
||||
|
||||
Map<Integer, RouteCalculationResult> reindexedRoutes = new HashMap<>();
|
||||
for (Map.Entry<Integer, RouteCalculationResult> entry : pickupToDeliveryRouteResults.entrySet()) {
|
||||
int oldIdx = entry.getKey();
|
||||
int newIdx = oldIdx > removeIdx ? oldIdx - 1 : oldIdx;
|
||||
reindexedRoutes.put(newIdx, entry.getValue());
|
||||
}
|
||||
pickupToDeliveryRouteResults.clear();
|
||||
pickupToDeliveryRouteResults.putAll(reindexedRoutes);
|
||||
Map<Integer, RouteCalculationResult> reindexedRoutes = new HashMap<>();
|
||||
for (Map.Entry<Integer, RouteCalculationResult> entry : pickupToDeliveryRouteResults.entrySet()) {
|
||||
int oldIdx = entry.getKey();
|
||||
int newIdx = oldIdx > removeIdx ? oldIdx - 1 : oldIdx;
|
||||
reindexedRoutes.put(newIdx, entry.getValue());
|
||||
}
|
||||
pickupToDeliveryRouteResults.clear();
|
||||
pickupToDeliveryRouteResults.putAll(reindexedRoutes);
|
||||
|
||||
for (SelectedServiceEntry selectedService : selectedServices) {
|
||||
Integer stationOrder = selectedService.getDeliveryStationOrder();
|
||||
if (stationOrder == null) {
|
||||
continue;
|
||||
}
|
||||
if (stationOrder == removeIdx) {
|
||||
selectedService.setDeliveryStationOrder(deliveryStationsState.isEmpty() ? null : 0);
|
||||
} else if (stationOrder > removeIdx) {
|
||||
selectedService.setDeliveryStationOrder(stationOrder - 1);
|
||||
}
|
||||
}
|
||||
stationsGridContainer.remove(removedSlot);
|
||||
for (SelectedServiceEntry selectedService : selectedServices) {
|
||||
Integer stationOrder = selectedService.getDeliveryStationOrder();
|
||||
if (stationOrder == null) {
|
||||
continue;
|
||||
}
|
||||
if (stationOrder == removeIdx) {
|
||||
selectedService.setDeliveryStationOrder(deliveryStationsState.isEmpty() ? null : 0);
|
||||
} else if (stationOrder > removeIdx) {
|
||||
selectedService.setDeliveryStationOrder(stationOrder - 1);
|
||||
}
|
||||
}
|
||||
stationsGridContainer.remove(removedSlot);
|
||||
|
||||
// Renumber remaining tiles and update click listeners
|
||||
for (int i = 0; i < deliveryStationTilesList.size(); i++) {
|
||||
StationTile t = deliveryStationTilesList.get(i);
|
||||
int newNumber = i + 1;
|
||||
t.updateStationNumber(newNumber);
|
||||
t.updateTitle(getTranslation("addjob.station.delivery", newNumber));
|
||||
// Update click listener to use correct index
|
||||
final int newIdx = i;
|
||||
t.setClickListener(tt -> openDeliveryDialog(tt, newIdx));
|
||||
// First station should not be removable
|
||||
if (i == 0) {
|
||||
t.setDeleteListener(null);
|
||||
}
|
||||
}
|
||||
// Renumber remaining tiles and update click listeners
|
||||
for (int i = 0; i < deliveryStationTilesList.size(); i++) {
|
||||
StationTile t = deliveryStationTilesList.get(i);
|
||||
int newNumber = i + 1;
|
||||
t.updateStationNumber(newNumber);
|
||||
t.updateTitle(getTranslation("addjob.station.delivery", newNumber));
|
||||
// Update click listener to use correct index
|
||||
final int newIdx = i;
|
||||
t.setClickListener(tt -> openDeliveryDialog(tt, newIdx));
|
||||
// First station should not be removable
|
||||
if (i == 0) {
|
||||
t.setDeleteListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure "+" button is visible if under max
|
||||
if (deliveryStationTilesList.size() < MAX_DELIVERY_STATIONS && addStationButtonSlot.getParent().isEmpty()) {
|
||||
stationsGridContainer.add(addStationButtonSlot);
|
||||
}
|
||||
// Ensure "+" button is visible if under max
|
||||
if (deliveryStationTilesList.size() < MAX_DELIVERY_STATIONS
|
||||
&& addStationButtonSlot.getParent().isEmpty()) {
|
||||
stationsGridContainer.add(addStationButtonSlot);
|
||||
}
|
||||
|
||||
resetRouteInformation();
|
||||
resetStationsAppliedState();
|
||||
if (servicesGrid != null) {
|
||||
servicesGrid.getDataProvider().refreshAll();
|
||||
}
|
||||
updatePriceSummary();
|
||||
triggerValidation();
|
||||
updateTabLabels();
|
||||
});
|
||||
resetRouteInformation();
|
||||
resetStationsAppliedState();
|
||||
if (servicesGrid != null) {
|
||||
servicesGrid.getDataProvider().refreshAll();
|
||||
}
|
||||
updatePriceSummary();
|
||||
triggerValidation();
|
||||
updateTabLabels();
|
||||
},
|
||||
ButtonVariant.LUMO_PRIMARY);
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import de.assecutor.votianlt.model.Service;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.model.InvoiceTemplate;
|
||||
import de.assecutor.votianlt.model.invoices.CustomerInvoice;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
|
||||
import de.assecutor.votianlt.pages.service.CustomerService;
|
||||
import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
|
||||
import de.assecutor.votianlt.repository.CustomerInvoiceRepository;
|
||||
@@ -45,7 +46,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.html.IFrame;
|
||||
|
||||
@@ -659,9 +659,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
private void showPdfPreviewDialog(byte[] pdfBytes, String templateData, User user) {
|
||||
String title = getTranslation("createinvoice.preview.title");
|
||||
|
||||
Dialog pdfDialog = new Dialog();
|
||||
pdfDialog.setHeaderTitle(title);
|
||||
pdfDialog.setWidth("90vw");
|
||||
Dialog pdfDialog = DialogStylingHelper.createStyledDialog(title, "90vw");
|
||||
pdfDialog.setHeight("90vh");
|
||||
|
||||
IFrame pdfFrame = new IFrame();
|
||||
@@ -676,19 +674,19 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
Button saveButton = new Button(getTranslation("createinvoice.button.save"), e -> {
|
||||
ConfirmDialog confirm = new ConfirmDialog();
|
||||
confirm.setHeader(getTranslation("createinvoice.confirm.save.title"));
|
||||
confirm.setText(getTranslation("createinvoice.confirm.save.message"));
|
||||
confirm.setConfirmText(getTranslation("createinvoice.confirm.save.confirm"));
|
||||
confirm.setConfirmButtonTheme("primary");
|
||||
confirm.setCancelText(getTranslation("button.cancel"));
|
||||
confirm.setCancelable(true);
|
||||
confirm.addConfirmListener(ev -> saveInvoice(templateData, user, pdfDialog));
|
||||
Dialog confirm = DialogStylingHelper.createConfirmationDialog(
|
||||
getTranslation("createinvoice.confirm.save.title"),
|
||||
getTranslation("createinvoice.confirm.save.message"),
|
||||
"560px",
|
||||
getTranslation("button.cancel"),
|
||||
getTranslation("createinvoice.confirm.save.confirm"),
|
||||
() -> saveInvoice(templateData, user, pdfDialog),
|
||||
ButtonVariant.LUMO_PRIMARY);
|
||||
confirm.open();
|
||||
});
|
||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||
|
||||
pdfDialog.add(pdfFrame);
|
||||
pdfDialog.add(DialogStylingHelper.wrapContent(pdfFrame, true));
|
||||
pdfDialog.getFooter().add(closeButton, saveButton);
|
||||
pdfDialog.open();
|
||||
}
|
||||
@@ -696,9 +694,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
public static void showSavedInvoiceDialog(byte[] pdfBytes, String invoiceNumber,
|
||||
com.vaadin.flow.component.Component parent) {
|
||||
String title = "Rechnung " + invoiceNumber;
|
||||
Dialog pdfDialog = new Dialog();
|
||||
pdfDialog.setHeaderTitle(title);
|
||||
pdfDialog.setWidth("90vw");
|
||||
Dialog pdfDialog = DialogStylingHelper.createStyledDialog(title, "90vw");
|
||||
pdfDialog.setHeight("90vh");
|
||||
|
||||
IFrame pdfFrame = new IFrame();
|
||||
@@ -720,7 +716,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
Button closeButton = new Button("Schließen", e -> pdfDialog.close());
|
||||
closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
pdfDialog.add(pdfFrame);
|
||||
pdfDialog.add(DialogStylingHelper.wrapContent(pdfFrame, true));
|
||||
pdfDialog.getFooter().add(downloadButton, closeButton);
|
||||
pdfDialog.open();
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
TextField faxField = new TextField(getTranslation("profile.fax"));
|
||||
TextField mobileField = new TextField(getTranslation("profile.mobile"));
|
||||
|
||||
EmailField emailField = new EmailField(getTranslation("profile.email.required"));
|
||||
EmailField emailField = new EmailField(getTranslation("profile.email"));
|
||||
emailField.addBlurListener(e -> validateEmailField(emailField));
|
||||
|
||||
TextField streetField = new TextField(getTranslation("profile.street"));
|
||||
@@ -882,9 +882,7 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
String dataUrl = "data:application/pdf;base64," + base64Pdf;
|
||||
|
||||
// Create dialog
|
||||
Dialog pdfDialog = new Dialog();
|
||||
pdfDialog.setHeaderTitle(getTranslation("profile.invoice.pdf.preview"));
|
||||
pdfDialog.setWidth("90vw");
|
||||
Dialog pdfDialog = DialogStylingHelper.createStyledDialog(getTranslation("profile.invoice.pdf.preview"), "90vw");
|
||||
pdfDialog.setHeight("90vh");
|
||||
|
||||
// Create a Div to hold the PDF viewer
|
||||
@@ -914,7 +912,7 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
||||
});
|
||||
downloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
pdfDialog.add(pdfContainer);
|
||||
pdfDialog.add(DialogStylingHelper.wrapContent(pdfContainer, true));
|
||||
pdfDialog.getFooter().add(downloadButton, closeButton);
|
||||
pdfDialog.open();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package de.assecutor.votianlt.pages.view;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.html.Anchor;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.H5;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
@@ -25,29 +28,82 @@ public class ImprintView extends VerticalLayout implements HasDynamicTitle {
|
||||
ViewToolbar toolbar = new ViewToolbar(getTranslation("page.title.imprint"));
|
||||
content.add(toolbar);
|
||||
|
||||
try {
|
||||
// Load HTML content from resources
|
||||
ClassPathResource resource = new ClassPathResource("html/imprint.html");
|
||||
String htmlContent = new String(resource.getInputStream().readAllBytes());
|
||||
Div imprintCard = new Div();
|
||||
imprintCard.addClassNames("form-card", "form-shell");
|
||||
imprintCard.getStyle().set("max-width", "800px").set("margin", "0 auto");
|
||||
|
||||
// Create a Div to hold the HTML content
|
||||
Div imprintDiv = new Div();
|
||||
imprintDiv.addClassNames("form-card", "form-shell");
|
||||
imprintDiv.getElement().setProperty("innerHTML", htmlContent);
|
||||
VerticalLayout imprintContent = new VerticalLayout();
|
||||
imprintContent.setPadding(false);
|
||||
imprintContent.setSpacing(false);
|
||||
imprintContent.getStyle().set("gap", "var(--lumo-space-l)");
|
||||
|
||||
content.add(imprintDiv);
|
||||
imprintContent.add(
|
||||
createSection("Assecutor Data Service GmbH",
|
||||
createLinesBlock(createLine("Gerhart-Hauptmann-Weg 14"), createLine("21502 Geesthacht"),
|
||||
createLine(getTranslation("imprint.country")),
|
||||
createLine(getTranslation("imprint.phone") + ": +49 40 18 123 771 0"),
|
||||
createEmailLine())),
|
||||
createSection(getTranslation("imprint.management"),
|
||||
createLinesBlock(createLine("Carsten Annacker"), createLine("Gunnar Timm"))),
|
||||
createSection(getTranslation("imprint.registeredoffice"),
|
||||
createLinesBlock(createLine("Gerhart-Hauptmann-Weg 14, 21502 Geesthacht"))),
|
||||
createSection(getTranslation("imprint.commercialregister"),
|
||||
createLinesBlock(createLine("HRB 8595 HL"))),
|
||||
createSection(getTranslation("imprint.vatid"), createLinesBlock(createLine("DE261094748"))),
|
||||
createSection(getTranslation("imprint.imagecredits"),
|
||||
createLinesBlock(createSectionHeading(getTranslation("imprint.backgroundimage")),
|
||||
createLine("MAN Financial Services (EURO-Leasing), flickr"),
|
||||
createLine(
|
||||
"(Creative Commons, Attribution-ShareAlike 2.0 Generic (CC BY-SA 2.0))"),
|
||||
createExternalLink(
|
||||
"https://www.flickr.com/photos/mbwa_pr/15969764443/in/album-72157632488355514/"))));
|
||||
|
||||
} catch (Exception e) {
|
||||
// Fallback content in case of error
|
||||
Div errorDiv = new Div();
|
||||
errorDiv.addClassNames("form-card", "form-shell");
|
||||
errorDiv.setText(getTranslation("imprint.error", e.getMessage()));
|
||||
content.add(errorDiv);
|
||||
}
|
||||
imprintCard.add(imprintContent);
|
||||
content.add(imprintCard);
|
||||
|
||||
add(content);
|
||||
}
|
||||
|
||||
private Component createSection(String title, Component body) {
|
||||
Div section = new Div();
|
||||
section.getStyle().set("text-align", "left");
|
||||
section.add(createSectionHeading(title), body);
|
||||
return section;
|
||||
}
|
||||
|
||||
private H5 createSectionHeading(String title) {
|
||||
H5 heading = new H5(title);
|
||||
heading.getStyle().set("margin", "0 0 var(--lumo-space-s) 0");
|
||||
return heading;
|
||||
}
|
||||
|
||||
private Div createLinesBlock(Component... lines) {
|
||||
Div block = new Div();
|
||||
block.getStyle().set("display", "flex").set("flex-direction", "column").set("gap", "4px");
|
||||
block.add(lines);
|
||||
return block;
|
||||
}
|
||||
|
||||
private Span createLine(String text) {
|
||||
Span line = new Span(text);
|
||||
line.getStyle().set("display", "block");
|
||||
return line;
|
||||
}
|
||||
|
||||
private Div createEmailLine() {
|
||||
Div line = new Div();
|
||||
line.getStyle().set("display", "block");
|
||||
line.add(new Span(getTranslation("imprint.email") + ": "));
|
||||
line.add(new Anchor("mailto:ahoi@assecutor.de", "ahoi@assecutor.de"));
|
||||
return line;
|
||||
}
|
||||
|
||||
private Anchor createExternalLink(String href) {
|
||||
Anchor link = new Anchor(href, href);
|
||||
link.setTarget("_blank");
|
||||
return link;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageTitle() {
|
||||
return getTranslation("page.title.imprint");
|
||||
|
||||
@@ -16,7 +16,6 @@ import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.component.html.Input;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.html.IFrame;
|
||||
import com.vaadin.flow.server.StreamResource;
|
||||
import elemental.json.JsonValue;
|
||||
import elemental.json.JsonType;
|
||||
import com.vaadin.flow.component.upload.Upload;
|
||||
@@ -306,15 +305,8 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
||||
}
|
||||
|
||||
private void showPdfInDialog(byte[] pdfBytes) {
|
||||
// Create a stream resource for the PDF
|
||||
StreamResource resource = new StreamResource("preview.pdf", () -> new java.io.ByteArrayInputStream(pdfBytes));
|
||||
resource.setContentType("application/pdf");
|
||||
resource.setCacheTime(0);
|
||||
|
||||
// Create dialog
|
||||
Dialog pdfDialog = new Dialog();
|
||||
pdfDialog.setHeaderTitle(getTranslation("invoicegenerator.pdf.preview.title"));
|
||||
pdfDialog.setWidth("90vw");
|
||||
Dialog pdfDialog = DialogStylingHelper.createStyledDialog(getTranslation("invoicegenerator.pdf.preview.title"),
|
||||
"90vw");
|
||||
pdfDialog.setHeight("90vh");
|
||||
|
||||
// Create a Div to hold the PDF viewer
|
||||
@@ -347,7 +339,7 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
||||
});
|
||||
downloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
pdfDialog.add(pdfContainer);
|
||||
pdfDialog.add(DialogStylingHelper.wrapContent(pdfContainer, true));
|
||||
pdfDialog.getFooter().add(downloadButton, closeButton);
|
||||
pdfDialog.open();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import de.assecutor.votianlt.model.JobHistoryType;
|
||||
import de.assecutor.votianlt.model.Barcode;
|
||||
import de.assecutor.votianlt.model.Photo;
|
||||
import de.assecutor.votianlt.model.Signature;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
||||
import de.assecutor.votianlt.repository.BarcodeRepository;
|
||||
import de.assecutor.votianlt.repository.JobRepository;
|
||||
@@ -182,8 +183,7 @@ 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(getLocalizedReason(entry));
|
||||
reason.addClassName("timeline-reason");
|
||||
|
||||
Span timestamp = new Span(formatDateTime(entry.getTimestamp()));
|
||||
@@ -202,8 +202,9 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
||||
cardContent.add(headerRow);
|
||||
|
||||
// Description
|
||||
if (entry.getDescription() != null && !entry.getDescription().isBlank()) {
|
||||
Span description = new Span(entry.getDescription());
|
||||
String localizedDescription = getLocalizedDescription(entry);
|
||||
if (localizedDescription != null && !localizedDescription.isBlank()) {
|
||||
Span description = new Span(localizedDescription);
|
||||
description.addClassName("timeline-description");
|
||||
cardContent.add(description);
|
||||
}
|
||||
@@ -252,6 +253,37 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
||||
return card;
|
||||
}
|
||||
|
||||
private String getLocalizedReason(JobHistory entry) {
|
||||
if (entry == null) {
|
||||
return "";
|
||||
}
|
||||
if (entry.getChangeType() == JobHistoryType.CREATE) {
|
||||
return getTranslation("jobhistory.entry.create.reason");
|
||||
}
|
||||
return entry.getReason() != null ? entry.getReason() : "";
|
||||
}
|
||||
|
||||
private String getLocalizedDescription(JobHistory entry) {
|
||||
if (entry == null || entry.getDescription() == null || entry.getDescription().isBlank()) {
|
||||
return entry != null ? entry.getDescription() : "";
|
||||
}
|
||||
if (entry.getChangeType() == JobHistoryType.CREATE) {
|
||||
return getTranslation("jobhistory.entry.create.description", extractDescriptionValue(entry.getDescription()));
|
||||
}
|
||||
return entry.getDescription();
|
||||
}
|
||||
|
||||
private String extractDescriptionValue(String description) {
|
||||
if (description == null || description.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
int separatorIndex = description.indexOf(':');
|
||||
if (separatorIndex < 0 || separatorIndex == description.length() - 1) {
|
||||
return description.trim();
|
||||
}
|
||||
return description.substring(separatorIndex + 1).trim();
|
||||
}
|
||||
|
||||
private Icon getTypeIcon(JobHistoryType type) {
|
||||
if (type == null)
|
||||
return new Icon(VaadinIcon.INFO_CIRCLE);
|
||||
@@ -300,15 +332,15 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
||||
|
||||
private String formatStatus(de.assecutor.votianlt.model.JobStatus status) {
|
||||
if (status == null)
|
||||
return getTranslation("jobhistory.entry.unknown");
|
||||
return "";
|
||||
|
||||
return switch (status) {
|
||||
case CREATED -> getTranslation("jobstatus.CREATED");
|
||||
case IN_PROGRESS -> getTranslation("jobstatus.IN_PROGRESS");
|
||||
case PICKUP_SCHEDULED -> "Abholung geplant";
|
||||
case PICKED_UP -> "Abgeholt";
|
||||
case IN_TRANSIT -> "Unterwegs";
|
||||
case DELIVERED -> "Zugestellt";
|
||||
case PICKUP_SCHEDULED -> getTranslation("jobhistory.status.pickupscheduled");
|
||||
case PICKED_UP -> getTranslation("jobhistory.status.pickedup");
|
||||
case IN_TRANSIT -> getTranslation("jobhistory.status.intransit");
|
||||
case DELIVERED -> getTranslation("jobhistory.status.delivered");
|
||||
case COMPLETED -> getTranslation("jobstatus.COMPLETED");
|
||||
case CANCELLED -> getTranslation("jobstatus.CANCELLED");
|
||||
};
|
||||
@@ -393,8 +425,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
||||
}
|
||||
|
||||
private void showEnlargedPhoto(String base64Photo) {
|
||||
Dialog photoDialog = new Dialog();
|
||||
photoDialog.setWidth("80vw");
|
||||
Dialog photoDialog = DialogStylingHelper.createStyledDialog(getTranslation("jobhistory.image.alt"), "80vw");
|
||||
photoDialog.setHeight("80vh");
|
||||
photoDialog.setModal(true);
|
||||
photoDialog.setCloseOnOutsideClick(true);
|
||||
@@ -412,7 +443,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
||||
dialogContent.setJustifyContentMode(VerticalLayout.JustifyContentMode.CENTER);
|
||||
dialogContent.setSizeFull();
|
||||
|
||||
photoDialog.add(dialogContent);
|
||||
photoDialog.add(DialogStylingHelper.wrapContent(dialogContent, true));
|
||||
photoDialog.open();
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -549,8 +580,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
||||
}
|
||||
|
||||
private void showEnlargedSignature(String svgContent) {
|
||||
Dialog signatureDialog = new Dialog();
|
||||
signatureDialog.setWidth("60vw");
|
||||
Dialog signatureDialog = DialogStylingHelper.createStyledDialog(getTranslation("tasktype.SIGNATURE"), "60vw");
|
||||
signatureDialog.setHeight("40vh");
|
||||
signatureDialog.setModal(true);
|
||||
signatureDialog.setCloseOnOutsideClick(true);
|
||||
@@ -567,7 +597,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
||||
dialogContent.setPadding(true);
|
||||
|
||||
dialogContent.add(enlargedSignature);
|
||||
signatureDialog.add(dialogContent);
|
||||
signatureDialog.add(DialogStylingHelper.wrapContent(dialogContent, true));
|
||||
signatureDialog.open();
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -60,7 +60,6 @@ import de.assecutor.votianlt.service.LocationService;
|
||||
import de.assecutor.votianlt.service.MessageService;
|
||||
import de.assecutor.votianlt.service.TaskAssignmentService;
|
||||
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
||||
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -135,8 +134,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
if (parameter == null || parameter.isBlank()) {
|
||||
content.removeAll();
|
||||
removeAll();
|
||||
add(new ViewToolbar("Zusammenfassung"));
|
||||
content.add(new Span("Fehler: Keine Job-ID angegeben"));
|
||||
add(new ViewToolbar(getTranslation("jobsummary.title")));
|
||||
content.add(new Span(getTranslation("jobsummary.error.noid")));
|
||||
add(content);
|
||||
return;
|
||||
}
|
||||
@@ -146,8 +145,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
} catch (Exception e) {
|
||||
content.removeAll();
|
||||
removeAll();
|
||||
add(new ViewToolbar("Zusammenfassung"));
|
||||
content.add(new Span("Fehler: Ungültige Job-ID Format: " + parameter));
|
||||
add(new ViewToolbar(getTranslation("jobsummary.title")));
|
||||
content.add(new Span(getTranslation("jobsummary.error.invalidid", parameter)));
|
||||
add(content);
|
||||
return;
|
||||
}
|
||||
@@ -186,8 +185,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
|
||||
Job job = jobRepository.findById(currentJobId).orElse(null);
|
||||
if (job == null) {
|
||||
add(new ViewToolbar("Zusammenfassung"));
|
||||
content.add(new Span("Fehler: Job mit ID " + currentJobId.toHexString() + " nicht gefunden"));
|
||||
add(new ViewToolbar(getTranslation("jobsummary.title")));
|
||||
content.add(new Span(getTranslation("jobsummary.error.notfound", currentJobId.toHexString())));
|
||||
add(content);
|
||||
return;
|
||||
}
|
||||
@@ -221,7 +220,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
});
|
||||
|
||||
// Add toolbar with both buttons in top right (Send Message button on the left)
|
||||
add(new ViewToolbar("Zusammenfassung", sendMessageButton, jobHistoryButton));
|
||||
add(new ViewToolbar(getTranslation("jobsummary.title"), sendMessageButton, jobHistoryButton));
|
||||
|
||||
List<CargoItem> cargo = cargoItemRepository.findByJobId(currentJobId);
|
||||
List<BaseTask> tasks = taskAssignmentService.findTasksForJob(job);
|
||||
@@ -325,33 +324,33 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
new Icon(VaadinIcon.CHECK_CIRCLE));
|
||||
completeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||
completeButton.addClickListener(e -> {
|
||||
ConfirmDialog dialog = new ConfirmDialog();
|
||||
dialog.setHeader(getTranslation("jobsummary.dialog.complete.title"));
|
||||
dialog.setText(getTranslation("jobsummary.dialog.complete.text", job.getJobNumber()));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setCancelText(getTranslation("jobsummary.dialog.complete.cancel"));
|
||||
dialog.setConfirmText(getTranslation("jobsummary.dialog.complete.confirm"));
|
||||
dialog.setConfirmButtonTheme("primary");
|
||||
dialog.addConfirmListener(ev -> {
|
||||
try {
|
||||
JobStatus oldStatus = job.getStatus();
|
||||
job.setStatus(JobStatus.COMPLETED);
|
||||
job.setUpdatedAt(LocalDateTime.now());
|
||||
jobRepository.save(job);
|
||||
jobHistoryService.logStatusChange(job, oldStatus, JobStatus.COMPLETED, "Manuell");
|
||||
Notification
|
||||
.show(getTranslation("jobsummary.notification.completed", job.getJobNumber()), 3000,
|
||||
Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
// Re-render the page
|
||||
getUI().ifPresent(ui -> ui.navigate("job_summary/" + job.getId().toHexString()));
|
||||
} catch (Exception ex) {
|
||||
Notification
|
||||
.show(getTranslation("jobsummary.notification.complete.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
}
|
||||
});
|
||||
Dialog dialog = DialogStylingHelper.createConfirmationDialog(
|
||||
getTranslation("jobsummary.dialog.complete.title"),
|
||||
getTranslation("jobsummary.dialog.complete.text", job.getJobNumber()),
|
||||
"560px",
|
||||
getTranslation("jobsummary.dialog.complete.cancel"),
|
||||
getTranslation("jobsummary.dialog.complete.confirm"),
|
||||
() -> {
|
||||
try {
|
||||
JobStatus oldStatus = job.getStatus();
|
||||
job.setStatus(JobStatus.COMPLETED);
|
||||
job.setUpdatedAt(LocalDateTime.now());
|
||||
jobRepository.save(job);
|
||||
jobHistoryService.logStatusChange(job, oldStatus, JobStatus.COMPLETED, "Manuell");
|
||||
Notification
|
||||
.show(getTranslation("jobsummary.notification.completed", job.getJobNumber()),
|
||||
3000, Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
// Re-render the page
|
||||
getUI().ifPresent(ui -> ui.navigate("job_summary/" + job.getId().toHexString()));
|
||||
} catch (Exception ex) {
|
||||
Notification
|
||||
.show(getTranslation("jobsummary.notification.complete.error",
|
||||
ex.getMessage()), 5000, Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
}
|
||||
},
|
||||
ButtonVariant.LUMO_PRIMARY);
|
||||
dialog.open();
|
||||
});
|
||||
|
||||
@@ -896,8 +895,9 @@ 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 = formatDurationShort(hours, minutes);
|
||||
String plannedRouteLabel = escapeJs(getTranslation("jobsummary.route.planned"));
|
||||
String durationLabel = escapeJs(getTranslation("createinvoice.route.duration"));
|
||||
|
||||
// Build waypoints JS array
|
||||
StringBuilder waypointsJs = new StringBuilder("[");
|
||||
@@ -925,6 +925,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
var hasSavedRouteData = %s;
|
||||
var savedDistance = %s;
|
||||
var savedDurationText = '%s';
|
||||
var plannedRouteLabel = '%s';
|
||||
var durationLabel = '%s';
|
||||
var waypoints = %s;
|
||||
|
||||
var appUserMarker = null;
|
||||
@@ -968,7 +970,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
savedRouteDiv.style.backgroundColor = '#e3f2fd';
|
||||
savedRouteDiv.style.borderRadius = '4px';
|
||||
savedRouteDiv.style.fontWeight = 'bold';
|
||||
savedRouteDiv.textContent = '📍 Geplante Route: ' + savedDistance.toFixed(1) + ' km • Fahrtzeit: ' + savedDurationText;
|
||||
savedRouteDiv.textContent = '📍 ' + plannedRouteLabel + ': ' + savedDistance.toFixed(1) + ' km • ' + durationLabel + ': ' + savedDurationText;
|
||||
infoEl.appendChild(savedRouteDiv);
|
||||
}
|
||||
|
||||
@@ -1075,7 +1077,16 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
||||
.formatted(escapeJs(origin), escapeJs(destination), escapeJs(apiKey), lat, lng,
|
||||
Boolean.toString(hasPosition), escapeJs(appUserId), Boolean.toString(shouldUpdate),
|
||||
Boolean.toString(hasSavedRouteData), savedDistanceStr, escapeJs(savedDurationText),
|
||||
waypointsJs.toString());
|
||||
plannedRouteLabel, durationLabel, waypointsJs.toString());
|
||||
}
|
||||
|
||||
private String formatDurationShort(int hours, int minutes) {
|
||||
String hourUnit = getTranslation("duration.hours.short");
|
||||
String minuteUnit = getTranslation("duration.minutes.short");
|
||||
if (hours > 0) {
|
||||
return String.format("%d %s %d %s", hours, hourUnit, minutes, minuteUnit);
|
||||
}
|
||||
return String.format("%d %s", minutes, minuteUnit);
|
||||
}
|
||||
|
||||
// Hilfsfunktion zum einfachen Escapen von JS-Zeichen in Strings
|
||||
|
||||
@@ -693,7 +693,7 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver, Has
|
||||
|
||||
private HorizontalLayout createMessageInputArea() {
|
||||
messageInput = new TextArea();
|
||||
messageInput.setPlaceholder("Nachricht eingeben...");
|
||||
messageInput.setPlaceholder(getTranslation("messagedetails.placeholder"));
|
||||
messageInput.setWidthFull();
|
||||
messageInput.getStyle().set("min-height", "60px");
|
||||
messageInput.getStyle().set("max-height", "120px");
|
||||
|
||||
@@ -3,7 +3,7 @@ package de.assecutor.votianlt.pages.view;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.combobox.ComboBox;
|
||||
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.datepicker.DatePicker;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.html.Anchor;
|
||||
@@ -21,6 +21,7 @@ import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.Job;
|
||||
import de.assecutor.votianlt.model.JobStatus;
|
||||
import de.assecutor.votianlt.messaging.MessagingPublisher;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
||||
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
||||
import de.assecutor.votianlt.repository.CustomerInvoiceRepository;
|
||||
@@ -214,53 +215,54 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
}
|
||||
|
||||
private void showCompleteJobDialog(Job job) {
|
||||
ConfirmDialog dialog = new ConfirmDialog();
|
||||
dialog.setHeader(getTranslation("jobs.dialog.complete.title"));
|
||||
dialog.setText(getTranslation("jobs.dialog.complete.text", job.getJobNumber()));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setCancelText(getTranslation("button.cancel"));
|
||||
dialog.setConfirmText(getTranslation("jobs.dialog.complete.confirm"));
|
||||
dialog.setConfirmButtonTheme("primary");
|
||||
dialog.addConfirmListener(e -> {
|
||||
try {
|
||||
JobStatus oldStatus = job.getStatus();
|
||||
job.setStatus(JobStatus.COMPLETED);
|
||||
job.setUpdatedAt(LocalDateTime.now());
|
||||
jobRepository.save(job);
|
||||
jobHistoryService.logStatusChange(job, oldStatus, JobStatus.COMPLETED, "Manuell");
|
||||
Notification.show(getTranslation("jobs.notification.completed", job.getJobNumber()), 3000,
|
||||
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);
|
||||
}
|
||||
});
|
||||
Dialog dialog = DialogStylingHelper.createConfirmationDialog(
|
||||
getTranslation("jobs.dialog.complete.title"),
|
||||
getTranslation("jobs.dialog.complete.text", job.getJobNumber()),
|
||||
"560px",
|
||||
getTranslation("button.cancel"),
|
||||
getTranslation("jobs.dialog.complete.confirm"),
|
||||
() -> {
|
||||
try {
|
||||
JobStatus oldStatus = job.getStatus();
|
||||
job.setStatus(JobStatus.COMPLETED);
|
||||
job.setUpdatedAt(LocalDateTime.now());
|
||||
jobRepository.save(job);
|
||||
jobHistoryService.logStatusChange(job, oldStatus, JobStatus.COMPLETED, "Manuell");
|
||||
Notification.show(getTranslation("jobs.notification.completed", job.getJobNumber()), 3000,
|
||||
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);
|
||||
}
|
||||
},
|
||||
ButtonVariant.LUMO_PRIMARY);
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
private void showDeleteJobDialog(Job job) {
|
||||
ConfirmDialog dialog = new ConfirmDialog();
|
||||
dialog.setHeader(getTranslation("jobs.dialog.delete.title"));
|
||||
dialog.setText(getTranslation("jobs.dialog.delete.text", job.getJobNumber()));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setCancelText(getTranslation("button.cancel"));
|
||||
dialog.setConfirmText(getTranslation("button.delete"));
|
||||
dialog.setConfirmButtonTheme("error primary");
|
||||
dialog.addConfirmListener(e -> {
|
||||
try {
|
||||
// Notify client before deleting if online
|
||||
notifyClientJobDeleted(job);
|
||||
Dialog dialog = DialogStylingHelper.createConfirmationDialog(
|
||||
getTranslation("jobs.dialog.delete.title"),
|
||||
getTranslation("jobs.dialog.delete.text", job.getJobNumber()),
|
||||
"560px",
|
||||
getTranslation("button.cancel"),
|
||||
getTranslation("button.delete"),
|
||||
() -> {
|
||||
try {
|
||||
// Notify client before deleting if online
|
||||
notifyClientJobDeleted(job);
|
||||
|
||||
jobRepository.delete(job);
|
||||
Notification.show(getTranslation("jobs.notification.deleted", job.getJobNumber()), 3000,
|
||||
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);
|
||||
}
|
||||
});
|
||||
jobRepository.delete(job);
|
||||
Notification.show(getTranslation("jobs.notification.deleted", job.getJobNumber()), 3000,
|
||||
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);
|
||||
}
|
||||
},
|
||||
ButtonVariant.LUMO_PRIMARY,
|
||||
ButtonVariant.LUMO_ERROR);
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import de.assecutor.votianlt.model.Language;
|
||||
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
|
||||
import de.assecutor.votianlt.security.SessionAuthenticationService;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.service.DemoModeService;
|
||||
@@ -241,17 +242,15 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
||||
String currentUser = securityService.getCurrentUsername();
|
||||
ComboBox<String> userCombo = new ComboBox<>();
|
||||
userCombo.setPlaceholder(currentUser);
|
||||
userCombo.setItems("Profil anzeigen", "Einstellungen", "Abmelden");
|
||||
userCombo.setItems("Profil anzeigen", "Abmelden");
|
||||
userCombo.addValueChangeListener(event -> {
|
||||
String value = event.getValue();
|
||||
if (value != null) {
|
||||
switch (value) {
|
||||
case "Profil anzeigen":
|
||||
break;
|
||||
case "Einstellungen":
|
||||
break;
|
||||
case "Abmelden":
|
||||
securityService.logout();
|
||||
openLogoutConfirmDialog();
|
||||
break;
|
||||
}
|
||||
userCombo.clear(); // Reset selection
|
||||
@@ -450,6 +449,19 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
||||
return footer;
|
||||
}
|
||||
|
||||
private void openLogoutConfirmDialog() {
|
||||
var dialog = DialogStylingHelper.createConfirmationDialog(
|
||||
getTranslation("logout.confirm.title"),
|
||||
getTranslation("logout.confirm.message"),
|
||||
"460px",
|
||||
getTranslation("button.cancel"),
|
||||
getTranslation("nav.logout"),
|
||||
securityService::logout,
|
||||
ButtonVariant.LUMO_PRIMARY,
|
||||
ButtonVariant.LUMO_ERROR);
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
private void register() {
|
||||
UI.getCurrent().navigate("register");
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface MessageRepository extends MongoRepository<Message, ObjectId> {
|
||||
@@ -66,6 +67,9 @@ public interface MessageRepository extends MongoRepository<Message, ObjectId> {
|
||||
List<Message> findByReceiverAndDeliveryStatusOrderByCreatedAtAsc(String receiver,
|
||||
MessageDeliveryStatus deliveryStatus);
|
||||
|
||||
Optional<Message> findFirstByReceiverAndOriginAndClientMessageId(String receiver, MessageOrigin origin,
|
||||
String clientMessageId);
|
||||
|
||||
/**
|
||||
* Find all undelivered messages (NOTSEND status) for a receiver
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -100,6 +101,16 @@ public class MessageService {
|
||||
* Inbound message payload where receiver = AppUser ID (clientId)
|
||||
*/
|
||||
public Message receiveMessageFromClient(ChatMessageInboundPayload payload) {
|
||||
String clientMessageId = payload.messageId();
|
||||
if (clientMessageId != null && !clientMessageId.isBlank()) {
|
||||
Optional<Message> existingMessage = messageRepository.findFirstByReceiverAndOriginAndClientMessageId(
|
||||
payload.receiver(), MessageOrigin.CLIENT, clientMessageId);
|
||||
if (existingMessage.isPresent()) {
|
||||
sendClientMessageAck(payload.receiver(), clientMessageId, existingMessage.get());
|
||||
return existingMessage.get();
|
||||
}
|
||||
}
|
||||
|
||||
Message message;
|
||||
MessageContentType contentType = payload.contentType();
|
||||
if (payload.hasJobContext()) {
|
||||
@@ -109,11 +120,36 @@ public class MessageService {
|
||||
} else {
|
||||
message = new Message(payload.content(), payload.receiver(), MessageOrigin.CLIENT, contentType);
|
||||
}
|
||||
message.setClientMessageId(clientMessageId);
|
||||
message = saveMessage(message);
|
||||
sendClientMessageAck(payload.receiver(), clientMessageId, message);
|
||||
eventPublisher.publishEvent(new MessageReceivedEvent(this, message));
|
||||
return message;
|
||||
}
|
||||
|
||||
private void sendClientMessageAck(String receiver, String clientMessageId, Message message) {
|
||||
if (receiver == null || receiver.isBlank() || clientMessageId == null || clientMessageId.isBlank()
|
||||
|| message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
LinkedHashMap<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("clientMessageId", clientMessageId);
|
||||
payload.put("messageId", message.getIdAsString());
|
||||
payload.put("createdAt", message.getCreatedAt());
|
||||
|
||||
byte[] data = objectMapper.writeValueAsString(payload).getBytes(StandardCharsets.UTF_8);
|
||||
webSocketService.sendToClient(receiver, "message_ack", data).exceptionally(ex -> {
|
||||
log.debug("[Messaging] Failed to send ACK for client message {} to {}: {}", clientMessageId, receiver,
|
||||
ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("[Messaging] Error sending ACK for client message {}: {}", clientMessageId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish message to topic for the receiver. Only sends if client is connected,
|
||||
* otherwise keeps NOTSEND status.
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=Benutzer
|
||||
nav.showprofile=Profil anzeigen
|
||||
nav.settings=Einstellungen
|
||||
nav.logout=Abmelden
|
||||
logout.confirm.title=Abmelden bestätigen
|
||||
logout.confirm.message=Möchten Sie sich wirklich abmelden?
|
||||
|
||||
# Profile View
|
||||
profile.title=Profil bearbeiten
|
||||
@@ -200,6 +202,10 @@ common.error=Fehler
|
||||
common.success=Erfolg
|
||||
common.required=Pflichtfeld
|
||||
|
||||
# Duration
|
||||
duration.hours.short=Std.
|
||||
duration.minutes.short=Min.
|
||||
|
||||
# Validation
|
||||
validation.required=Feld ist erforderlich
|
||||
validation.email=Ungültige E-Mail-Adresse
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=Maximal {0} Fotos erlaubt
|
||||
jobsummary.task.photo.taken=Aufgenommene Fotos ({0})
|
||||
jobsummary.task.button.text=Button-Text
|
||||
jobsummary.button.schliessen=Schließen
|
||||
jobsummary.route.planned=Geplante Route
|
||||
|
||||
# Jobs
|
||||
jobs.title=Aufträge
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Berechne...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=In Bearbeitung
|
||||
jobstatus.COMPLETED=Abgeschlossen
|
||||
jobstatus.CREATED=Erstellt
|
||||
jobstatus.CANCELLED=Storniert
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Bestätigung
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-Mail: ahoi@assecutor.de
|
||||
imprint.phone=Telefon
|
||||
imprint.email=E-Mail
|
||||
imprint.country=Deutschland
|
||||
imprint.management=Geschäftsführung
|
||||
imprint.registeredoffice=Firmensitz
|
||||
imprint.commercialregister=Handelsregister
|
||||
imprint.vatid=Umsatzsteuer-ID
|
||||
imprint.imagecredits=Quellenangaben für die verwendeten Bilder und Grafiken
|
||||
imprint.backgroundimage=Hintergrundbild Startseite
|
||||
start.cta.text=Registrieren Sie sich noch heute und nutzen den kostenfreien Probemonat, um das System auf Herz und Nieren zu testen.
|
||||
start.slogan=Betreiben Sie Ihr Geschäft smart … mit votianLT!
|
||||
start.version=Version
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Erstellt am: {0}
|
||||
jobhistory.info.status=Status: {0}
|
||||
jobhistory.count={0} Einträge in der Historie
|
||||
jobhistory.changedby=Geändert von: {0}
|
||||
jobhistory.entry.create.reason=Job erstellt
|
||||
jobhistory.entry.create.description=Neuer Job wurde erstellt: {0}
|
||||
|
||||
# Version
|
||||
version.label=Version
|
||||
|
||||
@@ -16,6 +16,8 @@ nav.users=Kasutajad
|
||||
nav.showprofile=Kuva profiil
|
||||
nav.settings=Seaded
|
||||
nav.logout=Logi v\u00e4lja
|
||||
logout.confirm.title=Kinnita v\u00e4ljalogimine
|
||||
logout.confirm.message=Kas soovite t\u00f5esti v\u00e4lja logida?
|
||||
profile.title=Profiili muutmine
|
||||
profile.language=Keel
|
||||
profile.company=Ettev\u00f5te
|
||||
@@ -181,6 +183,11 @@ common.loading=Laadimine...
|
||||
common.error=Viga
|
||||
common.success=Edukas
|
||||
common.required=Kohustuslik v\u00e4li
|
||||
|
||||
# Duration
|
||||
duration.hours.short=t
|
||||
duration.minutes.short=min
|
||||
|
||||
validation.required=V\u00e4li on kohustuslik
|
||||
validation.email=Vigane e-posti aadress
|
||||
validation.error=Valideerimise viga
|
||||
@@ -549,6 +556,7 @@ jobsummary.task.photo.maxonly=Maksimaalselt {0} fotot lubatud
|
||||
jobsummary.task.photo.taken=Tehtud fotod ({0})
|
||||
jobsummary.task.button.text=Nupu tekst
|
||||
jobsummary.button.schliessen=Sulge
|
||||
jobsummary.route.planned=Planeeritud marsruut
|
||||
jobs.title=Tellimused
|
||||
jobs.filter.search=Otsi
|
||||
jobs.filter.search.placeholder=Otsi tellimuse numbri j\u00e4rgi...
|
||||
@@ -662,6 +670,8 @@ statistics.data.fetched=Andmed on k\u00e4tte saadud
|
||||
statistics.loading=Arvutamine...
|
||||
jobstatus.IN_PROGRESS=T\u00f6\u00f6s
|
||||
jobstatus.COMPLETED=L\u00f5petatud
|
||||
jobstatus.CREATED=Loodud
|
||||
jobstatus.CANCELLED=T\u00fchistatud
|
||||
tasktype.CONFIRMATION=Kinnitus
|
||||
tasktype.SIGNATURE=Allkiri
|
||||
tasktype.TODOLIST=\u00dclesannete nimekiri
|
||||
@@ -754,6 +764,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-post: ahoi@assecutor.de
|
||||
imprint.phone=Telefon
|
||||
imprint.email=E-post
|
||||
imprint.country=Saksamaa
|
||||
imprint.management=Juhtkond
|
||||
imprint.registeredoffice=Registrijärgne asukoht
|
||||
imprint.commercialregister=Äriregister
|
||||
imprint.vatid=KMKR number
|
||||
imprint.imagecredits=Kasutatud piltide ja graafika allikad
|
||||
imprint.backgroundimage=Avalehe taustapilt
|
||||
start.cta.text=Registreeruge juba t\u00e4na ja kasutage tasuta proovikuud, et s\u00fcsteemi p\u00f5hjalikult testida.
|
||||
start.slogan=Ajage oma \u00e4ri targalt \u2026 votianLT-ga!
|
||||
start.version=Versioon
|
||||
@@ -824,6 +843,8 @@ jobhistory.info.createdat=Loodud: {0}
|
||||
jobhistory.info.status=Olek: {0}
|
||||
jobhistory.count={0} kirjet ajaloos
|
||||
jobhistory.changedby=Muutnud: {0}
|
||||
jobhistory.entry.create.reason=T\u00f6\u00f6 loodud
|
||||
jobhistory.entry.create.description=Uus t\u00f6\u00f6 loodi: {0}
|
||||
version.label=Versioon
|
||||
management.placeholder=Haldus
|
||||
management.customers=Kliendid
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=Users
|
||||
nav.showprofile=Show Profile
|
||||
nav.settings=Settings
|
||||
nav.logout=Log Out
|
||||
logout.confirm.title=Confirm logout
|
||||
logout.confirm.message=Do you really want to log out?
|
||||
|
||||
# Profile View
|
||||
profile.title=Edit Profile
|
||||
@@ -200,6 +202,10 @@ common.error=Error
|
||||
common.success=Success
|
||||
common.required=Required
|
||||
|
||||
# Duration
|
||||
duration.hours.short=hr
|
||||
duration.minutes.short=min
|
||||
|
||||
# Validation
|
||||
validation.required=Field is required
|
||||
validation.email=Invalid email address
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=Maximum {0} photos allowed
|
||||
jobsummary.task.photo.taken=Photos taken ({0})
|
||||
jobsummary.task.button.text=Button Text
|
||||
jobsummary.button.schliessen=Close
|
||||
jobsummary.route.planned=Planned Route
|
||||
|
||||
# Jobs
|
||||
jobs.title=Jobs
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Calculating...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=In Progress
|
||||
jobstatus.COMPLETED=Completed
|
||||
jobstatus.CREATED=Created
|
||||
jobstatus.CANCELLED=Cancelled
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Confirmation
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Phone: +49 40 18 123 771 0
|
||||
start.imprint.email=Email: ahoi@assecutor.de
|
||||
imprint.phone=Phone
|
||||
imprint.email=Email
|
||||
imprint.country=Germany
|
||||
imprint.management=Management
|
||||
imprint.registeredoffice=Registered Office
|
||||
imprint.commercialregister=Commercial Register
|
||||
imprint.vatid=VAT ID
|
||||
imprint.imagecredits=Image Credits for Pictures and Graphics Used
|
||||
imprint.backgroundimage=Homepage Background Image
|
||||
start.cta.text=Register today and use the free trial month to put the system through its paces.
|
||||
start.slogan=Run your business smart ... with votianLT!
|
||||
start.version=Version
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Created on: {0}
|
||||
jobhistory.info.status=Status: {0}
|
||||
jobhistory.count={0} entries in history
|
||||
jobhistory.changedby=Changed by: {0}
|
||||
jobhistory.entry.create.reason=Job Created
|
||||
jobhistory.entry.create.description=New job was created: {0}
|
||||
|
||||
# Version
|
||||
version.label=Version
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=Usuarios
|
||||
nav.showprofile=Ver perfil
|
||||
nav.settings=Configuraci\u00f3n
|
||||
nav.logout=Cerrar sesi\u00f3n
|
||||
logout.confirm.title=Confirmar cierre de sesi\u00f3n
|
||||
logout.confirm.message=\u00bfRealmente desea cerrar sesi\u00f3n?
|
||||
|
||||
# Profile View
|
||||
profile.title=Editar perfil
|
||||
@@ -200,6 +202,10 @@ common.error=Error
|
||||
common.success=\u00c9xito
|
||||
common.required=Campo obligatorio
|
||||
|
||||
# Duration
|
||||
duration.hours.short=h
|
||||
duration.minutes.short=min
|
||||
|
||||
# Validation
|
||||
validation.required=El campo es obligatorio
|
||||
validation.email=Direcci\u00f3n de correo electr\u00f3nico no v\u00e1lida
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=Se permiten como m\u00e1ximo {0} fotos
|
||||
jobsummary.task.photo.taken=Fotos tomadas ({0})
|
||||
jobsummary.task.button.text=Texto del bot\u00f3n
|
||||
jobsummary.button.schliessen=Cerrar
|
||||
jobsummary.route.planned=Ruta planificada
|
||||
|
||||
# Jobs
|
||||
jobs.title=Pedidos
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Calculando...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=En proceso
|
||||
jobstatus.COMPLETED=Completado
|
||||
jobstatus.CREATED=Creado
|
||||
jobstatus.CANCELLED=Cancelado
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Confirmaci\u00f3n
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Tel\u00e9fono: +49 40 18 123 771 0
|
||||
start.imprint.email=Correo electr\u00f3nico: ahoi@assecutor.de
|
||||
imprint.phone=Tel\u00e9fono
|
||||
imprint.email=Correo electr\u00f3nico
|
||||
imprint.country=Alemania
|
||||
imprint.management=Direcci\u00f3n
|
||||
imprint.registeredoffice=Domicilio social
|
||||
imprint.commercialregister=Registro mercantil
|
||||
imprint.vatid=ID de IVA
|
||||
imprint.imagecredits=Fuentes de las im\u00e1genes y gr\u00e1ficos utilizados
|
||||
imprint.backgroundimage=Imagen de fondo de la p\u00e1gina de inicio
|
||||
start.cta.text=Reg\u00edstrese hoy mismo y utilice el mes de prueba gratuito para probar el sistema a fondo.
|
||||
start.slogan=\u00a1Gestione su negocio de forma inteligente... con votianLT!
|
||||
start.version=Versi\u00f3n
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Creado el: {0}
|
||||
jobhistory.info.status=Estado: {0}
|
||||
jobhistory.count={0} entradas en el historial
|
||||
jobhistory.changedby=Modificado por: {0}
|
||||
jobhistory.entry.create.reason=Pedido creado
|
||||
jobhistory.entry.create.description=Se ha creado un nuevo pedido: {0}
|
||||
|
||||
# Version
|
||||
version.label=Versi\u00f3n
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=Utilisateurs
|
||||
nav.showprofile=Afficher le profil
|
||||
nav.settings=Param\u00e8tres
|
||||
nav.logout=D\u00e9connexion
|
||||
logout.confirm.title=Confirmer la d\u00e9connexion
|
||||
logout.confirm.message=Voulez-vous vraiment vous d\u00e9connecter ?
|
||||
|
||||
# Profile View
|
||||
profile.title=Modifier le profil
|
||||
@@ -200,6 +202,10 @@ common.error=Erreur
|
||||
common.success=Succ\u00e8s
|
||||
common.required=Champ obligatoire
|
||||
|
||||
# Duration
|
||||
duration.hours.short=h
|
||||
duration.minutes.short=min
|
||||
|
||||
# Validation
|
||||
validation.required=Le champ est obligatoire
|
||||
validation.email=Adresse e-mail invalide
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=Maximum {0} photos autoris\u00e9es
|
||||
jobsummary.task.photo.taken=Photos prises ({0})
|
||||
jobsummary.task.button.text=Texte du bouton
|
||||
jobsummary.button.schliessen=Fermer
|
||||
jobsummary.route.planned=Itin\u00e9raire pr\u00e9vu
|
||||
|
||||
# Jobs
|
||||
jobs.title=Missions
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Calcul en cours...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=En cours
|
||||
jobstatus.COMPLETED=Termin\u00e9e
|
||||
jobstatus.CREATED=Cr\u00e9\u00e9e
|
||||
jobstatus.CANCELLED=Annul\u00e9e
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Confirmation
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-Mail: ahoi@assecutor.de
|
||||
imprint.phone=T\u00e9l\u00e9phone
|
||||
imprint.email=E-mail
|
||||
imprint.country=Allemagne
|
||||
imprint.management=Direction
|
||||
imprint.registeredoffice=Si\u00e8ge social
|
||||
imprint.commercialregister=Registre du commerce
|
||||
imprint.vatid=ID TVA
|
||||
imprint.imagecredits=Cr\u00e9dits des images et graphiques utilis\u00e9s
|
||||
imprint.backgroundimage=Image d'arri\u00e8re-plan de la page d'accueil
|
||||
start.cta.text=Inscrivez-vous d\u00e8s aujourd'hui et profitez du mois d'essai gratuit pour tester le syst\u00e8me en profondeur.
|
||||
start.slogan=G\u00e9rez votre activit\u00e9 intelligemment ... avec votianLT !
|
||||
start.version=Version
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Cr\u00e9\u00e9 le : {0}
|
||||
jobhistory.info.status=Statut : {0}
|
||||
jobhistory.count={0} entr\u00e9es dans l'historique
|
||||
jobhistory.changedby=Modifi\u00e9 par : {0}
|
||||
jobhistory.entry.create.reason=Mission cr\u00e9\u00e9e
|
||||
jobhistory.entry.create.description=Nouvelle mission cr\u00e9\u00e9e : {0}
|
||||
|
||||
# Version
|
||||
version.label=Version
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=Naudotojai
|
||||
nav.showprofile=Rodyti profilį
|
||||
nav.settings=Nustatymai
|
||||
nav.logout=Atsijungti
|
||||
logout.confirm.title=Patvirtinti atsijungimą
|
||||
logout.confirm.message=Ar tikrai norite atsijungti?
|
||||
|
||||
# Profile View
|
||||
profile.title=Redaguoti profilį
|
||||
@@ -200,6 +202,10 @@ common.error=Klaida
|
||||
common.success=Sėkmė
|
||||
common.required=Privalomas laukas
|
||||
|
||||
# Duration
|
||||
duration.hours.short=val.
|
||||
duration.minutes.short=min.
|
||||
|
||||
# Validation
|
||||
validation.required=Laukas yra privalomas
|
||||
validation.email=Neteisingas el. pašto adresas
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=Daugiausiai {0} nuotraukų
|
||||
jobsummary.task.photo.taken=Padarytos nuotraukos ({0})
|
||||
jobsummary.task.button.text=Mygtuko tekstas
|
||||
jobsummary.button.schliessen=Uždaryti
|
||||
jobsummary.route.planned=Planuotas maršrutas
|
||||
|
||||
# Jobs
|
||||
jobs.title=Užsakymai
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Skaičiuojama...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=Vykdomas
|
||||
jobstatus.COMPLETED=Užbaigtas
|
||||
jobstatus.CREATED=Sukurtas
|
||||
jobstatus.CANCELLED=Atšauktas
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Patvirtinimas
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-Mail: ahoi@assecutor.de
|
||||
imprint.phone=Telefonas
|
||||
imprint.email=El. paštas
|
||||
imprint.country=Vokietija
|
||||
imprint.management=Vadovybė
|
||||
imprint.registeredoffice=Buveinė
|
||||
imprint.commercialregister=Komercinis registras
|
||||
imprint.vatid=PVM kodas
|
||||
imprint.imagecredits=Naudotų vaizdų ir grafikos šaltiniai
|
||||
imprint.backgroundimage=Pradžios puslapio fono paveikslėlis
|
||||
start.cta.text=Užsiregistruokite šiandien ir pasinaudokite nemokamu bandomuoju mėnesiu, kad galėtumėte išbandyti sistemą.
|
||||
start.slogan=Valdykite savo verslą protingai … su votianLT!
|
||||
start.version=Versija
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Sukurta: {0}
|
||||
jobhistory.info.status=Būsena: {0}
|
||||
jobhistory.count={0} įrašų istorijoje
|
||||
jobhistory.changedby=Pakeitė: {0}
|
||||
jobhistory.entry.create.reason=Užsakymas sukurtas
|
||||
jobhistory.entry.create.description=Sukurtas naujas užsakymas: {0}
|
||||
|
||||
# Version
|
||||
version.label=Versija
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=Lietotāji
|
||||
nav.showprofile=Rādīt profilu
|
||||
nav.settings=Iestatījumi
|
||||
nav.logout=Izrakstīties
|
||||
logout.confirm.title=Apstiprināt izrakstīšanos
|
||||
logout.confirm.message=Vai tiešām vēlaties izrakstīties?
|
||||
|
||||
# Profile View
|
||||
profile.title=Rediģēt profilu
|
||||
@@ -200,6 +202,10 @@ common.error=Kļūda
|
||||
common.success=Veiksmīgi
|
||||
common.required=Obligāts lauks
|
||||
|
||||
# Duration
|
||||
duration.hours.short=st.
|
||||
duration.minutes.short=min.
|
||||
|
||||
# Validation
|
||||
validation.required=Lauks ir obligāts
|
||||
validation.email=Nederīga e-pasta adrese
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=Atļautas ne vairāk kā {0} fotogrāfijas
|
||||
jobsummary.task.photo.taken=Uzņemtās fotogrāfijas ({0})
|
||||
jobsummary.task.button.text=Pogas teksts
|
||||
jobsummary.button.schliessen=Aizvērt
|
||||
jobsummary.route.planned=Plānotais maršruts
|
||||
|
||||
# Jobs
|
||||
jobs.title=Uzdevumi
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Aprēķina...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=Izpildē
|
||||
jobstatus.COMPLETED=Pabeigts
|
||||
jobstatus.CREATED=Izveidots
|
||||
jobstatus.CANCELLED=Atcelts
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Apstiprinājums
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-Mail: ahoi@assecutor.de
|
||||
imprint.phone=Tālrunis
|
||||
imprint.email=E-pasts
|
||||
imprint.country=Vācija
|
||||
imprint.management=Vadība
|
||||
imprint.registeredoffice=Juridiskā adrese
|
||||
imprint.commercialregister=Komercreģistrs
|
||||
imprint.vatid=PVN ID
|
||||
imprint.imagecredits=Izmantoto attēlu un grafiku avoti
|
||||
imprint.backgroundimage=Sākumlapas fona attēls
|
||||
start.cta.text=Reģistrējieties jau šodien un izmantojiet bezmaksas izmēģinājuma mēnesi, lai rūpīgi pārbaudītu sistēmu.
|
||||
start.slogan=Vadiet savu biznesu gudri ... ar votianLT!
|
||||
start.version=Versija
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Izveidots: {0}
|
||||
jobhistory.info.status=Statuss: {0}
|
||||
jobhistory.count={0} ieraksti vēsturē
|
||||
jobhistory.changedby=Mainīja: {0}
|
||||
jobhistory.entry.create.reason=Uzdevums izveidots
|
||||
jobhistory.entry.create.description=Izveidots jauns uzdevums: {0}
|
||||
|
||||
# Version
|
||||
version.label=Versija
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=U\u017cytkownicy
|
||||
nav.showprofile=Poka\u017c profil
|
||||
nav.settings=Ustawienia
|
||||
nav.logout=Wyloguj si\u0119
|
||||
logout.confirm.title=Potwierd\u017a wylogowanie
|
||||
logout.confirm.message=Czy na pewno chcesz si\u0119 wylogowa\u0107?
|
||||
|
||||
# Profile View
|
||||
profile.title=Edytuj profil
|
||||
@@ -200,6 +202,10 @@ common.error=B\u0142\u0105d
|
||||
common.success=Sukces
|
||||
common.required=Pole wymagane
|
||||
|
||||
# Duration
|
||||
duration.hours.short=godz.
|
||||
duration.minutes.short=min.
|
||||
|
||||
# Validation
|
||||
validation.required=Pole jest wymagane
|
||||
validation.email=Nieprawid\u0142owy adres e-mail
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=Dozwolone maksymalnie {0} zdj\u0119\u0107
|
||||
jobsummary.task.photo.taken=Wykonane zdj\u0119cia ({0})
|
||||
jobsummary.task.button.text=Tekst przycisku
|
||||
jobsummary.button.schliessen=Zamknij
|
||||
jobsummary.route.planned=Planowana trasa
|
||||
|
||||
# Jobs
|
||||
jobs.title=Zlecenia
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Obliczanie...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=W trakcie realizacji
|
||||
jobstatus.COMPLETED=Zako\u0144czone
|
||||
jobstatus.CREATED=Utworzone
|
||||
jobstatus.CANCELLED=Anulowane
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Potwierdzenie
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-Mail: ahoi@assecutor.de
|
||||
imprint.phone=Telefon
|
||||
imprint.email=E-mail
|
||||
imprint.country=Niemcy
|
||||
imprint.management=Zarz\u0105d
|
||||
imprint.registeredoffice=Siedziba firmy
|
||||
imprint.commercialregister=Rejestr handlowy
|
||||
imprint.vatid=Numer VAT
|
||||
imprint.imagecredits=\u0179r\u00f3d\u0142a wykorzystanych zdj\u0119\u0107 i grafik
|
||||
imprint.backgroundimage=Obraz t\u0142a strony startowej
|
||||
start.cta.text=Zarejestruj si\u0119 ju\u017c dzi\u015b i skorzystaj z bezp\u0142atnego miesi\u0105ca pr\u00f3bnego, aby dok\u0142adnie przetestowa\u0107 system.
|
||||
start.slogan=Prowad\u017a sw\u00f3j biznes m\u0105drze \u2026 z votianLT!
|
||||
start.version=Wersja
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Utworzono dnia: {0}
|
||||
jobhistory.info.status=Status: {0}
|
||||
jobhistory.count={0} wpis\u00f3w w historii
|
||||
jobhistory.changedby=Zmienione przez: {0}
|
||||
jobhistory.entry.create.reason=Zlecenie utworzone
|
||||
jobhistory.entry.create.description=Utworzono nowe zlecenie: {0}
|
||||
|
||||
# Version
|
||||
version.label=Wersja
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=Пользователи
|
||||
nav.showprofile=Показать профиль
|
||||
nav.settings=Настройки
|
||||
nav.logout=Выйти
|
||||
logout.confirm.title=Подтвердите выход
|
||||
logout.confirm.message=Вы действительно хотите выйти?
|
||||
|
||||
# Profile View
|
||||
profile.title=Редактирование профиля
|
||||
@@ -200,6 +202,10 @@ common.error=Ошибка
|
||||
common.success=Успех
|
||||
common.required=Обязательное поле
|
||||
|
||||
# Duration
|
||||
duration.hours.short=ч
|
||||
duration.minutes.short=мин.
|
||||
|
||||
# Validation
|
||||
validation.required=Поле обязательно для заполнения
|
||||
validation.email=Недействительный адрес электронной почты
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=Максимум {0} фото разрешено
|
||||
jobsummary.task.photo.taken=Сделанные фотографии ({0})
|
||||
jobsummary.task.button.text=Текст кнопки
|
||||
jobsummary.button.schliessen=Закрыть
|
||||
jobsummary.route.planned=Запланированный маршрут
|
||||
|
||||
# Jobs
|
||||
jobs.title=Заказы
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Вычисление...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=В обработке
|
||||
jobstatus.COMPLETED=Завершено
|
||||
jobstatus.CREATED=Создано
|
||||
jobstatus.CANCELLED=Отменено
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Подтверждение
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-Mail: ahoi@assecutor.de
|
||||
imprint.phone=Телефон
|
||||
imprint.email=Эл. почта
|
||||
imprint.country=Германия
|
||||
imprint.management=Руководство
|
||||
imprint.registeredoffice=Юридический адрес
|
||||
imprint.commercialregister=Торговый реестр
|
||||
imprint.vatid=Номер НДС
|
||||
imprint.imagecredits=Источники использованных изображений и графики
|
||||
imprint.backgroundimage=Фоновое изображение главной страницы
|
||||
start.cta.text=Зарегистрируйтесь сегодня и воспользуйтесь бесплатным пробным месяцем, чтобы тщательно протестировать систему.
|
||||
start.slogan=Ведите свой бизнес умно ... с votianLT!
|
||||
start.version=Версия
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Создано: {0}
|
||||
jobhistory.info.status=Статус: {0}
|
||||
jobhistory.count={0} записей в истории
|
||||
jobhistory.changedby=Изменено: {0}
|
||||
jobhistory.entry.create.reason=Заказ создан
|
||||
jobhistory.entry.create.description=Создан новый заказ: {0}
|
||||
|
||||
# Version
|
||||
version.label=Версия
|
||||
|
||||
@@ -18,6 +18,8 @@ nav.users=Kullan\u0131c\u0131lar
|
||||
nav.showprofile=Profili G\u00f6ster
|
||||
nav.settings=Ayarlar
|
||||
nav.logout=\u00c7\u0131k\u0131\u015f
|
||||
logout.confirm.title=\u00c7\u0131k\u0131\u015f\u0131 onayla
|
||||
logout.confirm.message=Ger\u00e7ekten \u00e7\u0131k\u0131\u015f yapmak istiyor musunuz?
|
||||
|
||||
# Profile View
|
||||
profile.title=Profili D\u00fczenle
|
||||
@@ -200,6 +202,10 @@ common.error=Hata
|
||||
common.success=Ba\u015far\u0131l\u0131
|
||||
common.required=Zorunlu Alan
|
||||
|
||||
# Duration
|
||||
duration.hours.short=sa.
|
||||
duration.minutes.short=dk.
|
||||
|
||||
# Validation
|
||||
validation.required=Alan gereklidir
|
||||
validation.email=Ge\u00e7ersiz e-posta adresi
|
||||
@@ -603,6 +609,7 @@ jobsummary.task.photo.maxonly=En fazla {0} foto\u011fraf izin verilir
|
||||
jobsummary.task.photo.taken=\u00c7ekilen Foto\u011fraflar ({0})
|
||||
jobsummary.task.button.text=Buton Metni
|
||||
jobsummary.button.schliessen=Kapat
|
||||
jobsummary.route.planned=Planlanan Rota
|
||||
|
||||
# Jobs
|
||||
jobs.title=\u0130\u015fler
|
||||
@@ -730,6 +737,8 @@ statistics.loading=Hesaplan\u0131yor...
|
||||
# Job Status
|
||||
jobstatus.IN_PROGRESS=Devam Ediyor
|
||||
jobstatus.COMPLETED=Tamamland\u0131
|
||||
jobstatus.CREATED=Olu\u015fturuldu
|
||||
jobstatus.CANCELLED=\u0130ptal Edildi
|
||||
|
||||
# Task Types
|
||||
tasktype.CONFIRMATION=Onay
|
||||
@@ -832,6 +841,15 @@ start.imprint.company=Assecutor Data Service GmbH
|
||||
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
|
||||
start.imprint.phone=Telefon: +49 40 18 123 771 0
|
||||
start.imprint.email=E-Mail: ahoi@assecutor.de
|
||||
imprint.phone=Telefon
|
||||
imprint.email=E-posta
|
||||
imprint.country=Almanya
|
||||
imprint.management=Y\u00f6netim
|
||||
imprint.registeredoffice=\u015eirket merkezi
|
||||
imprint.commercialregister=Ticaret sicili
|
||||
imprint.vatid=KDV kimli\u011fi
|
||||
imprint.imagecredits=Kullan\u0131lan g\u00f6rsel ve grafik kaynaklar\u0131
|
||||
imprint.backgroundimage=Ana sayfa arka plan g\u00f6rseli
|
||||
start.cta.text=Bug\u00fcn kay\u0131t olun ve sistemi ba\u015ftan sona test etmek i\u00e7in \u00fccretsiz deneme ay\u0131n\u0131 kullan\u0131n.
|
||||
start.slogan=\u0130\u015finizi ak\u0131ll\u0131ca y\u00f6netin ... votianLT ile!
|
||||
start.version=S\u00fcr\u00fcm
|
||||
@@ -914,6 +932,8 @@ jobhistory.info.createdat=Olu\u015fturulma Tarihi: {0}
|
||||
jobhistory.info.status=Durum: {0}
|
||||
jobhistory.count=Ge\u00e7mi\u015fte {0} kay\u0131t
|
||||
jobhistory.changedby=De\u011fi\u015ftiren: {0}
|
||||
jobhistory.entry.create.reason=\u0130\u015f olu\u015fturuldu
|
||||
jobhistory.entry.create.description=Yeni i\u015f olu\u015fturuldu: {0}
|
||||
|
||||
# Version
|
||||
version.label=S\u00fcr\u00fcm
|
||||
|
||||
Reference in New Issue
Block a user