Erweiterungen

This commit is contained in:
2026-02-20 16:23:16 +01:00
parent a4c3c67f8a
commit 8f57bb5ca4
7 changed files with 90 additions and 19 deletions

View File

@@ -0,0 +1,4 @@
/* Breite des linken Menüs (Drawer) */
vaadin-app-layout {
--vaadin-app-layout-drawer-width: 286px;
}

View File

@@ -114,10 +114,14 @@ public class MessageController {
*/
public void handleGetAssignedJobs(String appUserId) {
if (appUserId == null || appUserId.isBlank()) {
log.warn("[JOBS] appUserId is null or blank, cannot retrieve jobs");
return;
}
log.info("[JOBS] Retrieving assigned jobs for appUserId: {}", appUserId);
List<Job> assignedJobs = jobRepository.findByAppUser(appUserId);
log.info("[JOBS] Found {} jobs for appUserId: {}", assignedJobs.size(), appUserId);
List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> {
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
@@ -125,7 +129,9 @@ public class MessageController {
return new JobWithRelatedDataDTO(job, cargoItems, tasks);
}).toList();
log.info("[JOBS] Publishing {} jobs to client {} on topic /client/jobs", jobsWithRelatedData.size(), appUserId);
messagingPublisher.publishAsJson(appUserId, "jobs", jobsWithRelatedData);
log.info("[JOBS] Jobs published successfully for client {}", appUserId);
}
/**

View File

@@ -109,26 +109,35 @@ public class MessagingConfig {
if (response.isSuccess()) {
String appUserId = response.getAppUserId();
log.info("[Messaging] Login successful for appUserId: {}", appUserId);
webSocketService.registerAuthenticatedSession(wsSessionId, appUserId);
// Send success response to the now-authenticated session
// locationTrackingEnabled: true = client should send position updates
Map<String, Object> authResponse = Map.of("success", true, "message", response.getMessage(),
"locationTrackingEnabled", true);
// appUserId: wird an den Client gesendet für Referenz
Map<String, Object> authResponse = Map.of(
"success", true,
"message", response.getMessage(),
"locationTrackingEnabled", true,
"appUserId", appUserId
);
byte[] responseBytes = objectMapper.writeValueAsBytes(authResponse);
log.info("[Messaging] Sending auth response to appUserId: {}", appUserId);
webSocketService.sendToClient(appUserId, "auth", responseBytes);
// Register client - pending messages and jobs will be sent after
// client confirms buffer_flushed
clientConnectionService.registerClient(appUserId);
} else {
log.warn("[Messaging] Login failed: {}", response.getMessage());
// Send failure response to the pending session
Map<String, Object> authResponse = Map.of("success", false, "message", response.getMessage());
byte[] responseBytes = objectMapper.writeValueAsBytes(authResponse);
webSocketService.sendToSessionById(wsSessionId, "/client/auth", responseBytes);
}
} catch (Exception e) {
log.error("[Messaging] Login handling error: {}", e.getMessage());
log.error("[Messaging] Login handling error: {}", e.getMessage(), e);
}
}

View File

@@ -28,16 +28,29 @@ class MessagingPublisherImpl implements MessagingPublisher {
@Override
public void publishAsJson(String clientId, String messageType, Object payload) {
try {
// Prüfen ob Client verbunden ist
boolean isConnected = webSocketService.isClientConnected(clientId);
log.debug("[Messaging] Publishing to {}/{} - connected: {}", clientId, messageType, isConnected);
if (!isConnected) {
log.warn("[Messaging] Client {} is not connected, cannot send {}", clientId, messageType);
return;
}
String json = objectMapper.writeValueAsString(payload);
byte[] data = json.getBytes(StandardCharsets.UTF_8);
webSocketService.sendToClient(clientId, messageType, data).exceptionally(ex -> {
log.error("[Messaging] Failed to deliver to {}/{}: {}", clientId, messageType, ex.getMessage());
webSocketService.sendToClient(clientId, messageType, data)
.thenRun(() -> {
log.debug("[Messaging] Successfully sent {}/{} to client {}", messageType, clientId);
})
.exceptionally(ex -> {
log.error("[Messaging] Failed to deliver to {}/{}: {}", clientId, messageType, ex.getMessage(), ex);
return null;
});
} catch (Exception e) {
log.error("[Messaging] Failed to publish to {}/{}: {}", clientId, messageType, e.getMessage());
log.error("[Messaging] Failed to publish to {}/{}: {}", clientId, messageType, e.getMessage(), e);
}
}
}

View File

@@ -129,9 +129,19 @@ public class WebSocketService extends TextWebSocketHandler {
public CompletableFuture<Void> sendToClient(String clientId, String messageType, byte[] payload) {
WebSocketSession session = clientSessions.get(clientId);
if (session == null || !session.isOpen()) {
if (session == null) {
log.warn("[WebSocket] No session found for client {}", clientId);
return CompletableFuture
.failedFuture(new IOException("No active WebSocket session for client: " + clientId));
.failedFuture(new IOException("No WebSocket session for client: " + clientId));
}
if (!session.isOpen()) {
log.warn("[WebSocket] Session for client {} is closed", clientId);
// Session aus der Map entfernen
clientSessions.remove(clientId);
sessionToClient.remove(session.getId());
return CompletableFuture
.failedFuture(new IOException("WebSocket session closed for client: " + clientId));
}
try {
@@ -143,12 +153,13 @@ public class WebSocketService extends TextWebSocketHandler {
wireMessage.set("payload", objectMapper.readTree(payloadJson));
String wireJson = objectMapper.writeValueAsString(wireMessage);
log.info("[WebSocket OUT] {} -> {}", topic, wireJson);
log.info("[WebSocket OUT] {} to client {} (session open: {})", topic, clientId, session.isOpen());
sendToSession(session, wireJson);
log.debug("[WebSocket] Message sent successfully to client {}", clientId);
return CompletableFuture.completedFuture(null);
} catch (Exception e) {
log.error("[WebSocket] Failed to send to client {}: {}", clientId, e.getMessage());
log.error("[WebSocket] Failed to send to client {}: {}", clientId, e.getMessage(), e);
return CompletableFuture.failedFuture(new IOException("Failed to send WebSocket message", e));
}
}

View File

@@ -30,7 +30,6 @@ import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
import de.assecutor.votianlt.pages.view.EditProfileView;
import de.assecutor.votianlt.model.Language;
import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.service.LanguageService;
import de.assecutor.votianlt.service.MessageBadgeUpdateService;
import de.assecutor.votianlt.service.MessageService;
import lombok.extern.slf4j.Slf4j;
@@ -51,7 +50,6 @@ public final class MainLayout extends AppLayout {
private final MessageService messageService;
private final MessageBadgeUpdateService messageBadgeUpdateService;
private final AppUserService appUserService;
private final LanguageService languageService;
private Div headerRef;
private Scroller navRef;
private Component userMenuRef;
@@ -61,22 +59,29 @@ public final class MainLayout extends AppLayout {
public MainLayout(SecurityService securityService, UserInvoiceDataService userInvoiceDataService,
MessageService messageService, MessageBadgeUpdateService messageBadgeUpdateService,
AppUserService appUserService, LanguageService languageService) {
AppUserService appUserService) {
this.securityService = securityService;
this.userInvoiceDataService = userInvoiceDataService;
this.messageService = messageService;
this.messageBadgeUpdateService = messageBadgeUpdateService;
this.appUserService = appUserService;
this.languageService = languageService;
setPrimarySection(Section.DRAWER);
// Drawer Styles für volle Höhe
getStyle().set("--vaadin-app-layout-drawer-width", "286px");
// Always build the drawer; keep references and toggle visibility on attach and
// after navigation
headerRef = createHeader();
navRef = new Scroller(createSideNav());
// Scroller für Navigation mit maximaler Höhe
Component sideNav = createSideNav();
navRef = new Scroller(sideNav);
userMenuRef = createUserMenu();
addToDrawer(headerRef, navRef, userMenuRef);
updateDrawerVisibility();
// Re-check on attach (new UI/session) and on every navigation cycle
@@ -128,6 +133,8 @@ public final class MainLayout extends AppLayout {
treeData.addItem(null, benutzerItem);
// Add children to "Verwaltung"
treeData.addItem(verwaltungItem,
new MenuTreeItem(getTranslation("nav.jobs"), "jobs", VaadinIcon.CLIPBOARD_TEXT));
treeData.addItem(verwaltungItem,
new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS));
treeData.addItem(verwaltungItem,
@@ -153,6 +160,7 @@ public final class MainLayout extends AppLayout {
tree = new TreeGrid<>();
tree.setDataProvider(new TreeDataProvider<>(treeData));
tree.addClassNames(Margin.Horizontal.MEDIUM);
tree.setHeight("435px");
// Custom item renderer to show icon and label with badge
tree.addComponentHierarchyColumn(item -> {
@@ -350,6 +358,25 @@ public final class MainLayout extends AppLayout {
super.onAttach(attachEvent);
UI ui = attachEvent.getUI();
// Drawer-Layout anpassen nach kurzer Verzögerung
ui.access(() -> {
getElement().executeJs(
"setTimeout(() => {" +
" const drawer = this.shadowRoot?.querySelector('[part=drawer]');" +
" if (drawer) {" +
" drawer.style.display = 'flex';" +
" drawer.style.flexDirection = 'column';" +
" drawer.style.height = '100vh';" +
" const scroller = drawer.querySelector('vaadin-scroller');" +
" if (scroller) {" +
" scroller.style.flex = '1 1 auto';" +
" scroller.style.minHeight = '0';" +
" }" +
" }" +
"}, 100);"
);
});
// Apply user's preferred language immediately after login
applyUserLanguagePreference();

View File

@@ -102,10 +102,11 @@ public class ClientConnectionService {
*/
private void sendAssignedJobs(String appUserId) {
try {
log.info("[CLIENT] Sending assigned jobs to appUserId: {}", appUserId);
messageController.handleGetAssignedJobs(appUserId);
log.debug("[CLIENT] Sent assigned jobs to {}", appUserId);
log.info("[CLIENT] Assigned jobs sent successfully to {}", appUserId);
} catch (Exception e) {
log.error("[CLIENT] Error sending assigned jobs to {}: {}", appUserId, e.getMessage());
log.error("[CLIENT] Error sending assigned jobs to {}: {}", appUserId, e.getMessage(), e);
}
}