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) { public void handleGetAssignedJobs(String appUserId) {
if (appUserId == null || appUserId.isBlank()) { if (appUserId == null || appUserId.isBlank()) {
log.warn("[JOBS] appUserId is null or blank, cannot retrieve jobs");
return; return;
} }
log.info("[JOBS] Retrieving assigned jobs for appUserId: {}", appUserId);
List<Job> assignedJobs = jobRepository.findByAppUser(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<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> {
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId()); List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
@@ -125,7 +129,9 @@ public class MessageController {
return new JobWithRelatedDataDTO(job, cargoItems, tasks); return new JobWithRelatedDataDTO(job, cargoItems, tasks);
}).toList(); }).toList();
log.info("[JOBS] Publishing {} jobs to client {} on topic /client/jobs", jobsWithRelatedData.size(), appUserId);
messagingPublisher.publishAsJson(appUserId, "jobs", jobsWithRelatedData); 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()) { if (response.isSuccess()) {
String appUserId = response.getAppUserId(); String appUserId = response.getAppUserId();
log.info("[Messaging] Login successful for appUserId: {}", appUserId);
webSocketService.registerAuthenticatedSession(wsSessionId, appUserId); webSocketService.registerAuthenticatedSession(wsSessionId, appUserId);
// Send success response to the now-authenticated session // Send success response to the now-authenticated session
// locationTrackingEnabled: true = client should send position updates // locationTrackingEnabled: true = client should send position updates
Map<String, Object> authResponse = Map.of("success", true, "message", response.getMessage(), // appUserId: wird an den Client gesendet für Referenz
"locationTrackingEnabled", true); Map<String, Object> authResponse = Map.of(
"success", true,
"message", response.getMessage(),
"locationTrackingEnabled", true,
"appUserId", appUserId
);
byte[] responseBytes = objectMapper.writeValueAsBytes(authResponse); byte[] responseBytes = objectMapper.writeValueAsBytes(authResponse);
log.info("[Messaging] Sending auth response to appUserId: {}", appUserId);
webSocketService.sendToClient(appUserId, "auth", responseBytes); webSocketService.sendToClient(appUserId, "auth", responseBytes);
// Register client - pending messages and jobs will be sent after // Register client - pending messages and jobs will be sent after
// client confirms buffer_flushed // client confirms buffer_flushed
clientConnectionService.registerClient(appUserId); clientConnectionService.registerClient(appUserId);
} else { } else {
log.warn("[Messaging] Login failed: {}", response.getMessage());
// Send failure response to the pending session // Send failure response to the pending session
Map<String, Object> authResponse = Map.of("success", false, "message", response.getMessage()); Map<String, Object> authResponse = Map.of("success", false, "message", response.getMessage());
byte[] responseBytes = objectMapper.writeValueAsBytes(authResponse); byte[] responseBytes = objectMapper.writeValueAsBytes(authResponse);
webSocketService.sendToSessionById(wsSessionId, "/client/auth", responseBytes); webSocketService.sendToSessionById(wsSessionId, "/client/auth", responseBytes);
} }
} catch (Exception e) { } 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 @Override
public void publishAsJson(String clientId, String messageType, Object payload) { public void publishAsJson(String clientId, String messageType, Object payload) {
try { 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); String json = objectMapper.writeValueAsString(payload);
byte[] data = json.getBytes(StandardCharsets.UTF_8); byte[] data = json.getBytes(StandardCharsets.UTF_8);
webSocketService.sendToClient(clientId, messageType, data).exceptionally(ex -> { webSocketService.sendToClient(clientId, messageType, data)
log.error("[Messaging] Failed to deliver to {}/{}: {}", clientId, messageType, ex.getMessage()); .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; return null;
}); });
} catch (Exception e) { } 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) { public CompletableFuture<Void> sendToClient(String clientId, String messageType, byte[] payload) {
WebSocketSession session = clientSessions.get(clientId); WebSocketSession session = clientSessions.get(clientId);
if (session == null || !session.isOpen()) { if (session == null) {
log.warn("[WebSocket] No session found for client {}", clientId);
return CompletableFuture 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 { try {
@@ -143,12 +153,13 @@ public class WebSocketService extends TextWebSocketHandler {
wireMessage.set("payload", objectMapper.readTree(payloadJson)); wireMessage.set("payload", objectMapper.readTree(payloadJson));
String wireJson = objectMapper.writeValueAsString(wireMessage); 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); sendToSession(session, wireJson);
log.debug("[WebSocket] Message sent successfully to client {}", clientId);
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} catch (Exception e) { } 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)); 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.pages.view.EditProfileView;
import de.assecutor.votianlt.model.Language; import de.assecutor.votianlt.model.Language;
import de.assecutor.votianlt.security.SecurityService; import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.service.LanguageService;
import de.assecutor.votianlt.service.MessageBadgeUpdateService; import de.assecutor.votianlt.service.MessageBadgeUpdateService;
import de.assecutor.votianlt.service.MessageService; import de.assecutor.votianlt.service.MessageService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -51,7 +50,6 @@ public final class MainLayout extends AppLayout {
private final MessageService messageService; private final MessageService messageService;
private final MessageBadgeUpdateService messageBadgeUpdateService; private final MessageBadgeUpdateService messageBadgeUpdateService;
private final AppUserService appUserService; private final AppUserService appUserService;
private final LanguageService languageService;
private Div headerRef; private Div headerRef;
private Scroller navRef; private Scroller navRef;
private Component userMenuRef; private Component userMenuRef;
@@ -61,22 +59,29 @@ public final class MainLayout extends AppLayout {
public MainLayout(SecurityService securityService, UserInvoiceDataService userInvoiceDataService, public MainLayout(SecurityService securityService, UserInvoiceDataService userInvoiceDataService,
MessageService messageService, MessageBadgeUpdateService messageBadgeUpdateService, MessageService messageService, MessageBadgeUpdateService messageBadgeUpdateService,
AppUserService appUserService, LanguageService languageService) { AppUserService appUserService) {
this.securityService = securityService; this.securityService = securityService;
this.userInvoiceDataService = userInvoiceDataService; this.userInvoiceDataService = userInvoiceDataService;
this.messageService = messageService; this.messageService = messageService;
this.messageBadgeUpdateService = messageBadgeUpdateService; this.messageBadgeUpdateService = messageBadgeUpdateService;
this.appUserService = appUserService; this.appUserService = appUserService;
this.languageService = languageService;
setPrimarySection(Section.DRAWER); 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 // Always build the drawer; keep references and toggle visibility on attach and
// after navigation // after navigation
headerRef = createHeader(); headerRef = createHeader();
navRef = new Scroller(createSideNav());
// Scroller für Navigation mit maximaler Höhe
Component sideNav = createSideNav();
navRef = new Scroller(sideNav);
userMenuRef = createUserMenu(); userMenuRef = createUserMenu();
addToDrawer(headerRef, navRef, userMenuRef); addToDrawer(headerRef, navRef, userMenuRef);
updateDrawerVisibility(); updateDrawerVisibility();
// Re-check on attach (new UI/session) and on every navigation cycle // 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); treeData.addItem(null, benutzerItem);
// Add children to "Verwaltung" // Add children to "Verwaltung"
treeData.addItem(verwaltungItem,
new MenuTreeItem(getTranslation("nav.jobs"), "jobs", VaadinIcon.CLIPBOARD_TEXT));
treeData.addItem(verwaltungItem, treeData.addItem(verwaltungItem,
new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS)); new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS));
treeData.addItem(verwaltungItem, treeData.addItem(verwaltungItem,
@@ -153,6 +160,7 @@ public final class MainLayout extends AppLayout {
tree = new TreeGrid<>(); tree = new TreeGrid<>();
tree.setDataProvider(new TreeDataProvider<>(treeData)); tree.setDataProvider(new TreeDataProvider<>(treeData));
tree.addClassNames(Margin.Horizontal.MEDIUM); tree.addClassNames(Margin.Horizontal.MEDIUM);
tree.setHeight("435px");
// Custom item renderer to show icon and label with badge // Custom item renderer to show icon and label with badge
tree.addComponentHierarchyColumn(item -> { tree.addComponentHierarchyColumn(item -> {
@@ -350,6 +358,25 @@ public final class MainLayout extends AppLayout {
super.onAttach(attachEvent); super.onAttach(attachEvent);
UI ui = attachEvent.getUI(); 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 // Apply user's preferred language immediately after login
applyUserLanguagePreference(); applyUserLanguagePreference();

View File

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