Erweiterungen
This commit is contained in:
12
.claude/settings.local.json
Normal file
12
.claude/settings.local.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(./mvnw clean compile -q)",
|
||||||
|
"mcp__ide__getDiagnostics",
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(./mvnw:*)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package de.assecutor.votianlt.config;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class MailConfig {
|
|
||||||
|
|
||||||
@Value("${mail.smtp.username}")
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@Value("${mail.smtp.password}")
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
@Value("${mail.smtp.host:smtp.gmail.com}")
|
|
||||||
private String host;
|
|
||||||
|
|
||||||
@Value("${mail.smtp.port:587}")
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHost() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,55 +50,55 @@ public class MongoConfig {
|
|||||||
|
|
||||||
BaseTask task;
|
BaseTask task;
|
||||||
switch (className) {
|
switch (className) {
|
||||||
case "de.assecutor.votianlt.model.task.ConfirmationTask":
|
case "de.assecutor.votianlt.model.task.ConfirmationTask":
|
||||||
case "ConfirmationTask":
|
case "ConfirmationTask":
|
||||||
log.debug("Creating ConfirmationTask");
|
log.debug("Creating ConfirmationTask");
|
||||||
task = new ConfirmationTask();
|
task = new ConfirmationTask();
|
||||||
if (source.containsKey("button_text")) {
|
if (source.containsKey("button_text")) {
|
||||||
((ConfirmationTask) task).setButtonText(source.getString("button_text"));
|
((ConfirmationTask) task).setButtonText(source.getString("button_text"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "de.assecutor.votianlt.model.task.SignatureTask":
|
case "de.assecutor.votianlt.model.task.SignatureTask":
|
||||||
case "SignatureTask":
|
case "SignatureTask":
|
||||||
log.debug("Creating SignatureTask");
|
log.debug("Creating SignatureTask");
|
||||||
task = new SignatureTask();
|
task = new SignatureTask();
|
||||||
break;
|
break;
|
||||||
case "de.assecutor.votianlt.model.task.PhotoTask":
|
case "de.assecutor.votianlt.model.task.PhotoTask":
|
||||||
case "PhotoTask":
|
case "PhotoTask":
|
||||||
log.debug("Creating PhotoTask");
|
log.debug("Creating PhotoTask");
|
||||||
task = new PhotoTask();
|
task = new PhotoTask();
|
||||||
if (source.containsKey("min_photo_count")) {
|
if (source.containsKey("min_photo_count")) {
|
||||||
((PhotoTask) task).setMinPhotoCount(source.getInteger("min_photo_count"));
|
((PhotoTask) task).setMinPhotoCount(source.getInteger("min_photo_count"));
|
||||||
}
|
}
|
||||||
if (source.containsKey("max_photo_count")) {
|
if (source.containsKey("max_photo_count")) {
|
||||||
((PhotoTask) task).setMaxPhotoCount(source.getInteger("max_photo_count"));
|
((PhotoTask) task).setMaxPhotoCount(source.getInteger("max_photo_count"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "de.assecutor.votianlt.model.task.TodoListTask":
|
case "de.assecutor.votianlt.model.task.TodoListTask":
|
||||||
case "TodoListTask":
|
case "TodoListTask":
|
||||||
log.debug("Creating TodoListTask");
|
log.debug("Creating TodoListTask");
|
||||||
task = new TodoListTask();
|
task = new TodoListTask();
|
||||||
if (source.containsKey("todo_items")) {
|
if (source.containsKey("todo_items")) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<String> todoItems = (List<String>) source.get("todo_items");
|
List<String> todoItems = (List<String>) source.get("todo_items");
|
||||||
((TodoListTask) task).setTodoItems(todoItems);
|
((TodoListTask) task).setTodoItems(todoItems);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "de.assecutor.votianlt.model.task.BarcodeTask":
|
case "de.assecutor.votianlt.model.task.BarcodeTask":
|
||||||
case "BarcodeTask":
|
case "BarcodeTask":
|
||||||
log.debug("Creating BarcodeTask");
|
log.debug("Creating BarcodeTask");
|
||||||
task = new BarcodeTask();
|
task = new BarcodeTask();
|
||||||
if (source.containsKey("min_barcode_count")) {
|
if (source.containsKey("min_barcode_count")) {
|
||||||
((BarcodeTask) task).setMinBarcodeCount(source.getInteger("min_barcode_count"));
|
((BarcodeTask) task).setMinBarcodeCount(source.getInteger("min_barcode_count"));
|
||||||
}
|
}
|
||||||
if (source.containsKey("max_barcode_count")) {
|
if (source.containsKey("max_barcode_count")) {
|
||||||
((BarcodeTask) task).setMaxBarcodeCount(source.getInteger("max_barcode_count"));
|
((BarcodeTask) task).setMaxBarcodeCount(source.getInteger("max_barcode_count"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.warn("Unknown className '{}', falling back to ConfirmationTask", className);
|
log.warn("Unknown className '{}', falling back to ConfirmationTask", className);
|
||||||
task = new ConfirmationTask(); // fallback
|
task = new ConfirmationTask(); // fallback
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set common fields
|
// Set common fields
|
||||||
@@ -120,9 +120,11 @@ public class MongoConfig {
|
|||||||
if (source.containsKey("completed_at") && source.get("completed_at") != null) {
|
if (source.containsKey("completed_at") && source.get("completed_at") != null) {
|
||||||
Object completedAtObj = source.get("completed_at");
|
Object completedAtObj = source.get("completed_at");
|
||||||
if (completedAtObj instanceof String) {
|
if (completedAtObj instanceof String) {
|
||||||
task.setCompletedAt(LocalDateTime.parse((String) completedAtObj, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
task.setCompletedAt(
|
||||||
|
LocalDateTime.parse((String) completedAtObj, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||||
} else if (completedAtObj instanceof java.util.Date) {
|
} else if (completedAtObj instanceof java.util.Date) {
|
||||||
task.setCompletedAt(((java.util.Date) completedAtObj).toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
|
task.setCompletedAt(((java.util.Date) completedAtObj).toInstant()
|
||||||
|
.atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (source.containsKey("completed_by")) {
|
if (source.containsKey("completed_by")) {
|
||||||
@@ -137,18 +139,18 @@ public class MongoConfig {
|
|||||||
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
||||||
}
|
}
|
||||||
switch (taskType) {
|
switch (taskType) {
|
||||||
case "CONFIRMATION":
|
case "CONFIRMATION":
|
||||||
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
||||||
case "SIGNATURE":
|
case "SIGNATURE":
|
||||||
return "de.assecutor.votianlt.model.task.SignatureTask";
|
return "de.assecutor.votianlt.model.task.SignatureTask";
|
||||||
case "PHOTO":
|
case "PHOTO":
|
||||||
return "de.assecutor.votianlt.model.task.PhotoTask";
|
return "de.assecutor.votianlt.model.task.PhotoTask";
|
||||||
case "TODOLIST":
|
case "TODOLIST":
|
||||||
return "de.assecutor.votianlt.model.task.TodoListTask";
|
return "de.assecutor.votianlt.model.task.TodoListTask";
|
||||||
case "BARCODE":
|
case "BARCODE":
|
||||||
return "de.assecutor.votianlt.model.task.BarcodeTask";
|
return "de.assecutor.votianlt.model.task.BarcodeTask";
|
||||||
default:
|
default:
|
||||||
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,28 +31,99 @@ public class MqttProperties {
|
|||||||
/** Default retained flag for publishing */
|
/** Default retained flag for publishing */
|
||||||
private boolean defaultRetained = false;
|
private boolean defaultRetained = false;
|
||||||
|
|
||||||
public boolean isEnabled() { return enabled; }
|
public boolean isEnabled() {
|
||||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
return enabled;
|
||||||
public String getBrokerUri() { return brokerUri; }
|
}
|
||||||
public void setBrokerUri(String brokerUri) { this.brokerUri = brokerUri; }
|
|
||||||
public String getClientId() { return clientId; }
|
public void setEnabled(boolean enabled) {
|
||||||
public void setClientId(String clientId) { this.clientId = clientId; }
|
this.enabled = enabled;
|
||||||
public String getUsername() { return username; }
|
}
|
||||||
public void setUsername(String username) { this.username = username; }
|
|
||||||
public String getPassword() { return password; }
|
public String getBrokerUri() {
|
||||||
public void setPassword(String password) { this.password = password; }
|
return brokerUri;
|
||||||
public boolean isCleanStart() { return cleanStart; }
|
}
|
||||||
public void setCleanStart(boolean cleanStart) { this.cleanStart = cleanStart; }
|
|
||||||
public long getSessionExpiryInterval() { return sessionExpiryInterval; }
|
public void setBrokerUri(String brokerUri) {
|
||||||
public void setSessionExpiryInterval(long sessionExpiryInterval) { this.sessionExpiryInterval = sessionExpiryInterval; }
|
this.brokerUri = brokerUri;
|
||||||
public int getKeepAlive() { return keepAlive; }
|
}
|
||||||
public void setKeepAlive(int keepAlive) { this.keepAlive = keepAlive; }
|
|
||||||
public int getMaxInflight() { return maxInflight; }
|
public String getClientId() {
|
||||||
public void setMaxInflight(int maxInflight) { this.maxInflight = maxInflight; }
|
return clientId;
|
||||||
public boolean isAutomaticReconnect() { return automaticReconnect; }
|
}
|
||||||
public void setAutomaticReconnect(boolean automaticReconnect) { this.automaticReconnect = automaticReconnect; }
|
|
||||||
public int getDefaultQos() { return defaultQos; }
|
public void setClientId(String clientId) {
|
||||||
public void setDefaultQos(int defaultQos) { this.defaultQos = defaultQos; }
|
this.clientId = clientId;
|
||||||
public boolean isDefaultRetained() { return defaultRetained; }
|
}
|
||||||
public void setDefaultRetained(boolean defaultRetained) { this.defaultRetained = defaultRetained; }
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCleanStart() {
|
||||||
|
return cleanStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCleanStart(boolean cleanStart) {
|
||||||
|
this.cleanStart = cleanStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSessionExpiryInterval() {
|
||||||
|
return sessionExpiryInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionExpiryInterval(long sessionExpiryInterval) {
|
||||||
|
this.sessionExpiryInterval = sessionExpiryInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeepAlive() {
|
||||||
|
return keepAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeepAlive(int keepAlive) {
|
||||||
|
this.keepAlive = keepAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxInflight() {
|
||||||
|
return maxInflight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxInflight(int maxInflight) {
|
||||||
|
this.maxInflight = maxInflight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutomaticReconnect() {
|
||||||
|
return automaticReconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutomaticReconnect(boolean automaticReconnect) {
|
||||||
|
this.automaticReconnect = automaticReconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefaultQos() {
|
||||||
|
return defaultQos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultQos(int defaultQos) {
|
||||||
|
this.defaultQos = defaultQos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefaultRetained() {
|
||||||
|
return defaultRetained;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultRetained(boolean defaultRetained) {
|
||||||
|
this.defaultRetained = defaultRetained;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ public class MessageController {
|
|||||||
private final JobHistoryService jobHistoryService;
|
private final JobHistoryService jobHistoryService;
|
||||||
private final EmailService emailService;
|
private final EmailService emailService;
|
||||||
|
|
||||||
public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository, AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository, TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository, SignatureRepository signatureRepository, JobHistoryService jobHistoryService, EmailService emailService) {
|
public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository,
|
||||||
|
AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository,
|
||||||
|
TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository,
|
||||||
|
SignatureRepository signatureRepository, JobHistoryService jobHistoryService, EmailService emailService) {
|
||||||
this.mqttPublisher = mqttPublisher;
|
this.mqttPublisher = mqttPublisher;
|
||||||
this.appUserRepository = appUserRepository;
|
this.appUserRepository = appUserRepository;
|
||||||
this.appUserService = appUserService;
|
this.appUserService = appUserService;
|
||||||
@@ -75,20 +78,21 @@ public class MessageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication endpoint for mobile app users via MQTT.
|
* Authentication endpoint for mobile app users via MQTT. Client sends to
|
||||||
* Client sends to /server/login with payload { email, password, clientId }.
|
* /server/login with payload { email, password, clientId }. The response is
|
||||||
* The response is sent back to the requesting client on /client/{clientId}/auth
|
* sent back to the requesting client on /client/{clientId}/auth
|
||||||
*/
|
*/
|
||||||
public void handleAppLogin(AppLoginRequest request) {
|
public void handleAppLogin(AppLoginRequest request) {
|
||||||
log.info("MQTT Endpoint '/server/login' called with email: {}, clientId: {}",
|
log.info("MQTT Endpoint '/server/login' called with email: {}, clientId: {}",
|
||||||
request != null ? request.getEmail() : "null",
|
request != null ? request.getEmail() : "null", request != null ? request.getClientId() : "null");
|
||||||
request != null ? request.getClientId() : "null");
|
|
||||||
|
|
||||||
AppLoginResponse response;
|
AppLoginResponse response;
|
||||||
|
|
||||||
if (request == null || request.getEmail() == null || request.getPassword() == null || request.getClientId() == null
|
if (request == null || request.getEmail() == null || request.getPassword() == null
|
||||||
|| request.getEmail().isBlank() || request.getPassword().isBlank() || request.getClientId().isBlank()) {
|
|| request.getClientId() == null || request.getEmail().isBlank() || request.getPassword().isBlank()
|
||||||
response = new AppLoginResponse(false, "E-Mail, Passwort und Client-ID sind erforderlich", null, null, null);
|
|| request.getClientId().isBlank()) {
|
||||||
|
response = new AppLoginResponse(false, "E-Mail, Passwort und Client-ID sind erforderlich", null, null,
|
||||||
|
null);
|
||||||
} else {
|
} else {
|
||||||
AppUser user = appUserRepository.findByEmail(request.getEmail());
|
AppUser user = appUserRepository.findByEmail(request.getEmail());
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
@@ -108,20 +112,21 @@ public class MessageController {
|
|||||||
// Send response via MQTT to specific client
|
// Send response via MQTT to specific client
|
||||||
if (request != null && request.getClientId() != null && !request.getClientId().isBlank()) {
|
if (request != null && request.getClientId() != null && !request.getClientId().isBlank()) {
|
||||||
mqttPublisher.publishAsJson("/client/" + request.getClientId() + "/auth", response, false);
|
mqttPublisher.publishAsJson("/client/" + request.getClientId() + "/auth", response, false);
|
||||||
log.info("MQTT Response sent to '/client/{}/auth': success={}, message='{}'",
|
log.info("MQTT Response sent to '/client/{}/auth': success={}, message='{}'", request.getClientId(),
|
||||||
request.getClientId(), response.isSuccess(), response.getMessage());
|
response.isSuccess(), response.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint to retrieve jobs assigned to a specific app user with related cargo items and tasks.
|
* Endpoint to retrieve jobs assigned to a specific app user with related cargo
|
||||||
* Client sends to /server/{clientId}/jobs/assigned with payload { appUserId }.
|
* items and tasks. Client sends to /server/{clientId}/jobs/assigned with
|
||||||
* The response is sent back to the requesting client on /client/{clientId}/jobs
|
* payload { appUserId }. The response is sent back to the requesting client on
|
||||||
|
* /client/{clientId}/jobs
|
||||||
*/
|
*/
|
||||||
public void handleGetAssignedJobs(Map<String, Object> request) {
|
public void handleGetAssignedJobs(Map<String, Object> request) {
|
||||||
log.info("MQTT Endpoint '/server/{clientId}/jobs/assigned' called with data: {}", request);
|
log.info("MQTT Endpoint '/server/{clientId}/jobs/assigned' called with data: {}", request);
|
||||||
log.debug("Starting to process jobs request for MQTT endpoint");
|
log.debug("Starting to process jobs request for MQTT endpoint");
|
||||||
|
|
||||||
if (request == null || !request.containsKey("appUserId")) {
|
if (request == null || !request.containsKey("appUserId")) {
|
||||||
log.info("Assigned jobs request missing appUserId; returning empty list");
|
log.info("Assigned jobs request missing appUserId; returning empty list");
|
||||||
return; // Return empty list if no appUserId provided
|
return; // Return empty list if no appUserId provided
|
||||||
@@ -133,12 +138,15 @@ public class MessageController {
|
|||||||
return; // Return empty list if appUserId is blank
|
return; // Return empty list if appUserId is blank
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to get clientId from request (injected from topic) or from stored mapping
|
// Attempt to get clientId from request (injected from topic) or from stored
|
||||||
|
// mapping
|
||||||
String clientId = null;
|
String clientId = null;
|
||||||
try {
|
try {
|
||||||
Object cid = request.get("clientId");
|
Object cid = request.get("clientId");
|
||||||
if (cid != null) clientId = cid.toString();
|
if (cid != null)
|
||||||
} catch (Exception ignored) {}
|
clientId = cid.toString();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
if (clientId == null || clientId.isBlank()) {
|
if (clientId == null || clientId.isBlank()) {
|
||||||
clientId = getClientIdForUserId(appUserId);
|
clientId = getClientIdForUserId(appUserId);
|
||||||
}
|
}
|
||||||
@@ -148,26 +156,26 @@ public class MessageController {
|
|||||||
log.debug("Found {} jobs for appUserId: {}", assignedJobs.size(), appUserId);
|
log.debug("Found {} jobs for appUserId: {}", assignedJobs.size(), appUserId);
|
||||||
|
|
||||||
// For each job, fetch related cargo items and tasks (ordered by task order)
|
// For each job, fetch related cargo items and tasks (ordered by task order)
|
||||||
List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream()
|
List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> {
|
||||||
.map(job -> {
|
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
|
||||||
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
|
List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());
|
||||||
List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());
|
|
||||||
|
|
||||||
// Log task details for debugging
|
// Log task details for debugging
|
||||||
tasks.forEach(task -> log.info("Task details for job {}: type={}, order={}",
|
tasks.forEach(task -> log.info("Task details for job {}: type={}, order={}", job.getId(),
|
||||||
job.getId(), task.getTaskType(), task.getTaskOrder()));
|
task.getTaskType(), task.getTaskOrder()));
|
||||||
|
|
||||||
|
return new JobWithRelatedDataDTO(job, cargoItems, tasks);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
return new JobWithRelatedDataDTO(job, cargoItems, tasks);
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
// Publish to the requesting client's topic if clientId is known
|
// Publish to the requesting client's topic if clientId is known
|
||||||
if (clientId != null && !clientId.isBlank()) {
|
if (clientId != null && !clientId.isBlank()) {
|
||||||
String topic = "/client/" + clientId + "/jobs";
|
String topic = "/client/" + clientId + "/jobs";
|
||||||
mqttPublisher.publishAsJson(topic, jobsWithRelatedData, false);
|
mqttPublisher.publishAsJson(topic, jobsWithRelatedData, false);
|
||||||
log.info("Published {} assigned jobs for appUserId='{}' to topic '{}'", jobsWithRelatedData.size(), appUserId, topic);
|
log.info("Published {} assigned jobs for appUserId='{}' to topic '{}'", jobsWithRelatedData.size(),
|
||||||
|
appUserId, topic);
|
||||||
} else {
|
} else {
|
||||||
log.warn("No clientId available to publish assigned jobs for appUserId='{}'. Skipping MQTT publish.", appUserId);
|
log.warn("No clientId available to publish assigned jobs for appUserId='{}'. Skipping MQTT publish.",
|
||||||
|
appUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log complete JSON for debugging
|
// Log complete JSON for debugging
|
||||||
@@ -189,10 +197,10 @@ public class MessageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Report generic task completion from apps.
|
* Report generic task completion from apps. Client sends to /app/task/completed
|
||||||
* Client sends to /app/task/completed with payload { taskId, completedBy?, note? }.
|
* with payload { taskId, completedBy?, note? }. Broadcasts to
|
||||||
* Broadcasts to /topic/task-updates and /topic/tasks/{taskId}.
|
* /topic/task-updates and /topic/tasks/{taskId}. This endpoint accepts any task
|
||||||
* This endpoint accepts any task type (fallback for GENERIC or unknown types).
|
* type (fallback for GENERIC or unknown types).
|
||||||
*/
|
*/
|
||||||
public void handleTaskCompleted(Map<String, Object> payload) {
|
public void handleTaskCompleted(Map<String, Object> payload) {
|
||||||
// Backward-compatible entry point: extract taskType from payload (if present)
|
// Backward-compatible entry point: extract taskType from payload (if present)
|
||||||
@@ -200,14 +208,17 @@ public class MessageController {
|
|||||||
String taskType = null;
|
String taskType = null;
|
||||||
try {
|
try {
|
||||||
Object tt = payload != null ? payload.get("taskType") : null;
|
Object tt = payload != null ? payload.get("taskType") : null;
|
||||||
if (tt != null) taskType = tt.toString();
|
if (tt != null)
|
||||||
} catch (Exception ignored) {}
|
taskType = tt.toString();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
handleTaskCompleted(payload, taskType);
|
handleTaskCompleted(payload, taskType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Central dispatcher for task_completed messages. Decides handling based on taskType.
|
* Central dispatcher for task_completed messages. Decides handling based on
|
||||||
* PHOTO and CONFIRMATION are routed to specialized handlers; others go to generic processing.
|
* taskType. PHOTO and CONFIRMATION are routed to specialized handlers; others
|
||||||
|
* go to generic processing.
|
||||||
*/
|
*/
|
||||||
public void handleTaskCompleted(Map<String, Object> payload, String taskType) {
|
public void handleTaskCompleted(Map<String, Object> payload, String taskType) {
|
||||||
String key = taskType == null ? "" : taskType.trim().toUpperCase();
|
String key = taskType == null ? "" : taskType.trim().toUpperCase();
|
||||||
@@ -215,24 +226,24 @@ public class MessageController {
|
|||||||
log.info("handleTaskCompleted called with taskType={}, data: {}", taskType, payload);
|
log.info("handleTaskCompleted called with taskType={}, data: {}", taskType, payload);
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "PHOTO" -> {
|
case "PHOTO" -> {
|
||||||
processPhotoTaskCompletion(payload);
|
processPhotoTaskCompletion(payload);
|
||||||
}
|
}
|
||||||
case "CONFIRMATION" -> {
|
case "CONFIRMATION" -> {
|
||||||
processConfirmationTaskCompletion(payload);
|
processConfirmationTaskCompletion(payload);
|
||||||
}
|
}
|
||||||
case "SIGNATURE" -> {
|
case "SIGNATURE" -> {
|
||||||
processSignatureTaskCompletion(payload);
|
processSignatureTaskCompletion(payload);
|
||||||
}
|
}
|
||||||
case "TODOLIST" -> {
|
case "TODOLIST" -> {
|
||||||
processTodoListTaskCompletion(payload);
|
processTodoListTaskCompletion(payload);
|
||||||
}
|
}
|
||||||
case "BARCODE" -> {
|
case "BARCODE" -> {
|
||||||
processBarcodeTaskCompletion(payload);
|
processBarcodeTaskCompletion(payload);
|
||||||
}
|
}
|
||||||
default -> {
|
default -> {
|
||||||
log.info("ERROR: handleTaskCompleted called with taskType={}, data: {}", taskType, payload);
|
log.info("ERROR: handleTaskCompleted called with taskType={}, data: {}", taskType, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,16 +277,15 @@ public class MessageController {
|
|||||||
|
|
||||||
if (!barcodes.isEmpty()) {
|
if (!barcodes.isEmpty()) {
|
||||||
for (String barcodeString : barcodes) {
|
for (String barcodeString : barcodes) {
|
||||||
Barcode barcodeEntry = new Barcode(
|
Barcode barcodeEntry = new Barcode(new ObjectId(taskId.toString()), barcodeString,
|
||||||
new ObjectId(taskId.toString()),
|
task.getCompletedBy());
|
||||||
barcodeString,
|
|
||||||
task.getCompletedBy()
|
|
||||||
);
|
|
||||||
|
|
||||||
barcodeRepository.save(barcodeEntry);
|
barcodeRepository.save(barcodeEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
extraDataSummary = barcodes.size() + " Barcode(s) gescannt: " + String.join(", ", barcodes.subList(0, Math.min(3, barcodes.size()))) + (barcodes.size() > 3 ? "..." : "");
|
extraDataSummary = barcodes.size() + " Barcode(s) gescannt: "
|
||||||
|
+ String.join(", ", barcodes.subList(0, Math.min(3, barcodes.size())))
|
||||||
|
+ (barcodes.size() > 3 ? "..." : "");
|
||||||
log.info("Saved {} barcodes for taskId={}", barcodes.size(), taskId);
|
log.info("Saved {} barcodes for taskId={}", barcodes.size(), taskId);
|
||||||
} else {
|
} else {
|
||||||
extraDataSummary = "Keine Barcodes gescannt";
|
extraDataSummary = "Keine Barcodes gescannt";
|
||||||
@@ -315,11 +325,8 @@ public class MessageController {
|
|||||||
Object signatureSvgObj = extraData.get("signatureSvg");
|
Object signatureSvgObj = extraData.get("signatureSvg");
|
||||||
if (signatureSvgObj instanceof String signatureSvg) {
|
if (signatureSvgObj instanceof String signatureSvg) {
|
||||||
if (!signatureSvg.isBlank()) {
|
if (!signatureSvg.isBlank()) {
|
||||||
Signature signatureEntry = new Signature(
|
Signature signatureEntry = new Signature(new ObjectId(taskId.toString()), signatureSvg,
|
||||||
new ObjectId(taskId.toString()),
|
task.getCompletedBy());
|
||||||
signatureSvg,
|
|
||||||
task.getCompletedBy()
|
|
||||||
);
|
|
||||||
|
|
||||||
signatureRepository.save(signatureEntry);
|
signatureRepository.save(signatureEntry);
|
||||||
extraDataSummary = "Unterschrift erfasst (SVG, " + signatureSvg.length() + " Zeichen)";
|
extraDataSummary = "Unterschrift erfasst (SVG, " + signatureSvg.length() + " Zeichen)";
|
||||||
@@ -366,12 +373,9 @@ public class MessageController {
|
|||||||
List<String> photos = (List<String>) photosList;
|
List<String> photos = (List<String>) photosList;
|
||||||
|
|
||||||
if (!photos.isEmpty()) {
|
if (!photos.isEmpty()) {
|
||||||
for (String photoString: photos) {
|
for (String photoString : photos) {
|
||||||
Photo photoEntry = new Photo(
|
Photo photoEntry = new Photo(new ObjectId(taskId.toString()), photoString,
|
||||||
new ObjectId(taskId.toString()),
|
task.getCompletedBy());
|
||||||
photoString,
|
|
||||||
task.getCompletedBy()
|
|
||||||
);
|
|
||||||
|
|
||||||
photoRepository.save(photoEntry);
|
photoRepository.save(photoEntry);
|
||||||
}
|
}
|
||||||
@@ -424,8 +428,8 @@ public class MessageController {
|
|||||||
String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
|
String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
|
||||||
String taskDisplayName = task.getDisplayName() != null ? task.getDisplayName() : taskType;
|
String taskDisplayName = task.getDisplayName() != null ? task.getDisplayName() : taskType;
|
||||||
|
|
||||||
jobHistoryService.logTaskCompletion(jobId, taskType, taskIdStr, task.getCompletedBy(),
|
jobHistoryService.logTaskCompletion(jobId, taskType, taskIdStr, task.getCompletedBy(), taskDisplayName,
|
||||||
taskDisplayName, extraDataSummary);
|
extraDataSummary);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to log task completion history for task {}: {}", taskIdStr, e.getMessage());
|
log.warn("Failed to log task completion history for task {}: {}", taskIdStr, e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -441,11 +445,12 @@ public class MessageController {
|
|||||||
// Check if this was the last task and send job completion notification
|
// Check if this was the last task and send job completion notification
|
||||||
emailService.checkAndSendJobCompletionNotification(jobId, completedBy);
|
emailService.checkAndSendJobCompletionNotification(jobId, completedBy);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to send task completion email notification for task {}: {}", taskIdStr, e.getMessage());
|
log.warn("Failed to send task completion email notification for task {}: {}", taskIdStr,
|
||||||
|
e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Task marked completed. taskId={}, completedBy={}, extraData={}",
|
log.info("Task marked completed. taskId={}, completedBy={}, extraData={}", taskIdStr, task.getCompletedBy(),
|
||||||
taskIdStr, task.getCompletedBy(), extraDataSummary);
|
extraDataSummary);
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
log.error("Invalid taskId format for completion: {}", taskIdStr);
|
log.error("Invalid taskId format for completion: {}", taskIdStr);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import lombok.NoArgsConstructor;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO for returning job data with related cargo items and tasks.
|
* DTO for returning job data with related cargo items and tasks. This combines
|
||||||
* This combines Job entity with its associated CargoItems and TaskEntries.
|
* Job entity with its associated CargoItems and TaskEntries.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ public class AppUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ObjectId as string for JSON serialization.
|
* Returns the ObjectId as string for JSON serialization. This ensures that the
|
||||||
* This ensures that the app user id is returned as a string when users are retrieved via API.
|
* app user id is returned as a string when users are retrieved via API.
|
||||||
*/
|
*/
|
||||||
@JsonGetter("id")
|
@JsonGetter("id")
|
||||||
public String getIdAsString() {
|
public String getIdAsString() {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import org.bson.types.ObjectId;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Barcode entity for storing barcode data from task completions.
|
* Barcode entity for storing barcode data from task completions. References the
|
||||||
* References the task ObjectId and stores barcode strings.
|
* task ObjectId and stores barcode strings.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Document(collection = "barcodes")
|
@Document(collection = "barcodes")
|
||||||
|
|||||||
@@ -41,12 +41,11 @@ public class CargoItem {
|
|||||||
private Double heightMm;
|
private Double heightMm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ObjectId as string for JSON serialization.
|
* Returns the ObjectId as string for JSON serialization. This ensures that the
|
||||||
* This ensures that the cargo item id is returned as a string when items are retrieved via API.
|
* cargo item id is returned as a string when items are retrieved via API.
|
||||||
*/
|
*/
|
||||||
@JsonGetter("id")
|
@JsonGetter("id")
|
||||||
public String getIdAsString() {
|
public String getIdAsString() {
|
||||||
return id != null ? id.toString() : null;
|
return id != null ? id.toString() : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import lombok.Data;
|
|||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class Company
|
public class Company {
|
||||||
{
|
|
||||||
private ObjectId id;
|
private ObjectId id;
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import org.springframework.data.mongodb.core.mapping.Field;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Document(collection = "customers")
|
@Document(collection = "customers")
|
||||||
public class Customer
|
public class Customer {
|
||||||
{
|
|
||||||
@Id
|
@Id
|
||||||
private ObjectId id;
|
private ObjectId id;
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,3 @@ public class Invoice {
|
|||||||
private double betrag;
|
private double betrag;
|
||||||
private String beschreibung;
|
private String beschreibung;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ public class Job {
|
|||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ObjectId as string for JSON serialization.
|
* Returns the ObjectId as string for JSON serialization. This ensures that the
|
||||||
* This ensures that the job id is returned as a string when jobs are retrieved via API.
|
* job id is returned as a string when jobs are retrieved via API.
|
||||||
*/
|
*/
|
||||||
@JsonGetter("id")
|
@JsonGetter("id")
|
||||||
public String getIdAsString() {
|
public String getIdAsString() {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import org.bson.types.ObjectId;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Job History entity for tracking all changes made to a job.
|
* Job History entity for tracking all changes made to a job. Each entry
|
||||||
* Each entry represents a single change or action performed on a job.
|
* represents a single change or action performed on a job.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Document(collection = "job_history")
|
@Document(collection = "job_history")
|
||||||
@@ -34,7 +34,8 @@ public class JobHistory {
|
|||||||
private String reason;
|
private String reason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description of what was changed (e.g., "Status changed from CREATED to IN_PROGRESS")
|
* Description of what was changed (e.g., "Status changed from CREATED to
|
||||||
|
* IN_PROGRESS")
|
||||||
*/
|
*/
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@@ -78,8 +79,8 @@ public class JobHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Constructor for detailed history entry
|
// Constructor for detailed history entry
|
||||||
public JobHistory(ObjectId jobId, String reason, String description, String changedBy,
|
public JobHistory(ObjectId jobId, String reason, String description, String changedBy, JobHistoryType changeType,
|
||||||
JobHistoryType changeType, String oldValue, String newValue) {
|
String oldValue, String newValue) {
|
||||||
this(jobId, reason, description, changedBy);
|
this(jobId, reason, description, changedBy);
|
||||||
this.changeType = changeType;
|
this.changeType = changeType;
|
||||||
this.oldValue = oldValue;
|
this.oldValue = oldValue;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Photo entity for storing photo data from task completions.
|
* Photo entity for storing photo data from task completions. References the job
|
||||||
* References the job ObjectId and stores base64 encoded photos.
|
* ObjectId and stores base64 encoded photos.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Document(collection = "photos")
|
@Document(collection = "photos")
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ public class TaskEntry {
|
|||||||
private String completedBy;
|
private String completedBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ObjectId as string for JSON serialization.
|
* Returns the ObjectId as string for JSON serialization. This ensures that the
|
||||||
* This ensures that the task id is returned as a string when jobs are retrieved via API.
|
* task id is returned as a string when jobs are retrieved via API.
|
||||||
*/
|
*/
|
||||||
@JsonGetter("id")
|
@JsonGetter("id")
|
||||||
public String getIdAsString() {
|
public String getIdAsString() {
|
||||||
@@ -54,8 +54,8 @@ public class TaskEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the job ObjectId as string for JSON serialization.
|
* Returns the job ObjectId as string for JSON serialization. This ensures that
|
||||||
* This ensures that the job id is returned as a string instead of ObjectId object.
|
* the job id is returned as a string instead of ObjectId object.
|
||||||
*/
|
*/
|
||||||
@JsonGetter("jobId")
|
@JsonGetter("jobId")
|
||||||
public String getJobIdAsString() {
|
public String getJobIdAsString() {
|
||||||
@@ -66,7 +66,8 @@ public class TaskEntry {
|
|||||||
* Enum for different task types
|
* Enum for different task types
|
||||||
*/
|
*/
|
||||||
public enum TaskType {
|
public enum TaskType {
|
||||||
CONFIRMATION("Bestätigung"),
|
CONFIRMATION(
|
||||||
|
"Bestätigung"),
|
||||||
SIGNATURE("Unterschrift"),
|
SIGNATURE("Unterschrift"),
|
||||||
TODOLIST("To-Do Liste"),
|
TODOLIST("To-Do Liste"),
|
||||||
PHOTO("Foto"),
|
PHOTO("Foto"),
|
||||||
@@ -108,4 +109,3 @@ public class TaskEntry {
|
|||||||
private Map<String, Object> additionalConfig;
|
private Map<String, Object> additionalConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ public class User {
|
|||||||
|
|
||||||
private int usrId;
|
private int usrId;
|
||||||
private String title;
|
private String title;
|
||||||
private String name; // Nachname
|
private String name; // Nachname
|
||||||
private String firstname; // Vorname
|
private String firstname; // Vorname
|
||||||
|
|
||||||
// Firmen-/Adressdaten
|
// Firmen-/Adressdaten
|
||||||
private String company; // Firma
|
private String company; // Firma
|
||||||
private String street; // Straße
|
private String street; // Straße
|
||||||
private String houseNumber; // Hausnr
|
private String houseNumber; // Hausnr
|
||||||
private String addressAddition; // Adresszusatz (optional)
|
private String addressAddition; // Adresszusatz (optional)
|
||||||
private String zip; // Postleitzahl
|
private String zip; // Postleitzahl
|
||||||
private String city; // Stadt
|
private String city; // Stadt
|
||||||
|
|
||||||
@Indexed(unique = true)
|
@Indexed(unique = true)
|
||||||
private String email;
|
private String email;
|
||||||
|
|||||||
@@ -17,13 +17,11 @@ import java.time.LocalDateTime;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Document(collection = "tasks")
|
@Document(collection = "tasks")
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "taskType")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "taskType")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({ @JsonSubTypes.Type(value = ConfirmationTask.class, name = "CONFIRMATION"),
|
||||||
@JsonSubTypes.Type(value = ConfirmationTask.class, name = "CONFIRMATION"),
|
@JsonSubTypes.Type(value = SignatureTask.class, name = "SIGNATURE"),
|
||||||
@JsonSubTypes.Type(value = SignatureTask.class, name = "SIGNATURE"),
|
@JsonSubTypes.Type(value = TodoListTask.class, name = "TODOLIST"),
|
||||||
@JsonSubTypes.Type(value = TodoListTask.class, name = "TODOLIST"),
|
@JsonSubTypes.Type(value = PhotoTask.class, name = "PHOTO"),
|
||||||
@JsonSubTypes.Type(value = PhotoTask.class, name = "PHOTO"),
|
@JsonSubTypes.Type(value = BarcodeTask.class, name = "BARCODE") })
|
||||||
@JsonSubTypes.Type(value = BarcodeTask.class, name = "BARCODE")
|
|
||||||
})
|
|
||||||
public abstract class BaseTask {
|
public abstract class BaseTask {
|
||||||
@Id
|
@Id
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import lombok.NoArgsConstructor;
|
|||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class SignatureTask extends BaseTask {
|
public class SignatureTask extends BaseTask {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTaskType() {
|
public String getTaskType() {
|
||||||
return "SIGNATURE";
|
return "SIGNATURE";
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
package de.assecutor.votianlt.model.task;
|
package de.assecutor.votianlt.model.task;
|
||||||
|
|
||||||
public enum TaskType {
|
public enum TaskType {
|
||||||
CONFIRMATION("Bestätigung"),
|
CONFIRMATION("Bestätigung"), SIGNATURE("Unterschrift"), TODOLIST("To-Do Liste"), PHOTO("Foto"), BARCODE("Barcode");
|
||||||
SIGNATURE("Unterschrift"),
|
|
||||||
TODOLIST("To-Do Liste"),
|
|
||||||
PHOTO("Foto"),
|
|
||||||
BARCODE("Barcode");
|
|
||||||
|
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import org.springframework.context.event.EventListener;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kept for compatibility: The actual MQTT v5 lifecycle is managed by MqttV5ClientManager.
|
* Kept for compatibility: The actual MQTT v5 lifecycle is managed by
|
||||||
* This runner only logs application readiness.
|
* MqttV5ClientManager. This runner only logs application readiness.
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import org.springframework.context.annotation.Lazy;
|
|||||||
/**
|
/**
|
||||||
* Simple MQTT publishing helper to send JSON payloads.
|
* Simple MQTT publishing helper to send JSON payloads.
|
||||||
*
|
*
|
||||||
* Note: In environments where Spring Integration MQTT is unavailable (e.g., offline CI),
|
* Note: In environments where Spring Integration MQTT is unavailable (e.g.,
|
||||||
* this implementation degrades to a no-op publisher that logs the intended message.
|
* offline CI), this implementation degrades to a no-op publisher that logs the
|
||||||
|
* intended message.
|
||||||
*/
|
*/
|
||||||
public interface MqttPublisher {
|
public interface MqttPublisher {
|
||||||
void publishAsJson(String topic, Object payload);
|
void publishAsJson(String topic, Object payload);
|
||||||
|
|
||||||
void publishAsJson(String topic, Object payload, boolean retained);
|
void publishAsJson(String topic, Object payload, boolean retained);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import java.util.Map;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages a single MQTT v5 client connection with Spring lifecycle using HiveMQ MQTT Client.
|
* Manages a single MQTT v5 client connection with Spring lifecycle using HiveMQ
|
||||||
|
* MQTT Client.
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -50,23 +51,15 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
|||||||
String host = uri.getHost();
|
String host = uri.getHost();
|
||||||
int port = 42099;
|
int port = 42099;
|
||||||
|
|
||||||
var builder = Mqtt5Client.builder()
|
var builder = Mqtt5Client.builder().identifier(clientId).serverHost(host).serverPort(port);
|
||||||
.identifier(clientId)
|
|
||||||
.serverHost(host)
|
|
||||||
.serverPort(port);
|
|
||||||
if (props.isAutomaticReconnect()) {
|
if (props.isAutomaticReconnect()) {
|
||||||
builder = builder.automaticReconnectWithDefaultConfig();
|
builder = builder.automaticReconnectWithDefaultConfig();
|
||||||
}
|
}
|
||||||
client = builder.buildAsync();
|
client = builder.buildAsync();
|
||||||
|
|
||||||
var connect = client.connectWith()
|
var connect = client.connectWith().cleanStart(props.isCleanStart()).keepAlive(props.getKeepAlive())
|
||||||
.cleanStart(props.isCleanStart())
|
.sessionExpiryInterval(props.getSessionExpiryInterval()).simpleAuth().username("app")
|
||||||
.keepAlive(props.getKeepAlive())
|
.password("apppwd".getBytes(StandardCharsets.UTF_8)).applySimpleAuth();
|
||||||
.sessionExpiryInterval(props.getSessionExpiryInterval())
|
|
||||||
.simpleAuth()
|
|
||||||
.username("app")
|
|
||||||
.password("apppwd".getBytes(StandardCharsets.UTF_8))
|
|
||||||
.applySimpleAuth();
|
|
||||||
|
|
||||||
log.info("[MQTT] Connecting to {} with clientId={} ...", props.getBrokerUri(), clientId);
|
log.info("[MQTT] Connecting to {} with clientId={} ...", props.getBrokerUri(), clientId);
|
||||||
connect.send().join();
|
connect.send().join();
|
||||||
@@ -86,15 +79,9 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to topics with QoS
|
// Subscribe to topics with QoS
|
||||||
String[] topics = new String[]{
|
String[] topics = new String[] { "/server/+/task/photo/completed", "/server/+/task/confirm",
|
||||||
"/server/+/task/photo/completed",
|
"/server/+/task/completed", "/server/+/task_completed", "/server/+/job/status",
|
||||||
"/server/+/task/confirm",
|
"/server/+/jobs/assigned", "/server/login" };
|
||||||
"/server/+/task/completed",
|
|
||||||
"/server/+/task_completed",
|
|
||||||
"/server/+/job/status",
|
|
||||||
"/server/+/jobs/assigned",
|
|
||||||
"/server/login"
|
|
||||||
};
|
|
||||||
MqttQos qos = mapQos(props.getDefaultQos());
|
MqttQos qos = mapQos(props.getDefaultQos());
|
||||||
for (String topic : topics) {
|
for (String topic : topics) {
|
||||||
client.subscribeWith().topicFilter(topic).qos(qos).send().join();
|
client.subscribeWith().topicFilter(topic).qos(qos).send().join();
|
||||||
@@ -123,7 +110,8 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
|||||||
private void handleInbound(String topic, byte[] payload) {
|
private void handleInbound(String topic, byte[] payload) {
|
||||||
String json = new String(payload, StandardCharsets.UTF_8);
|
String json = new String(payload, StandardCharsets.UTF_8);
|
||||||
try {
|
try {
|
||||||
Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String, Object>>(){});
|
Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {
|
||||||
|
});
|
||||||
routeInbound(topic, map);
|
routeInbound(topic, map);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("Failed to parse inbound MQTT JSON on {}: {}", topic, ex.getMessage());
|
log.error("Failed to parse inbound MQTT JSON on {}: {}", topic, ex.getMessage());
|
||||||
@@ -134,8 +122,10 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
|||||||
try {
|
try {
|
||||||
// The consolidated topic /server/{clientId}/task_completed is used by apps to
|
// The consolidated topic /server/{clientId}/task_completed is used by apps to
|
||||||
// report completion of any task type. Only PHOTO and CONFIRMATION require
|
// report completion of any task type. Only PHOTO and CONFIRMATION require
|
||||||
// specialized processing on the server side. All other task types are handled by the
|
// specialized processing on the server side. All other task types are handled
|
||||||
// generic handler handleTaskCompleted(). This keeps routing simple while allowing
|
// by the
|
||||||
|
// generic handler handleTaskCompleted(). This keeps routing simple while
|
||||||
|
// allowing
|
||||||
// special logic (e.g., photo persistence) where necessary.
|
// special logic (e.g., photo persistence) where necessary.
|
||||||
if (topic.matches("/server/.+/task_completed")) {
|
if (topic.matches("/server/.+/task_completed")) {
|
||||||
try {
|
try {
|
||||||
@@ -161,7 +151,8 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
|||||||
}
|
}
|
||||||
} else if (topic.equals("/server/login")) {
|
} else if (topic.equals("/server/login")) {
|
||||||
var om = new ObjectMapper();
|
var om = new ObjectMapper();
|
||||||
de.assecutor.votianlt.dto.AppLoginRequest req = om.convertValue(payload, de.assecutor.votianlt.dto.AppLoginRequest.class);
|
de.assecutor.votianlt.dto.AppLoginRequest req = om.convertValue(payload,
|
||||||
|
de.assecutor.votianlt.dto.AppLoginRequest.class);
|
||||||
messageController.handleAppLogin(req);
|
messageController.handleAppLogin(req);
|
||||||
} else {
|
} else {
|
||||||
log.debug("No route for topic {}", topic);
|
log.debug("No route for topic {}", topic);
|
||||||
@@ -192,9 +183,9 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
|||||||
|
|
||||||
private MqttQos mapQos(int q) {
|
private MqttQos mapQos(int q) {
|
||||||
return switch (q) {
|
return switch (q) {
|
||||||
case 0 -> MqttQos.AT_MOST_ONCE;
|
case 0 -> MqttQos.AT_MOST_ONCE;
|
||||||
case 1 -> MqttQos.AT_LEAST_ONCE;
|
case 1 -> MqttQos.AT_LEAST_ONCE;
|
||||||
default -> MqttQos.EXACTLY_ONCE;
|
default -> MqttQos.EXACTLY_ONCE;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,12 +195,7 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
|||||||
log.warn("[MQTT] Not connected, dropping publish topic={}", topic);
|
log.warn("[MQTT] Not connected, dropping publish topic={}", topic);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.publishWith()
|
client.publishWith().topic(topic).payload(payload).qos(mapQos(qos)).retain(retained).send()
|
||||||
.topic(topic)
|
|
||||||
.payload(payload)
|
|
||||||
.qos(mapQos(qos))
|
|
||||||
.retain(retained)
|
|
||||||
.send()
|
|
||||||
.whenComplete((ack, ex) -> {
|
.whenComplete((ack, ex) -> {
|
||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
log.error("Failed to publish to {}: {}", topic, ex.getMessage(), ex);
|
log.error("Failed to publish to {}: {}", topic, ex.getMessage(), ex);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import de.assecutor.votianlt.pages.view.EditProfileView;
|
|||||||
import de.assecutor.votianlt.security.SecurityService;
|
import de.assecutor.votianlt.security.SecurityService;
|
||||||
|
|
||||||
import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||||
|
|
||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
|
|
||||||
@Layout
|
@Layout
|
||||||
@@ -37,8 +38,9 @@ public final class MainLayout extends AppLayout {
|
|||||||
public MainLayout(SecurityService securityService) {
|
public MainLayout(SecurityService securityService) {
|
||||||
this.securityService = securityService;
|
this.securityService = securityService;
|
||||||
setPrimarySection(Section.DRAWER);
|
setPrimarySection(Section.DRAWER);
|
||||||
|
|
||||||
// Always build the drawer; keep references and toggle visibility on attach and after navigation
|
// Always build the drawer; keep references and toggle visibility on attach and
|
||||||
|
// after navigation
|
||||||
headerRef = createHeader();
|
headerRef = createHeader();
|
||||||
navRef = new Scroller(createSideNav());
|
navRef = new Scroller(createSideNav());
|
||||||
userMenuRef = createUserMenu();
|
userMenuRef = createUserMenu();
|
||||||
@@ -52,9 +54,12 @@ public final class MainLayout extends AppLayout {
|
|||||||
|
|
||||||
private void updateDrawerVisibility() {
|
private void updateDrawerVisibility() {
|
||||||
boolean loggedIn = securityService.isUserLoggedIn();
|
boolean loggedIn = securityService.isUserLoggedIn();
|
||||||
if (headerRef != null) headerRef.setVisible( loggedIn );
|
if (headerRef != null)
|
||||||
if (navRef != null) navRef.setVisible( loggedIn );
|
headerRef.setVisible(loggedIn);
|
||||||
if (userMenuRef != null) userMenuRef.setVisible( loggedIn );
|
if (navRef != null)
|
||||||
|
navRef.setVisible(loggedIn);
|
||||||
|
if (userMenuRef != null)
|
||||||
|
userMenuRef.setVisible(loggedIn);
|
||||||
setDrawerOpened(loggedIn);
|
setDrawerOpened(loggedIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +126,8 @@ public final class MainLayout extends AppLayout {
|
|||||||
userContent.add(profile, myInvoices, imprint);
|
userContent.add(profile, myInvoices, imprint);
|
||||||
userDetails.add(userContent);
|
userDetails.add(userContent);
|
||||||
|
|
||||||
// Create a vertical layout to hold both regular menu items and collapsible sections
|
// Create a vertical layout to hold both regular menu items and collapsible
|
||||||
|
// sections
|
||||||
VerticalLayout navContainer = new VerticalLayout();
|
VerticalLayout navContainer = new VerticalLayout();
|
||||||
navContainer.setPadding(false);
|
navContainer.setPadding(false);
|
||||||
navContainer.setSpacing(false);
|
navContainer.setSpacing(false);
|
||||||
@@ -150,13 +156,12 @@ public final class MainLayout extends AppLayout {
|
|||||||
avatar.setColorIndex(5);
|
avatar.setColorIndex(5);
|
||||||
|
|
||||||
var userNameSpan = new Span();
|
var userNameSpan = new Span();
|
||||||
|
|
||||||
var userMenuItem = userMenu.addItem(avatar);
|
var userMenuItem = userMenu.addItem(avatar);
|
||||||
userMenuItem.add(userNameSpan);
|
userMenuItem.add(userNameSpan);
|
||||||
|
|
||||||
// Profil anzeigen mit Navigation
|
// Profil anzeigen mit Navigation
|
||||||
userMenuItem.getSubMenu().addItem("Profil anzeigen", e ->
|
userMenuItem.getSubMenu().addItem("Profil anzeigen", e -> UI.getCurrent().navigate(EditProfileView.class));
|
||||||
UI.getCurrent().navigate(EditProfileView.class));
|
|
||||||
userMenuItem.getSubMenu().addItem("Einstellungen");
|
userMenuItem.getSubMenu().addItem("Einstellungen");
|
||||||
userMenuItem.getSubMenu().addItem("Abmelden", e -> securityService.logout());
|
userMenuItem.getSubMenu().addItem("Abmelden", e -> securityService.logout());
|
||||||
|
|
||||||
@@ -166,7 +171,7 @@ public final class MainLayout extends AppLayout {
|
|||||||
avatar.setName(currentUser);
|
avatar.setName(currentUser);
|
||||||
userNameSpan.setText(currentUser);
|
userNameSpan.setText(currentUser);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial und bei Attach aktualisieren
|
// Initial und bei Attach aktualisieren
|
||||||
updateUserInfo.run();
|
updateUserInfo.run();
|
||||||
addAttachListener(e -> updateUserInfo.run());
|
addAttachListener(e -> updateUserInfo.run());
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ import org.springframework.data.mongodb.repository.MongoRepository;
|
|||||||
|
|
||||||
public interface AddCompanyRepository extends MongoRepository<Company, String> {
|
public interface AddCompanyRepository extends MongoRepository<Company, String> {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ import org.springframework.data.mongodb.repository.MongoRepository;
|
|||||||
|
|
||||||
public interface AddCustomerRepository extends MongoRepository<Customer, ObjectId> {
|
public interface AddCustomerRepository extends MongoRepository<Customer, ObjectId> {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,5 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface LoginRepository extends MongoRepository<User, String> {
|
public interface LoginRepository extends MongoRepository<User, String> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Optional<User> findByEmail(String email);
|
Optional<User> findByEmail(String email);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ public class AddCompanyService {
|
|||||||
this.addCompanyRepository = addCompanyRepository;
|
this.addCompanyRepository = addCompanyRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void addCompany(Company company) {
|
public void addCompany(Company company) {
|
||||||
addCompanyRepository.save(company);
|
addCompanyRepository.save(company);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ public class AddCustomerService {
|
|||||||
this.securityService = securityService;
|
this.securityService = securityService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void addCustomer(Customer customer) {
|
public void addCustomer(Customer customer) {
|
||||||
// Setze den aktuellen Benutzer als Ersteller - jetzt direkt aus der Session
|
// Setze den aktuellen Benutzer als Ersteller - jetzt direkt aus der Session
|
||||||
de.assecutor.votianlt.model.User currentUser = securityService.getCurrentDatabaseUser();
|
de.assecutor.votianlt.model.User currentUser = securityService.getCurrentDatabaseUser();
|
||||||
|
|||||||
@@ -32,10 +32,14 @@ public class AddJobService {
|
|||||||
private final SecurityService securityService;
|
private final SecurityService securityService;
|
||||||
private final JobHistoryService jobHistoryService;
|
private final JobHistoryService jobHistoryService;
|
||||||
private final EmailService emailService;
|
private final EmailService emailService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speichert einen neuen Auftrag samt CargoItems und Tasks
|
* Speichert einen neuen Auftrag samt CargoItems und Tasks
|
||||||
* @param job der Auftrag
|
*
|
||||||
* @param transientCargo zugehörige, noch nicht gespeicherte CargoItems aus der View
|
* @param job
|
||||||
|
* der Auftrag
|
||||||
|
* @param transientCargo
|
||||||
|
* zugehörige, noch nicht gespeicherte CargoItems aus der View
|
||||||
*/
|
*/
|
||||||
public Job addJobWithCargo(Job job, List<CargoItem> transientCargo, List<BaseTask> transientTasks) {
|
public Job addJobWithCargo(Job job, List<CargoItem> transientCargo, List<BaseTask> transientTasks) {
|
||||||
try {
|
try {
|
||||||
@@ -58,10 +62,8 @@ public class AddJobService {
|
|||||||
|
|
||||||
// CargoItems separat mit Referenz auf Job speichern, IDs im Job verknüpfen
|
// CargoItems separat mit Referenz auf Job speichern, IDs im Job verknüpfen
|
||||||
if (transientCargo != null && !transientCargo.isEmpty()) {
|
if (transientCargo != null && !transientCargo.isEmpty()) {
|
||||||
List<CargoItem> itemsWithJob = transientCargo.stream()
|
List<CargoItem> itemsWithJob = transientCargo.stream().filter(Objects::nonNull)
|
||||||
.filter(Objects::nonNull)
|
.filter(ci -> ci.getDescription() != null && !ci.getDescription().isBlank()).map(ci -> {
|
||||||
.filter(ci -> ci.getDescription() != null && !ci.getDescription().isBlank())
|
|
||||||
.map(ci -> {
|
|
||||||
CargoItem copy = new CargoItem();
|
CargoItem copy = new CargoItem();
|
||||||
copy.setJobId(jobId);
|
copy.setJobId(jobId);
|
||||||
copy.setDescription(ci.getDescription());
|
copy.setDescription(ci.getDescription());
|
||||||
@@ -75,13 +77,12 @@ public class AddJobService {
|
|||||||
cargoItemRepository.saveAll(itemsWithJob);
|
cargoItemRepository.saveAll(itemsWithJob);
|
||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tasks separat speichern und referenzieren mit korrekter Nummerierung
|
// Tasks separat speichern und referenzieren mit korrekter Nummerierung
|
||||||
if (transientTasks != null && !transientTasks.isEmpty()) {
|
if (transientTasks != null && !transientTasks.isEmpty()) {
|
||||||
var filteredTasks = transientTasks.stream()
|
var filteredTasks = transientTasks.stream().filter(Objects::nonNull)
|
||||||
.filter(Objects::nonNull)
|
.filter(task -> task.getTaskType() != null) // Filter nach TaskType statt Text
|
||||||
.filter(task -> task.getTaskType() != null) // Filter nach TaskType statt Text
|
.toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
// Setze JobId und stelle sicher, dass taskOrder korrekt ist
|
// Setze JobId und stelle sicher, dass taskOrder korrekt ist
|
||||||
for (int i = 0; i < filteredTasks.size(); i++) {
|
for (int i = 0; i < filteredTasks.size(); i++) {
|
||||||
@@ -113,7 +114,8 @@ public class AddJobService {
|
|||||||
try {
|
try {
|
||||||
emailService.sendJobCreationNotification(savedJob.getId(), savedJob.getCreatedBy());
|
emailService.sendJobCreationNotification(savedJob.getId(), savedJob.getCreatedBy());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to send job creation email notification for job {}: {}", savedJob.getIdAsString(), e.getMessage());
|
log.warn("Failed to send job creation email notification for job {}: {}", savedJob.getIdAsString(),
|
||||||
|
e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Auftrag erfolgreich gespeichert: {}", savedJob.getJobNumber());
|
log.info("Auftrag erfolgreich gespeichert: {}", savedJob.getJobNumber());
|
||||||
@@ -135,8 +137,7 @@ public class AddJobService {
|
|||||||
// Zähle Aufträge des aktuellen Tages
|
// Zähle Aufträge des aktuellen Tages
|
||||||
String todayPrefix = prefix + timestamp;
|
String todayPrefix = prefix + timestamp;
|
||||||
long todayCount = jobRepository.findAll().stream()
|
long todayCount = jobRepository.findAll().stream()
|
||||||
.filter(job -> job.getJobNumber() != null && job.getJobNumber().startsWith(todayPrefix))
|
.filter(job -> job.getJobNumber() != null && job.getJobNumber().startsWith(todayPrefix)).count();
|
||||||
.count();
|
|
||||||
|
|
||||||
// Generiere neue Nummer
|
// Generiere neue Nummer
|
||||||
String jobNumber;
|
String jobNumber;
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ public class AppDeviceService {
|
|||||||
// Set creation and update metadata
|
// Set creation and update metadata
|
||||||
appDevice.setErstelltAm(java.time.LocalDateTime.now());
|
appDevice.setErstelltAm(java.time.LocalDateTime.now());
|
||||||
appDevice.setAktualisiertAm(java.time.LocalDateTime.now());
|
appDevice.setAktualisiertAm(java.time.LocalDateTime.now());
|
||||||
|
|
||||||
// Set creator and updater - current user ID is required
|
// Set creator and updater - current user ID is required
|
||||||
ObjectId currentUserId = securityService.getCurrentUserId();
|
ObjectId currentUserId = securityService.getCurrentUserId();
|
||||||
appDevice.setErstelltVon(currentUserId);
|
appDevice.setErstelltVon(currentUserId);
|
||||||
appDevice.setAktualisiertVon(currentUserId);
|
appDevice.setAktualisiertVon(currentUserId);
|
||||||
|
|
||||||
return appDeviceRepository.save(appDevice);
|
return appDeviceRepository.save(appDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,17 +32,17 @@ public class AppUserService {
|
|||||||
String hashedPassword = passwordEncoder.encode(appUser.getPassword());
|
String hashedPassword = passwordEncoder.encode(appUser.getPassword());
|
||||||
appUser.setPassword(hashedPassword);
|
appUser.setPassword(hashedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set creation and update metadata
|
// Set creation and update metadata
|
||||||
appUser.setErstelltAm(java.time.LocalDateTime.now());
|
appUser.setErstelltAm(java.time.LocalDateTime.now());
|
||||||
appUser.setAktualisiertAm(java.time.LocalDateTime.now());
|
appUser.setAktualisiertAm(java.time.LocalDateTime.now());
|
||||||
|
|
||||||
// Set creator and updater - current user ID is required
|
// Set creator and updater - current user ID is required
|
||||||
ObjectId currentUserId = securityService.getCurrentUserId();
|
ObjectId currentUserId = securityService.getCurrentUserId();
|
||||||
appUser.setErstelltVon(currentUserId);
|
appUser.setErstelltVon(currentUserId);
|
||||||
appUser.setAktualisiertVon(currentUserId);
|
appUser.setAktualisiertVon(currentUserId);
|
||||||
appUser.setOwner(currentUserId);
|
appUser.setOwner(currentUserId);
|
||||||
|
|
||||||
return appUserRepository.save(appUser);
|
return appUserRepository.save(appUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,22 +62,26 @@ public class AppUserService {
|
|||||||
public AppUser updateAppUser(AppUser appUser) {
|
public AppUser updateAppUser(AppUser appUser) {
|
||||||
// Hash the password if it's being updated and not empty
|
// Hash the password if it's being updated and not empty
|
||||||
if (appUser.getPassword() != null && !appUser.getPassword().isEmpty()) {
|
if (appUser.getPassword() != null && !appUser.getPassword().isEmpty()) {
|
||||||
// Only hash if it's not already hashed (BCrypt hashes start with $2a$, $2b$, or $2y$)
|
// Only hash if it's not already hashed (BCrypt hashes start with $2a$, $2b$, or
|
||||||
|
// $2y$)
|
||||||
if (!appUser.getPassword().startsWith("$2")) {
|
if (!appUser.getPassword().startsWith("$2")) {
|
||||||
String hashedPassword = passwordEncoder.encode(appUser.getPassword());
|
String hashedPassword = passwordEncoder.encode(appUser.getPassword());
|
||||||
appUser.setPassword(hashedPassword);
|
appUser.setPassword(hashedPassword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appUser.setAktualisiertAm(java.time.LocalDateTime.now());
|
appUser.setAktualisiertAm(java.time.LocalDateTime.now());
|
||||||
appUser.setAktualisiertVon(securityService.getCurrentUserId());
|
appUser.setAktualisiertVon(securityService.getCurrentUserId());
|
||||||
return appUserRepository.save(appUser);
|
return appUserRepository.save(appUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify a plain text password against the stored hashed password
|
* Verify a plain text password against the stored hashed password
|
||||||
* @param plainPassword The plain text password to verify
|
*
|
||||||
* @param hashedPassword The stored BCrypt hashed password
|
* @param plainPassword
|
||||||
|
* The plain text password to verify
|
||||||
|
* @param hashedPassword
|
||||||
|
* The stored BCrypt hashed password
|
||||||
* @return true if the password matches, false otherwise
|
* @return true if the password matches, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean verifyPassword(String plainPassword, String hashedPassword) {
|
public boolean verifyPassword(String plainPassword, String hashedPassword) {
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ public class CustomerService {
|
|||||||
return todoRepository.findAllBy(pageable).toList();
|
return todoRepository.findAllBy(pageable).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public List<Customer> findAll() {
|
public List<Customer> findAll() {
|
||||||
return todoRepository.findAll();
|
return todoRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import de.assecutor.votianlt.model.AppUser;
|
|||||||
import de.assecutor.votianlt.model.User;
|
import de.assecutor.votianlt.model.User;
|
||||||
import de.assecutor.votianlt.repository.AppUserRepository;
|
import de.assecutor.votianlt.repository.AppUserRepository;
|
||||||
import de.assecutor.votianlt.repository.UserRepository;
|
import de.assecutor.votianlt.repository.UserRepository;
|
||||||
import de.assecutor.votianlt.util.MailUtil;
|
import de.assecutor.votianlt.service.EmailService;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -17,33 +17,35 @@ import java.util.Optional;
|
|||||||
@Service
|
@Service
|
||||||
public class PasswordResetService {
|
public class PasswordResetService {
|
||||||
|
|
||||||
public enum UserType { USERS, APP_USER }
|
public enum UserType {
|
||||||
|
USERS, APP_USER
|
||||||
|
}
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final AppUserRepository appUserRepository;
|
private final AppUserRepository appUserRepository;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final MailUtil mailUtil;
|
private final EmailService emailService;
|
||||||
private static final Duration TOKEN_VALIDITY = Duration.ofMinutes(15);
|
private static final Duration TOKEN_VALIDITY = Duration.ofMinutes(15);
|
||||||
|
|
||||||
public PasswordResetService(UserRepository userRepository,
|
public PasswordResetService(UserRepository userRepository, AppUserRepository appUserRepository,
|
||||||
AppUserRepository appUserRepository,
|
PasswordEncoder passwordEncoder, EmailService emailService) {
|
||||||
PasswordEncoder passwordEncoder,
|
|
||||||
MailUtil mailUtil) {
|
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.appUserRepository = appUserRepository;
|
this.appUserRepository = appUserRepository;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.mailUtil = mailUtil;
|
this.emailService = emailService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate reset without asking for user type. Looks up the email in both collections
|
* Initiate reset without asking for user type. Looks up the email in both
|
||||||
* and only proceeds if it exists in exactly one of them. Otherwise, it silently returns
|
* collections and only proceeds if it exists in exactly one of them. Otherwise,
|
||||||
* to avoid leaking account existence.
|
* it silently returns to avoid leaking account existence.
|
||||||
*/
|
*/
|
||||||
public void initiateResetAuto(String email, String baseUrl) {
|
public void initiateResetAuto(String email, String baseUrl) {
|
||||||
if (email == null) return;
|
if (email == null)
|
||||||
|
return;
|
||||||
String normalized = email.trim();
|
String normalized = email.trim();
|
||||||
if (normalized.isEmpty()) return;
|
if (normalized.isEmpty())
|
||||||
|
return;
|
||||||
var userOpt = userRepository.findByEmail(normalized);
|
var userOpt = userRepository.findByEmail(normalized);
|
||||||
var appUser = appUserRepository.findByEmail(normalized);
|
var appUser = appUserRepository.findByEmail(normalized);
|
||||||
boolean inUsers = userOpt.isPresent();
|
boolean inUsers = userOpt.isPresent();
|
||||||
@@ -63,75 +65,80 @@ public class PasswordResetService {
|
|||||||
String link = baseUrl + "/forget-password?token=" + token + "&type=" + typeParam;
|
String link = baseUrl + "/forget-password?token=" + token + "&type=" + typeParam;
|
||||||
|
|
||||||
switch (userType) {
|
switch (userType) {
|
||||||
case USERS -> {
|
case USERS -> {
|
||||||
Optional<User> optional = userRepository.findByEmail(email);
|
Optional<User> optional = userRepository.findByEmail(email);
|
||||||
if (optional.isEmpty()) {
|
if (optional.isEmpty()) {
|
||||||
// Do not leak existence; simply return
|
// Do not leak existence; simply return
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
User user = optional.get();
|
|
||||||
user.setPasswordCode(token);
|
|
||||||
user.setPasswordTimestamp(now);
|
|
||||||
userRepository.save(user);
|
|
||||||
sendMail(email, link);
|
|
||||||
}
|
}
|
||||||
case APP_USER -> {
|
User user = optional.get();
|
||||||
AppUser appUser = appUserRepository.findByEmail(email);
|
user.setPasswordCode(token);
|
||||||
if (appUser == null) {
|
user.setPasswordTimestamp(now);
|
||||||
return;
|
userRepository.save(user);
|
||||||
}
|
sendMail(email, link);
|
||||||
appUser.setPasswordCode(token);
|
}
|
||||||
appUser.setPasswordTimestamp(now);
|
case APP_USER -> {
|
||||||
appUserRepository.save(appUser);
|
AppUser appUser = appUserRepository.findByEmail(email);
|
||||||
sendMail(email, link);
|
if (appUser == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
appUser.setPasswordCode(token);
|
||||||
|
appUser.setPasswordTimestamp(now);
|
||||||
|
appUserRepository.save(appUser);
|
||||||
|
sendMail(email, link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMail(String to, String link) {
|
private void sendMail(String to, String link) {
|
||||||
String subject = "Passwort zurücksetzen";
|
String subject = "Passwort zurücksetzen";
|
||||||
String body = "Hallo,\n\n" +
|
String body = "Hallo,\n\n" + "Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt. "
|
||||||
"Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt. " +
|
+ "Dieser Link ist 15 Minuten gültig:\n" + link + "\n\n"
|
||||||
"Dieser Link ist 15 Minuten gültig:\n" + link + "\n\n" +
|
+ "Wenn Sie diese Anfrage nicht gestellt haben, können Sie diese E-Mail ignorieren.";
|
||||||
"Wenn Sie diese Anfrage nicht gestellt haben, können Sie diese E-Mail ignorieren.";
|
|
||||||
try {
|
try {
|
||||||
mailUtil.sendMail(to, subject, body);
|
emailService.sendSimpleEmail(to, subject, body);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// In this minimal implementation we swallow to avoid leaking details to attackers
|
// In this minimal implementation we swallow to avoid leaking details to
|
||||||
|
// attackers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTokenValid(String token, UserType userType) {
|
public boolean isTokenValid(String token, UserType userType) {
|
||||||
LocalDateTime ts = switch (userType) {
|
LocalDateTime ts = switch (userType) {
|
||||||
case USERS -> userRepository.findByPasswordCode(token).map(User::getPasswordTimestamp).orElse(null);
|
case USERS -> userRepository.findByPasswordCode(token).map(User::getPasswordTimestamp).orElse(null);
|
||||||
case APP_USER -> Optional.ofNullable(appUserRepository.findByPasswordCode(token)).map(AppUser::getPasswordTimestamp).orElse(null);
|
case APP_USER -> Optional.ofNullable(appUserRepository.findByPasswordCode(token))
|
||||||
|
.map(AppUser::getPasswordTimestamp).orElse(null);
|
||||||
};
|
};
|
||||||
if (ts == null) return false;
|
if (ts == null)
|
||||||
|
return false;
|
||||||
return Duration.between(ts, LocalDateTime.now()).compareTo(TOKEN_VALIDITY) <= 0;
|
return Duration.between(ts, LocalDateTime.now()).compareTo(TOKEN_VALIDITY) <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean resetPassword(String token, UserType userType, String newPassword) {
|
public boolean resetPassword(String token, UserType userType, String newPassword) {
|
||||||
if (!isTokenValid(token, userType)) return false;
|
if (!isTokenValid(token, userType))
|
||||||
|
return false;
|
||||||
switch (userType) {
|
switch (userType) {
|
||||||
case USERS -> {
|
case USERS -> {
|
||||||
Optional<User> optional = userRepository.findByPasswordCode(token);
|
Optional<User> optional = userRepository.findByPasswordCode(token);
|
||||||
if (optional.isEmpty()) return false;
|
if (optional.isEmpty())
|
||||||
User user = optional.get();
|
return false;
|
||||||
user.setPassword(passwordEncoder.encode(newPassword));
|
User user = optional.get();
|
||||||
user.setPasswordCode(null);
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
user.setPasswordTimestamp(null);
|
user.setPasswordCode(null);
|
||||||
userRepository.save(user);
|
user.setPasswordTimestamp(null);
|
||||||
return true;
|
userRepository.save(user);
|
||||||
}
|
return true;
|
||||||
case APP_USER -> {
|
}
|
||||||
AppUser appUser = appUserRepository.findByPasswordCode(token);
|
case APP_USER -> {
|
||||||
if (appUser == null) return false;
|
AppUser appUser = appUserRepository.findByPasswordCode(token);
|
||||||
appUser.setPassword(passwordEncoder.encode(newPassword));
|
if (appUser == null)
|
||||||
appUser.setPasswordCode(null);
|
return false;
|
||||||
appUser.setPasswordTimestamp(null);
|
appUser.setPassword(passwordEncoder.encode(newPassword));
|
||||||
appUserRepository.save(appUser);
|
appUser.setPasswordCode(null);
|
||||||
return true;
|
appUser.setPasswordTimestamp(null);
|
||||||
}
|
appUserRepository.save(appUser);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ public class RegisterService {
|
|||||||
|
|
||||||
public void registerUser(User user) {
|
public void registerUser(User user) {
|
||||||
registerRepository.save(user);
|
registerRepository.save(user);
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,37 +19,37 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
|
|
||||||
@PageTitle("Neues Endgerät anlegen")
|
@PageTitle("Neues Endgerät anlegen")
|
||||||
@Route(value = "add-app-device", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "add-app-device", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class AddAppDeviceView extends VerticalLayout {
|
public class AddAppDeviceView extends VerticalLayout {
|
||||||
|
|
||||||
private final AppDeviceService appDeviceService;
|
private final AppDeviceService appDeviceService;
|
||||||
private final Binder<AppDevice> binder;
|
private final Binder<AppDevice> binder;
|
||||||
|
|
||||||
// Formularfelder
|
// Formularfelder
|
||||||
private final TextField nameField;
|
private final TextField nameField;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public AddAppDeviceView(AppDeviceService appDeviceService) {
|
public AddAppDeviceView(AppDeviceService appDeviceService) {
|
||||||
this.appDeviceService = appDeviceService;
|
this.appDeviceService = appDeviceService;
|
||||||
|
|
||||||
// Binder initialisieren
|
// Binder initialisieren
|
||||||
binder = new Binder<>(AppDevice.class);
|
binder = new Binder<>(AppDevice.class);
|
||||||
|
|
||||||
// Formularfelder erstellen
|
// Formularfelder erstellen
|
||||||
nameField = new TextField("Gerätename");
|
nameField = new TextField("Gerätename");
|
||||||
nameField.setRequired(true);
|
nameField.setRequired(true);
|
||||||
nameField.setPlaceholder("z.B. iPhone 15, Samsung Galaxy S24");
|
nameField.setPlaceholder("z.B. iPhone 15, Samsung Galaxy S24");
|
||||||
nameField.setWidth("100%");
|
nameField.setWidth("100%");
|
||||||
|
|
||||||
// Layout konfigurieren
|
// Layout konfigurieren
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
setSpacing(true);
|
setSpacing(true);
|
||||||
|
|
||||||
// Content zentrieren
|
// Content zentrieren
|
||||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
// Hauptcontainer erstellen
|
// Hauptcontainer erstellen
|
||||||
VerticalLayout contentContainer = new VerticalLayout();
|
VerticalLayout contentContainer = new VerticalLayout();
|
||||||
contentContainer.setWidth("600px");
|
contentContainer.setWidth("600px");
|
||||||
@@ -59,78 +59,79 @@ public class AddAppDeviceView extends VerticalLayout {
|
|||||||
contentContainer.getStyle().set("box-shadow", "0 2px 8px rgba(0,0,0,0.1)");
|
contentContainer.getStyle().set("box-shadow", "0 2px 8px rgba(0,0,0,0.1)");
|
||||||
contentContainer.setPadding(true);
|
contentContainer.setPadding(true);
|
||||||
contentContainer.setSpacing(true);
|
contentContainer.setSpacing(true);
|
||||||
|
|
||||||
// Titel
|
// Titel
|
||||||
H2 title = new H2("Neues Endgerät anlegen");
|
H2 title = new H2("Neues Endgerät anlegen");
|
||||||
title.getStyle().set("margin", "0");
|
title.getStyle().set("margin", "0");
|
||||||
title.getStyle().set("text-align", "center");
|
title.getStyle().set("text-align", "center");
|
||||||
contentContainer.add(title);
|
contentContainer.add(title);
|
||||||
|
|
||||||
// Formular
|
// Formular
|
||||||
FormLayout formLayout = new FormLayout();
|
FormLayout formLayout = new FormLayout();
|
||||||
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
|
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
|
||||||
formLayout.add(nameField);
|
formLayout.add(nameField);
|
||||||
contentContainer.add(formLayout);
|
contentContainer.add(formLayout);
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||||
buttonLayout.setWidthFull();
|
buttonLayout.setWidthFull();
|
||||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
buttonLayout.setSpacing(true);
|
buttonLayout.setSpacing(true);
|
||||||
|
|
||||||
Button backButton = new Button("Zurück");
|
Button backButton = new Button("Zurück");
|
||||||
backButton.addClickListener(e -> navigateBack());
|
backButton.addClickListener(e -> navigateBack());
|
||||||
|
|
||||||
Button saveButton = new Button("Speichern");
|
Button saveButton = new Button("Speichern");
|
||||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
saveButton.addClickListener(e -> createAppDevice());
|
saveButton.addClickListener(e -> createAppDevice());
|
||||||
|
|
||||||
buttonLayout.add(backButton, saveButton);
|
buttonLayout.add(backButton, saveButton);
|
||||||
contentContainer.add(buttonLayout);
|
contentContainer.add(buttonLayout);
|
||||||
|
|
||||||
add(contentContainer);
|
add(contentContainer);
|
||||||
|
|
||||||
// Testdaten einfügen
|
// Testdaten einfügen
|
||||||
populateTestData();
|
populateTestData();
|
||||||
|
|
||||||
// Binder konfigurieren
|
// Binder konfigurieren
|
||||||
setupBinder();
|
setupBinder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupBinder() {
|
private void setupBinder() {
|
||||||
binder.forField(nameField)
|
binder.forField(nameField).asRequired("Gerätename ist erforderlich").bind(AppDevice::getName,
|
||||||
.asRequired("Gerätename ist erforderlich")
|
AppDevice::setName);
|
||||||
.bind(AppDevice::getName, AppDevice::setName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateTestData() {
|
private void populateTestData() {
|
||||||
nameField.setValue("iPhone 15 Pro");
|
nameField.setValue("iPhone 15 Pro");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAppDevice() {
|
private void createAppDevice() {
|
||||||
if (binder.validate().isOk()) {
|
if (binder.validate().isOk()) {
|
||||||
try {
|
try {
|
||||||
AppDevice appDevice = new AppDevice();
|
AppDevice appDevice = new AppDevice();
|
||||||
binder.writeBean(appDevice);
|
binder.writeBean(appDevice);
|
||||||
|
|
||||||
// Explizit als nicht zugeordnet speichern
|
// Explizit als nicht zugeordnet speichern
|
||||||
appDevice.setAppUserId(null);
|
appDevice.setAppUserId(null);
|
||||||
|
|
||||||
AppDevice savedDevice = appDeviceService.createAppDevice(appDevice);
|
AppDevice savedDevice = appDeviceService.createAppDevice(appDevice);
|
||||||
|
|
||||||
Notification.show("Endgerät erfolgreich angelegt: " + savedDevice.getName(), 3000, Notification.Position.MIDDLE);
|
Notification.show("Endgerät erfolgreich angelegt: " + savedDevice.getName(), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
|
|
||||||
// Zurück zur Übersicht
|
// Zurück zur Übersicht
|
||||||
navigateBack();
|
navigateBack();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Notification.show("Fehler beim Anlegen des Endgeräts: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
Notification.show("Fehler beim Anlegen des Endgeräts: " + e.getMessage(), 5000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Notification.show("Bitte füllen Sie alle erforderlichen Felder aus", 3000, Notification.Position.MIDDLE);
|
Notification.show("Bitte füllen Sie alle erforderlichen Felder aus", 3000, Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
getUI().ifPresent(ui -> ui.navigate("app-devices"));
|
getUI().ifPresent(ui -> ui.navigate("app-devices"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
|
|
||||||
@PageTitle("Neuen App-Nutzer anlegen")
|
@PageTitle("Neuen App-Nutzer anlegen")
|
||||||
@Route(value = "add-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "add-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class AddAppUserView extends VerticalLayout {
|
public class AddAppUserView extends VerticalLayout {
|
||||||
|
|
||||||
private final AppUserService appUserService;
|
private final AppUserService appUserService;
|
||||||
@@ -51,7 +51,7 @@ public class AddAppUserView extends VerticalLayout {
|
|||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
setSpacing(true);
|
setSpacing(true);
|
||||||
|
|
||||||
// Center content vertically
|
// Center content vertically
|
||||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
@@ -70,37 +70,35 @@ public class AddAppUserView extends VerticalLayout {
|
|||||||
HorizontalLayout header = new HorizontalLayout();
|
HorizontalLayout header = new HorizontalLayout();
|
||||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||||
header.setSpacing(true);
|
header.setSpacing(true);
|
||||||
|
|
||||||
H2 title = new H2("Neuen App-Nutzer anlegen");
|
H2 title = new H2("Neuen App-Nutzer anlegen");
|
||||||
title.getStyle().set("margin", "0");
|
title.getStyle().set("margin", "0");
|
||||||
|
|
||||||
Button backButton = new Button("Zurück", new Icon(VaadinIcon.ARROW_LEFT));
|
Button backButton = new Button("Zurück", new Icon(VaadinIcon.ARROW_LEFT));
|
||||||
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
backButton.addClickListener(e -> navigateBack());
|
backButton.addClickListener(e -> navigateBack());
|
||||||
|
|
||||||
header.add(title, backButton);
|
header.add(title, backButton);
|
||||||
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||||
contentContainer.add(header);
|
contentContainer.add(header);
|
||||||
|
|
||||||
// Form layout
|
// Form layout
|
||||||
FormLayout formLayout = new FormLayout();
|
FormLayout formLayout = new FormLayout();
|
||||||
formLayout.setResponsiveSteps(
|
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
|
||||||
new FormLayout.ResponsiveStep("0", 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Configure fields
|
// Configure fields
|
||||||
designationField.setPlaceholder("(HH H 000)");
|
designationField.setPlaceholder("(HH H 000)");
|
||||||
designationField.setWidthFull();
|
designationField.setWidthFull();
|
||||||
|
|
||||||
firstnameField.setWidthFull();
|
firstnameField.setWidthFull();
|
||||||
lastnameField.setWidthFull();
|
lastnameField.setWidthFull();
|
||||||
|
|
||||||
// Create horizontal layout for firstname and lastname
|
// Create horizontal layout for firstname and lastname
|
||||||
HorizontalLayout nameLayout = new HorizontalLayout();
|
HorizontalLayout nameLayout = new HorizontalLayout();
|
||||||
nameLayout.setWidthFull();
|
nameLayout.setWidthFull();
|
||||||
nameLayout.setSpacing(true);
|
nameLayout.setSpacing(true);
|
||||||
nameLayout.add(firstnameField, lastnameField);
|
nameLayout.add(firstnameField, lastnameField);
|
||||||
|
|
||||||
phoneField.setWidthFull();
|
phoneField.setWidthFull();
|
||||||
appCodeField.setWidthFull();
|
appCodeField.setWidthFull();
|
||||||
emailField.setWidthFull();
|
emailField.setWidthFull();
|
||||||
@@ -108,7 +106,7 @@ public class AddAppUserView extends VerticalLayout {
|
|||||||
passwordField.setRequired(true);
|
passwordField.setRequired(true);
|
||||||
confirmPasswordField.setWidthFull();
|
confirmPasswordField.setWidthFull();
|
||||||
confirmPasswordField.setRequired(true);
|
confirmPasswordField.setRequired(true);
|
||||||
|
|
||||||
// Configure device dropdown
|
// Configure device dropdown
|
||||||
// Geräteauswahl vorbereiten
|
// Geräteauswahl vorbereiten
|
||||||
deviceComboBox.setWidthFull();
|
deviceComboBox.setWidthFull();
|
||||||
@@ -140,7 +138,7 @@ public class AddAppUserView extends VerticalLayout {
|
|||||||
|
|
||||||
// Setup binder
|
// Setup binder
|
||||||
setupBinder();
|
setupBinder();
|
||||||
|
|
||||||
// Fill with test data
|
// Fill with test data
|
||||||
populateTestData();
|
populateTestData();
|
||||||
}
|
}
|
||||||
@@ -153,25 +151,22 @@ public class AddAppUserView extends VerticalLayout {
|
|||||||
binder.forField(phoneField).bind(AppUser::getTelefon, AppUser::setTelefon);
|
binder.forField(phoneField).bind(AppUser::getTelefon, AppUser::setTelefon);
|
||||||
binder.forField(appCodeField).bind(AppUser::getAppCode, AppUser::setAppCode);
|
binder.forField(appCodeField).bind(AppUser::getAppCode, AppUser::setAppCode);
|
||||||
binder.forField(emailField).bind(AppUser::getEmail, AppUser::setEmail);
|
binder.forField(emailField).bind(AppUser::getEmail, AppUser::setEmail);
|
||||||
binder.forField(passwordField)
|
binder.forField(passwordField).asRequired("Passwort ist erforderlich").bind(AppUser::getPassword,
|
||||||
.asRequired("Passwort ist erforderlich")
|
AppUser::setPassword);
|
||||||
.bind(AppUser::getPassword, AppUser::setPassword);
|
|
||||||
|
|
||||||
// Confirm password field validation
|
// Confirm password field validation
|
||||||
binder.forField(confirmPasswordField)
|
binder.forField(confirmPasswordField).asRequired("Passwort wiederholen ist erforderlich")
|
||||||
.asRequired("Passwort wiederholen ist erforderlich")
|
.withValidator(confirmPassword -> confirmPassword.equals(passwordField.getValue()),
|
||||||
.withValidator(confirmPassword -> confirmPassword.equals(passwordField.getValue()),
|
"Passwörter stimmen nicht überein")
|
||||||
"Passwörter stimmen nicht überein")
|
.bind(appUser -> "", // Dummy getter - this field is not stored
|
||||||
.bind(
|
(appUser, value) -> {
|
||||||
appUser -> "", // Dummy getter - this field is not stored
|
} // Dummy setter - this field is not stored
|
||||||
(appUser, value) -> {} // Dummy setter - this field is not stored
|
);
|
||||||
);
|
binder.forField(deviceComboBox).asRequired("Bitte ein Gerät auswählen").bind(appUser -> null, // Initialwert,
|
||||||
binder.forField(deviceComboBox)
|
// wird beim
|
||||||
.asRequired("Bitte ein Gerät auswählen")
|
// Erstellen
|
||||||
.bind(
|
// gesetzt
|
||||||
appUser -> null, // Initialwert, wird beim Erstellen gesetzt
|
(appUser, appDevice) -> appUser.setAppDeviceId(appDevice != null ? appDevice.getId() : null));
|
||||||
(appUser, appDevice) -> appUser.setAppDeviceId(appDevice != null ? appDevice.getId() : null)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAppUser() {
|
private void createAppUser() {
|
||||||
@@ -191,49 +186,42 @@ public class AddAppUserView extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show success message
|
// Show success message
|
||||||
Notification.show(
|
Notification.show("App-Nutzer erfolgreich angelegt", 3000, Notification.Position.MIDDLE);
|
||||||
"App-Nutzer erfolgreich angelegt",
|
|
||||||
3000,
|
|
||||||
Notification.Position.MIDDLE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Navigate back to app user list
|
// Navigate back to app user list
|
||||||
navigateBack();
|
navigateBack();
|
||||||
|
|
||||||
} catch (ValidationException e) {
|
} catch (ValidationException e) {
|
||||||
Notification.show(
|
Notification.show("Bitte überprüfen Sie die Eingaben", 3000, Notification.Position.MIDDLE);
|
||||||
"Bitte überprüfen Sie die Eingaben",
|
|
||||||
3000,
|
|
||||||
Notification.Position.MIDDLE
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateTestData() {
|
private void populateTestData() {
|
||||||
// Fill designation field
|
// Fill designation field
|
||||||
designationField.setValue("HH H 001");
|
designationField.setValue("HH H 001");
|
||||||
|
|
||||||
// Fill name fields
|
// Fill name fields
|
||||||
firstnameField.setValue("Max");
|
firstnameField.setValue("Max");
|
||||||
lastnameField.setValue("Mustermann");
|
lastnameField.setValue("Mustermann");
|
||||||
|
|
||||||
// Fill phone field
|
// Fill phone field
|
||||||
phoneField.setValue("+49 123 456789");
|
phoneField.setValue("+49 123 456789");
|
||||||
|
|
||||||
// Fill app code field
|
// Fill app code field
|
||||||
appCodeField.setValue("APP001");
|
appCodeField.setValue("APP001");
|
||||||
|
|
||||||
// Fill email field
|
// Fill email field
|
||||||
emailField.setValue("max.mustermann@example.com");
|
emailField.setValue("max.mustermann@example.com");
|
||||||
|
|
||||||
// Fill password field
|
// Fill password field
|
||||||
passwordField.setValue("testpassword123");
|
passwordField.setValue("testpassword123");
|
||||||
|
|
||||||
// Fill confirm password field
|
// Fill confirm password field
|
||||||
confirmPasswordField.setValue("testpassword123");
|
confirmPasswordField.setValue("testpassword123");
|
||||||
|
|
||||||
// Set device to iPhone
|
// Set device to iPhone
|
||||||
// deviceComboBox.setValue("iPhone"); // This line is removed as deviceComboBox is now a ComboBox<AppDevice>
|
// deviceComboBox.setValue("iPhone"); // This line is removed as deviceComboBox
|
||||||
|
// is now a ComboBox<AppDevice>
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import java.time.Clock;
|
|||||||
|
|
||||||
@Route(value = "add_company", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "add_company", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@PageTitle("Neuen Firma anlegen")
|
@PageTitle("Neuen Firma anlegen")
|
||||||
//@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neue Firma anlegen")
|
// @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neue Firma
|
||||||
|
// anlegen")
|
||||||
@RolesAllowed("USER")
|
@RolesAllowed("USER")
|
||||||
public class AddCompanyView extends Main {
|
public class AddCompanyView extends Main {
|
||||||
private final AddCompanyService addCompanyService;
|
private final AddCompanyService addCompanyService;
|
||||||
@@ -45,8 +46,7 @@ public class AddCompanyView extends Main {
|
|||||||
|
|
||||||
companyName = new TextField("Firmenname");
|
companyName = new TextField("Firmenname");
|
||||||
companyName.setRequiredIndicatorVisible(true);
|
companyName.setRequiredIndicatorVisible(true);
|
||||||
binder.forField(companyName)
|
binder.forField(companyName).asRequired("Firmenname ist ein Pflichtfeld") // Pflichtfeldmeldung
|
||||||
.asRequired("Firmenname ist ein Pflichtfeld") // Pflichtfeldmeldung
|
|
||||||
.bind(Company::getName, Company::setName);
|
.bind(Company::getName, Company::setName);
|
||||||
|
|
||||||
firstName = new TextField("Vorname");
|
firstName = new TextField("Vorname");
|
||||||
@@ -66,7 +66,10 @@ public class AddCompanyView extends Main {
|
|||||||
|
|
||||||
// Erstelle ein Div als Container (oder direkt ein Layout)
|
// Erstelle ein Div als Container (oder direkt ein Layout)
|
||||||
VerticalLayout formLayout = new VerticalLayout();
|
VerticalLayout formLayout = new VerticalLayout();
|
||||||
formLayout.add(companyName, /*firstName, lastName, telephone, fax, mail, street, houseNumber, addressAddition, zip, city,*/ submitButton);
|
formLayout.add(companyName, /*
|
||||||
|
* firstName, lastName, telephone, fax, mail, street, houseNumber,
|
||||||
|
* addressAddition, zip, city,
|
||||||
|
*/ submitButton);
|
||||||
|
|
||||||
// Zentriere die Inhalte vertikal und horizontal
|
// Zentriere die Inhalte vertikal und horizontal
|
||||||
formLayout.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
formLayout.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import java.time.Clock;
|
|||||||
|
|
||||||
@Route(value = "add-customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "add-customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@PageTitle("Neuen Kunden anlegen")
|
@PageTitle("Neuen Kunden anlegen")
|
||||||
//@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neuen Kunden anlegen")
|
// @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neuen Kunden
|
||||||
|
// anlegen")
|
||||||
@RolesAllowed("USER")
|
@RolesAllowed("USER")
|
||||||
public class AddCustomerView extends Main {
|
public class AddCustomerView extends Main {
|
||||||
private final AddCustomerService addCustomerService;
|
private final AddCustomerService addCustomerService;
|
||||||
@@ -151,54 +152,38 @@ public class AddCustomerView extends Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void configureBinder() {
|
private void configureBinder() {
|
||||||
binder.forField(companyName)
|
binder.forField(companyName).asRequired("Firma ist ein Pflichtfeld").bind(Customer::getCompanyName,
|
||||||
.asRequired("Firma ist ein Pflichtfeld")
|
Customer::setCompanyName);
|
||||||
.bind(Customer::getCompanyName, Customer::setCompanyName);
|
|
||||||
|
|
||||||
binder.forField(title)
|
binder.forField(title).bind(Customer::getTitle, Customer::setTitle);
|
||||||
.bind(Customer::getTitle, Customer::setTitle);
|
|
||||||
|
|
||||||
binder.forField(firstName)
|
binder.forField(firstName).asRequired("Vorname ist ein Pflichtfeld").bind(Customer::getFirstname,
|
||||||
.asRequired("Vorname ist ein Pflichtfeld")
|
Customer::setFirstname);
|
||||||
.bind(Customer::getFirstname, Customer::setFirstname);
|
|
||||||
|
|
||||||
binder.forField(lastName)
|
binder.forField(lastName).asRequired("Nachname ist ein Pflichtfeld").bind(Customer::getLastName,
|
||||||
.asRequired("Nachname ist ein Pflichtfeld")
|
Customer::setLastName);
|
||||||
.bind(Customer::getLastName, Customer::setLastName);
|
|
||||||
|
|
||||||
binder.forField(telephone)
|
binder.forField(telephone).asRequired("Telefonnummer ist ein Pflichtfeld").bind(Customer::getTelephone,
|
||||||
.asRequired("Telefonnummer ist ein Pflichtfeld")
|
Customer::setTelephone);
|
||||||
.bind(Customer::getTelephone, Customer::setTelephone);
|
|
||||||
|
|
||||||
binder.forField(fax)
|
binder.forField(fax).bind(Customer::getFax, Customer::setFax);
|
||||||
.bind(Customer::getFax, Customer::setFax);
|
|
||||||
|
|
||||||
binder.forField(mail)
|
binder.forField(mail).asRequired("E-Mail-Adresse ist ein Pflichtfeld")
|
||||||
.asRequired("E-Mail-Adresse ist ein Pflichtfeld")
|
|
||||||
.withValidator(email -> email.contains("@"), "Bitte geben Sie eine gültige E-Mail-Adresse ein")
|
.withValidator(email -> email.contains("@"), "Bitte geben Sie eine gültige E-Mail-Adresse ein")
|
||||||
.bind(Customer::getMail, Customer::setMail);
|
.bind(Customer::getMail, Customer::setMail);
|
||||||
|
|
||||||
binder.forField(street)
|
binder.forField(street).asRequired("Straße ist ein Pflichtfeld").bind(Customer::getStreet, Customer::setStreet);
|
||||||
.asRequired("Straße ist ein Pflichtfeld")
|
|
||||||
.bind(Customer::getStreet, Customer::setStreet);
|
|
||||||
|
|
||||||
binder.forField(houseNumber)
|
binder.forField(houseNumber).asRequired("Hausnummer ist ein Pflichtfeld").bind(Customer::getHouseNumber,
|
||||||
.asRequired("Hausnummer ist ein Pflichtfeld")
|
Customer::setHouseNumber);
|
||||||
.bind(Customer::getHouseNumber, Customer::setHouseNumber);
|
|
||||||
|
|
||||||
binder.forField(addressAddition)
|
binder.forField(addressAddition).bind(Customer::getAddressAddition, Customer::setAddressAddition);
|
||||||
.bind(Customer::getAddressAddition, Customer::setAddressAddition);
|
|
||||||
|
|
||||||
binder.forField(zip)
|
binder.forField(zip).asRequired("Postleitzahl ist ein Pflichtfeld").bind(Customer::getZip, Customer::setZip);
|
||||||
.asRequired("Postleitzahl ist ein Pflichtfeld")
|
|
||||||
.bind(Customer::getZip, Customer::setZip);
|
|
||||||
|
|
||||||
binder.forField(city)
|
binder.forField(city).asRequired("Ort ist ein Pflichtfeld").bind(Customer::getCity, Customer::setCity);
|
||||||
.asRequired("Ort ist ein Pflichtfeld")
|
|
||||||
.bind(Customer::getCity, Customer::setCity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void setTestData() {
|
private void setTestData() {
|
||||||
companyName.setValue("Mustermann Transport GmbH");
|
companyName.setValue("Mustermann Transport GmbH");
|
||||||
title.setValue("Herr");
|
title.setValue("Herr");
|
||||||
@@ -213,6 +198,7 @@ public class AddCustomerView extends Main {
|
|||||||
zip.setValue("20095");
|
zip.setValue("20095");
|
||||||
city.setValue("Hamburg");
|
city.setValue("Hamburg");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void submit() {
|
private void submit() {
|
||||||
try {
|
try {
|
||||||
Customer customer = new Customer();
|
Customer customer = new Customer();
|
||||||
@@ -221,20 +207,17 @@ public class AddCustomerView extends Main {
|
|||||||
addCustomerService.addCustomer(customer);
|
addCustomerService.addCustomer(customer);
|
||||||
|
|
||||||
// Erfolg anzeigen und zur Kundenliste navigieren
|
// Erfolg anzeigen und zur Kundenliste navigieren
|
||||||
com.vaadin.flow.component.notification.Notification.show(
|
com.vaadin.flow.component.notification.Notification.show("Kunde erfolgreich angelegt", 3000,
|
||||||
"Kunde erfolgreich angelegt", 3000,
|
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
|
||||||
|
|
||||||
getUI().ifPresent(ui -> ui.navigate("customers"));
|
getUI().ifPresent(ui -> ui.navigate("customers"));
|
||||||
|
|
||||||
} catch (ValidationException e) {
|
} catch (ValidationException e) {
|
||||||
com.vaadin.flow.component.notification.Notification.show(
|
com.vaadin.flow.component.notification.Notification.show("Bitte überprüfen Sie Ihre Eingaben", 3000,
|
||||||
"Bitte überprüfen Sie Ihre Eingaben", 3000,
|
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
com.vaadin.flow.component.notification.Notification.show(
|
com.vaadin.flow.component.notification.Notification.show("Fehler beim Speichern: " + e.getMessage(), 5000,
|
||||||
"Fehler beim Speichern: " + e.getMessage(), 5000,
|
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
|
|
||||||
@PageTitle("Endgeräte")
|
@PageTitle("Endgeräte")
|
||||||
@Route(value = "app-devices", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "app-devices", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class AppDevicesView extends VerticalLayout {
|
public class AppDevicesView extends VerticalLayout {
|
||||||
|
|
||||||
private final AppDeviceService appDeviceService;
|
private final AppDeviceService appDeviceService;
|
||||||
@@ -32,7 +32,7 @@ public class AppDevicesView extends VerticalLayout {
|
|||||||
public AppDevicesView(AppDeviceService appDeviceService, AppUserService appUserService) {
|
public AppDevicesView(AppDeviceService appDeviceService, AppUserService appUserService) {
|
||||||
this.appDeviceService = appDeviceService;
|
this.appDeviceService = appDeviceService;
|
||||||
this.appUserService = appUserService;
|
this.appUserService = appUserService;
|
||||||
|
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
setSpacing(true);
|
setSpacing(true);
|
||||||
@@ -41,14 +41,14 @@ public class AppDevicesView extends VerticalLayout {
|
|||||||
HorizontalLayout header = new HorizontalLayout();
|
HorizontalLayout header = new HorizontalLayout();
|
||||||
header.setWidthFull();
|
header.setWidthFull();
|
||||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
H2 title = new H2("Endgeräte");
|
H2 title = new H2("Endgeräte");
|
||||||
title.getStyle().set("margin", "0");
|
title.getStyle().set("margin", "0");
|
||||||
|
|
||||||
Button addButton = new Button("Neues Endgerät anlegen", new Icon(VaadinIcon.PLUS));
|
Button addButton = new Button("Neues Endgerät anlegen", new Icon(VaadinIcon.PLUS));
|
||||||
addButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
addButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
addButton.addClickListener(e -> navigateToAddAppDevice());
|
addButton.addClickListener(e -> navigateToAddAppDevice());
|
||||||
|
|
||||||
header.add(title, addButton);
|
header.add(title, addButton);
|
||||||
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||||
add(header);
|
add(header);
|
||||||
@@ -56,17 +56,16 @@ public class AppDevicesView extends VerticalLayout {
|
|||||||
// Grid für Endgeräte
|
// Grid für Endgeräte
|
||||||
appDeviceGrid = new Grid<>(AppDevice.class, false);
|
appDeviceGrid = new Grid<>(AppDevice.class, false);
|
||||||
appDeviceGrid.setSizeFull();
|
appDeviceGrid.setSizeFull();
|
||||||
|
|
||||||
// Grid-Spalten konfigurieren
|
// Grid-Spalten konfigurieren
|
||||||
appDeviceGrid.addColumn(AppDevice::getName).setHeader("Gerätename").setAutoWidth(true);
|
appDeviceGrid.addColumn(AppDevice::getName).setHeader("Gerätename").setAutoWidth(true);
|
||||||
|
|
||||||
// App-Nutzer Spalte mit Name anzeigen
|
// App-Nutzer Spalte mit Name anzeigen
|
||||||
appDeviceGrid.addColumn(appDevice -> {
|
appDeviceGrid.addColumn(appDevice -> {
|
||||||
if (appDevice.getAppUserId() != null) {
|
if (appDevice.getAppUserId() != null) {
|
||||||
try {
|
try {
|
||||||
AppUser appUser = appUserService.findByCurrentUser().stream()
|
AppUser appUser = appUserService.findByCurrentUser().stream()
|
||||||
.filter(user -> user.getId().equals(appDevice.getAppUserId()))
|
.filter(user -> user.getId().equals(appDevice.getAppUserId())).findFirst().orElse(null);
|
||||||
.findFirst().orElse(null);
|
|
||||||
if (appUser != null) {
|
if (appUser != null) {
|
||||||
return appUser.getVorname() + " " + appUser.getNachname();
|
return appUser.getVorname() + " " + appUser.getNachname();
|
||||||
}
|
}
|
||||||
@@ -76,13 +75,13 @@ public class AppDevicesView extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
return "Nicht zugeordnet";
|
return "Nicht zugeordnet";
|
||||||
}).setHeader("Zugeordneter App-Nutzer").setAutoWidth(true);
|
}).setHeader("Zugeordneter App-Nutzer").setAutoWidth(true);
|
||||||
|
|
||||||
appDeviceGrid.addColumn(AppDevice::getErstelltAm).setHeader("Erstellt am").setAutoWidth(true);
|
appDeviceGrid.addColumn(AppDevice::getErstelltAm).setHeader("Erstellt am").setAutoWidth(true);
|
||||||
|
|
||||||
// Make grid rows clickable
|
// Make grid rows clickable
|
||||||
appDeviceGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
appDeviceGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
||||||
appDeviceGrid.getStyle().set("cursor", "pointer");
|
appDeviceGrid.getStyle().set("cursor", "pointer");
|
||||||
|
|
||||||
// Add click listener to navigate to edit view
|
// Add click listener to navigate to edit view
|
||||||
appDeviceGrid.addItemClickListener(event -> {
|
appDeviceGrid.addItemClickListener(event -> {
|
||||||
AppDevice appDevice = event.getItem();
|
AppDevice appDevice = event.getItem();
|
||||||
@@ -90,7 +89,7 @@ public class AppDevicesView extends VerticalLayout {
|
|||||||
getUI().ifPresent(ui -> ui.navigate("edit-app-device/" + appDevice.getId().toHexString()));
|
getUI().ifPresent(ui -> ui.navigate("edit-app-device/" + appDevice.getId().toHexString()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add(appDeviceGrid);
|
add(appDeviceGrid);
|
||||||
|
|
||||||
// Daten laden
|
// Daten laden
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
|
|
||||||
@PageTitle("App-Nutzer")
|
@PageTitle("App-Nutzer")
|
||||||
@Route(value = "app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class AppUserView extends VerticalLayout {
|
public class AppUserView extends VerticalLayout {
|
||||||
|
|
||||||
private final AppUserService appUserService;
|
private final AppUserService appUserService;
|
||||||
@@ -38,14 +38,14 @@ public class AppUserView extends VerticalLayout {
|
|||||||
HorizontalLayout header = new HorizontalLayout();
|
HorizontalLayout header = new HorizontalLayout();
|
||||||
header.setWidthFull();
|
header.setWidthFull();
|
||||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
H2 title = new H2("App-Nutzer");
|
H2 title = new H2("App-Nutzer");
|
||||||
title.getStyle().set("margin", "0");
|
title.getStyle().set("margin", "0");
|
||||||
|
|
||||||
Button addButton = new Button("Neuen App-Nutzer anlegen", new Icon(VaadinIcon.PLUS));
|
Button addButton = new Button("Neuen App-Nutzer anlegen", new Icon(VaadinIcon.PLUS));
|
||||||
addButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
addButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
addButton.addClickListener(e -> navigateToAddAppUser());
|
addButton.addClickListener(e -> navigateToAddAppUser());
|
||||||
|
|
||||||
header.add(title, addButton);
|
header.add(title, addButton);
|
||||||
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||||
add(header);
|
add(header);
|
||||||
@@ -53,7 +53,7 @@ public class AppUserView extends VerticalLayout {
|
|||||||
// Grid für App-Nutzer
|
// Grid für App-Nutzer
|
||||||
appUserGrid = new Grid<>(AppUser.class, false);
|
appUserGrid = new Grid<>(AppUser.class, false);
|
||||||
appUserGrid.setSizeFull();
|
appUserGrid.setSizeFull();
|
||||||
|
|
||||||
// Grid-Spalten konfigurieren
|
// Grid-Spalten konfigurieren
|
||||||
appUserGrid.addColumn(AppUser::getBezeichnung).setHeader("Bezeichnung").setAutoWidth(true);
|
appUserGrid.addColumn(AppUser::getBezeichnung).setHeader("Bezeichnung").setAutoWidth(true);
|
||||||
appUserGrid.addColumn(AppUser::getVorname).setHeader("Vorname").setAutoWidth(true);
|
appUserGrid.addColumn(AppUser::getVorname).setHeader("Vorname").setAutoWidth(true);
|
||||||
@@ -68,11 +68,11 @@ public class AppUserView extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
return "Kein Gerät zugewiesen";
|
return "Kein Gerät zugewiesen";
|
||||||
}).setHeader("Endgerät").setAutoWidth(true);
|
}).setHeader("Endgerät").setAutoWidth(true);
|
||||||
|
|
||||||
// Make grid rows clickable
|
// Make grid rows clickable
|
||||||
appUserGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
appUserGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
||||||
appUserGrid.getStyle().set("cursor", "pointer");
|
appUserGrid.getStyle().set("cursor", "pointer");
|
||||||
|
|
||||||
// Add click listener to navigate to edit view
|
// Add click listener to navigate to edit view
|
||||||
appUserGrid.addItemClickListener(event -> {
|
appUserGrid.addItemClickListener(event -> {
|
||||||
AppUser appUser = event.getItem();
|
AppUser appUser = event.getItem();
|
||||||
@@ -80,7 +80,7 @@ public class AppUserView extends VerticalLayout {
|
|||||||
getUI().ifPresent(ui -> ui.navigate("edit-app-user/" + appUser.getId().toHexString()));
|
getUI().ifPresent(ui -> ui.navigate("edit-app-user/" + appUser.getId().toHexString()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add(appUserGrid);
|
add(appUserGrid);
|
||||||
|
|
||||||
// Daten laden
|
// Daten laden
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ public class AuthenticatedStartView extends VerticalLayout {
|
|||||||
heroSection.setPadding(true);
|
heroSection.setPadding(true);
|
||||||
heroSection.setSpacing(true);
|
heroSection.setSpacing(true);
|
||||||
heroSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
heroSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
heroSection.getStyle().set("background", "linear-gradient(135deg, var(--lumo-primary-color-10pct), var(--lumo-primary-color-50pct))");
|
heroSection.getStyle().set("background",
|
||||||
|
"linear-gradient(135deg, var(--lumo-primary-color-10pct), var(--lumo-primary-color-50pct))");
|
||||||
heroSection.getStyle().set("min-height", "300px");
|
heroSection.getStyle().set("min-height", "300px");
|
||||||
heroSection.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
heroSection.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
|
|
||||||
@@ -58,8 +59,7 @@ public class AuthenticatedStartView extends VerticalLayout {
|
|||||||
welcomeTitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
|
welcomeTitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
|
||||||
|
|
||||||
Paragraph welcomeDescription = new Paragraph(
|
Paragraph welcomeDescription = new Paragraph(
|
||||||
"Nutzen Sie die Navigation links, um neue Aufträge zu erstellen oder Ihre Verwaltung zu bearbeiten."
|
"Nutzen Sie die Navigation links, um neue Aufträge zu erstellen oder Ihre Verwaltung zu bearbeiten.");
|
||||||
);
|
|
||||||
welcomeDescription.getStyle().set("text-align", "center");
|
welcomeDescription.getStyle().set("text-align", "center");
|
||||||
welcomeDescription.getStyle().set("max-width", "600px");
|
welcomeDescription.getStyle().set("max-width", "600px");
|
||||||
welcomeDescription.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
welcomeDescription.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
||||||
@@ -82,9 +82,8 @@ public class AuthenticatedStartView extends VerticalLayout {
|
|||||||
systemTitle.getStyle().set("text-align", "center");
|
systemTitle.getStyle().set("text-align", "center");
|
||||||
|
|
||||||
Paragraph systemIntro = new Paragraph(
|
Paragraph systemIntro = new Paragraph(
|
||||||
"Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe ist von entscheidender Bedeutung, " +
|
"Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe ist von entscheidender Bedeutung, "
|
||||||
"dass sie sich in erster Linie auf ihr eigentliches Geschäft konzentrieren können: Kunden gewinnen und Waren von A nach B liefern."
|
+ "dass sie sich in erster Linie auf ihr eigentliches Geschäft konzentrieren können: Kunden gewinnen und Waren von A nach B liefern.");
|
||||||
);
|
|
||||||
systemIntro.getStyle().set("text-align", "center");
|
systemIntro.getStyle().set("text-align", "center");
|
||||||
systemIntro.getStyle().set("max-width", "800px");
|
systemIntro.getStyle().set("max-width", "800px");
|
||||||
systemIntro.getStyle().set("margin-bottom", "var(--lumo-space-xl)");
|
systemIntro.getStyle().set("margin-bottom", "var(--lumo-space-xl)");
|
||||||
@@ -96,14 +95,12 @@ public class AuthenticatedStartView extends VerticalLayout {
|
|||||||
featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START);
|
featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START);
|
||||||
|
|
||||||
// Feature Cards
|
// Feature Cards
|
||||||
featuresGrid.add(
|
featuresGrid.add(createFeatureCard(VaadinIcon.COG, "Einrichtungsassistent",
|
||||||
createFeatureCard(VaadinIcon.COG, "Einrichtungsassistent",
|
|
||||||
"Mithilfe des Einrichtungsassistenten haben Sie die Möglichkeit, Ihr Nutzerprofil zu vervollständigen."),
|
"Mithilfe des Einrichtungsassistenten haben Sie die Möglichkeit, Ihr Nutzerprofil zu vervollständigen."),
|
||||||
createFeatureCard(VaadinIcon.USERS, "Kunden- und Auftragsverwaltung",
|
createFeatureCard(VaadinIcon.USERS, "Kunden- und Auftragsverwaltung",
|
||||||
"Mit der Kunden- und Auftragsverwaltung haben Sie alle Kontaktdaten und Auftragsdetails stets im Blick."),
|
"Mit der Kunden- und Auftragsverwaltung haben Sie alle Kontaktdaten und Auftragsdetails stets im Blick."),
|
||||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, "Auftragserstellung",
|
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, "Auftragserstellung",
|
||||||
"Stellen Sie mit wenigen Mausklicks Aufträge ins System ein und legen Sie fest, welcher Mitarbeiter welchen Transportauftrag abarbeiten soll.")
|
"Stellen Sie mit wenigen Mausklicks Aufträge ins System ein und legen Sie fest, welcher Mitarbeiter welchen Transportauftrag abarbeiten soll."));
|
||||||
);
|
|
||||||
|
|
||||||
systemSection.add(systemTitle, systemIntro, featuresGrid);
|
systemSection.add(systemTitle, systemIntro, featuresGrid);
|
||||||
return systemSection;
|
return systemSection;
|
||||||
@@ -153,9 +150,8 @@ public class AuthenticatedStartView extends VerticalLayout {
|
|||||||
appTitle.getStyle().set("text-align", "center");
|
appTitle.getStyle().set("text-align", "center");
|
||||||
|
|
||||||
Paragraph appDescription = new Paragraph(
|
Paragraph appDescription = new Paragraph(
|
||||||
"Mit unserer mobilen App bleiben Sie auch unterwegs immer über Ihre Aufträge informiert " +
|
"Mit unserer mobilen App bleiben Sie auch unterwegs immer über Ihre Aufträge informiert "
|
||||||
"und können wichtige Aufgaben direkt vom Smartphone aus erledigen."
|
+ "und können wichtige Aufgaben direkt vom Smartphone aus erledigen.");
|
||||||
);
|
|
||||||
appDescription.getStyle().set("text-align", "center");
|
appDescription.getStyle().set("text-align", "center");
|
||||||
appDescription.getStyle().set("max-width", "600px");
|
appDescription.getStyle().set("max-width", "600px");
|
||||||
|
|
||||||
|
|||||||
@@ -25,17 +25,17 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
|
|
||||||
@PageTitle("Endgerät bearbeiten")
|
@PageTitle("Endgerät bearbeiten")
|
||||||
@Route(value = "edit-app-device", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "edit-app-device", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class EditAppDeviceView extends VerticalLayout implements HasUrlParameter<String> {
|
public class EditAppDeviceView extends VerticalLayout implements HasUrlParameter<String> {
|
||||||
|
|
||||||
private final AppDeviceService appDeviceService;
|
private final AppDeviceService appDeviceService;
|
||||||
private final AppUserService appUserService;
|
private final AppUserService appUserService;
|
||||||
private final Binder<AppDevice> binder;
|
private final Binder<AppDevice> binder;
|
||||||
|
|
||||||
// Formularfelder
|
// Formularfelder
|
||||||
private final TextField nameField;
|
private final TextField nameField;
|
||||||
private final ComboBox<AppUser> appUserComboBox;
|
private final ComboBox<AppUser> appUserComboBox;
|
||||||
|
|
||||||
// Aktuelles Endgerät
|
// Aktuelles Endgerät
|
||||||
private AppDevice currentAppDevice;
|
private AppDevice currentAppDevice;
|
||||||
|
|
||||||
@@ -43,39 +43,40 @@ public class EditAppDeviceView extends VerticalLayout implements HasUrlParameter
|
|||||||
public EditAppDeviceView(AppDeviceService appDeviceService, AppUserService appUserService) {
|
public EditAppDeviceView(AppDeviceService appDeviceService, AppUserService appUserService) {
|
||||||
this.appDeviceService = appDeviceService;
|
this.appDeviceService = appDeviceService;
|
||||||
this.appUserService = appUserService;
|
this.appUserService = appUserService;
|
||||||
|
|
||||||
// Binder initialisieren
|
// Binder initialisieren
|
||||||
binder = new Binder<>(AppDevice.class);
|
binder = new Binder<>(AppDevice.class);
|
||||||
|
|
||||||
// Formularfelder erstellen
|
// Formularfelder erstellen
|
||||||
nameField = new TextField("Gerätename");
|
nameField = new TextField("Gerätename");
|
||||||
nameField.setRequired(true);
|
nameField.setRequired(true);
|
||||||
nameField.setPlaceholder("z.B. iPhone 15, Samsung Galaxy S24");
|
nameField.setPlaceholder("z.B. iPhone 15, Samsung Galaxy S24");
|
||||||
nameField.setWidth("100%");
|
nameField.setWidth("100%");
|
||||||
|
|
||||||
// AppUser ComboBox
|
// AppUser ComboBox
|
||||||
appUserComboBox = new ComboBox<>("App-Nutzer zuordnen");
|
appUserComboBox = new ComboBox<>("App-Nutzer zuordnen");
|
||||||
appUserComboBox.setPlaceholder("App-Nutzer auswählen (optional)");
|
appUserComboBox.setPlaceholder("App-Nutzer auswählen (optional)");
|
||||||
appUserComboBox.setWidth("100%");
|
appUserComboBox.setWidth("100%");
|
||||||
appUserComboBox.setItemLabelGenerator(appUser ->
|
appUserComboBox.setItemLabelGenerator(
|
||||||
appUser.getVorname() + " " + appUser.getNachname() + " (" + appUser.getEmail() + ")");
|
appUser -> appUser.getVorname() + " " + appUser.getNachname() + " (" + appUser.getEmail() + ")");
|
||||||
|
|
||||||
// Lade verfügbare App-Nutzer
|
// Lade verfügbare App-Nutzer
|
||||||
try {
|
try {
|
||||||
appUserComboBox.setItems(appUserService.findByCurrentUser());
|
appUserComboBox.setItems(appUserService.findByCurrentUser());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Notification.show("Fehler beim Laden der App-Nutzer: " + e.getMessage(), 3000, Notification.Position.MIDDLE);
|
Notification.show("Fehler beim Laden der App-Nutzer: " + e.getMessage(), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout konfigurieren
|
// Layout konfigurieren
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
setSpacing(true);
|
setSpacing(true);
|
||||||
|
|
||||||
// Content zentrieren
|
// Content zentrieren
|
||||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
// Hauptcontainer erstellen
|
// Hauptcontainer erstellen
|
||||||
VerticalLayout contentContainer = new VerticalLayout();
|
VerticalLayout contentContainer = new VerticalLayout();
|
||||||
contentContainer.setWidth("600px");
|
contentContainer.setWidth("600px");
|
||||||
@@ -85,45 +86,45 @@ public class EditAppDeviceView extends VerticalLayout implements HasUrlParameter
|
|||||||
contentContainer.getStyle().set("box-shadow", "0 2px 8px rgba(0,0,0,0.1)");
|
contentContainer.getStyle().set("box-shadow", "0 2px 8px rgba(0,0,0,0.1)");
|
||||||
contentContainer.setPadding(true);
|
contentContainer.setPadding(true);
|
||||||
contentContainer.setSpacing(true);
|
contentContainer.setSpacing(true);
|
||||||
|
|
||||||
// Titel
|
// Titel
|
||||||
H2 title = new H2("Endgerät bearbeiten");
|
H2 title = new H2("Endgerät bearbeiten");
|
||||||
title.getStyle().set("margin", "0");
|
title.getStyle().set("margin", "0");
|
||||||
title.getStyle().set("text-align", "center");
|
title.getStyle().set("text-align", "center");
|
||||||
contentContainer.add(title);
|
contentContainer.add(title);
|
||||||
|
|
||||||
// Formular
|
// Formular
|
||||||
FormLayout formLayout = new FormLayout();
|
FormLayout formLayout = new FormLayout();
|
||||||
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
|
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
|
||||||
formLayout.add(nameField, appUserComboBox);
|
formLayout.add(nameField, appUserComboBox);
|
||||||
contentContainer.add(formLayout);
|
contentContainer.add(formLayout);
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||||
buttonLayout.setWidthFull();
|
buttonLayout.setWidthFull();
|
||||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
buttonLayout.setSpacing(true);
|
buttonLayout.setSpacing(true);
|
||||||
|
|
||||||
Button backButton = new Button("Zurück");
|
Button backButton = new Button("Zurück");
|
||||||
backButton.addClickListener(e -> navigateBack());
|
backButton.addClickListener(e -> navigateBack());
|
||||||
|
|
||||||
Button saveButton = new Button("Speichern");
|
Button saveButton = new Button("Speichern");
|
||||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
saveButton.addClickListener(e -> saveAppDevice());
|
saveButton.addClickListener(e -> saveAppDevice());
|
||||||
|
|
||||||
Button deleteButton = new Button("Löschen");
|
Button deleteButton = new Button("Löschen");
|
||||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||||
deleteButton.addClickListener(e -> deleteAppDevice());
|
deleteButton.addClickListener(e -> deleteAppDevice());
|
||||||
|
|
||||||
buttonLayout.add(backButton, saveButton, deleteButton);
|
buttonLayout.add(backButton, saveButton, deleteButton);
|
||||||
contentContainer.add(buttonLayout);
|
contentContainer.add(buttonLayout);
|
||||||
|
|
||||||
add(contentContainer);
|
add(contentContainer);
|
||||||
|
|
||||||
// Binder konfigurieren
|
// Binder konfigurieren
|
||||||
setupBinder();
|
setupBinder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setParameter(com.vaadin.flow.router.BeforeEvent event, String parameter) {
|
public void setParameter(com.vaadin.flow.router.BeforeEvent event, String parameter) {
|
||||||
try {
|
try {
|
||||||
@@ -134,10 +135,10 @@ public class EditAppDeviceView extends VerticalLayout implements HasUrlParameter
|
|||||||
navigateBack();
|
navigateBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAppDevice(ObjectId deviceId) {
|
private void loadAppDevice(ObjectId deviceId) {
|
||||||
currentAppDevice = appDeviceService.findById(deviceId);
|
currentAppDevice = appDeviceService.findById(deviceId);
|
||||||
|
|
||||||
if (currentAppDevice != null) {
|
if (currentAppDevice != null) {
|
||||||
// Formular mit aktuellen Daten füllen
|
// Formular mit aktuellen Daten füllen
|
||||||
binder.readBean(currentAppDevice);
|
binder.readBean(currentAppDevice);
|
||||||
@@ -146,51 +147,50 @@ public class EditAppDeviceView extends VerticalLayout implements HasUrlParameter
|
|||||||
navigateBack();
|
navigateBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupBinder() {
|
private void setupBinder() {
|
||||||
binder.forField(nameField)
|
binder.forField(nameField).asRequired("Gerätename ist erforderlich").bind(AppDevice::getName,
|
||||||
.asRequired("Gerätename ist erforderlich")
|
AppDevice::setName);
|
||||||
.bind(AppDevice::getName, AppDevice::setName);
|
|
||||||
|
binder.forField(appUserComboBox).bind(appDevice -> {
|
||||||
binder.forField(appUserComboBox)
|
if (appDevice.getAppUserId() != null) {
|
||||||
.bind(appDevice -> {
|
return appUserService.findByCurrentUser().stream()
|
||||||
if (appDevice.getAppUserId() != null) {
|
.filter(user -> user.getId().equals(appDevice.getAppUserId())).findFirst().orElse(null);
|
||||||
return appUserService.findByCurrentUser().stream()
|
}
|
||||||
.filter(user -> user.getId().equals(appDevice.getAppUserId()))
|
return null;
|
||||||
.findFirst().orElse(null);
|
}, (appDevice, appUser) -> {
|
||||||
}
|
if (appUser != null) {
|
||||||
return null;
|
appDevice.setAppUserId(appUser.getId());
|
||||||
}, (appDevice, appUser) -> {
|
} else {
|
||||||
if (appUser != null) {
|
appDevice.setAppUserId(null);
|
||||||
appDevice.setAppUserId(appUser.getId());
|
}
|
||||||
} else {
|
});
|
||||||
appDevice.setAppUserId(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAppDevice() {
|
private void saveAppDevice() {
|
||||||
if (binder.validate().isOk()) {
|
if (binder.validate().isOk()) {
|
||||||
try {
|
try {
|
||||||
// Aktuelle Daten in das Modell schreiben
|
// Aktuelle Daten in das Modell schreiben
|
||||||
binder.writeBean(currentAppDevice);
|
binder.writeBean(currentAppDevice);
|
||||||
|
|
||||||
// Endgerät aktualisieren
|
// Endgerät aktualisieren
|
||||||
AppDevice updatedDevice = appDeviceService.updateAppDevice(currentAppDevice);
|
AppDevice updatedDevice = appDeviceService.updateAppDevice(currentAppDevice);
|
||||||
|
|
||||||
Notification.show("Endgerät erfolgreich aktualisiert: " + updatedDevice.getName(), 3000, Notification.Position.MIDDLE);
|
Notification.show("Endgerät erfolgreich aktualisiert: " + updatedDevice.getName(), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
|
|
||||||
// Zurück zur Übersicht
|
// Zurück zur Übersicht
|
||||||
navigateBack();
|
navigateBack();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Notification.show("Fehler beim Aktualisieren des Endgeräts: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
Notification.show("Fehler beim Aktualisieren des Endgeräts: " + e.getMessage(), 5000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Notification.show("Bitte füllen Sie alle erforderlichen Felder aus", 3000, Notification.Position.MIDDLE);
|
Notification.show("Bitte füllen Sie alle erforderlichen Felder aus", 3000, Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteAppDevice() {
|
private void deleteAppDevice() {
|
||||||
if (currentAppDevice != null && currentAppDevice.getId() != null) {
|
if (currentAppDevice != null && currentAppDevice.getId() != null) {
|
||||||
ConfirmDialog confirmDialog = new ConfirmDialog();
|
ConfirmDialog confirmDialog = new ConfirmDialog();
|
||||||
@@ -199,21 +199,22 @@ public class EditAppDeviceView extends VerticalLayout implements HasUrlParameter
|
|||||||
confirmDialog.setCancelText("Abbrechen");
|
confirmDialog.setCancelText("Abbrechen");
|
||||||
confirmDialog.setConfirmText("Löschen");
|
confirmDialog.setConfirmText("Löschen");
|
||||||
confirmDialog.setConfirmButtonTheme("error primary");
|
confirmDialog.setConfirmButtonTheme("error primary");
|
||||||
|
|
||||||
confirmDialog.addConfirmListener(event -> {
|
confirmDialog.addConfirmListener(event -> {
|
||||||
try {
|
try {
|
||||||
appDeviceService.deleteById(currentAppDevice.getId());
|
appDeviceService.deleteById(currentAppDevice.getId());
|
||||||
Notification.show("Endgerät erfolgreich gelöscht", 3000, Notification.Position.MIDDLE);
|
Notification.show("Endgerät erfolgreich gelöscht", 3000, Notification.Position.MIDDLE);
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Notification.show("Fehler beim Löschen des Endgeräts: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
Notification.show("Fehler beim Löschen des Endgeräts: " + e.getMessage(), 5000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
confirmDialog.open();
|
confirmDialog.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
getUI().ifPresent(ui -> ui.navigate("app-devices"));
|
getUI().ifPresent(ui -> ui.navigate("app-devices"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@PageTitle("App-Nutzer bearbeiten")
|
@PageTitle("App-Nutzer bearbeiten")
|
||||||
@Route(value = "edit-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "edit-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class EditAppUserView extends VerticalLayout implements HasUrlParameter<String> {
|
public class EditAppUserView extends VerticalLayout implements HasUrlParameter<String> {
|
||||||
|
|
||||||
private final AppUserService appUserService;
|
private final AppUserService appUserService;
|
||||||
@@ -60,7 +60,7 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
setSpacing(true);
|
setSpacing(true);
|
||||||
|
|
||||||
// Center content vertically
|
// Center content vertically
|
||||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
@@ -79,47 +79,45 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
HorizontalLayout header = new HorizontalLayout();
|
HorizontalLayout header = new HorizontalLayout();
|
||||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||||
header.setSpacing(true);
|
header.setSpacing(true);
|
||||||
|
|
||||||
H2 title = new H2("App-Nutzer bearbeiten");
|
H2 title = new H2("App-Nutzer bearbeiten");
|
||||||
title.getStyle().set("margin", "0");
|
title.getStyle().set("margin", "0");
|
||||||
|
|
||||||
Button backButton = new Button("Zurück", new Icon(VaadinIcon.ARROW_LEFT));
|
Button backButton = new Button("Zurück", new Icon(VaadinIcon.ARROW_LEFT));
|
||||||
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
backButton.addClickListener(e -> navigateBack());
|
backButton.addClickListener(e -> navigateBack());
|
||||||
|
|
||||||
header.add(title, backButton);
|
header.add(title, backButton);
|
||||||
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||||
contentContainer.add(header);
|
contentContainer.add(header);
|
||||||
|
|
||||||
// Form layout
|
// Form layout
|
||||||
FormLayout formLayout = new FormLayout();
|
FormLayout formLayout = new FormLayout();
|
||||||
formLayout.setResponsiveSteps(
|
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
|
||||||
new FormLayout.ResponsiveStep("0", 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Configure fields
|
// Configure fields
|
||||||
designationField.setPlaceholder("(HH H 000)");
|
designationField.setPlaceholder("(HH H 000)");
|
||||||
designationField.setWidthFull();
|
designationField.setWidthFull();
|
||||||
|
|
||||||
firstnameField.setWidthFull();
|
firstnameField.setWidthFull();
|
||||||
lastnameField.setWidthFull();
|
lastnameField.setWidthFull();
|
||||||
|
|
||||||
// Create horizontal layout for firstname and lastname
|
// Create horizontal layout for firstname and lastname
|
||||||
HorizontalLayout nameLayout = new HorizontalLayout();
|
HorizontalLayout nameLayout = new HorizontalLayout();
|
||||||
nameLayout.setWidthFull();
|
nameLayout.setWidthFull();
|
||||||
nameLayout.setSpacing(true);
|
nameLayout.setSpacing(true);
|
||||||
nameLayout.add(firstnameField, lastnameField);
|
nameLayout.add(firstnameField, lastnameField);
|
||||||
|
|
||||||
phoneField.setWidthFull();
|
phoneField.setWidthFull();
|
||||||
appCodeField.setWidthFull();
|
appCodeField.setWidthFull();
|
||||||
emailField.setWidthFull();
|
emailField.setWidthFull();
|
||||||
|
|
||||||
// Configure password fields
|
// Configure password fields
|
||||||
changePasswordField.setWidthFull();
|
changePasswordField.setWidthFull();
|
||||||
changePasswordField.setPlaceholder("Leer lassen, wenn nicht ändern");
|
changePasswordField.setPlaceholder("Leer lassen, wenn nicht ändern");
|
||||||
confirmChangePasswordField.setWidthFull();
|
confirmChangePasswordField.setWidthFull();
|
||||||
confirmChangePasswordField.setPlaceholder("Leer lassen, wenn nicht ändern");
|
confirmChangePasswordField.setPlaceholder("Leer lassen, wenn nicht ändern");
|
||||||
|
|
||||||
// Configure device dropdown
|
// Configure device dropdown
|
||||||
deviceComboBox.setWidthFull();
|
deviceComboBox.setWidthFull();
|
||||||
deviceComboBox.setItemLabelGenerator(device -> device.getName());
|
deviceComboBox.setItemLabelGenerator(device -> device.getName());
|
||||||
@@ -141,10 +139,10 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
buttonLayout.setWidthFull();
|
buttonLayout.setWidthFull();
|
||||||
|
|
||||||
Button saveButton = new Button("Speichern", e -> saveAppUser());
|
Button saveButton = new Button("Speichern", e -> saveAppUser());
|
||||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
|
||||||
Button deleteButton = new Button("Löschen", e -> deleteAppUser());
|
Button deleteButton = new Button("Löschen", e -> deleteAppUser());
|
||||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||||
|
|
||||||
@@ -167,11 +165,8 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
binder.forField(phoneField).bind(AppUser::getTelefon, AppUser::setTelefon);
|
binder.forField(phoneField).bind(AppUser::getTelefon, AppUser::setTelefon);
|
||||||
binder.forField(appCodeField).bind(AppUser::getAppCode, AppUser::setAppCode);
|
binder.forField(appCodeField).bind(AppUser::getAppCode, AppUser::setAppCode);
|
||||||
binder.forField(emailField).bind(AppUser::getEmail, AppUser::setEmail);
|
binder.forField(emailField).bind(AppUser::getEmail, AppUser::setEmail);
|
||||||
binder.forField(deviceComboBox)
|
binder.forField(deviceComboBox).bind(appUser -> getCurrentDevice(appUser), // Get current device
|
||||||
.bind(
|
(appUser, appDevice) -> appUser.setAppDeviceId(appDevice != null ? appDevice.getId() : null));
|
||||||
appUser -> getCurrentDevice(appUser), // Get current device
|
|
||||||
(appUser, appDevice) -> appUser.setAppDeviceId(appDevice != null ? appDevice.getId() : null)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -183,10 +178,10 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
|
|
||||||
// Setup device ComboBox with available devices
|
// Setup device ComboBox with available devices
|
||||||
setupDeviceComboBox();
|
setupDeviceComboBox();
|
||||||
|
|
||||||
// Load app user data into form
|
// Load app user data into form
|
||||||
binder.readBean(appUser);
|
binder.readBean(appUser);
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Notification.show("Ungültige App-Nutzer-ID", 3000, Notification.Position.MIDDLE);
|
Notification.show("Ungültige App-Nutzer-ID", 3000, Notification.Position.MIDDLE);
|
||||||
navigateBack();
|
navigateBack();
|
||||||
@@ -199,16 +194,16 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
if (!validatePasswordFields()) {
|
if (!validatePasswordFields()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save current password to restore if not changing
|
// Save current password to restore if not changing
|
||||||
String originalPassword = appUser.getPassword();
|
String originalPassword = appUser.getPassword();
|
||||||
|
|
||||||
binder.writeBean(appUser);
|
binder.writeBean(appUser);
|
||||||
|
|
||||||
// Handle password change logic
|
// Handle password change logic
|
||||||
String newPassword = changePasswordField.getValue();
|
String newPassword = changePasswordField.getValue();
|
||||||
String confirmPassword = confirmChangePasswordField.getValue();
|
String confirmPassword = confirmChangePasswordField.getValue();
|
||||||
|
|
||||||
if (newPassword != null && !newPassword.trim().isEmpty()) {
|
if (newPassword != null && !newPassword.trim().isEmpty()) {
|
||||||
// User wants to change password
|
// User wants to change password
|
||||||
if (confirmPassword != null && newPassword.equals(confirmPassword)) {
|
if (confirmPassword != null && newPassword.equals(confirmPassword)) {
|
||||||
@@ -222,7 +217,7 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
// No password change requested, restore original password
|
// No password change requested, restore original password
|
||||||
appUser.setPassword(originalPassword);
|
appUser.setPassword(originalPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle device assignment changes
|
// Handle device assignment changes
|
||||||
AppDevice selectedDevice = deviceComboBox.getValue();
|
AppDevice selectedDevice = deviceComboBox.getValue();
|
||||||
|
|
||||||
@@ -239,7 +234,7 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
appDeviceService.updateAppDevice(newDevice);
|
appDeviceService.updateAppDevice(newDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appUserService.updateAppUser(appUser);
|
appUserService.updateAppUser(appUser);
|
||||||
Notification.show("App-Nutzer erfolgreich gespeichert", 3000, Notification.Position.MIDDLE);
|
Notification.show("App-Nutzer erfolgreich gespeichert", 3000, Notification.Position.MIDDLE);
|
||||||
navigateBack();
|
navigateBack();
|
||||||
@@ -247,56 +242,56 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
Notification.show("Bitte überprüfen Sie die Eingaben", 3000, Notification.Position.MIDDLE);
|
Notification.show("Bitte überprüfen Sie die Eingaben", 3000, Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validatePasswordFields() {
|
private boolean validatePasswordFields() {
|
||||||
String newPassword = changePasswordField.getValue();
|
String newPassword = changePasswordField.getValue();
|
||||||
String confirmPassword = confirmChangePasswordField.getValue();
|
String confirmPassword = confirmChangePasswordField.getValue();
|
||||||
|
|
||||||
// If one field is filled, both must be filled
|
// If one field is filled, both must be filled
|
||||||
boolean newPasswordFilled = newPassword != null && !newPassword.trim().isEmpty();
|
boolean newPasswordFilled = newPassword != null && !newPassword.trim().isEmpty();
|
||||||
boolean confirmPasswordFilled = confirmPassword != null && !confirmPassword.trim().isEmpty();
|
boolean confirmPasswordFilled = confirmPassword != null && !confirmPassword.trim().isEmpty();
|
||||||
|
|
||||||
if (newPasswordFilled && !confirmPasswordFilled) {
|
if (newPasswordFilled && !confirmPasswordFilled) {
|
||||||
Notification.show("Bitte bestätigen Sie das neue Passwort", 3000, Notification.Position.MIDDLE);
|
Notification.show("Bitte bestätigen Sie das neue Passwort", 3000, Notification.Position.MIDDLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newPasswordFilled && confirmPasswordFilled) {
|
if (!newPasswordFilled && confirmPasswordFilled) {
|
||||||
Notification.show("Bitte geben Sie das neue Passwort ein", 3000, Notification.Position.MIDDLE);
|
Notification.show("Bitte geben Sie das neue Passwort ein", 3000, Notification.Position.MIDDLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both are filled, they must match
|
// If both are filled, they must match
|
||||||
if (newPasswordFilled && confirmPasswordFilled && newPassword != null && !newPassword.equals(confirmPassword)) {
|
if (newPasswordFilled && confirmPasswordFilled && newPassword != null && !newPassword.equals(confirmPassword)) {
|
||||||
Notification.show("Passwörter stimmen nicht überein", 3000, Notification.Position.MIDDLE);
|
Notification.show("Passwörter stimmen nicht überein", 3000, Notification.Position.MIDDLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppDevice getCurrentDevice(AppUser appUser) {
|
private AppDevice getCurrentDevice(AppUser appUser) {
|
||||||
if (appUser != null && appUser.getAppDeviceId() != null) {
|
if (appUser != null && appUser.getAppDeviceId() != null) {
|
||||||
return appDeviceService.findById(appUser.getAppDeviceId());
|
return appDeviceService.findById(appUser.getAppDeviceId());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupDeviceComboBox() {
|
private void setupDeviceComboBox() {
|
||||||
List<AppDevice> availableDevices = new ArrayList<>();
|
List<AppDevice> availableDevices = new ArrayList<>();
|
||||||
|
|
||||||
// First, add the currently assigned device if it exists
|
// First, add the currently assigned device if it exists
|
||||||
AppDevice currentDevice = getCurrentDevice(appUser);
|
AppDevice currentDevice = getCurrentDevice(appUser);
|
||||||
if (currentDevice != null) {
|
if (currentDevice != null) {
|
||||||
availableDevices.add(currentDevice);
|
availableDevices.add(currentDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then add all unassigned devices
|
// Then add all unassigned devices
|
||||||
List<AppDevice> unassignedDevices = appDeviceService.findUnassignedDevices();
|
List<AppDevice> unassignedDevices = appDeviceService.findUnassignedDevices();
|
||||||
availableDevices.addAll(unassignedDevices);
|
availableDevices.addAll(unassignedDevices);
|
||||||
|
|
||||||
deviceComboBox.setItems(availableDevices);
|
deviceComboBox.setItems(availableDevices);
|
||||||
|
|
||||||
// Set the current device as selected
|
// Set the current device as selected
|
||||||
if (currentDevice != null) {
|
if (currentDevice != null) {
|
||||||
deviceComboBox.setValue(currentDevice);
|
deviceComboBox.setValue(currentDevice);
|
||||||
@@ -307,7 +302,7 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
// Show confirmation dialog
|
// Show confirmation dialog
|
||||||
com.vaadin.flow.component.dialog.Dialog confirmDialog = new com.vaadin.flow.component.dialog.Dialog();
|
com.vaadin.flow.component.dialog.Dialog confirmDialog = new com.vaadin.flow.component.dialog.Dialog();
|
||||||
confirmDialog.add("Möchten Sie diesen App-Nutzer wirklich löschen?");
|
confirmDialog.add("Möchten Sie diesen App-Nutzer wirklich löschen?");
|
||||||
|
|
||||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||||
Button confirmDeleteButton = new Button("Ja, löschen", e -> {
|
Button confirmDeleteButton = new Button("Ja, löschen", e -> {
|
||||||
if (appUser != null && appUser.getId() != null) {
|
if (appUser != null && appUser.getId() != null) {
|
||||||
@@ -318,13 +313,13 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmDeleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
confirmDeleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||||
|
|
||||||
Button cancelDeleteButton = new Button("Abbrechen", e -> confirmDialog.close());
|
Button cancelDeleteButton = new Button("Abbrechen", e -> confirmDialog.close());
|
||||||
|
|
||||||
buttonLayout.add(confirmDeleteButton, cancelDeleteButton);
|
buttonLayout.add(confirmDeleteButton, cancelDeleteButton);
|
||||||
buttonLayout.setSpacing(true);
|
buttonLayout.setSpacing(true);
|
||||||
confirmDialog.add(buttonLayout);
|
confirmDialog.add(buttonLayout);
|
||||||
|
|
||||||
confirmDialog.open();
|
confirmDialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
|||||||
|
|
||||||
@PageTitle("Kunde bearbeiten")
|
@PageTitle("Kunde bearbeiten")
|
||||||
@Route(value = "edit-customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "edit-customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class EditCustomerView extends VerticalLayout implements HasUrlParameter<String> {
|
public class EditCustomerView extends VerticalLayout implements HasUrlParameter<String> {
|
||||||
|
|
||||||
private final CustomerService customerService;
|
private final CustomerService customerService;
|
||||||
@@ -52,7 +52,7 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
setSpacing(true);
|
setSpacing(true);
|
||||||
|
|
||||||
// Center content vertically
|
// Center content vertically
|
||||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
@@ -75,9 +75,7 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
|
|
||||||
// Form layout
|
// Form layout
|
||||||
FormLayout formLayout = new FormLayout();
|
FormLayout formLayout = new FormLayout();
|
||||||
formLayout.setResponsiveSteps(
|
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
|
||||||
new FormLayout.ResponsiveStep("0", 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add fields to form - all fields in single column
|
// Add fields to form - all fields in single column
|
||||||
formLayout.add(titleField);
|
formLayout.add(titleField);
|
||||||
@@ -99,10 +97,10 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
buttonLayout.setWidthFull();
|
buttonLayout.setWidthFull();
|
||||||
|
|
||||||
Button saveButton = new Button("Speichern", e -> saveCustomer());
|
Button saveButton = new Button("Speichern", e -> saveCustomer());
|
||||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
|
||||||
Button cancelButton = new Button("Abbrechen", e -> navigateBack());
|
Button cancelButton = new Button("Abbrechen", e -> navigateBack());
|
||||||
Button deleteButton = new Button("Löschen", e -> deleteCustomer());
|
Button deleteButton = new Button("Löschen", e -> deleteCustomer());
|
||||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||||
@@ -138,7 +136,7 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
try {
|
try {
|
||||||
ObjectId customerId = new ObjectId(parameter);
|
ObjectId customerId = new ObjectId(parameter);
|
||||||
customer = customerService.findById(customerId);
|
customer = customerService.findById(customerId);
|
||||||
|
|
||||||
if (customer == null) {
|
if (customer == null) {
|
||||||
Notification.show("Kunde nicht gefunden", 3000, Notification.Position.MIDDLE);
|
Notification.show("Kunde nicht gefunden", 3000, Notification.Position.MIDDLE);
|
||||||
navigateBack();
|
navigateBack();
|
||||||
@@ -147,7 +145,7 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
|
|
||||||
// Load customer data into form
|
// Load customer data into form
|
||||||
binder.readBean(customer);
|
binder.readBean(customer);
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Notification.show("Ungültige Kunden-ID", 3000, Notification.Position.MIDDLE);
|
Notification.show("Ungültige Kunden-ID", 3000, Notification.Position.MIDDLE);
|
||||||
navigateBack();
|
navigateBack();
|
||||||
@@ -169,7 +167,7 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
// Show confirmation dialog
|
// Show confirmation dialog
|
||||||
Dialog confirmDialog = new Dialog();
|
Dialog confirmDialog = new Dialog();
|
||||||
confirmDialog.add("Möchten Sie diesen Kunden wirklich löschen?");
|
confirmDialog.add("Möchten Sie diesen Kunden wirklich löschen?");
|
||||||
|
|
||||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||||
Button confirmDeleteButton = new Button("Ja, löschen", e -> {
|
Button confirmDeleteButton = new Button("Ja, löschen", e -> {
|
||||||
if (customer != null && customer.getId() != null) {
|
if (customer != null && customer.getId() != null) {
|
||||||
@@ -179,13 +177,13 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmDeleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
confirmDeleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||||
|
|
||||||
Button cancelDeleteButton = new Button("Abbrechen", e -> confirmDialog.close());
|
Button cancelDeleteButton = new Button("Abbrechen", e -> confirmDialog.close());
|
||||||
|
|
||||||
buttonLayout.add(confirmDeleteButton, cancelDeleteButton);
|
buttonLayout.add(confirmDeleteButton, cancelDeleteButton);
|
||||||
buttonLayout.setSpacing(true);
|
buttonLayout.setSpacing(true);
|
||||||
confirmDialog.add(buttonLayout);
|
confirmDialog.add(buttonLayout);
|
||||||
|
|
||||||
confirmDialog.open();
|
confirmDialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import jakarta.annotation.security.RolesAllowed;
|
|||||||
|
|
||||||
@PageTitle("Profil bearbeiten")
|
@PageTitle("Profil bearbeiten")
|
||||||
@Route(value = "edit-profile", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "edit-profile", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class EditProfileView extends HorizontalLayout {
|
public class EditProfileView extends HorizontalLayout {
|
||||||
private final TextField prefixField;
|
private final TextField prefixField;
|
||||||
private final TextField ustIdField;
|
private final TextField ustIdField;
|
||||||
@@ -71,7 +71,6 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
tabSheet.setSizeFull();
|
tabSheet.setSizeFull();
|
||||||
formColumn.setFlexGrow(1, tabSheet);
|
formColumn.setFlexGrow(1, tabSheet);
|
||||||
|
|
||||||
|
|
||||||
FormLayout form = new FormLayout();
|
FormLayout form = new FormLayout();
|
||||||
form.setWidthFull();
|
form.setWidthFull();
|
||||||
form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
|
form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
|
||||||
@@ -163,35 +162,22 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
zipField.setRequiredIndicatorVisible(true);
|
zipField.setRequiredIndicatorVisible(true);
|
||||||
cityField.setRequiredIndicatorVisible(true);
|
cityField.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
binder.forField(companyField)
|
binder.forField(companyField).asRequired("").bind(user -> null, (user, v) -> {
|
||||||
.asRequired("")
|
});
|
||||||
.bind(user -> null, (user, v) -> {});
|
binder.forField(streetField).asRequired("").bind(user -> null, (user, v) -> {
|
||||||
binder.forField(streetField)
|
});
|
||||||
.asRequired("")
|
binder.forField(houseNumberField).asRequired("").bind(user -> null, (user, v) -> {
|
||||||
.bind(user -> null, (user, v) -> {});
|
});
|
||||||
binder.forField(houseNumberField)
|
binder.forField(zipField).asRequired("").bind(user -> null, (user, v) -> {
|
||||||
.asRequired("")
|
});
|
||||||
.bind(user -> null, (user, v) -> {});
|
binder.forField(cityField).asRequired("").bind(user -> null, (user, v) -> {
|
||||||
binder.forField(zipField)
|
});
|
||||||
.asRequired("")
|
|
||||||
.bind(user -> null, (user, v) -> {});
|
|
||||||
binder.forField(cityField)
|
|
||||||
.asRequired("")
|
|
||||||
.bind(user -> null, (user, v) -> {});
|
|
||||||
|
|
||||||
binder.forField(firstnameField)
|
binder.forField(firstnameField).asRequired("").bind(User::getFirstname, User::setFirstname);
|
||||||
.asRequired("")
|
binder.forField(lastnameField).asRequired("").bind(User::getName, User::setName);
|
||||||
.bind(User::getFirstname, User::setFirstname);
|
binder.forField(phoneField).asRequired("").bind(User::getPhone, User::setPhone);
|
||||||
binder.forField(lastnameField)
|
binder.forField(emailField).asRequired("").withValidator(new EmailValidator("Ungültige E-Mail-Adresse"))
|
||||||
.asRequired("")
|
.bind(User::getEmail, User::setEmail);
|
||||||
.bind(User::getName, User::setName);
|
|
||||||
binder.forField(phoneField)
|
|
||||||
.asRequired("")
|
|
||||||
.bind(User::getPhone, User::setPhone);
|
|
||||||
binder.forField(emailField)
|
|
||||||
.asRequired("")
|
|
||||||
.withValidator(new EmailValidator("Ungültige E-Mail-Adresse"))
|
|
||||||
.bind(User::getEmail, User::setEmail);
|
|
||||||
// Optionale Felder
|
// Optionale Felder
|
||||||
binder.forField(mobileField).bind(User::getPhone2, User::setPhone2);
|
binder.forField(mobileField).bind(User::getPhone2, User::setPhone2);
|
||||||
binder.forField(faxField).bind(User::getFax, User::setFax);
|
binder.forField(faxField).bind(User::getFax, User::setFax);
|
||||||
@@ -224,8 +210,8 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
mapDiv.setWidth("100%");
|
mapDiv.setWidth("100%");
|
||||||
mapDiv.setHeight("400px");
|
mapDiv.setHeight("400px");
|
||||||
mapDiv.getElement().setProperty("innerHTML",
|
mapDiv.getElement().setProperty("innerHTML",
|
||||||
"<iframe width='100%' height='100%' frameborder='0' style='border:0' " +
|
"<iframe width='100%' height='100%' frameborder='0' style='border:0' "
|
||||||
"src='https://www.google.com/maps/embed/v1/place?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&q=53.6070,10.1125' allowfullscreen></iframe>");
|
+ "src='https://www.google.com/maps/embed/v1/place?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&q=53.6070,10.1125' allowfullscreen></iframe>");
|
||||||
VerticalLayout mapTab = new VerticalLayout();
|
VerticalLayout mapTab = new VerticalLayout();
|
||||||
mapTab.setPadding(false);
|
mapTab.setPadding(false);
|
||||||
mapTab.setSpacing(true);
|
mapTab.setSpacing(true);
|
||||||
@@ -282,8 +268,8 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
introTextArea.addValueChangeListener(e -> refreshPdf());
|
introTextArea.addValueChangeListener(e -> refreshPdf());
|
||||||
termsTextArea.addValueChangeListener(e -> refreshPdf());
|
termsTextArea.addValueChangeListener(e -> refreshPdf());
|
||||||
|
|
||||||
billingLeft.add(partsTitle, billingEnabled, prefixField, ustIdField, taxNumberField, bankNameField,
|
billingLeft.add(partsTitle, billingEnabled, prefixField, ustIdField, taxNumberField, bankNameField, ibanField,
|
||||||
ibanField, taxRateField, introTextArea, termsTextArea);
|
taxRateField, introTextArea, termsTextArea);
|
||||||
|
|
||||||
// Rechte Spalte: Vorschau
|
// Rechte Spalte: Vorschau
|
||||||
VerticalLayout billingRight = new VerticalLayout();
|
VerticalLayout billingRight = new VerticalLayout();
|
||||||
@@ -299,10 +285,8 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
Div previewWrapper = new Div();
|
Div previewWrapper = new Div();
|
||||||
previewWrapper.setWidth("100%");
|
previewWrapper.setWidth("100%");
|
||||||
previewWrapper.setHeight("650px");
|
previewWrapper.setHeight("650px");
|
||||||
previewWrapper.getStyle()
|
previewWrapper.getStyle().set("overflow", "hidden").set("background", "var(--lumo-contrast-10pct)")
|
||||||
.set("overflow", "hidden")
|
.set("padding", "0");
|
||||||
.set("background", "var(--lumo-contrast-10pct)")
|
|
||||||
.set("padding", "0");
|
|
||||||
|
|
||||||
// Initial noch keine PDF laden (erst bei aktiver Checkbox)
|
// Initial noch keine PDF laden (erst bei aktiver Checkbox)
|
||||||
pdfFrame = new IFrame();
|
pdfFrame = new IFrame();
|
||||||
@@ -315,11 +299,9 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
|
|
||||||
billingRight.add(previewTitle, previewWrapper);
|
billingRight.add(previewTitle, previewWrapper);
|
||||||
|
|
||||||
|
|
||||||
billingTab.add(billingLeft, billingRight);
|
billingTab.add(billingLeft, billingRight);
|
||||||
tabSheet.add("Rechnungsstellung", billingTab);
|
tabSheet.add("Rechnungsstellung", billingTab);
|
||||||
|
|
||||||
|
|
||||||
// Zweiter Tab: Einstellungen (Beispiel mit Schaltern)
|
// Zweiter Tab: Einstellungen (Beispiel mit Schaltern)
|
||||||
VerticalLayout switches = new VerticalLayout();
|
VerticalLayout switches = new VerticalLayout();
|
||||||
switches.setPadding(false);
|
switches.setPadding(false);
|
||||||
@@ -392,14 +374,22 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
// PDF neu rendern und iframe aktualisieren
|
// PDF neu rendern und iframe aktualisieren
|
||||||
// Felder im Billing-Tab aktivieren/deaktivieren
|
// Felder im Billing-Tab aktivieren/deaktivieren
|
||||||
private void setBillingFieldsEnabled(boolean enabled) {
|
private void setBillingFieldsEnabled(boolean enabled) {
|
||||||
if (prefixField != null) prefixField.setEnabled(enabled);
|
if (prefixField != null)
|
||||||
if (ustIdField != null) ustIdField.setEnabled(enabled);
|
prefixField.setEnabled(enabled);
|
||||||
if (taxNumberField != null) taxNumberField.setEnabled(enabled);
|
if (ustIdField != null)
|
||||||
if (bankNameField != null) bankNameField.setEnabled(enabled);
|
ustIdField.setEnabled(enabled);
|
||||||
if (ibanField != null) ibanField.setEnabled(enabled);
|
if (taxNumberField != null)
|
||||||
if (taxRateField != null) taxRateField.setEnabled(enabled);
|
taxNumberField.setEnabled(enabled);
|
||||||
if (introTextArea != null) introTextArea.setEnabled(enabled);
|
if (bankNameField != null)
|
||||||
if (termsTextArea != null) termsTextArea.setEnabled(enabled);
|
bankNameField.setEnabled(enabled);
|
||||||
|
if (ibanField != null)
|
||||||
|
ibanField.setEnabled(enabled);
|
||||||
|
if (taxRateField != null)
|
||||||
|
taxRateField.setEnabled(enabled);
|
||||||
|
if (introTextArea != null)
|
||||||
|
introTextArea.setEnabled(enabled);
|
||||||
|
if (termsTextArea != null)
|
||||||
|
termsTextArea.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkbox steuert Aktivierung und PDF
|
// Checkbox steuert Aktivierung und PDF
|
||||||
@@ -424,14 +414,14 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
private void refreshPdf() {
|
private void refreshPdf() {
|
||||||
byte[] bytes = generatePreviewPdf();
|
byte[] bytes = generatePreviewPdf();
|
||||||
String dataUrl = "data:application/pdf;base64," + Base64.getEncoder().encodeToString(bytes)
|
String dataUrl = "data:application/pdf;base64," + Base64.getEncoder().encodeToString(bytes)
|
||||||
+ "#toolbar=0&navpanes=0&statusbar=0&view=Fit&zoom=page-fit";
|
+ "#toolbar=0&navpanes=0&statusbar=0&view=Fit&zoom=page-fit";
|
||||||
if (pdfFrame != null) {
|
if (pdfFrame != null) {
|
||||||
pdfFrame.setSrc(dataUrl);
|
pdfFrame.setSrc(dataUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Einfache PDF-Vorschau generieren (kann später durch echte Logik ersetzt
|
||||||
// Einfache PDF-Vorschau generieren (kann später durch echte Logik ersetzt werden)
|
// werden)
|
||||||
private byte[] generatePreviewPdf() {
|
private byte[] generatePreviewPdf() {
|
||||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||||
com.lowagie.text.Document document = new com.lowagie.text.Document();
|
com.lowagie.text.Document document = new com.lowagie.text.Document();
|
||||||
@@ -459,7 +449,12 @@ public class EditProfileView extends HorizontalLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Utility: safe getter für TextField/TextArea
|
// Utility: safe getter für TextField/TextArea
|
||||||
private String safe(TextField f) { return f != null && f.getValue() != null ? f.getValue() : ""; }
|
private String safe(TextField f) {
|
||||||
private String safe(TextArea f) { return f != null && f.getValue() != null ? f.getValue() : ""; }
|
return f != null && f.getValue() != null ? f.getValue() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safe(TextArea f) {
|
||||||
|
return f != null && f.getValue() != null ? f.getValue() : "";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,13 @@ public class ForgetPasswordView extends VerticalLayout implements BeforeEnterObs
|
|||||||
String tokenParam = params.getOrDefault("token", java.util.List.of("")).getFirst();
|
String tokenParam = params.getOrDefault("token", java.util.List.of("")).getFirst();
|
||||||
String typeParam = params.getOrDefault("type", java.util.List.of("")).getFirst();
|
String typeParam = params.getOrDefault("type", java.util.List.of("")).getFirst();
|
||||||
this.token = tokenParam != null ? tokenParam.trim() : "";
|
this.token = tokenParam != null ? tokenParam.trim() : "";
|
||||||
this.userType = "app_user".equalsIgnoreCase(typeParam) ? PasswordResetService.UserType.APP_USER : PasswordResetService.UserType.USERS;
|
this.userType = "app_user".equalsIgnoreCase(typeParam) ? PasswordResetService.UserType.APP_USER
|
||||||
|
: PasswordResetService.UserType.USERS;
|
||||||
|
|
||||||
if (this.token.isEmpty() || !passwordResetService.isTokenValid(this.token, this.userType)) {
|
if (this.token.isEmpty() || !passwordResetService.isTokenValid(this.token, this.userType)) {
|
||||||
// Store a flash message in the VaadinSession so it persists through reroute
|
// Store a flash message in the VaadinSession so it persists through reroute
|
||||||
com.vaadin.flow.server.VaadinSession.getCurrent().setAttribute("flashMessage", "Ungültiger oder abgelaufener Token.");
|
com.vaadin.flow.server.VaadinSession.getCurrent().setAttribute("flashMessage",
|
||||||
|
"Ungültiger oder abgelaufener Token.");
|
||||||
event.rerouteTo(LoginView.class);
|
event.rerouteTo(LoginView.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ public class ForgotPasswordRequestView extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
String baseUrl = getBaseUrl();
|
String baseUrl = getBaseUrl();
|
||||||
passwordResetService.initiateResetAuto(email.trim(), baseUrl);
|
passwordResetService.initiateResetAuto(email.trim(), baseUrl);
|
||||||
Notification.show("Falls die E-Mail existiert, wurde ein Link versendet.", 4000, Notification.Position.MIDDLE);
|
Notification.show("Falls die E-Mail existiert, wurde ein Link versendet.", 4000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
getUI().ifPresent(ui -> ui.navigate("login"));
|
getUI().ifPresent(ui -> ui.navigate("login"));
|
||||||
});
|
});
|
||||||
submit.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
submit.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
@@ -69,8 +70,8 @@ public class ForgotPasswordRequestView extends VerticalLayout {
|
|||||||
String serverName = req.getServerName();
|
String serverName = req.getServerName();
|
||||||
int serverPort = req.getServerPort();
|
int serverPort = req.getServerPort();
|
||||||
String contextPath = req.getContextPath();
|
String contextPath = req.getContextPath();
|
||||||
String portPart = ("http".equals(scheme) && serverPort == 80) || ("https".equals(scheme) && serverPort == 443)
|
String portPart = ("http".equals(scheme) && serverPort == 80)
|
||||||
? "" : ":" + serverPort;
|
|| ("https".equals(scheme) && serverPort == 443) ? "" : ":" + serverPort;
|
||||||
return scheme + "://" + serverName + portPart + contextPath;
|
return scheme + "://" + serverName + portPart + contextPath;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ public class ImprintView extends VerticalLayout {
|
|||||||
|
|
||||||
Paragraph p1 = new Paragraph("Max Mustermann\nMusterstraße 1\n12345 Musterstadt\nDeutschland");
|
Paragraph p1 = new Paragraph("Max Mustermann\nMusterstraße 1\n12345 Musterstadt\nDeutschland");
|
||||||
Paragraph p2 = new Paragraph("Telefon: +49 123 456789\nE-Mail: info@example.com");
|
Paragraph p2 = new Paragraph("Telefon: +49 123 456789\nE-Mail: info@example.com");
|
||||||
Paragraph p3 = new Paragraph("Umsatzsteuer-ID: DE123456789\nHandelsregister: Amtsgericht Musterstadt, HRB 12345");
|
Paragraph p3 = new Paragraph(
|
||||||
|
"Umsatzsteuer-ID: DE123456789\nHandelsregister: Amtsgericht Musterstadt, HRB 12345");
|
||||||
Paragraph p4 = new Paragraph("Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV: Max Mustermann");
|
Paragraph p4 = new Paragraph("Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV: Max Mustermann");
|
||||||
|
|
||||||
add(p1, p2, p3, p4);
|
add(p1, p2, p3, p4);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import com.vaadin.flow.server.StreamRegistration;
|
|||||||
|
|
||||||
@PageTitle("Rechnungen")
|
@PageTitle("Rechnungen")
|
||||||
@Route(value = "invoices", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "invoices", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class InvoicesView extends VerticalLayout {
|
public class InvoicesView extends VerticalLayout {
|
||||||
|
|
||||||
private final Grid<Invoice> invoiceGrid;
|
private final Grid<Invoice> invoiceGrid;
|
||||||
@@ -47,10 +47,11 @@ public class InvoicesView extends VerticalLayout {
|
|||||||
|
|
||||||
// Testdaten
|
// Testdaten
|
||||||
List<Invoice> testInvoices = List.of(
|
List<Invoice> testInvoices = List.of(
|
||||||
new Invoice("R-2024-001", "Max Mustermann", LocalDate.now().minusDays(2), 199.99, "Transport Hamburg-Berlin"),
|
new Invoice("R-2024-001", "Max Mustermann", LocalDate.now().minusDays(2), 199.99,
|
||||||
new Invoice("R-2024-002", "Erika Musterfrau", LocalDate.now().minusDays(1), 299.49, "Express München-Köln"),
|
"Transport Hamburg-Berlin"),
|
||||||
new Invoice("R-2024-003", "Hans Beispiel", LocalDate.now(), 149.00, "Standard Leipzig-Dresden")
|
new Invoice("R-2024-002", "Erika Musterfrau", LocalDate.now().minusDays(1), 299.49,
|
||||||
);
|
"Express München-Köln"),
|
||||||
|
new Invoice("R-2024-003", "Hans Beispiel", LocalDate.now(), 149.00, "Standard Leipzig-Dresden"));
|
||||||
invoiceGrid.setItems(testInvoices);
|
invoiceGrid.setItems(testInvoices);
|
||||||
|
|
||||||
invoiceGrid.addItemClickListener(event -> {
|
invoiceGrid.addItemClickListener(event -> {
|
||||||
@@ -67,17 +68,16 @@ public class InvoicesView extends VerticalLayout {
|
|||||||
try {
|
try {
|
||||||
// PDF generieren
|
// PDF generieren
|
||||||
byte[] pdfBytes = generatePdf(invoice);
|
byte[] pdfBytes = generatePdf(invoice);
|
||||||
StreamResource resource = new StreamResource(
|
StreamResource resource = new StreamResource(invoice.getId() + ".pdf",
|
||||||
invoice.getId() + ".pdf",
|
() -> new ByteArrayInputStream(pdfBytes));
|
||||||
() -> new ByteArrayInputStream(pdfBytes)
|
|
||||||
);
|
|
||||||
resource.setContentType("application/pdf");
|
resource.setContentType("application/pdf");
|
||||||
resource.setCacheTime(0);
|
resource.setCacheTime(0);
|
||||||
|
|
||||||
// Direkter Download über UI
|
// Direkter Download über UI
|
||||||
StreamRegistration registration = UI.getCurrent().getSession().getResourceRegistry().registerResource(resource);
|
StreamRegistration registration = UI.getCurrent().getSession().getResourceRegistry()
|
||||||
|
.registerResource(resource);
|
||||||
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Notification.show("Fehler beim Erstellen der PDF: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
Notification.show("Fehler beim Erstellen der PDF: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
@@ -101,4 +101,3 @@ public class InvoicesView extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
private final SignatureRepository signatureRepository;
|
private final SignatureRepository signatureRepository;
|
||||||
private final VerticalLayout content;
|
private final VerticalLayout content;
|
||||||
|
|
||||||
public JobHistoryView(JobRepository jobRepository, JobHistoryService jobHistoryService, PhotoRepository photoRepository, BarcodeRepository barcodeRepository, SignatureRepository signatureRepository) {
|
public JobHistoryView(JobRepository jobRepository, JobHistoryService jobHistoryService,
|
||||||
|
PhotoRepository photoRepository, BarcodeRepository barcodeRepository,
|
||||||
|
SignatureRepository signatureRepository) {
|
||||||
this.jobRepository = jobRepository;
|
this.jobRepository = jobRepository;
|
||||||
this.jobHistoryService = jobHistoryService;
|
this.jobHistoryService = jobHistoryService;
|
||||||
this.photoRepository = photoRepository;
|
this.photoRepository = photoRepository;
|
||||||
@@ -53,9 +55,8 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
this.signatureRepository = signatureRepository;
|
this.signatureRepository = signatureRepository;
|
||||||
|
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX,
|
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||||
LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM,
|
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||||
LumoUtility.Gap.SMALL);
|
|
||||||
|
|
||||||
add(new ViewToolbar("Job Historie"));
|
add(new ViewToolbar("Job Historie"));
|
||||||
|
|
||||||
@@ -96,7 +97,8 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
content.removeAll();
|
content.removeAll();
|
||||||
|
|
||||||
// Header mit Job-Informationen
|
// Header mit Job-Informationen
|
||||||
H2 header = new H2("Job Historie - " + (job.getJobNumber() != null ? job.getJobNumber() : "Unbekannte Auftragsnummer"));
|
H2 header = new H2(
|
||||||
|
"Job Historie - " + (job.getJobNumber() != null ? job.getJobNumber() : "Unbekannte Auftragsnummer"));
|
||||||
content.add(header);
|
content.add(header);
|
||||||
|
|
||||||
// Job basic info for context
|
// Job basic info for context
|
||||||
@@ -131,12 +133,9 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private Div createJobInfoBox(Job job) {
|
private Div createJobInfoBox(Job job) {
|
||||||
Div infoBox = new Div();
|
Div infoBox = new Div();
|
||||||
infoBox.getStyle()
|
infoBox.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
.set("border-radius", "var(--lumo-border-radius-m)").set("padding", "var(--lumo-space-m)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-m)")
|
.set("background-color", "var(--lumo-base-color)").set("margin-bottom", "var(--lumo-space-m)");
|
||||||
.set("padding", "var(--lumo-space-m)")
|
|
||||||
.set("background-color", "var(--lumo-base-color)")
|
|
||||||
.set("margin-bottom", "var(--lumo-space-m)");
|
|
||||||
|
|
||||||
VerticalLayout infoContent = new VerticalLayout();
|
VerticalLayout infoContent = new VerticalLayout();
|
||||||
infoContent.setPadding(false);
|
infoContent.setPadding(false);
|
||||||
@@ -172,15 +171,11 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private Div createHistoryEntryCard(JobHistory entry) {
|
private Div createHistoryEntryCard(JobHistory entry) {
|
||||||
Div card = new Div();
|
Div card = new Div();
|
||||||
card.getStyle()
|
card.getStyle().set("border", "1px solid var(--lumo-contrast-10pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-10pct)")
|
.set("border-left", "4px solid " + getTypeColor(entry.getChangeType()))
|
||||||
.set("border-left", "4px solid " + getTypeColor(entry.getChangeType()))
|
.set("border-radius", "var(--lumo-border-radius-s)").set("padding", "var(--lumo-space-m)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-s)")
|
.set("margin-bottom", "var(--lumo-space-s)").set("background-color", "var(--lumo-base-color)")
|
||||||
.set("padding", "var(--lumo-space-m)")
|
.set("width", "100%").set("box-sizing", "border-box");
|
||||||
.set("margin-bottom", "var(--lumo-space-s)")
|
|
||||||
.set("background-color", "var(--lumo-base-color)")
|
|
||||||
.set("width", "100%")
|
|
||||||
.set("box-sizing", "border-box");
|
|
||||||
|
|
||||||
// Header row with icon, reason and timestamp
|
// Header row with icon, reason and timestamp
|
||||||
HorizontalLayout headerRow = new HorizontalLayout();
|
HorizontalLayout headerRow = new HorizontalLayout();
|
||||||
@@ -194,9 +189,8 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
reason.getStyle().set("font-weight", "500");
|
reason.getStyle().set("font-weight", "500");
|
||||||
|
|
||||||
Span timestamp = new Span(formatDateTime(entry.getTimestamp()));
|
Span timestamp = new Span(formatDateTime(entry.getTimestamp()));
|
||||||
timestamp.getStyle()
|
timestamp.getStyle().set("color", "var(--lumo-secondary-text-color)").set("font-size",
|
||||||
.set("color", "var(--lumo-secondary-text-color)")
|
"var(--lumo-font-size-s)");
|
||||||
.set("font-size", "var(--lumo-font-size-s)");
|
|
||||||
|
|
||||||
HorizontalLayout leftSide = new HorizontalLayout(typeIcon, reason);
|
HorizontalLayout leftSide = new HorizontalLayout(typeIcon, reason);
|
||||||
leftSide.setAlignItems(HorizontalLayout.Alignment.CENTER);
|
leftSide.setAlignItems(HorizontalLayout.Alignment.CENTER);
|
||||||
@@ -213,17 +207,14 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
// Description
|
// Description
|
||||||
if (entry.getDescription() != null && !entry.getDescription().isBlank()) {
|
if (entry.getDescription() != null && !entry.getDescription().isBlank()) {
|
||||||
Span description = new Span(entry.getDescription());
|
Span description = new Span(entry.getDescription());
|
||||||
description.getStyle()
|
description.getStyle().set("color", "var(--lumo-body-text-color)").set("margin-top", "var(--lumo-space-xs)")
|
||||||
.set("color", "var(--lumo-body-text-color)")
|
.set("display", "block");
|
||||||
.set("margin-top", "var(--lumo-space-xs)")
|
|
||||||
.set("display", "block");
|
|
||||||
cardContent.add(description);
|
cardContent.add(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Photo preview for photo tasks
|
// Photo preview for photo tasks
|
||||||
if (entry.getChangeType() == JobHistoryType.TASK_COMPLETED &&
|
if (entry.getChangeType() == JobHistoryType.TASK_COMPLETED && entry.getDetails() != null
|
||||||
entry.getDetails() != null &&
|
&& entry.getDetails().contains("Task-Typ: PHOTO")) {
|
||||||
entry.getDetails().contains("Task-Typ: PHOTO")) {
|
|
||||||
|
|
||||||
HorizontalLayout photoPreview = createPhotoPreview(entry);
|
HorizontalLayout photoPreview = createPhotoPreview(entry);
|
||||||
if (photoPreview != null) {
|
if (photoPreview != null) {
|
||||||
@@ -232,9 +223,8 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Barcode preview for barcode tasks
|
// Barcode preview for barcode tasks
|
||||||
if (entry.getChangeType() == JobHistoryType.TASK_COMPLETED &&
|
if (entry.getChangeType() == JobHistoryType.TASK_COMPLETED && entry.getDetails() != null
|
||||||
entry.getDetails() != null &&
|
&& entry.getDetails().contains("Task-Typ: BARCODE")) {
|
||||||
entry.getDetails().contains("Task-Typ: BARCODE")) {
|
|
||||||
|
|
||||||
VerticalLayout barcodePreview = createBarcodePreview(entry);
|
VerticalLayout barcodePreview = createBarcodePreview(entry);
|
||||||
if (barcodePreview != null) {
|
if (barcodePreview != null) {
|
||||||
@@ -243,9 +233,8 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Signature preview for signature tasks
|
// Signature preview for signature tasks
|
||||||
if (entry.getChangeType() == JobHistoryType.TASK_COMPLETED &&
|
if (entry.getChangeType() == JobHistoryType.TASK_COMPLETED && entry.getDetails() != null
|
||||||
entry.getDetails() != null &&
|
&& entry.getDetails().contains("Task-Typ: SIGNATURE")) {
|
||||||
entry.getDetails().contains("Task-Typ: SIGNATURE")) {
|
|
||||||
|
|
||||||
Div signaturePreview = createSignaturePreview(entry);
|
Div signaturePreview = createSignaturePreview(entry);
|
||||||
if (signaturePreview != null) {
|
if (signaturePreview != null) {
|
||||||
@@ -256,11 +245,9 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
// Changed by (if available)
|
// Changed by (if available)
|
||||||
if (entry.getChangedBy() != null && !entry.getChangedBy().isBlank()) {
|
if (entry.getChangedBy() != null && !entry.getChangedBy().isBlank()) {
|
||||||
Span changedBy = new Span("von: " + entry.getChangedBy());
|
Span changedBy = new Span("von: " + entry.getChangedBy());
|
||||||
changedBy.getStyle()
|
changedBy.getStyle().set("color", "var(--lumo-secondary-text-color)")
|
||||||
.set("color", "var(--lumo-secondary-text-color)")
|
.set("font-size", "var(--lumo-font-size-xs)").set("margin-top", "var(--lumo-space-xs)")
|
||||||
.set("font-size", "var(--lumo-font-size-xs)")
|
.set("display", "block");
|
||||||
.set("margin-top", "var(--lumo-space-xs)")
|
|
||||||
.set("display", "block");
|
|
||||||
cardContent.add(changedBy);
|
cardContent.add(changedBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,44 +257,47 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Icon getTypeIcon(JobHistoryType type) {
|
private Icon getTypeIcon(JobHistoryType type) {
|
||||||
if (type == null) return new Icon(VaadinIcon.INFO_CIRCLE);
|
if (type == null)
|
||||||
|
return new Icon(VaadinIcon.INFO_CIRCLE);
|
||||||
|
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
case CREATE -> new Icon(VaadinIcon.PLUS_CIRCLE);
|
case CREATE -> new Icon(VaadinIcon.PLUS_CIRCLE);
|
||||||
case UPDATE -> new Icon(VaadinIcon.EDIT);
|
case UPDATE -> new Icon(VaadinIcon.EDIT);
|
||||||
case STATUS_CHANGE -> new Icon(VaadinIcon.ARROW_RIGHT);
|
case STATUS_CHANGE -> new Icon(VaadinIcon.ARROW_RIGHT);
|
||||||
case TASK_COMPLETED -> new Icon(VaadinIcon.CHECK);
|
case TASK_COMPLETED -> new Icon(VaadinIcon.CHECK);
|
||||||
case ASSIGNMENT -> new Icon(VaadinIcon.USER);
|
case ASSIGNMENT -> new Icon(VaadinIcon.USER);
|
||||||
case EXPORT -> new Icon(VaadinIcon.DOWNLOAD);
|
case EXPORT -> new Icon(VaadinIcon.DOWNLOAD);
|
||||||
case DELETE -> new Icon(VaadinIcon.TRASH);
|
case DELETE -> new Icon(VaadinIcon.TRASH);
|
||||||
case SYSTEM -> new Icon(VaadinIcon.COG);
|
case SYSTEM -> new Icon(VaadinIcon.COG);
|
||||||
case COMMENT -> new Icon(VaadinIcon.COMMENT);
|
case COMMENT -> new Icon(VaadinIcon.COMMENT);
|
||||||
default -> new Icon(VaadinIcon.INFO_CIRCLE);
|
default -> new Icon(VaadinIcon.INFO_CIRCLE);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTypeColor(JobHistoryType type) {
|
private String getTypeColor(JobHistoryType type) {
|
||||||
if (type == null) return "var(--lumo-contrast-60pct)";
|
if (type == null)
|
||||||
|
return "var(--lumo-contrast-60pct)";
|
||||||
|
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
case CREATE -> "var(--lumo-success-color)";
|
case CREATE -> "var(--lumo-success-color)";
|
||||||
case UPDATE -> "var(--lumo-primary-color)";
|
case UPDATE -> "var(--lumo-primary-color)";
|
||||||
case STATUS_CHANGE -> "var(--lumo-contrast-color)";
|
case STATUS_CHANGE -> "var(--lumo-contrast-color)";
|
||||||
case TASK_COMPLETED -> "var(--lumo-success-color)";
|
case TASK_COMPLETED -> "var(--lumo-success-color)";
|
||||||
case ASSIGNMENT -> "var(--lumo-primary-color)";
|
case ASSIGNMENT -> "var(--lumo-primary-color)";
|
||||||
case EXPORT -> "var(--lumo-contrast-color)";
|
case EXPORT -> "var(--lumo-contrast-color)";
|
||||||
case DELETE -> "var(--lumo-error-color)";
|
case DELETE -> "var(--lumo-error-color)";
|
||||||
case SYSTEM -> "var(--lumo-contrast-60pct)";
|
case SYSTEM -> "var(--lumo-contrast-60pct)";
|
||||||
case COMMENT -> "var(--lumo-primary-color)";
|
case COMMENT -> "var(--lumo-primary-color)";
|
||||||
default -> "var(--lumo-contrast-60pct)";
|
default -> "var(--lumo-contrast-60pct)";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatDateTime(java.time.LocalDateTime dateTime) {
|
private String formatDateTime(java.time.LocalDateTime dateTime) {
|
||||||
if (dateTime == null) return "";
|
if (dateTime == null)
|
||||||
|
return "";
|
||||||
try {
|
try {
|
||||||
java.time.format.DateTimeFormatter formatter =
|
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter
|
||||||
java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
|
.ofPattern("dd.MM.yyyy HH:mm");
|
||||||
return dateTime.format(formatter);
|
return dateTime.format(formatter);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return dateTime.toString();
|
return dateTime.toString();
|
||||||
@@ -315,18 +305,19 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String formatStatus(de.assecutor.votianlt.model.JobStatus status) {
|
private String formatStatus(de.assecutor.votianlt.model.JobStatus status) {
|
||||||
if (status == null) return "Unbekannt";
|
if (status == null)
|
||||||
|
return "Unbekannt";
|
||||||
|
|
||||||
return switch (status) {
|
return switch (status) {
|
||||||
case CREATED -> "Erstellt";
|
case CREATED -> "Erstellt";
|
||||||
case IN_PROGRESS -> "In Bearbeitung";
|
case IN_PROGRESS -> "In Bearbeitung";
|
||||||
case PICKUP_SCHEDULED -> "Abholung geplant";
|
case PICKUP_SCHEDULED -> "Abholung geplant";
|
||||||
case PICKED_UP -> "Abgeholt";
|
case PICKED_UP -> "Abgeholt";
|
||||||
case IN_TRANSIT -> "Unterwegs";
|
case IN_TRANSIT -> "Unterwegs";
|
||||||
case DELIVERED -> "Zugestellt";
|
case DELIVERED -> "Zugestellt";
|
||||||
case COMPLETED -> "Abgeschlossen";
|
case COMPLETED -> "Abgeschlossen";
|
||||||
case CANCELLED -> "Storniert";
|
case CANCELLED -> "Storniert";
|
||||||
default -> status.toString();
|
default -> status.toString();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,9 +342,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
HorizontalLayout photoLayout = new HorizontalLayout();
|
HorizontalLayout photoLayout = new HorizontalLayout();
|
||||||
photoLayout.setSpacing(true);
|
photoLayout.setSpacing(true);
|
||||||
photoLayout.getStyle()
|
photoLayout.getStyle().set("margin-top", "var(--lumo-space-s)").set("flex-wrap", "wrap");
|
||||||
.set("margin-top", "var(--lumo-space-s)")
|
|
||||||
.set("flex-wrap", "wrap");
|
|
||||||
|
|
||||||
for (Photo photo : photos) {
|
for (Photo photo : photos) {
|
||||||
if (photo.getPhoto() != null && !photo.getPhoto().isBlank()) {
|
if (photo.getPhoto() != null && !photo.getPhoto().isBlank()) {
|
||||||
@@ -395,18 +384,13 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private com.vaadin.flow.component.html.Image createPhotoThumbnail(String base64Photo) {
|
private com.vaadin.flow.component.html.Image createPhotoThumbnail(String base64Photo) {
|
||||||
try {
|
try {
|
||||||
String imageData = base64Photo.startsWith("data:")
|
String imageData = base64Photo.startsWith("data:") ? base64Photo : "data:image/jpeg;base64," + base64Photo;
|
||||||
? base64Photo
|
|
||||||
: "data:image/jpeg;base64," + base64Photo;
|
|
||||||
|
|
||||||
com.vaadin.flow.component.html.Image thumbnail = new com.vaadin.flow.component.html.Image(imageData, "Foto");
|
com.vaadin.flow.component.html.Image thumbnail = new com.vaadin.flow.component.html.Image(imageData,
|
||||||
thumbnail.getStyle()
|
"Foto");
|
||||||
.set("width", "100px")
|
thumbnail.getStyle().set("width", "100px").set("height", "100px").set("object-fit", "cover")
|
||||||
.set("height", "100px")
|
.set("border-radius", "var(--lumo-border-radius-s)")
|
||||||
.set("object-fit", "cover")
|
.set("border", "1px solid var(--lumo-contrast-20pct)").set("cursor", "pointer");
|
||||||
.set("border-radius", "var(--lumo-border-radius-s)")
|
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
|
||||||
.set("cursor", "pointer");
|
|
||||||
|
|
||||||
return thumbnail;
|
return thumbnail;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -424,15 +408,11 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
photoDialog.setCloseOnEsc(true);
|
photoDialog.setCloseOnEsc(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String imageData = base64Photo.startsWith("data:")
|
String imageData = base64Photo.startsWith("data:") ? base64Photo : "data:image/jpeg;base64," + base64Photo;
|
||||||
? base64Photo
|
|
||||||
: "data:image/jpeg;base64," + base64Photo;
|
|
||||||
|
|
||||||
com.vaadin.flow.component.html.Image enlargedImage = new com.vaadin.flow.component.html.Image(imageData, "Vergrößertes Foto");
|
com.vaadin.flow.component.html.Image enlargedImage = new com.vaadin.flow.component.html.Image(imageData,
|
||||||
enlargedImage.getStyle()
|
"Vergrößertes Foto");
|
||||||
.set("max-width", "100%")
|
enlargedImage.getStyle().set("max-width", "100%").set("max-height", "100%").set("object-fit", "contain");
|
||||||
.set("max-height", "100%")
|
|
||||||
.set("object-fit", "contain");
|
|
||||||
|
|
||||||
VerticalLayout dialogContent = new VerticalLayout(enlargedImage);
|
VerticalLayout dialogContent = new VerticalLayout(enlargedImage);
|
||||||
dialogContent.setAlignItems(VerticalLayout.Alignment.CENTER);
|
dialogContent.setAlignItems(VerticalLayout.Alignment.CENTER);
|
||||||
@@ -470,8 +450,7 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
VerticalLayout barcodeLayout = new VerticalLayout();
|
VerticalLayout barcodeLayout = new VerticalLayout();
|
||||||
barcodeLayout.setPadding(false);
|
barcodeLayout.setPadding(false);
|
||||||
barcodeLayout.setSpacing(true);
|
barcodeLayout.setSpacing(true);
|
||||||
barcodeLayout.getStyle()
|
barcodeLayout.getStyle().set("margin-top", "var(--lumo-space-s)");
|
||||||
.set("margin-top", "var(--lumo-space-s)");
|
|
||||||
|
|
||||||
for (Barcode barcode : barcodes) {
|
for (Barcode barcode : barcodes) {
|
||||||
if (barcode.getBarcode() != null && !barcode.getBarcode().isBlank()) {
|
if (barcode.getBarcode() != null && !barcode.getBarcode().isBlank()) {
|
||||||
@@ -490,15 +469,11 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private Div createBarcodeBox(String barcodeValue) {
|
private Div createBarcodeBox(String barcodeValue) {
|
||||||
Div barcodeBox = new Div();
|
Div barcodeBox = new Div();
|
||||||
barcodeBox.getStyle()
|
barcodeBox.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
.set("border-radius", "var(--lumo-border-radius-s)").set("padding", "var(--lumo-space-xs)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-s)")
|
.set("background-color", "var(--lumo-contrast-5pct)").set("font-family", "monospace")
|
||||||
.set("padding", "var(--lumo-space-xs)")
|
.set("font-size", "var(--lumo-font-size-s)").set("margin-bottom", "var(--lumo-space-xs)")
|
||||||
.set("background-color", "var(--lumo-contrast-5pct)")
|
.set("word-break", "break-all");
|
||||||
.set("font-family", "monospace")
|
|
||||||
.set("font-size", "var(--lumo-font-size-s)")
|
|
||||||
.set("margin-bottom", "var(--lumo-space-xs)")
|
|
||||||
.set("word-break", "break-all");
|
|
||||||
|
|
||||||
barcodeBox.add(new Span(barcodeValue));
|
barcodeBox.add(new Span(barcodeValue));
|
||||||
return barcodeBox;
|
return barcodeBox;
|
||||||
@@ -531,22 +506,16 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Div previewContainer = new Div();
|
Div previewContainer = new Div();
|
||||||
previewContainer.getStyle()
|
previewContainer.getStyle().set("margin-top", "var(--lumo-space-s)")
|
||||||
.set("margin-top", "var(--lumo-space-s)")
|
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
.set("border-radius", "var(--lumo-border-radius-s)").set("padding", "var(--lumo-space-xs)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-s)")
|
.set("background-color", "var(--lumo-base-color)").set("cursor", "pointer").set("width", "200px")
|
||||||
.set("padding", "var(--lumo-space-xs)")
|
.set("height", "100px").set("overflow", "hidden").set("display", "flex")
|
||||||
.set("background-color", "var(--lumo-base-color)")
|
.set("align-items", "center").set("justify-content", "center");
|
||||||
.set("cursor", "pointer")
|
|
||||||
.set("width", "200px")
|
|
||||||
.set("height", "100px")
|
|
||||||
.set("overflow", "hidden")
|
|
||||||
.set("display", "flex")
|
|
||||||
.set("align-items", "center")
|
|
||||||
.set("justify-content", "center");
|
|
||||||
|
|
||||||
// Create responsive SVG for preview
|
// Create responsive SVG for preview
|
||||||
com.vaadin.flow.component.Html signatureSvg = createResponsiveSignatureSvg(signature.getSignatureSvg(), "100%", "100%");
|
com.vaadin.flow.component.Html signatureSvg = createResponsiveSignatureSvg(signature.getSignatureSvg(),
|
||||||
|
"100%", "100%");
|
||||||
previewContainer.add(signatureSvg);
|
previewContainer.add(signatureSvg);
|
||||||
|
|
||||||
// Add click listener for enlarged view
|
// Add click listener for enlarged view
|
||||||
@@ -560,27 +529,28 @@ public class JobHistoryView extends Main implements HasUrlParameter<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private com.vaadin.flow.component.Html createResponsiveSignatureSvg(String svgContent, String width, String height) {
|
private com.vaadin.flow.component.Html createResponsiveSignatureSvg(String svgContent, String width,
|
||||||
|
String height) {
|
||||||
// Make SVG responsive by ensuring proper viewBox and dimensions
|
// Make SVG responsive by ensuring proper viewBox and dimensions
|
||||||
String responsiveSvg = svgContent;
|
String responsiveSvg = svgContent;
|
||||||
|
|
||||||
if (!responsiveSvg.contains("viewBox")) {
|
if (!responsiveSvg.contains("viewBox")) {
|
||||||
// Try to extract width and height from SVG and create viewBox
|
// Try to extract width and height from SVG and create viewBox
|
||||||
responsiveSvg = responsiveSvg.replaceFirst("<svg",
|
responsiveSvg = responsiveSvg.replaceFirst("<svg",
|
||||||
"<svg viewBox=\"0 0 300 150\" preserveAspectRatio=\"xMidYMid meet\"");
|
"<svg viewBox=\"0 0 300 150\" preserveAspectRatio=\"xMidYMid meet\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the SVG has proper responsive attributes
|
// Ensure the SVG has proper responsive attributes
|
||||||
if (!responsiveSvg.contains("preserveAspectRatio")) {
|
if (!responsiveSvg.contains("preserveAspectRatio")) {
|
||||||
responsiveSvg = responsiveSvg.replaceFirst("<svg",
|
responsiveSvg = responsiveSvg.replaceFirst("<svg", "<svg preserveAspectRatio=\"xMidYMid meet\"");
|
||||||
"<svg preserveAspectRatio=\"xMidYMid meet\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set responsive dimensions
|
// Set responsive dimensions
|
||||||
responsiveSvg = responsiveSvg.replaceFirst("width=\"[^\"]*\"", "width=\"" + width + "\"");
|
responsiveSvg = responsiveSvg.replaceFirst("width=\"[^\"]*\"", "width=\"" + width + "\"");
|
||||||
responsiveSvg = responsiveSvg.replaceFirst("height=\"[^\"]*\"", "height=\"" + height + "\"");
|
responsiveSvg = responsiveSvg.replaceFirst("height=\"[^\"]*\"", "height=\"" + height + "\"");
|
||||||
|
|
||||||
return new com.vaadin.flow.component.Html("<div style=\"width: " + width + "; height: " + height + ";\">" + responsiveSvg + "</div>");
|
return new com.vaadin.flow.component.Html(
|
||||||
|
"<div style=\"width: " + width + "; height: " + height + ";\">" + responsiveSvg + "</div>");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showEnlargedSignature(String svgContent) {
|
private void showEnlargedSignature(String svgContent) {
|
||||||
|
|||||||
@@ -61,13 +61,9 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
private final VerticalLayout content;
|
private final VerticalLayout content;
|
||||||
private final List<Div> taskCards = new ArrayList<>();
|
private final List<Div> taskCards = new ArrayList<>();
|
||||||
|
|
||||||
public JobSummaryView(JobRepository jobRepository,
|
public JobSummaryView(JobRepository jobRepository, CargoItemRepository cargoItemRepository,
|
||||||
CargoItemRepository cargoItemRepository,
|
TaskRepository taskRepository, SignatureRepository signatureRepository, BarcodeRepository barcodeRepository,
|
||||||
TaskRepository taskRepository,
|
PhotoRepository photoRepository, AppUserService appUserService) {
|
||||||
SignatureRepository signatureRepository,
|
|
||||||
BarcodeRepository barcodeRepository,
|
|
||||||
PhotoRepository photoRepository,
|
|
||||||
AppUserService appUserService) {
|
|
||||||
this.jobRepository = jobRepository;
|
this.jobRepository = jobRepository;
|
||||||
this.cargoItemRepository = cargoItemRepository;
|
this.cargoItemRepository = cargoItemRepository;
|
||||||
this.taskRepository = taskRepository;
|
this.taskRepository = taskRepository;
|
||||||
@@ -77,9 +73,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
this.appUserService = appUserService;
|
this.appUserService = appUserService;
|
||||||
|
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX,
|
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||||
LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM,
|
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||||
LumoUtility.Gap.SMALL);
|
|
||||||
|
|
||||||
content = new VerticalLayout();
|
content = new VerticalLayout();
|
||||||
content.setSpacing(true);
|
content.setSpacing(true);
|
||||||
@@ -145,22 +140,19 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
VerticalLayout pickupBox = borderedBox();
|
VerticalLayout pickupBox = borderedBox();
|
||||||
pickupBox.add(new H3("Abholung " + (job.getPickupDate() != null ? formatLocalDate(job.getPickupDate()) : "")));
|
pickupBox.add(new H3("Abholung " + (job.getPickupDate() != null ? formatLocalDate(job.getPickupDate()) : "")));
|
||||||
pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany())));
|
pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany())));
|
||||||
pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation())
|
pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation()) + (job.getPickupSalutation() != null ? " " : "")
|
||||||
+ (job.getPickupSalutation() != null ? " " : "")
|
+ valueOrEmpty(job.getPickupFirstName()) + (job.getPickupFirstName() != null ? " " : "")
|
||||||
+ valueOrEmpty(job.getPickupFirstName())
|
|
||||||
+ (job.getPickupFirstName() != null ? " " : "")
|
|
||||||
+ valueOrEmpty(job.getPickupLastName())));
|
+ valueOrEmpty(job.getPickupLastName())));
|
||||||
pickupBox.add(new Span(concatAddress(job.getPickupStreet(), job.getPickupHouseNumber())));
|
pickupBox.add(new Span(concatAddress(job.getPickupStreet(), job.getPickupHouseNumber())));
|
||||||
pickupBox.add(new Span(concatZipCity(job.getPickupZip(), job.getPickupCity())));
|
pickupBox.add(new Span(concatZipCity(job.getPickupZip(), job.getPickupCity())));
|
||||||
|
|
||||||
VerticalLayout deliveryBox = borderedBox();
|
VerticalLayout deliveryBox = borderedBox();
|
||||||
deliveryBox.add(new H3("Lieferung " + (job.getDeliveryDate() != null ? formatLocalDate(job.getDeliveryDate()) : "")));
|
deliveryBox.add(
|
||||||
|
new H3("Lieferung " + (job.getDeliveryDate() != null ? formatLocalDate(job.getDeliveryDate()) : "")));
|
||||||
deliveryBox.add(new Span(valueOrEmpty(job.getDeliveryCompany())));
|
deliveryBox.add(new Span(valueOrEmpty(job.getDeliveryCompany())));
|
||||||
deliveryBox.add(new Span(valueOrEmpty(job.getDeliverySalutation())
|
deliveryBox.add(new Span(valueOrEmpty(job.getDeliverySalutation())
|
||||||
+ (job.getDeliverySalutation() != null ? " " : "")
|
+ (job.getDeliverySalutation() != null ? " " : "") + valueOrEmpty(job.getDeliveryFirstName())
|
||||||
+ valueOrEmpty(job.getDeliveryFirstName())
|
+ (job.getDeliveryFirstName() != null ? " " : "") + valueOrEmpty(job.getDeliveryLastName())));
|
||||||
+ (job.getDeliveryFirstName() != null ? " " : "")
|
|
||||||
+ valueOrEmpty(job.getDeliveryLastName())));
|
|
||||||
deliveryBox.add(new Span(concatAddress(job.getDeliveryStreet(), job.getDeliveryHouseNumber())));
|
deliveryBox.add(new Span(concatAddress(job.getDeliveryStreet(), job.getDeliveryHouseNumber())));
|
||||||
deliveryBox.add(new Span(concatZipCity(job.getDeliveryZip(), job.getDeliveryCity())));
|
deliveryBox.add(new Span(concatZipCity(job.getDeliveryZip(), job.getDeliveryCity())));
|
||||||
|
|
||||||
@@ -208,13 +200,16 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
cargoBox.add(new Span("Keine Frachtangaben"));
|
cargoBox.add(new Span("Keine Frachtangaben"));
|
||||||
} else {
|
} else {
|
||||||
for (CargoItem ci : cargoItems) {
|
for (CargoItem ci : cargoItems) {
|
||||||
if (ci == null) continue;
|
if (ci == null)
|
||||||
|
continue;
|
||||||
String desc = ci.getDescription();
|
String desc = ci.getDescription();
|
||||||
Integer qty = ci.getQuantity();
|
Integer qty = ci.getQuantity();
|
||||||
String dims = dimString(ci);
|
String dims = dimString(ci);
|
||||||
String weight = ci.getWeightKg() != null ? ci.getWeightKg() + " kg" : "";
|
String weight = ci.getWeightKg() != null ? ci.getWeightKg() + " kg" : "";
|
||||||
String line = (qty != null ? qty + " x " : "") + (desc != null ? desc : "") + (dims.isBlank() ? "" : " " + dims) + (weight.isBlank() ? "" : " " + weight);
|
String line = (qty != null ? qty + " x " : "") + (desc != null ? desc : "")
|
||||||
if (!line.isBlank()) cargoBox.add(new Span(line));
|
+ (dims.isBlank() ? "" : " " + dims) + (weight.isBlank() ? "" : " " + weight);
|
||||||
|
if (!line.isBlank())
|
||||||
|
cargoBox.add(new Span(line));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,12 +247,17 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private String formatLocalDate(java.time.LocalDate date) {
|
private String formatLocalDate(java.time.LocalDate date) {
|
||||||
try {
|
try {
|
||||||
java.time.format.DateTimeFormatter fmt = java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy").withLocale(Locale.GERMANY);
|
java.time.format.DateTimeFormatter fmt = java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy")
|
||||||
|
.withLocale(Locale.GERMANY);
|
||||||
return date.format(fmt);
|
return date.format(fmt);
|
||||||
} catch (Exception e) { return ""; }
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String valueOrEmpty(String v) { return v == null ? "" : v; }
|
private String valueOrEmpty(String v) {
|
||||||
|
return v == null ? "" : v;
|
||||||
|
}
|
||||||
|
|
||||||
private String concatAddress(String street, String house) {
|
private String concatAddress(String street, String house) {
|
||||||
String s = valueOrEmpty(street);
|
String s = valueOrEmpty(street);
|
||||||
@@ -268,7 +268,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
private String concatZipCity(String zip, String city) {
|
private String concatZipCity(String zip, String city) {
|
||||||
String z = valueOrEmpty(zip);
|
String z = valueOrEmpty(zip);
|
||||||
String c = valueOrEmpty(city);
|
String c = valueOrEmpty(city);
|
||||||
if (!z.isBlank() && !c.isBlank()) return z + " " + c;
|
if (!z.isBlank() && !c.isBlank())
|
||||||
|
return z + " " + c;
|
||||||
return (z + " " + c).trim();
|
return (z + " " + c).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,8 +277,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
String len = ci.getLengthMm() != null ? ci.getLengthMm().intValue() + " mm" : "";
|
String len = ci.getLengthMm() != null ? ci.getLengthMm().intValue() + " mm" : "";
|
||||||
String wid = ci.getWidthMm() != null ? ci.getWidthMm().intValue() + " mm" : "";
|
String wid = ci.getWidthMm() != null ? ci.getWidthMm().intValue() + " mm" : "";
|
||||||
String hei = ci.getHeightMm() != null ? ci.getHeightMm().intValue() + " mm" : "";
|
String hei = ci.getHeightMm() != null ? ci.getHeightMm().intValue() + " mm" : "";
|
||||||
String combined = String.join(" x ", java.util.stream.Stream.of(len, wid, hei)
|
String combined = String.join(" x ",
|
||||||
.filter(s -> !s.isBlank()).toList());
|
java.util.stream.Stream.of(len, wid, hei).filter(s -> !s.isBlank()).toList());
|
||||||
return combined.isBlank() ? "" : combined;
|
return combined.isBlank() ? "" : combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,19 +294,26 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
if (au != null) {
|
if (au != null) {
|
||||||
String fn = au.getVorname();
|
String fn = au.getVorname();
|
||||||
String ln = au.getNachname();
|
String ln = au.getNachname();
|
||||||
String name = (fn != null ? fn : "").trim() + (fn != null && ln != null ? " " : "") + (ln != null ? ln : "");
|
String name = (fn != null ? fn : "").trim() + (fn != null && ln != null ? " " : "")
|
||||||
if (!name.isBlank()) return name;
|
+ (ln != null ? ln : "");
|
||||||
if (au.getBezeichnung() != null && !au.getBezeichnung().isBlank()) return au.getBezeichnung();
|
if (!name.isBlank())
|
||||||
if (au.getEmail() != null && !au.getEmail().isBlank()) return au.getEmail();
|
return name;
|
||||||
|
if (au.getBezeichnung() != null && !au.getBezeichnung().isBlank())
|
||||||
|
return au.getBezeichnung();
|
||||||
|
if (au.getEmail() != null && !au.getEmail().isBlank())
|
||||||
|
return au.getEmail();
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) { }
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
return appUserIdString; // Fallback: show raw string if lookup fails
|
return appUserIdString; // Fallback: show raw string if lookup fails
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addRouteMap(Job job) {
|
private void addRouteMap(Job job) {
|
||||||
// Baue Adress-Strings
|
// Baue Adress-Strings
|
||||||
String origin = (concatAddress(job.getPickupStreet(), job.getPickupHouseNumber()) + ", " + concatZipCity(job.getPickupZip(), job.getPickupCity())).trim();
|
String origin = (concatAddress(job.getPickupStreet(), job.getPickupHouseNumber()) + ", "
|
||||||
String destination = (concatAddress(job.getDeliveryStreet(), job.getDeliveryHouseNumber()) + ", " + concatZipCity(job.getDeliveryZip(), job.getDeliveryCity())).trim();
|
+ concatZipCity(job.getPickupZip(), job.getPickupCity())).trim();
|
||||||
|
String destination = (concatAddress(job.getDeliveryStreet(), job.getDeliveryHouseNumber()) + ", "
|
||||||
|
+ concatZipCity(job.getDeliveryZip(), job.getDeliveryCity())).trim();
|
||||||
|
|
||||||
if (origin.isBlank() || destination.isBlank()) {
|
if (origin.isBlank() || destination.isBlank()) {
|
||||||
// Wenn nicht genug Daten vorhanden sind, Karte nicht anzeigen
|
// Wenn nicht genug Daten vorhanden sind, Karte nicht anzeigen
|
||||||
@@ -328,66 +336,48 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
content.add(map, routeInfo);
|
content.add(map, routeInfo);
|
||||||
|
|
||||||
String js = (
|
String js = ("(function(){" + " var host = $0; var infoEl = $1;" + " function init(){"
|
||||||
"(function(){" +
|
+ " var map = new google.maps.Map(host, {center: {lat: 51.163, lng: 10.447}, zoom: 6, mapTypeControl: false});"
|
||||||
" var host = $0; var infoEl = $1;" +
|
+ " var trafficLayer = new google.maps.TrafficLayer(); trafficLayer.setMap(map);"
|
||||||
" function init(){" +
|
+ " var ds = new google.maps.DirectionsService();" + " ds.route({" + " origin: '"
|
||||||
" var map = new google.maps.Map(host, {center: {lat: 51.163, lng: 10.447}, zoom: 6, mapTypeControl: false});" +
|
+ escapeJs(origin) + "'," + " destination: '" + escapeJs(destination) + "',"
|
||||||
" var trafficLayer = new google.maps.TrafficLayer(); trafficLayer.setMap(map);" +
|
+ " travelMode: google.maps.TravelMode.DRIVING," + " provideRouteAlternatives: true,"
|
||||||
" var ds = new google.maps.DirectionsService();" +
|
+ " drivingOptions: { departureTime: new Date(), trafficModel: google.maps.TrafficModel.BEST_GUESS }"
|
||||||
" ds.route({" +
|
+ " }, function(res, status){ if(status==='OK'){ " + " infoEl.innerHTML='';"
|
||||||
" origin: '" + escapeJs(origin) + "'," +
|
+ " var bounds = new google.maps.LatLngBounds();"
|
||||||
" destination: '" + escapeJs(destination) + "'," +
|
+ " var renderers = []; var polylines = [];" + " res.routes.forEach(function(route, idx){"
|
||||||
" travelMode: google.maps.TravelMode.DRIVING," +
|
+ " var dr = new google.maps.DirectionsRenderer({map: map, preserveViewport: idx>0, suppressMarkers:false, suppressPolylines:true});"
|
||||||
" provideRouteAlternatives: true," +
|
+ " dr.setRouteIndex(idx); dr.setDirections(res);" + " renderers.push(dr);"
|
||||||
" drivingOptions: { departureTime: new Date(), trafficModel: google.maps.TrafficModel.BEST_GUESS }" +
|
+ " var path = route.overview_path || [];"
|
||||||
" }, function(res, status){ if(status==='OK'){ " +
|
+ " var poly = new google.maps.Polyline({path: path, strokeColor: idx===0?'#1976d2':'#90caf9', strokeOpacity: 0.95, strokeWeight: idx===0?6:4});"
|
||||||
" infoEl.innerHTML='';" +
|
+ " poly.setMap(map); polylines.push(poly);"
|
||||||
" var bounds = new google.maps.LatLngBounds();" +
|
+ " var leg = route.legs && route.legs[0];" + " if (leg) {"
|
||||||
" var renderers = []; var polylines = [];" +
|
+ " var dur = leg.duration ? leg.duration.text : '';"
|
||||||
" res.routes.forEach(function(route, idx){" +
|
+ " var durT = leg.duration_in_traffic ? leg.duration_in_traffic.text : '';"
|
||||||
" var dr = new google.maps.DirectionsRenderer({map: map, preserveViewport: idx>0, suppressMarkers:false, suppressPolylines:true});" +
|
+ " var dist = leg.distance ? leg.distance.text : '';"
|
||||||
" dr.setRouteIndex(idx); dr.setDirections(res);" +
|
+ " var alt = (idx===0?'Schnellste Route':'Alternative '+idx);"
|
||||||
" renderers.push(dr);" +
|
+ " var row = document.createElement('div'); row.style.margin='4px 0'; row.style.cursor='pointer';"
|
||||||
" var path = route.overview_path || [];" +
|
+ " row.textContent = alt + ': ' + dist + ' • Dauer: ' + dur + (durT?(' (mit Verkehr: '+durT+')'):'');"
|
||||||
" var poly = new google.maps.Polyline({path: path, strokeColor: idx===0?'#1976d2':'#90caf9', strokeOpacity: 0.95, strokeWeight: idx===0?6:4});" +
|
+ " row.onmouseenter = function(){"
|
||||||
" poly.setMap(map); polylines.push(poly);" +
|
+ " polylines.forEach(function(p,i){ p.setOptions({strokeColor: i===0?'#90caf9':'#e3f2fd', strokeOpacity: 0.6, strokeWeight: 3}); });"
|
||||||
" var leg = route.legs && route.legs[0];" +
|
+ " poly.setOptions({strokeColor:'#0d47a1', strokeOpacity:1, strokeWeight:7});"
|
||||||
" if (leg) {" +
|
+ " };" + " row.onmouseleave = function(){"
|
||||||
" var dur = leg.duration ? leg.duration.text : '';" +
|
+ " polylines.forEach(function(p,i){ p.setOptions({strokeColor: i===0?'#1976d2':'#90caf9', strokeOpacity:0.95, strokeWeight: i===0?6:4}); });"
|
||||||
" var durT = leg.duration_in_traffic ? leg.duration_in_traffic.text : '';" +
|
+ " };" + " infoEl.appendChild(row);"
|
||||||
" var dist = leg.distance ? leg.distance.text : '';" +
|
+ " if (path && path.length){ path.forEach(function(pt){ bounds.extend(pt); }); }"
|
||||||
" var alt = (idx===0?'Schnellste Route':'Alternative '+idx);" +
|
+ " }" + " });" + " if (!bounds.isEmpty()) { map.fitBounds(bounds); }"
|
||||||
" var row = document.createElement('div'); row.style.margin='4px 0'; row.style.cursor='pointer';" +
|
+ " }});" + " }" + " if (!(window.google && window.google.maps)) {"
|
||||||
" row.textContent = alt + ': ' + dist + ' • Dauer: ' + dur + (durT?(' (mit Verkehr: '+durT+')'):'');" +
|
+ " var s=document.createElement('script');"
|
||||||
" row.onmouseenter = function(){" +
|
+ " s.src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&libraries=places';"
|
||||||
" polylines.forEach(function(p,i){ p.setOptions({strokeColor: i===0?'#90caf9':'#e3f2fd', strokeOpacity: 0.6, strokeWeight: 3}); });" +
|
+ " s.onload=init; document.head.appendChild(s);" + " } else { init(); }" + "})();");
|
||||||
" poly.setOptions({strokeColor:'#0d47a1', strokeOpacity:1, strokeWeight:7});" +
|
|
||||||
" };" +
|
|
||||||
" row.onmouseleave = function(){" +
|
|
||||||
" polylines.forEach(function(p,i){ p.setOptions({strokeColor: i===0?'#1976d2':'#90caf9', strokeOpacity:0.95, strokeWeight: i===0?6:4}); });" +
|
|
||||||
" };" +
|
|
||||||
" infoEl.appendChild(row);" +
|
|
||||||
" if (path && path.length){ path.forEach(function(pt){ bounds.extend(pt); }); }" +
|
|
||||||
" }" +
|
|
||||||
" });" +
|
|
||||||
" if (!bounds.isEmpty()) { map.fitBounds(bounds); }" +
|
|
||||||
" }});" +
|
|
||||||
" }" +
|
|
||||||
" if (!(window.google && window.google.maps)) {" +
|
|
||||||
" var s=document.createElement('script');" +
|
|
||||||
" s.src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&libraries=places';" +
|
|
||||||
" s.onload=init; document.head.appendChild(s);" +
|
|
||||||
" } else { init(); }" +
|
|
||||||
"})();"
|
|
||||||
);
|
|
||||||
|
|
||||||
map.getElement().executeJs(js, map.getElement(), routeInfo.getElement());
|
map.getElement().executeJs(js, map.getElement(), routeInfo.getElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hilfsfunktion zum einfachen Escapen von JS-Zeichen in Strings
|
// Hilfsfunktion zum einfachen Escapen von JS-Zeichen in Strings
|
||||||
private String escapeJs(String s) {
|
private String escapeJs(String s) {
|
||||||
if (s == null) return "";
|
if (s == null)
|
||||||
|
return "";
|
||||||
return s.replace("\\", "\\\\").replace("'", "\\'").replace("\n", " ").replace("\r", " ");
|
return s.replace("\\", "\\\\").replace("'", "\\'").replace("\n", " ").replace("\r", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +416,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
// Completion details if completed
|
// Completion details if completed
|
||||||
if (task.isCompleted()) {
|
if (task.isCompleted()) {
|
||||||
dialogContent.add(new Span("")); // Spacer
|
dialogContent.add(new Span("")); // Spacer
|
||||||
if (task.getCompletedAt() != null) {
|
if (task.getCompletedAt() != null) {
|
||||||
dialogContent.add(new Span("Abgeschlossen am: " + formatDateTime(task.getCompletedAt())));
|
dialogContent.add(new Span("Abgeschlossen am: " + formatDateTime(task.getCompletedAt())));
|
||||||
}
|
}
|
||||||
@@ -468,7 +458,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
if (photoTask.getMinPhotoCount() != null || photoTask.getMaxPhotoCount() != null) {
|
if (photoTask.getMinPhotoCount() != null || photoTask.getMaxPhotoCount() != null) {
|
||||||
String photoInfo = "Fotos: ";
|
String photoInfo = "Fotos: ";
|
||||||
if (photoTask.getMinPhotoCount() != null && photoTask.getMaxPhotoCount() != null) {
|
if (photoTask.getMinPhotoCount() != null && photoTask.getMaxPhotoCount() != null) {
|
||||||
photoInfo += photoTask.getMinPhotoCount() + " - " + photoTask.getMaxPhotoCount() + " Fotos erforderlich";
|
photoInfo += photoTask.getMinPhotoCount() + " - " + photoTask.getMaxPhotoCount()
|
||||||
|
+ " Fotos erforderlich";
|
||||||
} else if (photoTask.getMinPhotoCount() != null) {
|
} else if (photoTask.getMinPhotoCount() != null) {
|
||||||
photoInfo += "Mindestens " + photoTask.getMinPhotoCount() + " Fotos erforderlich";
|
photoInfo += "Mindestens " + photoTask.getMinPhotoCount() + " Fotos erforderlich";
|
||||||
} else if (photoTask.getMaxPhotoCount() != null) {
|
} else if (photoTask.getMaxPhotoCount() != null) {
|
||||||
@@ -484,7 +475,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
List<Photo> photos = photoRepository.findByTaskId(taskId);
|
List<Photo> photos = photoRepository.findByTaskId(taskId);
|
||||||
|
|
||||||
if (!photos.isEmpty()) {
|
if (!photos.isEmpty()) {
|
||||||
content.add(new Span("")); // Spacer
|
content.add(new Span("")); // Spacer
|
||||||
|
|
||||||
// Collect all photos from all Photo entries
|
// Collect all photos from all Photo entries
|
||||||
List<String> allPhotos = new ArrayList<>();
|
List<String> allPhotos = new ArrayList<>();
|
||||||
@@ -520,7 +511,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
List<Signature> signatures = signatureRepository.findByTaskId(taskId);
|
List<Signature> signatures = signatureRepository.findByTaskId(taskId);
|
||||||
|
|
||||||
if (!signatures.isEmpty()) {
|
if (!signatures.isEmpty()) {
|
||||||
content.add(new Span("")); // Spacer
|
content.add(new Span("")); // Spacer
|
||||||
content.add(new Span("Gespeicherte Unterschrift:"));
|
content.add(new Span("Gespeicherte Unterschrift:"));
|
||||||
|
|
||||||
// Display the latest signature (assuming one signature per task)
|
// Display the latest signature (assuming one signature per task)
|
||||||
@@ -530,17 +521,12 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
if (svgContent != null && !svgContent.isBlank()) {
|
if (svgContent != null && !svgContent.isBlank()) {
|
||||||
// Create a div to hold the SVG
|
// Create a div to hold the SVG
|
||||||
Div svgContainer = new Div();
|
Div svgContainer = new Div();
|
||||||
svgContainer.getStyle()
|
svgContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
.set("border-radius", "var(--lumo-border-radius-m)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-m)")
|
.set("padding", "var(--lumo-space-s)").set("background-color", "white")
|
||||||
.set("padding", "var(--lumo-space-s)")
|
.set("width", "100%").set("max-width", "450px").set("overflow", "hidden")
|
||||||
.set("background-color", "white")
|
.set("display", "flex").set("align-items", "center")
|
||||||
.set("width", "100%")
|
.set("justify-content", "center");
|
||||||
.set("max-width", "450px")
|
|
||||||
.set("overflow", "hidden")
|
|
||||||
.set("display", "flex")
|
|
||||||
.set("align-items", "center")
|
|
||||||
.set("justify-content", "center");
|
|
||||||
|
|
||||||
// Process SVG to make it responsive
|
// Process SVG to make it responsive
|
||||||
String responsiveSvg = makeResponsiveSvg(svgContent);
|
String responsiveSvg = makeResponsiveSvg(svgContent);
|
||||||
@@ -562,7 +548,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
List<Barcode> barcodes = barcodeRepository.findByTaskId(taskId);
|
List<Barcode> barcodes = barcodeRepository.findByTaskId(taskId);
|
||||||
|
|
||||||
if (!barcodes.isEmpty()) {
|
if (!barcodes.isEmpty()) {
|
||||||
content.add(new Span("")); // Spacer
|
content.add(new Span("")); // Spacer
|
||||||
content.add(new Span("Gescannte Barcodes (" + barcodes.size() + "):"));
|
content.add(new Span("Gescannte Barcodes (" + barcodes.size() + "):"));
|
||||||
|
|
||||||
// Display all scanned barcodes
|
// Display all scanned barcodes
|
||||||
@@ -573,15 +559,12 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
if (barcodeValue != null && !barcodeValue.isBlank()) {
|
if (barcodeValue != null && !barcodeValue.isBlank()) {
|
||||||
// Create a styled container for each barcode
|
// Create a styled container for each barcode
|
||||||
Div barcodeContainer = new Div();
|
Div barcodeContainer = new Div();
|
||||||
barcodeContainer.getStyle()
|
barcodeContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
.set("border-radius", "var(--lumo-border-radius-s)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-s)")
|
.set("padding", "var(--lumo-space-s)").set("margin", "var(--lumo-space-xs) 0")
|
||||||
.set("padding", "var(--lumo-space-s)")
|
.set("background-color", "var(--lumo-contrast-5pct)")
|
||||||
.set("margin", "var(--lumo-space-xs) 0")
|
.set("font-family", "monospace").set("font-size", "var(--lumo-font-size-s)")
|
||||||
.set("background-color", "var(--lumo-contrast-5pct)")
|
.set("word-break", "break-all");
|
||||||
.set("font-family", "monospace")
|
|
||||||
.set("font-size", "var(--lumo-font-size-s)")
|
|
||||||
.set("word-break", "break-all");
|
|
||||||
|
|
||||||
Span barcodeSpan = new Span((i + 1) + ". " + barcodeValue);
|
Span barcodeSpan = new Span((i + 1) + ". " + barcodeValue);
|
||||||
barcodeContainer.add(barcodeSpan);
|
barcodeContainer.add(barcodeSpan);
|
||||||
@@ -598,7 +581,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private String formatDateTime(java.time.LocalDateTime dateTime) {
|
private String formatDateTime(java.time.LocalDateTime dateTime) {
|
||||||
try {
|
try {
|
||||||
java.time.format.DateTimeFormatter fmt = java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm").withLocale(Locale.GERMANY);
|
java.time.format.DateTimeFormatter fmt = java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
|
||||||
|
.withLocale(Locale.GERMANY);
|
||||||
return dateTime.format(fmt);
|
return dateTime.format(fmt);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return dateTime.toString();
|
return dateTime.toString();
|
||||||
@@ -609,39 +593,28 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
Div taskCard = new Div();
|
Div taskCard = new Div();
|
||||||
|
|
||||||
// Card styling with fixed width
|
// Card styling with fixed width
|
||||||
taskCard.getStyle()
|
taskCard.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
.set("border-radius", "var(--lumo-border-radius-m)").set("padding", "var(--lumo-space-m)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-m)")
|
.set("margin", "var(--lumo-space-xs) 0").set("background-color", "var(--lumo-base-color)")
|
||||||
.set("padding", "var(--lumo-space-m)")
|
.set("cursor", "pointer").set("transition", "all 0.2s ease")
|
||||||
.set("margin", "var(--lumo-space-xs) 0")
|
.set("box-shadow", "0 1px 3px rgba(0, 0, 0, 0.1)").set("display", "flex").set("align-items", "center")
|
||||||
.set("background-color", "var(--lumo-base-color)")
|
.set("gap", "var(--lumo-space-m)").set("width", "100%").set("box-sizing", "border-box");
|
||||||
.set("cursor", "pointer")
|
|
||||||
.set("transition", "all 0.2s ease")
|
|
||||||
.set("box-shadow", "0 1px 3px rgba(0, 0, 0, 0.1)")
|
|
||||||
.set("display", "flex")
|
|
||||||
.set("align-items", "center")
|
|
||||||
.set("gap", "var(--lumo-space-m)")
|
|
||||||
.set("width", "100%")
|
|
||||||
.set("box-sizing", "border-box");
|
|
||||||
|
|
||||||
// Hover effects
|
// Hover effects
|
||||||
taskCard.getElement().addEventListener("mouseenter", e -> {
|
taskCard.getElement().addEventListener("mouseenter", e -> {
|
||||||
taskCard.getStyle()
|
taskCard.getStyle().set("transform", "translateY(-2px)").set("box-shadow", "0 4px 12px rgba(0, 0, 0, 0.15)")
|
||||||
.set("transform", "translateY(-2px)")
|
.set("border-color", "var(--lumo-primary-color-50pct)");
|
||||||
.set("box-shadow", "0 4px 12px rgba(0, 0, 0, 0.15)")
|
|
||||||
.set("border-color", "var(--lumo-primary-color-50pct)");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
taskCard.getElement().addEventListener("mouseleave", e -> {
|
taskCard.getElement().addEventListener("mouseleave", e -> {
|
||||||
taskCard.getStyle()
|
taskCard.getStyle().set("transform", "translateY(0)").set("box-shadow", "0 1px 3px rgba(0, 0, 0, 0.1)")
|
||||||
.set("transform", "translateY(0)")
|
.set("border-color", "var(--lumo-contrast-20pct)");
|
||||||
.set("box-shadow", "0 1px 3px rgba(0, 0, 0, 0.1)")
|
|
||||||
.set("border-color", "var(--lumo-contrast-20pct)");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Task icon based on type
|
// Task icon based on type
|
||||||
Icon taskIcon = getTaskIcon(task);
|
Icon taskIcon = getTaskIcon(task);
|
||||||
taskIcon.getStyle().set("color", task.isCompleted() ? "var(--lumo-success-color)" : "var(--lumo-primary-color)");
|
taskIcon.getStyle().set("color",
|
||||||
|
task.isCompleted() ? "var(--lumo-success-color)" : "var(--lumo-primary-color)");
|
||||||
|
|
||||||
// Task content
|
// Task content
|
||||||
VerticalLayout taskContent = new VerticalLayout();
|
VerticalLayout taskContent = new VerticalLayout();
|
||||||
@@ -652,27 +625,20 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
// Task name with order number (display as 1-based instead of 0-based)
|
// Task name with order number (display as 1-based instead of 0-based)
|
||||||
String taskNameWithOrder = (task.getTaskOrder() != null ? (task.getTaskOrder() + 1) + ". " : "") + displayName;
|
String taskNameWithOrder = (task.getTaskOrder() != null ? (task.getTaskOrder() + 1) + ". " : "") + displayName;
|
||||||
Span taskName = new Span(taskNameWithOrder);
|
Span taskName = new Span(taskNameWithOrder);
|
||||||
taskName.getStyle()
|
taskName.getStyle().set("font-weight", "500").set("font-size", "var(--lumo-font-size-m)").set("color",
|
||||||
.set("font-weight", "500")
|
task.isCompleted() ? "var(--lumo-success-text-color)" : "var(--lumo-body-text-color)");
|
||||||
.set("font-size", "var(--lumo-font-size-m)")
|
|
||||||
.set("color", task.isCompleted() ? "var(--lumo-success-text-color)" : "var(--lumo-body-text-color)");
|
|
||||||
|
|
||||||
// Task status/description
|
// Task status/description
|
||||||
Span taskDescription = new Span(getTaskDescription(task));
|
Span taskDescription = new Span(getTaskDescription(task));
|
||||||
taskDescription.getStyle()
|
taskDescription.getStyle().set("font-size", "var(--lumo-font-size-s)")
|
||||||
.set("font-size", "var(--lumo-font-size-s)")
|
.set("color", "var(--lumo-secondary-text-color)").set("margin-top", "var(--lumo-space-xs)");
|
||||||
.set("color", "var(--lumo-secondary-text-color)")
|
|
||||||
.set("margin-top", "var(--lumo-space-xs)");
|
|
||||||
|
|
||||||
taskContent.add(taskName, taskDescription);
|
taskContent.add(taskName, taskDescription);
|
||||||
|
|
||||||
// Status indicator
|
// Status indicator
|
||||||
Div statusIndicator = new Div();
|
Div statusIndicator = new Div();
|
||||||
statusIndicator.getStyle()
|
statusIndicator.getStyle().set("width", "8px").set("height", "8px").set("border-radius", "50%")
|
||||||
.set("width", "8px")
|
.set("background-color", task.isCompleted() ? "var(--lumo-success-color)" : "var(--lumo-error-color)");
|
||||||
.set("height", "8px")
|
|
||||||
.set("border-radius", "50%")
|
|
||||||
.set("background-color", task.isCompleted() ? "var(--lumo-success-color)" : "var(--lumo-error-color)");
|
|
||||||
|
|
||||||
taskCard.add(taskIcon, taskContent, statusIndicator);
|
taskCard.add(taskIcon, taskContent, statusIndicator);
|
||||||
|
|
||||||
@@ -680,10 +646,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
taskCard.addClickListener(event -> {
|
taskCard.addClickListener(event -> {
|
||||||
showTaskDetailsDialog(task);
|
showTaskDetailsDialog(task);
|
||||||
// Reset hover state after dialog interaction
|
// Reset hover state after dialog interaction
|
||||||
taskCard.getStyle()
|
taskCard.getStyle().set("transform", "translateY(0)").set("box-shadow", "0 1px 3px rgba(0, 0, 0, 0.1)")
|
||||||
.set("transform", "translateY(0)")
|
.set("border-color", "var(--lumo-contrast-20pct)");
|
||||||
.set("box-shadow", "0 1px 3px rgba(0, 0, 0, 0.1)")
|
|
||||||
.set("border-color", "var(--lumo-contrast-20pct)");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return taskCard;
|
return taskCard;
|
||||||
@@ -707,7 +671,9 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private String getTaskDescription(BaseTask task) {
|
private String getTaskDescription(BaseTask task) {
|
||||||
if (task.isCompleted()) {
|
if (task.isCompleted()) {
|
||||||
return "Abgeschlossen" + (task.getCompletedAt() != null ? " am " + formatLocalDate(task.getCompletedAt().toLocalDate()) : "");
|
return "Abgeschlossen"
|
||||||
|
+ (task.getCompletedAt() != null ? " am " + formatLocalDate(task.getCompletedAt().toLocalDate())
|
||||||
|
: "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task instanceof TodoListTask todoTask) {
|
if (task instanceof TodoListTask todoTask) {
|
||||||
@@ -717,9 +683,11 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
if (photoTask.getMinPhotoCount() != null && photoTask.getMaxPhotoCount() != null) {
|
if (photoTask.getMinPhotoCount() != null && photoTask.getMaxPhotoCount() != null) {
|
||||||
return photoTask.getMinPhotoCount() + "-" + photoTask.getMaxPhotoCount() + " Fotos erforderlich";
|
return photoTask.getMinPhotoCount() + "-" + photoTask.getMaxPhotoCount() + " Fotos erforderlich";
|
||||||
} else if (photoTask.getMinPhotoCount() != null) {
|
} else if (photoTask.getMinPhotoCount() != null) {
|
||||||
return "Mind. " + photoTask.getMinPhotoCount() + " Foto" + (photoTask.getMinPhotoCount() != 1 ? "s" : "");
|
return "Mind. " + photoTask.getMinPhotoCount() + " Foto"
|
||||||
|
+ (photoTask.getMinPhotoCount() != 1 ? "s" : "");
|
||||||
} else if (photoTask.getMaxPhotoCount() != null) {
|
} else if (photoTask.getMaxPhotoCount() != null) {
|
||||||
return "Max. " + photoTask.getMaxPhotoCount() + " Foto" + (photoTask.getMaxPhotoCount() != 1 ? "s" : "");
|
return "Max. " + photoTask.getMaxPhotoCount() + " Foto"
|
||||||
|
+ (photoTask.getMaxPhotoCount() != 1 ? "s" : "");
|
||||||
} else {
|
} else {
|
||||||
return "Foto erforderlich";
|
return "Foto erforderlich";
|
||||||
}
|
}
|
||||||
@@ -740,83 +708,56 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private Div createPhotoGallery(List<String> photos) {
|
private Div createPhotoGallery(List<String> photos) {
|
||||||
Div galleryContainer = new Div();
|
Div galleryContainer = new Div();
|
||||||
galleryContainer.getStyle()
|
galleryContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
.set("border-radius", "var(--lumo-border-radius-m)").set("padding", "var(--lumo-space-m)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-m)")
|
.set("background-color", "white").set("max-width", "600px").set("min-height", "500px")
|
||||||
.set("padding", "var(--lumo-space-m)")
|
.set("height", "500px").set("position", "relative").set("display", "flex").set("align-items", "center")
|
||||||
.set("background-color", "white")
|
.set("justify-content", "center");
|
||||||
.set("max-width", "600px")
|
|
||||||
.set("min-height", "500px")
|
|
||||||
.set("height", "500px")
|
|
||||||
.set("position", "relative")
|
|
||||||
.set("display", "flex")
|
|
||||||
.set("align-items", "center")
|
|
||||||
.set("justify-content", "center");
|
|
||||||
|
|
||||||
if (photos.size() == 1) {
|
if (photos.size() == 1) {
|
||||||
// Single photo - no navigation needed
|
// Single photo - no navigation needed
|
||||||
Div photoContainer = createPhotoContainer(photos.get(0));
|
Div photoContainer = createPhotoContainer(photos.get(0));
|
||||||
photoContainer.getStyle()
|
photoContainer.getStyle().set("flex", "1").set("display", "flex").set("align-items", "center")
|
||||||
.set("flex", "1")
|
.set("justify-content", "center");
|
||||||
.set("display", "flex")
|
|
||||||
.set("align-items", "center")
|
|
||||||
.set("justify-content", "center");
|
|
||||||
galleryContainer.add(photoContainer);
|
galleryContainer.add(photoContainer);
|
||||||
} else {
|
} else {
|
||||||
// Multiple photos - add navigation
|
// Multiple photos - add navigation
|
||||||
final int[] currentIndex = {0}; // Use array to make it effectively final
|
final int[] currentIndex = { 0 }; // Use array to make it effectively final
|
||||||
|
|
||||||
// Photo counter
|
// Photo counter
|
||||||
Span photoCounter = new Span((currentIndex[0] + 1) + " / " + photos.size());
|
Span photoCounter = new Span((currentIndex[0] + 1) + " / " + photos.size());
|
||||||
photoCounter.getStyle()
|
photoCounter.getStyle().set("position", "absolute").set("top", "var(--lumo-space-s)")
|
||||||
.set("position", "absolute")
|
.set("right", "var(--lumo-space-s)").set("background-color", "rgba(0, 0, 0, 0.6)")
|
||||||
.set("top", "var(--lumo-space-s)")
|
.set("color", "white").set("padding", "var(--lumo-space-xs) var(--lumo-space-s)")
|
||||||
.set("right", "var(--lumo-space-s)")
|
.set("border-radius", "var(--lumo-border-radius-s)").set("font-size", "var(--lumo-font-size-s)")
|
||||||
.set("background-color", "rgba(0, 0, 0, 0.6)")
|
.set("z-index", "10");
|
||||||
.set("color", "white")
|
|
||||||
.set("padding", "var(--lumo-space-xs) var(--lumo-space-s)")
|
|
||||||
.set("border-radius", "var(--lumo-border-radius-s)")
|
|
||||||
.set("font-size", "var(--lumo-font-size-s)")
|
|
||||||
.set("z-index", "10");
|
|
||||||
|
|
||||||
// Photo container
|
// Photo container
|
||||||
Div photoContainer = createPhotoContainer(photos.get(0));
|
Div photoContainer = createPhotoContainer(photos.get(0));
|
||||||
photoContainer.getStyle()
|
photoContainer.getStyle().set("margin", "0 40px") // Space for buttons
|
||||||
.set("margin", "0 40px") // Space for buttons
|
.set("flex", "1").set("display", "flex").set("align-items", "center")
|
||||||
.set("flex", "1")
|
.set("justify-content", "center");
|
||||||
.set("display", "flex")
|
|
||||||
.set("align-items", "center")
|
|
||||||
.set("justify-content", "center");
|
|
||||||
|
|
||||||
// Previous button
|
// Previous button
|
||||||
Button prevButton = new Button(new Icon(VaadinIcon.CHEVRON_LEFT));
|
Button prevButton = new Button(new Icon(VaadinIcon.CHEVRON_LEFT));
|
||||||
prevButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ICON);
|
prevButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ICON);
|
||||||
prevButton.getStyle()
|
prevButton.getStyle().set("position", "absolute").set("left", "var(--lumo-space-s)").set("top", "50%")
|
||||||
.set("position", "absolute")
|
.set("transform", "translateY(-50%)").set("background-color", "rgba(255, 255, 255, 0.8)")
|
||||||
.set("left", "var(--lumo-space-s)")
|
.set("border-radius", "50%").set("z-index", "10");
|
||||||
.set("top", "50%")
|
|
||||||
.set("transform", "translateY(-50%)")
|
|
||||||
.set("background-color", "rgba(255, 255, 255, 0.8)")
|
|
||||||
.set("border-radius", "50%")
|
|
||||||
.set("z-index", "10");
|
|
||||||
|
|
||||||
// Next button
|
// Next button
|
||||||
Button nextButton = new Button(new Icon(VaadinIcon.CHEVRON_RIGHT));
|
Button nextButton = new Button(new Icon(VaadinIcon.CHEVRON_RIGHT));
|
||||||
nextButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ICON);
|
nextButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ICON);
|
||||||
nextButton.getStyle()
|
nextButton.getStyle().set("position", "absolute").set("right", "var(--lumo-space-s)").set("top", "50%")
|
||||||
.set("position", "absolute")
|
.set("transform", "translateY(-50%)").set("background-color", "rgba(255, 255, 255, 0.8)")
|
||||||
.set("right", "var(--lumo-space-s)")
|
.set("border-radius", "50%").set("z-index", "10");
|
||||||
.set("top", "50%")
|
|
||||||
.set("transform", "translateY(-50%)")
|
|
||||||
.set("background-color", "rgba(255, 255, 255, 0.8)")
|
|
||||||
.set("border-radius", "50%")
|
|
||||||
.set("z-index", "10");
|
|
||||||
|
|
||||||
// Navigation logic
|
// Navigation logic
|
||||||
prevButton.addClickListener(e -> {
|
prevButton.addClickListener(e -> {
|
||||||
if (currentIndex[0] > 0) {
|
if (currentIndex[0] > 0) {
|
||||||
currentIndex[0]--;
|
currentIndex[0]--;
|
||||||
updatePhotoDisplay(photoContainer, photos.get(currentIndex[0]), photoCounter, currentIndex[0] + 1, photos.size());
|
updatePhotoDisplay(photoContainer, photos.get(currentIndex[0]), photoCounter, currentIndex[0] + 1,
|
||||||
|
photos.size());
|
||||||
}
|
}
|
||||||
prevButton.setEnabled(currentIndex[0] > 0);
|
prevButton.setEnabled(currentIndex[0] > 0);
|
||||||
nextButton.setEnabled(currentIndex[0] < photos.size() - 1);
|
nextButton.setEnabled(currentIndex[0] < photos.size() - 1);
|
||||||
@@ -825,7 +766,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
nextButton.addClickListener(e -> {
|
nextButton.addClickListener(e -> {
|
||||||
if (currentIndex[0] < photos.size() - 1) {
|
if (currentIndex[0] < photos.size() - 1) {
|
||||||
currentIndex[0]++;
|
currentIndex[0]++;
|
||||||
updatePhotoDisplay(photoContainer, photos.get(currentIndex[0]), photoCounter, currentIndex[0] + 1, photos.size());
|
updatePhotoDisplay(photoContainer, photos.get(currentIndex[0]), photoCounter, currentIndex[0] + 1,
|
||||||
|
photos.size());
|
||||||
}
|
}
|
||||||
prevButton.setEnabled(currentIndex[0] > 0);
|
prevButton.setEnabled(currentIndex[0] > 0);
|
||||||
nextButton.setEnabled(currentIndex[0] < photos.size() - 1);
|
nextButton.setEnabled(currentIndex[0] < photos.size() - 1);
|
||||||
@@ -843,19 +785,14 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
private Div createPhotoContainer(String base64Photo) {
|
private Div createPhotoContainer(String base64Photo) {
|
||||||
Div photoContainer = new Div();
|
Div photoContainer = new Div();
|
||||||
photoContainer.getStyle()
|
photoContainer.getStyle().set("width", "100%").set("height", "100%").set("display", "flex")
|
||||||
.set("width", "100%")
|
.set("align-items", "center").set("justify-content", "center").set("overflow", "hidden");
|
||||||
.set("height", "100%")
|
|
||||||
.set("display", "flex")
|
|
||||||
.set("align-items", "center")
|
|
||||||
.set("justify-content", "center")
|
|
||||||
.set("overflow", "hidden");
|
|
||||||
|
|
||||||
// Create image element
|
// Create image element
|
||||||
String imgSrc = base64Photo.startsWith("data:") ? base64Photo : "data:image/jpeg;base64," + base64Photo;
|
String imgSrc = base64Photo.startsWith("data:") ? base64Photo : "data:image/jpeg;base64," + base64Photo;
|
||||||
|
|
||||||
photoContainer.getElement().setProperty("innerHTML",
|
photoContainer.getElement().setProperty("innerHTML", "<img src='" + imgSrc
|
||||||
"<img src='" + imgSrc + "' style='max-width: 100%; max-height: 450px; object-fit: contain; border-radius: var(--lumo-border-radius-s); box-shadow: 0 2px 8px rgba(0,0,0,0.1);' />");
|
+ "' style='max-width: 100%; max-height: 450px; object-fit: contain; border-radius: var(--lumo-border-radius-s); box-shadow: 0 2px 8px rgba(0,0,0,0.1);' />");
|
||||||
|
|
||||||
return photoContainer;
|
return photoContainer;
|
||||||
}
|
}
|
||||||
@@ -863,8 +800,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
private void updatePhotoDisplay(Div photoContainer, String base64Photo, Span counter, int current, int total) {
|
private void updatePhotoDisplay(Div photoContainer, String base64Photo, Span counter, int current, int total) {
|
||||||
String imgSrc = base64Photo.startsWith("data:") ? base64Photo : "data:image/jpeg;base64," + base64Photo;
|
String imgSrc = base64Photo.startsWith("data:") ? base64Photo : "data:image/jpeg;base64," + base64Photo;
|
||||||
|
|
||||||
photoContainer.getElement().setProperty("innerHTML",
|
photoContainer.getElement().setProperty("innerHTML", "<img src='" + imgSrc
|
||||||
"<img src='" + imgSrc + "' style='max-width: 100%; max-height: 450px; object-fit: contain; border-radius: var(--lumo-border-radius-s); box-shadow: 0 2px 8px rgba(0,0,0,0.1);' />");
|
+ "' style='max-width: 100%; max-height: 450px; object-fit: contain; border-radius: var(--lumo-border-radius-s); box-shadow: 0 2px 8px rgba(0,0,0,0.1);' />");
|
||||||
|
|
||||||
counter.setText(current + " / " + total);
|
counter.setText(current + " / " + total);
|
||||||
}
|
}
|
||||||
@@ -875,12 +812,11 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove any existing width and height attributes and add responsive styling
|
// Remove any existing width and height attributes and add responsive styling
|
||||||
String responsiveSvg = svgContent
|
String responsiveSvg = svgContent.replaceAll("width\\s*=\\s*[\"'][^\"']*[\"']", "")
|
||||||
.replaceAll("width\\s*=\\s*[\"'][^\"']*[\"']", "")
|
.replaceAll("height\\s*=\\s*[\"'][^\"']*[\"']", "").replaceAll("style\\s*=\\s*[\"'][^\"']*[\"']", "");
|
||||||
.replaceAll("height\\s*=\\s*[\"'][^\"']*[\"']", "")
|
|
||||||
.replaceAll("style\\s*=\\s*[\"'][^\"']*[\"']", "");
|
|
||||||
|
|
||||||
// Add responsive styling - preserve viewBox if it exists, otherwise try to extract from width/height
|
// Add responsive styling - preserve viewBox if it exists, otherwise try to
|
||||||
|
// extract from width/height
|
||||||
if (!responsiveSvg.contains("viewBox")) {
|
if (!responsiveSvg.contains("viewBox")) {
|
||||||
// Try to extract original dimensions for viewBox
|
// Try to extract original dimensions for viewBox
|
||||||
String widthMatch = extractAttribute(svgContent, "width");
|
String widthMatch = extractAttribute(svgContent, "width");
|
||||||
@@ -894,7 +830,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
if (!cleanWidth.isEmpty() && !cleanHeight.isEmpty()) {
|
if (!cleanWidth.isEmpty() && !cleanHeight.isEmpty()) {
|
||||||
responsiveSvg = responsiveSvg.replaceFirst("<svg",
|
responsiveSvg = responsiveSvg.replaceFirst("<svg",
|
||||||
"<svg viewBox=\"0 0 " + cleanWidth + " " + cleanHeight + "\"");
|
"<svg viewBox=\"0 0 " + cleanWidth + " " + cleanHeight + "\"");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Ignore extraction errors
|
// Ignore extraction errors
|
||||||
@@ -904,7 +840,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
|
|
||||||
// Add responsive styling
|
// Add responsive styling
|
||||||
responsiveSvg = responsiveSvg.replaceFirst("<svg",
|
responsiveSvg = responsiveSvg.replaceFirst("<svg",
|
||||||
"<svg style=\"max-width: 100%; max-height: 200px; width: auto; height: auto;\"");
|
"<svg style=\"max-width: 100%; max-height: 200px; width: auto; height: auto;\"");
|
||||||
|
|
||||||
return responsiveSvg;
|
return responsiveSvg;
|
||||||
}
|
}
|
||||||
@@ -923,13 +859,9 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
|
|||||||
// Reset hover state for all task cards
|
// Reset hover state for all task cards
|
||||||
for (Div taskCard : taskCards) {
|
for (Div taskCard : taskCards) {
|
||||||
if (taskCard != null) {
|
if (taskCard != null) {
|
||||||
taskCard.getStyle()
|
taskCard.getStyle().set("transform", "translateY(0)").set("box-shadow", "0 1px 3px rgba(0, 0, 0, 0.1)")
|
||||||
.set("transform", "translateY(0)")
|
.set("border-color", "var(--lumo-contrast-20pct)");
|
||||||
.set("box-shadow", "0 1px 3px rgba(0, 0, 0, 0.1)")
|
|
||||||
.set("border-color", "var(--lumo-contrast-20pct)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
|||||||
|
|
||||||
private Authentication pendingAuth;
|
private Authentication pendingAuth;
|
||||||
|
|
||||||
public LoginView() {
|
public LoginView() {
|
||||||
addClassName("login-view");
|
addClassName("login-view");
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
|
|
||||||
@@ -72,23 +72,18 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
|||||||
H1 title = new H1("VotianLT");
|
H1 title = new H1("VotianLT");
|
||||||
title.getStyle().set("color", "var(--lumo-primary-color)");
|
title.getStyle().set("color", "var(--lumo-primary-color)");
|
||||||
|
|
||||||
Button registerButton = new Button("Noch kein Konto? Registrieren",
|
Button registerButton = new Button("Noch kein Konto? Registrieren", e -> UI.getCurrent().navigate("register"));
|
||||||
e -> UI.getCurrent().navigate("register"));
|
|
||||||
registerButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
registerButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
|
|
||||||
// Inline flash message box (hidden by default)
|
// Inline flash message box (hidden by default)
|
||||||
flashBox.getStyle()
|
flashBox.getStyle().set("background", "var(--lumo-error-color-10pct)")
|
||||||
.set("background", "var(--lumo-error-color-10pct)")
|
.set("color", "var(--lumo-error-text-color)").set("border", "1px solid var(--lumo-error-color)")
|
||||||
.set("color", "var(--lumo-error-text-color)")
|
.set("border-radius", "var(--lumo-border-radius-m)").set("padding", "var(--lumo-space-m)")
|
||||||
.set("border", "1px solid var(--lumo-error-color)")
|
.set("width", "100%").set("display", "none");
|
||||||
.set("border-radius", "var(--lumo-border-radius-m)")
|
|
||||||
.set("padding", "var(--lumo-space-m)")
|
|
||||||
.set("width", "100%")
|
|
||||||
.set("display", "none");
|
|
||||||
|
|
||||||
VerticalLayout loginLayout = new VerticalLayout();
|
VerticalLayout loginLayout = new VerticalLayout();
|
||||||
loginLayout.setAlignItems(FlexComponent.Alignment.CENTER);
|
loginLayout.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
loginLayout.add(flashBox, title, loginForm, twoFaField, verify2faButton, registerButton);
|
loginLayout.add(flashBox, title, loginForm, twoFaField, verify2faButton, registerButton);
|
||||||
loginLayout.setMaxWidth("400px");
|
loginLayout.setMaxWidth("400px");
|
||||||
loginLayout.setPadding(true);
|
loginLayout.setPadding(true);
|
||||||
@@ -101,8 +96,9 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
|||||||
private void handlePasswordLogin(String username, String password) {
|
private void handlePasswordLogin(String username, String password) {
|
||||||
try {
|
try {
|
||||||
// Prüfe Benutzername/Passwort
|
// Prüfe Benutzername/Passwort
|
||||||
Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
Authentication auth = authenticationManager
|
||||||
|
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
||||||
|
|
||||||
if (twoFactorEnabled) {
|
if (twoFactorEnabled) {
|
||||||
// 2FA aktiviert: Benutzer noch nicht in SecurityContext setzen
|
// 2FA aktiviert: Benutzer noch nicht in SecurityContext setzen
|
||||||
this.pendingAuth = auth;
|
this.pendingAuth = auth;
|
||||||
@@ -129,7 +125,8 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
|||||||
|
|
||||||
private void handleVerify2fa() {
|
private void handleVerify2fa() {
|
||||||
if (pendingAuth == null) {
|
if (pendingAuth == null) {
|
||||||
Notification.show("Bitte zuerst Benutzername und Passwort eingeben.", 3000, Notification.Position.BOTTOM_CENTER);
|
Notification.show("Bitte zuerst Benutzername und Passwort eingeben.", 3000,
|
||||||
|
Notification.Position.BOTTOM_CENTER);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String username = pendingAuth.getName();
|
String username = pendingAuth.getName();
|
||||||
@@ -145,7 +142,8 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
|||||||
}
|
}
|
||||||
// 2FA korrekt: Benutzer nun anmelden
|
// 2FA korrekt: Benutzer nun anmelden
|
||||||
SecurityContextHolder.getContext().setAuthentication(pendingAuth);
|
SecurityContextHolder.getContext().setAuthentication(pendingAuth);
|
||||||
// Persistiere SecurityContext in der HTTP-Session, damit Vaadin/Security ihn in neuen Requests sieht
|
// Persistiere SecurityContext in der HTTP-Session, damit Vaadin/Security ihn in
|
||||||
|
// neuen Requests sieht
|
||||||
var vaadinSession = VaadinSession.getCurrent();
|
var vaadinSession = VaadinSession.getCurrent();
|
||||||
if (vaadinSession != null) {
|
if (vaadinSession != null) {
|
||||||
var wrappedSession = vaadinSession.getSession();
|
var wrappedSession = vaadinSession.getSession();
|
||||||
@@ -160,10 +158,7 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
|||||||
@Override
|
@Override
|
||||||
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
|
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
|
||||||
// Zeige Fehlermeldung bei fehlgeschlagener Anmeldung
|
// Zeige Fehlermeldung bei fehlgeschlagener Anmeldung
|
||||||
if (beforeEnterEvent.getLocation()
|
if (beforeEnterEvent.getLocation().getQueryParameters().getParameters().containsKey("error")) {
|
||||||
.getQueryParameters()
|
|
||||||
.getParameters()
|
|
||||||
.containsKey("error")) {
|
|
||||||
loginForm.setError(true);
|
loginForm.setError(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* Meine Rechnungen – nutzerzentrierte Übersicht.
|
* Meine Rechnungen – nutzerzentrierte Übersicht.
|
||||||
*
|
*
|
||||||
* Layout orientiert am bereitgestellten Screenshot:
|
* Layout orientiert am bereitgestellten Screenshot: - Zwei Karten oben (Offene
|
||||||
* - Zwei Karten oben (Offene Rechnungen, Bankverbindung)
|
* Rechnungen, Bankverbindung) - Darunter ein Bereich „Rechnungen" mit Grid,
|
||||||
* - Darunter ein Bereich „Rechnungen" mit Grid, Suche und Seitengröße
|
* Suche und Seitengröße
|
||||||
*/
|
*/
|
||||||
@PageTitle("Meine Rechnungen")
|
@PageTitle("Meine Rechnungen")
|
||||||
@Route(value = "my-invoices", layout = MainLayout.class)
|
@Route(value = "my-invoices", layout = MainLayout.class)
|
||||||
@@ -54,14 +54,11 @@ public class MyInvoicesView extends Main {
|
|||||||
private Component createTopCards() {
|
private Component createTopCards() {
|
||||||
// Container mit zwei Spalten (responsiv)
|
// Container mit zwei Spalten (responsiv)
|
||||||
Div container = new Div();
|
Div container = new Div();
|
||||||
container.getStyle()
|
container.getStyle().set("display", "grid").set("grid-template-columns", "48% 2% 48%");
|
||||||
.set("display", "grid")
|
// .set("gap", "10px");
|
||||||
.set("grid-template-columns", "48% 2% 48%");
|
|
||||||
//.set("gap", "10px");
|
|
||||||
// Spaltenabstände: 2% zwischen den beiden Spalten
|
// Spaltenabstände: 2% zwischen den beiden Spalten
|
||||||
container.getStyle().set("column-gap", "0");
|
container.getStyle().set("column-gap", "0");
|
||||||
|
|
||||||
|
|
||||||
// Karte: Offene Rechnungen
|
// Karte: Offene Rechnungen
|
||||||
Paragraph hint = new Paragraph("Momentan sind keine neuen Rechnungen für Sie im System gespeichert.");
|
Paragraph hint = new Paragraph("Momentan sind keine neuen Rechnungen für Sie im System gespeichert.");
|
||||||
hint.getStyle().set("color", "var(--lumo-success-text-color)");
|
hint.getStyle().set("color", "var(--lumo-success-text-color)");
|
||||||
@@ -71,12 +68,9 @@ public class MyInvoicesView extends Main {
|
|||||||
VerticalLayout bankData = new VerticalLayout();
|
VerticalLayout bankData = new VerticalLayout();
|
||||||
bankData.setPadding(false);
|
bankData.setPadding(false);
|
||||||
bankData.setSpacing(false);
|
bankData.setSpacing(false);
|
||||||
bankData.add(
|
bankData.add(labeledValue("Kreditinstitut", "Hamburger Sparkasse"),
|
||||||
labeledValue("Kreditinstitut", "Hamburger Sparkasse"),
|
|
||||||
labeledValue("Begünstigter", "Assecutor Data Service GmbH"),
|
labeledValue("Begünstigter", "Assecutor Data Service GmbH"),
|
||||||
labeledValue("IBAN", "DE67200505501217139888"),
|
labeledValue("IBAN", "DE67200505501217139888"), labeledValue("Verwendungszweck", "vlt-00000610"));
|
||||||
labeledValue("Verwendungszweck", "vlt-00000610")
|
|
||||||
);
|
|
||||||
Div bankCard = createCard("Bankverbindung", bankData);
|
Div bankCard = createCard("Bankverbindung", bankData);
|
||||||
|
|
||||||
container.add(openInvoicesCard, bankCard);
|
container.add(openInvoicesCard, bankCard);
|
||||||
@@ -144,12 +138,11 @@ public class MyInvoicesView extends Main {
|
|||||||
|
|
||||||
private void applyFilter(String filter) {
|
private void applyFilter(String filter) {
|
||||||
String f = filter == null ? "" : filter.toLowerCase();
|
String f = filter == null ? "" : filter.toLowerCase();
|
||||||
grid.setItems(allRows.stream().filter(row ->
|
grid.setItems(allRows.stream()
|
||||||
row.status.toLowerCase().contains(f)
|
.filter(row -> row.status.toLowerCase().contains(f) || row.invoiceNumber.toLowerCase().contains(f)
|
||||||
|| row.invoiceNumber.toLowerCase().contains(f)
|
|
||||||
|| row.date.toString().toLowerCase().contains(f)
|
|| row.date.toString().toLowerCase().contains(f)
|
||||||
|| String.valueOf(row.amount).toLowerCase().contains(f)
|
|| String.valueOf(row.amount).toLowerCase().contains(f))
|
||||||
).toList());
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Div createCard(String title, Component content) {
|
private Div createCard(String title, Component content) {
|
||||||
@@ -174,16 +167,13 @@ public class MyInvoicesView extends Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void styleCard(Div card) {
|
private void styleCard(Div card) {
|
||||||
card.getStyle()
|
card.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-20pct)")
|
.set("border-radius", "var(--lumo-border-radius-l)").set("padding", "var(--lumo-space-m)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-l)")
|
.set("background", "var(--lumo-base-color)").set("box-shadow", "0 1px 1px rgba(0,0,0,0.02)");
|
||||||
.set("padding", "var(--lumo-space-m)")
|
|
||||||
.set("background", "var(--lumo-base-color)")
|
|
||||||
.set("box-shadow", "0 1px 1px rgba(0,0,0,0.02)");
|
|
||||||
card.setWidthFull();
|
card.setWidthFull();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schlanke lokale Repräsentation für das Grid
|
// Schlanke lokale Repräsentation für das Grid
|
||||||
public record MyInvoiceRow(String status, String invoiceNumber, LocalDate date, double amount) {}
|
public record MyInvoiceRow(String status, String invoiceNumber, LocalDate date, double amount) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ import com.vaadin.flow.router.Route;
|
|||||||
import com.vaadin.flow.server.VaadinSession;
|
import com.vaadin.flow.server.VaadinSession;
|
||||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||||
import de.assecutor.votianlt.pages.service.UserService;
|
import de.assecutor.votianlt.pages.service.UserService;
|
||||||
import de.assecutor.votianlt.util.MailUtil;
|
import de.assecutor.votianlt.service.EmailService;
|
||||||
import jakarta.mail.MessagingException;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@@ -28,7 +27,7 @@ import java.time.LocalDateTime;
|
|||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
public class RegisterView extends VerticalLayout {
|
public class RegisterView extends VerticalLayout {
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final MailUtil mailUtil;
|
private final EmailService emailService;
|
||||||
|
|
||||||
private TextField emailField;
|
private TextField emailField;
|
||||||
private PasswordField passwordField;
|
private PasswordField passwordField;
|
||||||
@@ -54,9 +53,9 @@ public class RegisterView extends VerticalLayout {
|
|||||||
private LocalDateTime lastSentAt;
|
private LocalDateTime lastSentAt;
|
||||||
private boolean awaitingVerification = false;
|
private boolean awaitingVerification = false;
|
||||||
|
|
||||||
public RegisterView(UserService userService, MailUtil mailUtil) {
|
public RegisterView(UserService userService, EmailService emailService) {
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.mailUtil = mailUtil;
|
this.emailService = emailService;
|
||||||
// Layout-Konfiguration für vollständige Zentrierung
|
// Layout-Konfiguration für vollständige Zentrierung
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
@@ -166,17 +165,14 @@ public class RegisterView extends VerticalLayout {
|
|||||||
resendButton.setVisible(false);
|
resendButton.setVisible(false);
|
||||||
|
|
||||||
// Zurück-Link
|
// Zurück-Link
|
||||||
Button backButton = new Button("Zurück zur Startseite", event ->
|
Button backButton = new Button("Zurück zur Startseite", event -> getUI().ifPresent(ui -> ui.navigate("")));
|
||||||
getUI().ifPresent(ui -> ui.navigate("")));
|
|
||||||
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
backButton.setWidthFull();
|
backButton.setWidthFull();
|
||||||
|
|
||||||
// Zweispaltiges Formular
|
// Zweispaltiges Formular
|
||||||
FormLayout form = new FormLayout();
|
FormLayout form = new FormLayout();
|
||||||
form.setWidthFull();
|
form.setWidthFull();
|
||||||
form.setResponsiveSteps(
|
form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
|
||||||
new FormLayout.ResponsiveStep("0", 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Firma zuerst, volle Breite
|
// Firma zuerst, volle Breite
|
||||||
form.add(companyField);
|
form.add(companyField);
|
||||||
@@ -238,7 +234,8 @@ public class RegisterView extends VerticalLayout {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (userService.existsByEmail(email)) {
|
if (userService.existsByEmail(email)) {
|
||||||
Notification.show("Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.", 4000, Notification.Position.MIDDLE);
|
Notification.show("Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.", 4000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (password.isEmpty()) {
|
if (password.isEmpty()) {
|
||||||
@@ -315,7 +312,8 @@ public class RegisterView extends VerticalLayout {
|
|||||||
// Rate-Limit: 60 Sekunden zwischen Sendungen
|
// Rate-Limit: 60 Sekunden zwischen Sendungen
|
||||||
if (lastSentAt != null && Duration.between(lastSentAt, LocalDateTime.now()).getSeconds() < 60) {
|
if (lastSentAt != null && Duration.between(lastSentAt, LocalDateTime.now()).getSeconds() < 60) {
|
||||||
long wait = 60 - Duration.between(lastSentAt, LocalDateTime.now()).getSeconds();
|
long wait = 60 - Duration.between(lastSentAt, LocalDateTime.now()).getSeconds();
|
||||||
Notification.show("Bitte warten Sie " + wait + " Sekunden, bevor Sie den Code erneut senden.", 4000, Notification.Position.MIDDLE);
|
Notification.show("Bitte warten Sie " + wait + " Sekunden, bevor Sie den Code erneut senden.", 4000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String code = generateSixDigitCode();
|
String code = generateSixDigitCode();
|
||||||
@@ -324,11 +322,10 @@ public class RegisterView extends VerticalLayout {
|
|||||||
lastSentAt = LocalDateTime.now();
|
lastSentAt = LocalDateTime.now();
|
||||||
|
|
||||||
String subject = "Ihr VotianLT Bestätigungscode";
|
String subject = "Ihr VotianLT Bestätigungscode";
|
||||||
String body = "Ihr Bestätigungscode lautet: " + code + "\n\n" +
|
String body = "Ihr Bestätigungscode lautet: " + code + "\n\n" + "Dieser Code ist 10 Minuten gültig.\n"
|
||||||
"Dieser Code ist 10 Minuten gültig.\n" +
|
+ "Wenn Sie diese Registrierung nicht angefragt haben, ignorieren Sie diese E-Mail.";
|
||||||
"Wenn Sie diese Registrierung nicht angefragt haben, ignorieren Sie diese E-Mail.";
|
|
||||||
try {
|
try {
|
||||||
mailUtil.sendMail(email, subject, body);
|
emailService.sendSimpleEmail(email, subject, body);
|
||||||
awaitingVerification = true;
|
awaitingVerification = true;
|
||||||
// UI umstellen: Code-Eingabe anzeigen
|
// UI umstellen: Code-Eingabe anzeigen
|
||||||
codeField.clear();
|
codeField.clear();
|
||||||
@@ -352,8 +349,9 @@ public class RegisterView extends VerticalLayout {
|
|||||||
|
|
||||||
submitButton.setEnabled(false);
|
submitButton.setEnabled(false);
|
||||||
|
|
||||||
Notification.show("Ein Bestätigungscode wurde an " + email + " gesendet.", 4000, Notification.Position.MIDDLE);
|
Notification.show("Ein Bestätigungscode wurde an " + email + " gesendet.", 4000,
|
||||||
} catch (MessagingException e) {
|
Notification.Position.MIDDLE);
|
||||||
|
} catch (Exception e) {
|
||||||
awaitingVerification = false;
|
awaitingVerification = false;
|
||||||
Notification.show("Fehler beim Senden der E-Mail: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
Notification.show("Fehler beim Senden der E-Mail: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
@@ -370,7 +368,8 @@ public class RegisterView extends VerticalLayout {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (codeExpiresAt == null || LocalDateTime.now().isAfter(codeExpiresAt)) {
|
if (codeExpiresAt == null || LocalDateTime.now().isAfter(codeExpiresAt)) {
|
||||||
Notification.show("Der Code ist abgelaufen. Bitte senden Sie einen neuen Code.", 4000, Notification.Position.MIDDLE);
|
Notification.show("Der Code ist abgelaufen. Bitte senden Sie einen neuen Code.", 4000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!entered.equals(pendingCode)) {
|
if (!entered.equals(pendingCode)) {
|
||||||
@@ -387,7 +386,8 @@ public class RegisterView extends VerticalLayout {
|
|||||||
try {
|
try {
|
||||||
var user = userService.createUser(email, password, firstName, lastName);
|
var user = userService.createUser(email, password, firstName, lastName);
|
||||||
// Persistiere zusätzliche Profil-/Adressdaten
|
// Persistiere zusätzliche Profil-/Adressdaten
|
||||||
if (!phone.isBlank()) user.setPhone(phone);
|
if (!phone.isBlank())
|
||||||
|
user.setPhone(phone);
|
||||||
var company = companyField.getValue() != null ? companyField.getValue().trim() : null;
|
var company = companyField.getValue() != null ? companyField.getValue().trim() : null;
|
||||||
var street = streetField.getValue() != null ? streetField.getValue().trim() : null;
|
var street = streetField.getValue() != null ? streetField.getValue().trim() : null;
|
||||||
var houseNo = houseNumberField.getValue() != null ? houseNumberField.getValue().trim() : null;
|
var houseNo = houseNumberField.getValue() != null ? houseNumberField.getValue().trim() : null;
|
||||||
@@ -399,7 +399,8 @@ public class RegisterView extends VerticalLayout {
|
|||||||
user.setZip(zip);
|
user.setZip(zip);
|
||||||
user.setCity(city);
|
user.setCity(city);
|
||||||
userService.save(user);
|
userService.save(user);
|
||||||
VaadinSession.getCurrent().setAttribute("flashMessage", "Registrierung erfolgreich. Bitte melden Sie sich an.");
|
VaadinSession.getCurrent().setAttribute("flashMessage",
|
||||||
|
"Registrierung erfolgreich. Bitte melden Sie sich an.");
|
||||||
getUI().ifPresent(ui -> ui.navigate("login"));
|
getUI().ifPresent(ui -> ui.navigate("login"));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Notification.show("Registrierung fehlgeschlagen: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
Notification.show("Registrierung fehlgeschlagen: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
|
|
||||||
@PageTitle("Kunden")
|
@PageTitle("Kunden")
|
||||||
@Route(value = "customers", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "customers", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class ShowCustomersView extends VerticalLayout {
|
public class ShowCustomersView extends VerticalLayout {
|
||||||
|
|
||||||
private final CustomerService customerService;
|
private final CustomerService customerService;
|
||||||
@@ -43,32 +43,33 @@ public class ShowCustomersView extends VerticalLayout {
|
|||||||
add(header);
|
add(header);
|
||||||
|
|
||||||
// Add hint text
|
// Add hint text
|
||||||
var hintText = new com.vaadin.flow.component.html.Paragraph("Klicken Sie auf einen Eintrag, um ihn zu bearbeiten.");
|
var hintText = new com.vaadin.flow.component.html.Paragraph(
|
||||||
|
"Klicken Sie auf einen Eintrag, um ihn zu bearbeiten.");
|
||||||
hintText.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
hintText.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||||
hintText.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
hintText.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||||
add(hintText);
|
add(hintText);
|
||||||
|
|
||||||
// Configure grid columns
|
// Configure grid columns
|
||||||
grid.addColumn(Customer::getCompanyName).setHeader("Firma").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
grid.addColumn(Customer::getCompanyName).setHeader("Firma").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||||
grid.addColumn(customer -> (customer.getFirstname() != null ? customer.getFirstname() : "") + " " +
|
grid.addColumn(customer -> (customer.getFirstname() != null ? customer.getFirstname() : "") + " "
|
||||||
(customer.getLastName() != null ? customer.getLastName() : ""))
|
+ (customer.getLastName() != null ? customer.getLastName() : "")).setHeader("Name").setAutoWidth(true)
|
||||||
.setHeader("Name").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
.setFlexGrow(1).setSortable(true);
|
||||||
grid.addColumn(Customer::getMail).setHeader("E-Mail").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
grid.addColumn(Customer::getMail).setHeader("E-Mail").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||||
grid.addColumn(Customer::getTelephone).setHeader("Telefon").setAutoWidth(true).setSortable(true);
|
grid.addColumn(Customer::getTelephone).setHeader("Telefon").setAutoWidth(true).setSortable(true);
|
||||||
grid.addColumn(customer -> (customer.getStreet() != null ? customer.getStreet() : "") + " " +
|
grid.addColumn(customer -> (customer.getStreet() != null ? customer.getStreet() : "") + " "
|
||||||
(customer.getHouseNumber() != null ? customer.getHouseNumber() : ""))
|
+ (customer.getHouseNumber() != null ? customer.getHouseNumber() : "")).setHeader("Straße")
|
||||||
.setHeader("Straße").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
.setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||||
grid.addColumn(customer -> (customer.getZip() != null ? customer.getZip() : "") + " " +
|
grid.addColumn(customer -> (customer.getZip() != null ? customer.getZip() : "") + " "
|
||||||
(customer.getCity() != null ? customer.getCity() : ""))
|
+ (customer.getCity() != null ? customer.getCity() : "")).setHeader("Ort").setAutoWidth(true)
|
||||||
.setHeader("Ort").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
.setFlexGrow(1).setSortable(true);
|
||||||
|
|
||||||
grid.setMultiSort(true);
|
grid.setMultiSort(true);
|
||||||
grid.setSizeFull();
|
grid.setSizeFull();
|
||||||
|
|
||||||
// Make grid rows clickable
|
// Make grid rows clickable
|
||||||
grid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
grid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
||||||
grid.getStyle().set("cursor", "pointer");
|
grid.getStyle().set("cursor", "pointer");
|
||||||
|
|
||||||
// Add click listener to navigate to edit view
|
// Add click listener to navigate to edit view
|
||||||
grid.addItemClickListener(event -> {
|
grid.addItemClickListener(event -> {
|
||||||
Customer customer = event.getItem();
|
Customer customer = event.getItem();
|
||||||
@@ -76,13 +77,11 @@ public class ShowCustomersView extends VerticalLayout {
|
|||||||
getUI().ifPresent(ui -> ui.navigate("edit-customer/" + customer.getId().toHexString()));
|
getUI().ifPresent(ui -> ui.navigate("edit-customer/" + customer.getId().toHexString()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add(grid);
|
add(grid);
|
||||||
|
|
||||||
// Button action
|
// Button action
|
||||||
addCustomerButton.addClickListener(e ->
|
addCustomerButton.addClickListener(e -> getUI().ifPresent(ui -> ui.navigate("add-customer")));
|
||||||
getUI().ifPresent(ui -> ui.navigate("add-customer"))
|
|
||||||
);
|
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
@@ -91,8 +90,7 @@ public class ShowCustomersView extends VerticalLayout {
|
|||||||
var customers = customerService.findAll();
|
var customers = customerService.findAll();
|
||||||
var currentUserId = securityService.getCurrentUserId();
|
var currentUserId = securityService.getCurrentUserId();
|
||||||
var ownCustomers = customers.stream()
|
var ownCustomers = customers.stream()
|
||||||
.filter(c -> c.getCreatedBy() != null && c.getCreatedBy().equals(currentUserId))
|
.filter(c -> c.getCreatedBy() != null && c.getCreatedBy().equals(currentUserId)).toList();
|
||||||
.toList();
|
|
||||||
grid.setItems(ownCustomers);
|
grid.setItems(ownCustomers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
|
|
||||||
@PageTitle("Aufträge")
|
@PageTitle("Aufträge")
|
||||||
@Route(value = "jobs", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "jobs", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER"})
|
@RolesAllowed({ "USER" })
|
||||||
public class ShowJobsView extends VerticalLayout {
|
public class ShowJobsView extends VerticalLayout {
|
||||||
|
|
||||||
private final DatePicker startDate = new DatePicker("Startdatum");
|
private final DatePicker startDate = new DatePicker("Startdatum");
|
||||||
@@ -64,7 +64,6 @@ public class ShowJobsView extends VerticalLayout {
|
|||||||
filterBar.setAlignItems(Alignment.END);
|
filterBar.setAlignItems(Alignment.END);
|
||||||
add(filterBar);
|
add(filterBar);
|
||||||
|
|
||||||
|
|
||||||
H2 title = new H2("Aufträge");
|
H2 title = new H2("Aufträge");
|
||||||
add(title);
|
add(title);
|
||||||
// Init default period: last 30 days
|
// Init default period: last 30 days
|
||||||
@@ -80,7 +79,6 @@ public class ShowJobsView extends VerticalLayout {
|
|||||||
startDate.addValueChangeListener(e -> loadData());
|
startDate.addValueChangeListener(e -> loadData());
|
||||||
endDate.addValueChangeListener(e -> loadData());
|
endDate.addValueChangeListener(e -> loadData());
|
||||||
|
|
||||||
|
|
||||||
// Configure grid columns: Kunde, Auftragsnummer, Auftragsdatum, Zielort
|
// Configure grid columns: Kunde, Auftragsnummer, Auftragsdatum, Zielort
|
||||||
grid.addColumn(Job::getDeliveryCompany).setHeader("Kunde").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
grid.addColumn(Job::getDeliveryCompany).setHeader("Kunde").setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||||
grid.addColumn(Job::getJobNumber).setHeader("Auftragsnummer").setAutoWidth(true).setSortable(true);
|
grid.addColumn(Job::getJobNumber).setHeader("Auftragsnummer").setAutoWidth(true).setSortable(true);
|
||||||
@@ -89,11 +87,11 @@ public class ShowJobsView extends VerticalLayout {
|
|||||||
|
|
||||||
grid.setMultiSort(true);
|
grid.setMultiSort(true);
|
||||||
grid.setSizeFull();
|
grid.setSizeFull();
|
||||||
|
|
||||||
// Make grid rows clickable
|
// Make grid rows clickable
|
||||||
grid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
grid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
||||||
grid.getStyle().set("cursor", "pointer");
|
grid.getStyle().set("cursor", "pointer");
|
||||||
|
|
||||||
// Add click listener to navigate to job summary view
|
// Add click listener to navigate to job summary view
|
||||||
grid.addItemClickListener(event -> {
|
grid.addItemClickListener(event -> {
|
||||||
Job job = event.getItem();
|
Job job = event.getItem();
|
||||||
@@ -101,7 +99,7 @@ public class ShowJobsView extends VerticalLayout {
|
|||||||
getUI().ifPresent(ui -> ui.navigate("job_summary/" + job.getId().toHexString()));
|
getUI().ifPresent(ui -> ui.navigate("job_summary/" + job.getId().toHexString()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add(grid);
|
add(grid);
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
@@ -110,8 +108,10 @@ public class ShowJobsView extends VerticalLayout {
|
|||||||
private void loadData() {
|
private void loadData() {
|
||||||
var start = startDate.getValue();
|
var start = startDate.getValue();
|
||||||
var end = endDate.getValue();
|
var end = endDate.getValue();
|
||||||
java.time.LocalDateTime startDt = start != null ? start.atStartOfDay() : java.time.LocalDate.now().minusDays(30).atStartOfDay();
|
java.time.LocalDateTime startDt = start != null ? start.atStartOfDay()
|
||||||
java.time.LocalDateTime endDt = end != null ? end.atTime(23,59,59) : java.time.LocalDate.now().atTime(23,59,59);
|
: java.time.LocalDate.now().minusDays(30).atStartOfDay();
|
||||||
|
java.time.LocalDateTime endDt = end != null ? end.atTime(23, 59, 59)
|
||||||
|
: java.time.LocalDate.now().atTime(23, 59, 59);
|
||||||
|
|
||||||
// Aktuellen Benutzer (ObjectId Hex) ermitteln
|
// Aktuellen Benutzer (ObjectId Hex) ermitteln
|
||||||
String currentUserIdHex = securityService.getCurrentUserId().toHexString();
|
String currentUserIdHex = securityService.getCurrentUserId().toHexString();
|
||||||
@@ -123,29 +123,31 @@ public class ShowJobsView extends VerticalLayout {
|
|||||||
if ("Erledigt".equals(selectedStatus)) {
|
if ("Erledigt".equals(selectedStatus)) {
|
||||||
statusList = java.util.List.of(JobStatus.DELIVERED, JobStatus.COMPLETED, JobStatus.CANCELLED);
|
statusList = java.util.List.of(JobStatus.DELIVERED, JobStatus.COMPLETED, JobStatus.CANCELLED);
|
||||||
} else if ("Offen".equals(selectedStatus)) {
|
} else if ("Offen".equals(selectedStatus)) {
|
||||||
statusList = java.util.List.of(JobStatus.CREATED, JobStatus.IN_PROGRESS,
|
statusList = java.util.List.of(JobStatus.CREATED, JobStatus.IN_PROGRESS, JobStatus.PICKUP_SCHEDULED,
|
||||||
JobStatus.PICKUP_SCHEDULED, JobStatus.PICKED_UP,
|
JobStatus.PICKED_UP, JobStatus.IN_TRANSIT);
|
||||||
JobStatus.IN_TRANSIT);
|
|
||||||
} else { // "Alle"
|
} else { // "Alle"
|
||||||
statusList = java.util.Arrays.asList(JobStatus.values());
|
statusList = java.util.Arrays.asList(JobStatus.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suchtext für Auftragsnummer
|
// Suchtext für Auftragsnummer
|
||||||
String searchText = searchField.getValue();
|
String searchText = searchField.getValue();
|
||||||
String jobNumberPattern = searchText != null && !searchText.trim().isEmpty()
|
String jobNumberPattern = searchText != null && !searchText.trim().isEmpty() ? searchText.trim() : ".*"; // Regex
|
||||||
? searchText.trim()
|
// für
|
||||||
: ".*"; // Regex für alle wenn leer
|
// alle
|
||||||
|
// wenn
|
||||||
|
// leer
|
||||||
|
|
||||||
// Verwende die erweiterte Suchmethode
|
// Verwende die erweiterte Suchmethode
|
||||||
var filteredJobs = jobRepository.findWithFilters(startDt, endDt, currentUserIdHex,
|
var filteredJobs = jobRepository.findWithFilters(startDt, endDt, currentUserIdHex, jobNumberPattern,
|
||||||
jobNumberPattern, statusList);
|
statusList);
|
||||||
grid.setItems(filteredJobs);
|
grid.setItems(filteredJobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportToCsv() {
|
private void exportToCsv() {
|
||||||
var items = grid.getListDataView().getItems().toList();
|
var items = grid.getListDataView().getItems().toList();
|
||||||
|
|
||||||
StreamResource resource = new StreamResource("jobs.csv", () -> new java.io.ByteArrayInputStream(generateCsv(items).getBytes(java.nio.charset.StandardCharsets.UTF_8)));
|
StreamResource resource = new StreamResource("jobs.csv", () -> new java.io.ByteArrayInputStream(
|
||||||
|
generateCsv(items).getBytes(java.nio.charset.StandardCharsets.UTF_8)));
|
||||||
resource.setContentType("text/csv");
|
resource.setContentType("text/csv");
|
||||||
resource.setCacheTime(0);
|
resource.setCacheTime(0);
|
||||||
|
|
||||||
@@ -157,9 +159,8 @@ public class ShowJobsView extends VerticalLayout {
|
|||||||
// Add to UI and trigger download via JavaScript
|
// Add to UI and trigger download via JavaScript
|
||||||
add(downloadAnchor);
|
add(downloadAnchor);
|
||||||
getUI().ifPresent(ui -> ui.getPage().executeJs(
|
getUI().ifPresent(ui -> ui.getPage().executeJs(
|
||||||
"const link = arguments[0]; link.click(); setTimeout(() => link.remove(), 100);",
|
"const link = arguments[0]; link.click(); setTimeout(() => link.remove(), 100);",
|
||||||
downloadAnchor.getElement()
|
downloadAnchor.getElement()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateCsv(java.util.List<Job> jobs) {
|
private String generateCsv(java.util.List<Job> jobs) {
|
||||||
@@ -179,11 +180,11 @@ public class ShowJobsView extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String escapeCsv(String value) {
|
private String escapeCsv(String value) {
|
||||||
if (value == null) return "";
|
if (value == null)
|
||||||
|
return "";
|
||||||
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
|
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
|
||||||
return "\"" + value.replace("\"", "\"\"") + "\"";
|
return "\"" + value.replace("\"", "\"\"") + "\"";
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
logo.getStyle().set("font-weight", "bold");
|
logo.getStyle().set("font-weight", "bold");
|
||||||
|
|
||||||
// Navigation - abhängig vom Anmeldestatus
|
// Navigation - abhängig vom Anmeldestatus
|
||||||
Component navigation = securityService.isUserLoggedIn()
|
Component navigation = securityService.isUserLoggedIn() ? createAuthenticatedNavigation()
|
||||||
? createAuthenticatedNavigation()
|
: createAnonymousNavigation();
|
||||||
: createAnonymousNavigation();
|
|
||||||
|
|
||||||
header.add(logo, navigation);
|
header.add(logo, navigation);
|
||||||
|
|
||||||
@@ -103,8 +102,7 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
navLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
|
navLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
// Auftragserstellung Button
|
// Auftragserstellung Button
|
||||||
Button createOrderBtn = new Button("Auftragserstellung", event ->
|
Button createOrderBtn = new Button("Auftragserstellung", event -> UI.getCurrent().navigate("add_job"));
|
||||||
UI.getCurrent().navigate("add_job"));
|
|
||||||
createOrderBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
createOrderBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
|
||||||
// Verwaltung ComboBox
|
// Verwaltung ComboBox
|
||||||
@@ -115,15 +113,15 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
String value = event.getValue();
|
String value = event.getValue();
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case "Kunden":
|
case "Kunden":
|
||||||
UI.getCurrent().navigate("customer");
|
UI.getCurrent().navigate("customer");
|
||||||
break;
|
break;
|
||||||
case "Aufträge":
|
case "Aufträge":
|
||||||
UI.getCurrent().navigate("orders");
|
UI.getCurrent().navigate("orders");
|
||||||
break;
|
break;
|
||||||
case "Firmen":
|
case "Firmen":
|
||||||
UI.getCurrent().navigate("add_company");
|
UI.getCurrent().navigate("add_company");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
managementCombo.clear(); // Reset selection
|
managementCombo.clear(); // Reset selection
|
||||||
}
|
}
|
||||||
@@ -138,13 +136,13 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
String value = event.getValue();
|
String value = event.getValue();
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case "Profil anzeigen":
|
case "Profil anzeigen":
|
||||||
break;
|
break;
|
||||||
case "Einstellungen":
|
case "Einstellungen":
|
||||||
break;
|
break;
|
||||||
case "Abmelden":
|
case "Abmelden":
|
||||||
securityService.logout();
|
securityService.logout();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
userCombo.clear(); // Reset selection
|
userCombo.clear(); // Reset selection
|
||||||
}
|
}
|
||||||
@@ -157,7 +155,7 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
notificationBtn.setTooltipText("Benachrichtigungen");
|
notificationBtn.setTooltipText("Benachrichtigungen");
|
||||||
notificationBtn.addClickListener(event -> {
|
notificationBtn.addClickListener(event -> {
|
||||||
com.vaadin.flow.component.notification.Notification.show("Keine neuen Benachrichtigungen", 3000,
|
com.vaadin.flow.component.notification.Notification.show("Keine neuen Benachrichtigungen", 3000,
|
||||||
com.vaadin.flow.component.notification.Notification.Position.TOP_END);
|
com.vaadin.flow.component.notification.Notification.Position.TOP_END);
|
||||||
});
|
});
|
||||||
|
|
||||||
navLayout.add(createOrderBtn, managementCombo, userCombo, notificationBtn);
|
navLayout.add(createOrderBtn, managementCombo, userCombo, notificationBtn);
|
||||||
@@ -170,7 +168,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
heroSection.setPadding(true);
|
heroSection.setPadding(true);
|
||||||
heroSection.setSpacing(true);
|
heroSection.setSpacing(true);
|
||||||
heroSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
heroSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
heroSection.getStyle().set("background", "linear-gradient(135deg, var(--lumo-primary-color-10pct), var(--lumo-primary-color-50pct))");
|
heroSection.getStyle().set("background",
|
||||||
|
"linear-gradient(135deg, var(--lumo-primary-color-10pct), var(--lumo-primary-color-50pct))");
|
||||||
heroSection.getStyle().set("min-height", "400px");
|
heroSection.getStyle().set("min-height", "400px");
|
||||||
heroSection.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
heroSection.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
|
|
||||||
@@ -184,11 +183,9 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
heroTitle.getStyle().set("color", "var(--lumo-primary-text-color)");
|
heroTitle.getStyle().set("color", "var(--lumo-primary-text-color)");
|
||||||
heroTitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
|
heroTitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
|
||||||
|
|
||||||
Paragraph heroDescription = new Paragraph(
|
Paragraph heroDescription = new Paragraph("Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe - "
|
||||||
"Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe - " +
|
+ "volldigital und aus einem Guss. Konzentrieren Sie sich auf Ihr Geschäft, "
|
||||||
"volldigital und aus einem Guss. Konzentrieren Sie sich auf Ihr Geschäft, " +
|
+ "wir kümmern uns um die Büroarbeit.");
|
||||||
"wir kümmern uns um die Büroarbeit."
|
|
||||||
);
|
|
||||||
heroDescription.getStyle().set("text-align", "center");
|
heroDescription.getStyle().set("text-align", "center");
|
||||||
heroDescription.getStyle().set("max-width", "600px");
|
heroDescription.getStyle().set("max-width", "600px");
|
||||||
heroDescription.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
heroDescription.getStyle().set("font-size", "var(--lumo-font-size-l)");
|
||||||
@@ -214,9 +211,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
systemTitle.getStyle().set("text-align", "center");
|
systemTitle.getStyle().set("text-align", "center");
|
||||||
|
|
||||||
Paragraph systemIntro = new Paragraph(
|
Paragraph systemIntro = new Paragraph(
|
||||||
"Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe ist von entscheidender Bedeutung, " +
|
"Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe ist von entscheidender Bedeutung, "
|
||||||
"dass sie sich in erster Linie auf ihr eigentliches Geschäft konzentrieren können: Kunden gewinnen und Waren von A nach B liefern."
|
+ "dass sie sich in erster Linie auf ihr eigentliches Geschäft konzentrieren können: Kunden gewinnen und Waren von A nach B liefern.");
|
||||||
);
|
|
||||||
systemIntro.getStyle().set("text-align", "center");
|
systemIntro.getStyle().set("text-align", "center");
|
||||||
systemIntro.getStyle().set("max-width", "800px");
|
systemIntro.getStyle().set("max-width", "800px");
|
||||||
systemIntro.getStyle().set("margin-bottom", "var(--lumo-space-xl)");
|
systemIntro.getStyle().set("margin-bottom", "var(--lumo-space-xl)");
|
||||||
@@ -228,14 +224,12 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START);
|
featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START);
|
||||||
|
|
||||||
// Feature Cards
|
// Feature Cards
|
||||||
featuresGrid.add(
|
featuresGrid.add(createFeatureCard(VaadinIcon.COG, "Einrichtungsassistent",
|
||||||
createFeatureCard(VaadinIcon.COG, "Einrichtungsassistent",
|
|
||||||
"Mithilfe des Einrichtungsassistenten haben Sie die Möglichkeit, Ihr Nutzerprofil zu vervollständigen."),
|
"Mithilfe des Einrichtungsassistenten haben Sie die Möglichkeit, Ihr Nutzerprofil zu vervollständigen."),
|
||||||
createFeatureCard(VaadinIcon.USERS, "Kunden- und Auftragsverwaltung",
|
createFeatureCard(VaadinIcon.USERS, "Kunden- und Auftragsverwaltung",
|
||||||
"Mit der Kunden- und Auftragsverwaltung haben Sie alle Kontaktdaten und Auftragsdetails stets im Blick."),
|
"Mit der Kunden- und Auftragsverwaltung haben Sie alle Kontaktdaten und Auftragsdetails stets im Blick."),
|
||||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, "Auftragserstellung",
|
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, "Auftragserstellung",
|
||||||
"Stellen Sie mit wenigen Mausklicks Aufträge ins System ein und legen Sie fest, welcher Mitarbeiter welchen Transportauftrag abarbeiten soll.")
|
"Stellen Sie mit wenigen Mausklicks Aufträge ins System ein und legen Sie fest, welcher Mitarbeiter welchen Transportauftrag abarbeiten soll."));
|
||||||
);
|
|
||||||
|
|
||||||
systemSection.add(systemTitle, systemIntro, featuresGrid);
|
systemSection.add(systemTitle, systemIntro, featuresGrid);
|
||||||
return systemSection;
|
return systemSection;
|
||||||
@@ -281,9 +275,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
appTitle.getStyle().set("text-align", "center");
|
appTitle.getStyle().set("text-align", "center");
|
||||||
|
|
||||||
Paragraph appDescription = new Paragraph(
|
Paragraph appDescription = new Paragraph(
|
||||||
"Jeder Auftrag kann optional über die votianLT-App abgearbeitet werden – ganz ohne \"Zettelwirtschaft\". " +
|
"Jeder Auftrag kann optional über die votianLT-App abgearbeitet werden – ganz ohne \"Zettelwirtschaft\". "
|
||||||
"So gelangen alle relevanten Auftragsinformationen direkt auf das Smartphone des Fahrers."
|
+ "So gelangen alle relevanten Auftragsinformationen direkt auf das Smartphone des Fahrers.");
|
||||||
);
|
|
||||||
appDescription.getStyle().set("text-align", "center");
|
appDescription.getStyle().set("text-align", "center");
|
||||||
appDescription.getStyle().set("max-width", "800px");
|
appDescription.getStyle().set("max-width", "800px");
|
||||||
|
|
||||||
@@ -314,18 +307,12 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
|
|||||||
companyInfo.setPadding(false);
|
companyInfo.setPadding(false);
|
||||||
companyInfo.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
companyInfo.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
companyInfo.add(
|
companyInfo.add(new Paragraph("Assecutor Data Service GmbH"), new Paragraph("Ottensener Str. 8, 22525 Hamburg"),
|
||||||
new Paragraph("Assecutor Data Service GmbH"),
|
new Paragraph("Telefon: +49 40 18 123 771 0"), new Paragraph("E-Mail: ahoi@assecutor.de"));
|
||||||
new Paragraph("Ottensener Str. 8, 22525 Hamburg"),
|
|
||||||
new Paragraph("Telefon: +49 40 18 123 771 0"),
|
|
||||||
new Paragraph("E-Mail: ahoi@assecutor.de")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Call to Action
|
// Call to Action
|
||||||
Paragraph ctaText = new Paragraph(
|
Paragraph ctaText = new Paragraph("Registrieren Sie sich noch heute und nutzen den kostenfreien Probemonat, "
|
||||||
"Registrieren Sie sich noch heute und nutzen den kostenfreien Probemonat, " +
|
+ "um das System auf Herz und Nieren zu testen.");
|
||||||
"um das System auf Herz und Nieren zu testen."
|
|
||||||
);
|
|
||||||
ctaText.getStyle().set("text-align", "center");
|
ctaText.getStyle().set("text-align", "center");
|
||||||
ctaText.getStyle().set("font-weight", "bold");
|
ctaText.getStyle().set("font-weight", "bold");
|
||||||
ctaText.getStyle().set("color", "var(--lumo-primary-color)");
|
ctaText.getStyle().set("color", "var(--lumo-primary-color)");
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import jakarta.annotation.security.RolesAllowed;
|
|||||||
|
|
||||||
@PageTitle("Statistiken")
|
@PageTitle("Statistiken")
|
||||||
@Route(value = "statistics", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
@Route(value = "statistics", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||||
@RolesAllowed({"USER","ADMIN"})
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
@JavaScript("https://cdn.jsdelivr.net/npm/chart.js")
|
@JavaScript("https://cdn.jsdelivr.net/npm/chart.js")
|
||||||
public class StatisticsView extends VerticalLayout {
|
public class StatisticsView extends VerticalLayout {
|
||||||
|
|
||||||
@@ -56,11 +56,11 @@ public class StatisticsView extends VerticalLayout {
|
|||||||
revenueContainer.setWidthFull();
|
revenueContainer.setWidthFull();
|
||||||
revenueContainer.setHeight("400px");
|
revenueContainer.setHeight("400px");
|
||||||
revenueContainer.setPadding(false);
|
revenueContainer.setPadding(false);
|
||||||
|
|
||||||
Div revenueChart = createRevenueByCustomerChart();
|
Div revenueChart = createRevenueByCustomerChart();
|
||||||
revenueChart.setSizeFull();
|
revenueChart.setSizeFull();
|
||||||
revenueContainer.add(revenueChart);
|
revenueContainer.add(revenueChart);
|
||||||
|
|
||||||
add(revenueContainer);
|
add(revenueContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,13 +72,13 @@ public class StatisticsView extends VerticalLayout {
|
|||||||
|
|
||||||
// Gesamtaufträge
|
// Gesamtaufträge
|
||||||
Div totalOrdersCard = createKpiCard("Gesamtaufträge", "247", "success");
|
Div totalOrdersCard = createKpiCard("Gesamtaufträge", "247", "success");
|
||||||
|
|
||||||
// Offene Aufträge
|
// Offene Aufträge
|
||||||
Div openOrdersCard = createKpiCard("Offene Aufträge", "34", "warning");
|
Div openOrdersCard = createKpiCard("Offene Aufträge", "34", "warning");
|
||||||
|
|
||||||
// Umsatz diesen Monat
|
// Umsatz diesen Monat
|
||||||
Div revenueCard = createKpiCard("Umsatz (Monat)", "€ 24.500", "primary");
|
Div revenueCard = createKpiCard("Umsatz (Monat)", "€ 24.500", "primary");
|
||||||
|
|
||||||
// Neue Kunden
|
// Neue Kunden
|
||||||
Div newCustomersCard = createKpiCard("Neue Kunden", "12", "success");
|
Div newCustomersCard = createKpiCard("Neue Kunden", "12", "success");
|
||||||
|
|
||||||
@@ -89,23 +89,17 @@ public class StatisticsView extends VerticalLayout {
|
|||||||
private Div createKpiCard(String title, String value, String theme) {
|
private Div createKpiCard(String title, String value, String theme) {
|
||||||
Div card = new Div();
|
Div card = new Div();
|
||||||
card.addClassName("kpi-card");
|
card.addClassName("kpi-card");
|
||||||
card.getStyle()
|
card.getStyle().set("background", "var(--lumo-base-color)")
|
||||||
.set("background", "var(--lumo-base-color)")
|
.set("border", "1px solid var(--lumo-contrast-10pct)")
|
||||||
.set("border", "1px solid var(--lumo-contrast-10pct)")
|
.set("border-radius", "var(--lumo-border-radius-m)").set("padding", "var(--lumo-space-m)")
|
||||||
.set("border-radius", "var(--lumo-border-radius-m)")
|
.set("text-align", "center").set("box-shadow", "var(--lumo-box-shadow-xs)").set("min-width", "150px");
|
||||||
.set("padding", "var(--lumo-space-m)")
|
|
||||||
.set("text-align", "center")
|
|
||||||
.set("box-shadow", "var(--lumo-box-shadow-xs)")
|
|
||||||
.set("min-width", "150px");
|
|
||||||
|
|
||||||
H3 titleElement = new H3(title);
|
H3 titleElement = new H3(title);
|
||||||
titleElement.getStyle().set("margin", "0 0 var(--lumo-space-s) 0").set("font-size", "var(--lumo-font-size-s)");
|
titleElement.getStyle().set("margin", "0 0 var(--lumo-space-s) 0").set("font-size", "var(--lumo-font-size-s)");
|
||||||
|
|
||||||
Span valueElement = new Span(value);
|
Span valueElement = new Span(value);
|
||||||
valueElement.getStyle()
|
valueElement.getStyle().set("font-size", "var(--lumo-font-size-xl)").set("font-weight", "bold").set("color",
|
||||||
.set("font-size", "var(--lumo-font-size-xl)")
|
getThemeColor(theme));
|
||||||
.set("font-weight", "bold")
|
|
||||||
.set("color", getThemeColor(theme));
|
|
||||||
|
|
||||||
card.add(titleElement, valueElement);
|
card.add(titleElement, valueElement);
|
||||||
return card;
|
return card;
|
||||||
@@ -113,197 +107,197 @@ public class StatisticsView extends VerticalLayout {
|
|||||||
|
|
||||||
private String getThemeColor(String theme) {
|
private String getThemeColor(String theme) {
|
||||||
return switch (theme) {
|
return switch (theme) {
|
||||||
case "success" -> "var(--lumo-success-color)";
|
case "success" -> "var(--lumo-success-color)";
|
||||||
case "warning" -> "var(--lumo-warning-color)";
|
case "warning" -> "var(--lumo-warning-color)";
|
||||||
case "error" -> "var(--lumo-error-color)";
|
case "error" -> "var(--lumo-error-color)";
|
||||||
default -> "var(--lumo-primary-color)";
|
default -> "var(--lumo-primary-color)";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Div createMonthlyOrdersChart() {
|
private Div createMonthlyOrdersChart() {
|
||||||
Div chartContainer = new Div();
|
Div chartContainer = new Div();
|
||||||
chartContainer.setId("monthlyOrdersChart");
|
chartContainer.setId("monthlyOrdersChart");
|
||||||
|
|
||||||
String canvasHtml = "<canvas id='monthlyOrdersCanvas' style='width: 100%; height: 100%;'></canvas>";
|
String canvasHtml = "<canvas id='monthlyOrdersCanvas' style='width: 100%; height: 100%;'></canvas>";
|
||||||
Html canvas = new Html(canvasHtml);
|
Html canvas = new Html(canvasHtml);
|
||||||
chartContainer.add(canvas);
|
chartContainer.add(canvas);
|
||||||
|
|
||||||
String script = """
|
String script = """
|
||||||
<script>
|
<script>
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
const ctx = document.getElementById('monthlyOrdersCanvas');
|
const ctx = document.getElementById('monthlyOrdersCanvas');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
new Chart(ctx, {
|
new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
|
labels: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: '2024',
|
label: '2024',
|
||||||
data: [15, 18, 22, 28, 32, 35, 42, 38, 41, 35, 28, 25],
|
data: [15, 18, 22, 28, 32, 35, 42, 38, 41, 35, 28, 25],
|
||||||
borderColor: 'rgb(75, 192, 192)',
|
borderColor: 'rgb(75, 192, 192)',
|
||||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
tension: 0.1
|
tension: 0.1
|
||||||
}, {
|
}, {
|
||||||
label: '2023',
|
label: '2023',
|
||||||
data: [12, 15, 18, 25, 28, 30, 35, 32, 36, 30, 25, 22],
|
data: [12, 15, 18, 25, 28, 30, 35, 32, 36, 30, 25, 22],
|
||||||
borderColor: 'rgb(135, 206, 235)',
|
borderColor: 'rgb(135, 206, 235)',
|
||||||
backgroundColor: 'rgba(135, 206, 235, 0.2)',
|
backgroundColor: 'rgba(135, 206, 235, 0.2)',
|
||||||
tension: 0.1
|
tension: 0.1
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'Aufträge pro Monat'
|
text: 'Aufträge pro Monat'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'Anzahl Aufträge'
|
text: 'Anzahl Aufträge'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
});
|
}, 100);
|
||||||
}
|
</script>
|
||||||
}, 100);
|
""";
|
||||||
</script>
|
|
||||||
""";
|
|
||||||
|
|
||||||
Html scriptElement = new Html(script);
|
Html scriptElement = new Html(script);
|
||||||
chartContainer.add(scriptElement);
|
chartContainer.add(scriptElement);
|
||||||
|
|
||||||
return chartContainer;
|
return chartContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Div createStatusPieChart() {
|
private Div createStatusPieChart() {
|
||||||
Div chartContainer = new Div();
|
Div chartContainer = new Div();
|
||||||
chartContainer.setId("statusPieChart");
|
chartContainer.setId("statusPieChart");
|
||||||
|
|
||||||
String canvasHtml = "<canvas id='statusPieCanvas' style='width: 100%; height: 100%;'></canvas>";
|
String canvasHtml = "<canvas id='statusPieCanvas' style='width: 100%; height: 100%;'></canvas>";
|
||||||
Html canvas = new Html(canvasHtml);
|
Html canvas = new Html(canvasHtml);
|
||||||
chartContainer.add(canvas);
|
chartContainer.add(canvas);
|
||||||
|
|
||||||
String script = """
|
String script = """
|
||||||
<script>
|
<script>
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
const ctx = document.getElementById('statusPieCanvas');
|
const ctx = document.getElementById('statusPieCanvas');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
new Chart(ctx, {
|
new Chart(ctx, {
|
||||||
type: 'doughnut',
|
type: 'doughnut',
|
||||||
data: {
|
data: {
|
||||||
labels: ['Abgeschlossen', 'In Bearbeitung', 'Geplant', 'Storniert'],
|
labels: ['Abgeschlossen', 'In Bearbeitung', 'Geplant', 'Storniert'],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
data: [156, 34, 28, 12],
|
data: [156, 34, 28, 12],
|
||||||
backgroundColor: [
|
backgroundColor: [
|
||||||
'rgba(54, 162, 235, 0.8)',
|
'rgba(54, 162, 235, 0.8)',
|
||||||
'rgba(255, 206, 86, 0.8)',
|
'rgba(255, 206, 86, 0.8)',
|
||||||
'rgba(75, 192, 192, 0.8)',
|
'rgba(75, 192, 192, 0.8)',
|
||||||
'rgba(255, 99, 132, 0.8)'
|
'rgba(255, 99, 132, 0.8)'
|
||||||
],
|
],
|
||||||
borderColor: [
|
borderColor: [
|
||||||
'rgba(54, 162, 235, 1)',
|
'rgba(54, 162, 235, 1)',
|
||||||
'rgba(255, 206, 86, 1)',
|
'rgba(255, 206, 86, 1)',
|
||||||
'rgba(75, 192, 192, 1)',
|
'rgba(75, 192, 192, 1)',
|
||||||
'rgba(255, 99, 132, 1)'
|
'rgba(255, 99, 132, 1)'
|
||||||
],
|
],
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
}]
|
}]
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Aufträge nach Status'
|
|
||||||
},
|
},
|
||||||
legend: {
|
options: {
|
||||||
position: 'right'
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Aufträge nach Status'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'right'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
});
|
}, 100);
|
||||||
}
|
</script>
|
||||||
}, 100);
|
""";
|
||||||
</script>
|
|
||||||
""";
|
|
||||||
|
|
||||||
Html scriptElement = new Html(script);
|
Html scriptElement = new Html(script);
|
||||||
chartContainer.add(scriptElement);
|
chartContainer.add(scriptElement);
|
||||||
|
|
||||||
return chartContainer;
|
return chartContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Div createRevenueByCustomerChart() {
|
private Div createRevenueByCustomerChart() {
|
||||||
Div chartContainer = new Div();
|
Div chartContainer = new Div();
|
||||||
chartContainer.setId("revenueByCustomerChart");
|
chartContainer.setId("revenueByCustomerChart");
|
||||||
|
|
||||||
String canvasHtml = "<canvas id='revenueByCustomerCanvas' style='width: 100%; height: 100%;'></canvas>";
|
String canvasHtml = "<canvas id='revenueByCustomerCanvas' style='width: 100%; height: 100%;'></canvas>";
|
||||||
Html canvas = new Html(canvasHtml);
|
Html canvas = new Html(canvasHtml);
|
||||||
chartContainer.add(canvas);
|
chartContainer.add(canvas);
|
||||||
|
|
||||||
String script = """
|
String script = """
|
||||||
<script>
|
<script>
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
const ctx = document.getElementById('revenueByCustomerCanvas');
|
const ctx = document.getElementById('revenueByCustomerCanvas');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
new Chart(ctx, {
|
new Chart(ctx, {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: {
|
data: {
|
||||||
labels: ['Firma A GmbH', 'Logistics B', 'Transport C', 'Spediteur D', 'Handel E',
|
labels: ['Firma A GmbH', 'Logistics B', 'Transport C', 'Spediteur D', 'Handel E',
|
||||||
'Industrie F', 'Service G', 'Vertrieb H', 'Export I', 'Import J'],
|
'Industrie F', 'Service G', 'Vertrieb H', 'Export I', 'Import J'],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Umsatz (€)',
|
label: 'Umsatz (€)',
|
||||||
data: [8500, 7200, 6800, 5900, 5400, 4800, 4200, 3900, 3500, 3100],
|
data: [8500, 7200, 6800, 5900, 5400, 4800, 4200, 3900, 3500, 3100],
|
||||||
backgroundColor: 'rgba(75, 192, 192, 0.8)',
|
backgroundColor: 'rgba(75, 192, 192, 0.8)',
|
||||||
borderColor: 'rgba(75, 192, 192, 1)',
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
}]
|
}]
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Top 10 Kunden nach Umsatz'
|
|
||||||
},
|
},
|
||||||
legend: {
|
options: {
|
||||||
display: false
|
responsive: true,
|
||||||
}
|
maintainAspectRatio: false,
|
||||||
},
|
plugins: {
|
||||||
scales: {
|
title: {
|
||||||
x: {
|
display: true,
|
||||||
ticks: {
|
text: 'Top 10 Kunden nach Umsatz'
|
||||||
maxRotation: 45,
|
},
|
||||||
minRotation: 45
|
legend: {
|
||||||
}
|
display: false
|
||||||
},
|
}
|
||||||
y: {
|
},
|
||||||
beginAtZero: true,
|
scales: {
|
||||||
title: {
|
x: {
|
||||||
display: true,
|
ticks: {
|
||||||
text: 'Umsatz (€)'
|
maxRotation: 45,
|
||||||
|
minRotation: 45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Umsatz (€)'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
});
|
}, 100);
|
||||||
}
|
</script>
|
||||||
}, 100);
|
""";
|
||||||
</script>
|
|
||||||
""";
|
|
||||||
|
|
||||||
Html scriptElement = new Html(script);
|
Html scriptElement = new Html(script);
|
||||||
chartContainer.add(scriptElement);
|
chartContainer.add(scriptElement);
|
||||||
|
|
||||||
return chartContainer;
|
return chartContainer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,18 +27,18 @@ public class VerwaltungView extends Main {
|
|||||||
|
|
||||||
// Content
|
// Content
|
||||||
VerticalLayout content = new VerticalLayout();
|
VerticalLayout content = new VerticalLayout();
|
||||||
|
|
||||||
H1 title = new H1("Verwaltung");
|
H1 title = new H1("Verwaltung");
|
||||||
title.getStyle().set("color", "var(--lumo-primary-color)");
|
title.getStyle().set("color", "var(--lumo-primary-color)");
|
||||||
|
|
||||||
Paragraph description = new Paragraph("Willkommen im Verwaltungsbereich. Wählen Sie eine Option aus dem Menü.");
|
Paragraph description = new Paragraph("Willkommen im Verwaltungsbereich. Wählen Sie eine Option aus dem Menü.");
|
||||||
description.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
description.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||||
|
|
||||||
content.add(title, description);
|
content.add(title, description);
|
||||||
content.setDefaultHorizontalComponentAlignment(VerticalLayout.Alignment.CENTER);
|
content.setDefaultHorizontalComponentAlignment(VerticalLayout.Alignment.CENTER);
|
||||||
content.setJustifyContentMode(VerticalLayout.JustifyContentMode.CENTER);
|
content.setJustifyContentMode(VerticalLayout.JustifyContentMode.CENTER);
|
||||||
content.setSizeFull();
|
content.setSizeFull();
|
||||||
|
|
||||||
add(content);
|
add(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface AppDeviceRepository extends MongoRepository<AppDevice, ObjectId> {
|
public interface AppDeviceRepository extends MongoRepository<AppDevice, ObjectId> {
|
||||||
|
|
||||||
// Find all devices created by a specific user
|
// Find all devices created by a specific user
|
||||||
List<AppDevice> findByErstelltVon(ObjectId erstelltVon);
|
List<AppDevice> findByErstelltVon(ObjectId erstelltVon);
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import java.util.List;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface AppUserRepository extends MongoRepository<AppUser, ObjectId> {
|
public interface AppUserRepository extends MongoRepository<AppUser, ObjectId> {
|
||||||
|
|
||||||
// Find all AppUsers created by a specific user
|
// Find all AppUsers created by a specific user
|
||||||
List<AppUser> findByErstelltVon(ObjectId erstelltVon);
|
List<AppUser> findByErstelltVon(ObjectId erstelltVon);
|
||||||
|
|
||||||
// Find AppUser by email for login
|
// Find AppUser by email for login
|
||||||
AppUser findByEmail(String email);
|
AppUser findByEmail(String email);
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,3 @@ import java.util.List;
|
|||||||
public interface CargoItemRepository extends MongoRepository<CargoItem, ObjectId> {
|
public interface CargoItemRepository extends MongoRepository<CargoItem, ObjectId> {
|
||||||
List<CargoItem> findByJobId(ObjectId jobId);
|
List<CargoItem> findByJobId(ObjectId jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,26 +14,27 @@ import java.util.List;
|
|||||||
public interface JobHistoryRepository extends MongoRepository<JobHistory, ObjectId> {
|
public interface JobHistoryRepository extends MongoRepository<JobHistory, ObjectId> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all history entries for a specific job, ordered by timestamp descending (newest first)
|
* Find all history entries for a specific job, ordered by timestamp descending
|
||||||
|
* (newest first)
|
||||||
*/
|
*/
|
||||||
List<JobHistory> findByJobIdOrderByTimestampDesc(ObjectId jobId);
|
List<JobHistory> findByJobIdOrderByTimestampDesc(ObjectId jobId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all history entries for a specific job, ordered by timestamp ascending (oldest first)
|
* Find all history entries for a specific job, ordered by timestamp ascending
|
||||||
|
* (oldest first)
|
||||||
*/
|
*/
|
||||||
List<JobHistory> findByJobIdOrderByTimestampAsc(ObjectId jobId);
|
List<JobHistory> findByJobIdOrderByTimestampAsc(ObjectId jobId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find history entries for a job within a specific time range
|
* Find history entries for a job within a specific time range
|
||||||
*/
|
*/
|
||||||
List<JobHistory> findByJobIdAndTimestampBetweenOrderByTimestampDesc(
|
List<JobHistory> findByJobIdAndTimestampBetweenOrderByTimestampDesc(ObjectId jobId, LocalDateTime start,
|
||||||
ObjectId jobId, LocalDateTime start, LocalDateTime end);
|
LocalDateTime end);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find history entries by change type for a specific job
|
* Find history entries by change type for a specific job
|
||||||
*/
|
*/
|
||||||
List<JobHistory> findByJobIdAndChangeTypeOrderByTimestampDesc(
|
List<JobHistory> findByJobIdAndChangeTypeOrderByTimestampDesc(ObjectId jobId, JobHistoryType changeType);
|
||||||
ObjectId jobId, JobHistoryType changeType);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find history entries made by a specific user
|
* Find history entries made by a specific user
|
||||||
|
|||||||
@@ -99,10 +99,8 @@ public interface JobRepository extends MongoRepository<Job, ObjectId> {
|
|||||||
/**
|
/**
|
||||||
* Erweiterte Suche: Zeitraum, Auftragsnummer und Status kombiniert
|
* Erweiterte Suche: Zeitraum, Auftragsnummer und Status kombiniert
|
||||||
*/
|
*/
|
||||||
@Query("{'createdAt': {'$gte': ?0, '$lte': ?1}, 'createdBy': ?2, " +
|
@Query("{'createdAt': {'$gte': ?0, '$lte': ?1}, 'createdBy': ?2, "
|
||||||
"'jobNumber': {'$regex': ?3, '$options': 'i'}, " +
|
+ "'jobNumber': {'$regex': ?3, '$options': 'i'}, " + "'status': {'$in': ?4}}")
|
||||||
"'status': {'$in': ?4}}")
|
List<Job> findWithFilters(LocalDateTime startDate, LocalDateTime endDate, String createdBy, String jobNumberPattern,
|
||||||
List<Job> findWithFilters(LocalDateTime startDate, LocalDateTime endDate,
|
List<JobStatus> statusList);
|
||||||
String createdBy, String jobNumberPattern,
|
|
||||||
List<JobStatus> statusList);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,21 +8,25 @@ import org.springframework.stereotype.Repository;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository interface for Photo entities.
|
* Repository interface for Photo entities. Provides database operations for the
|
||||||
* Provides database operations for the photos collection.
|
* photos collection.
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface PhotoRepository extends MongoRepository<Photo, ObjectId> {
|
public interface PhotoRepository extends MongoRepository<Photo, ObjectId> {
|
||||||
/**
|
/**
|
||||||
* Find all photos associated with a specific task ID.
|
* Find all photos associated with a specific task ID.
|
||||||
* @param taskId The ObjectId of the task
|
*
|
||||||
|
* @param taskId
|
||||||
|
* The ObjectId of the task
|
||||||
* @return List of photos for the task
|
* @return List of photos for the task
|
||||||
*/
|
*/
|
||||||
List<Photo> findByTaskId(ObjectId taskId);
|
List<Photo> findByTaskId(ObjectId taskId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find photos by task ID as string.
|
* Find photos by task ID as string.
|
||||||
* @param taskId The task ID as string
|
*
|
||||||
|
* @param taskId
|
||||||
|
* The task ID as string
|
||||||
* @return List of photos for the task
|
* @return List of photos for the task
|
||||||
*/
|
*/
|
||||||
default List<Photo> findByTaskId(String taskId) {
|
default List<Photo> findByTaskId(String taskId) {
|
||||||
|
|||||||
@@ -15,4 +15,3 @@ public interface TaskRepository extends MongoRepository<BaseTask, ObjectId> {
|
|||||||
return findByJobIdOrderByTaskOrderAsc(jobId);
|
return findByJobIdOrderByTaskOrderAsc(jobId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import java.util.Optional;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends MongoRepository<User, ObjectId> {
|
public interface UserRepository extends MongoRepository<User, ObjectId> {
|
||||||
|
|
||||||
Optional<User> findByEmail(String email);
|
Optional<User> findByEmail(String email);
|
||||||
|
|
||||||
boolean existsByEmail(String email);
|
boolean existsByEmail(String email);
|
||||||
|
|
||||||
void deleteByEmail(String email);
|
void deleteByEmail(String email);
|
||||||
|
|
||||||
Optional<User> findByPasswordCode(String passwordCode);
|
Optional<User> findByPasswordCode(String passwordCode);
|
||||||
|
|||||||
@@ -11,61 +11,60 @@ import java.util.Set;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom UserDetails implementation that holds a reference to the MongoDB User entity.
|
* Custom UserDetails implementation that holds a reference to the MongoDB User
|
||||||
* This allows access to the complete User object from the session without additional database queries.
|
* entity. This allows access to the complete User object from the session
|
||||||
|
* without additional database queries.
|
||||||
*/
|
*/
|
||||||
public class CustomUserPrincipal implements UserDetails {
|
public class CustomUserPrincipal implements UserDetails {
|
||||||
|
|
||||||
private final User user; // MongoDB User entity
|
private final User user; // MongoDB User entity
|
||||||
|
|
||||||
public CustomUserPrincipal(User user) {
|
public CustomUserPrincipal(User user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the complete MongoDB User entity
|
* Get the complete MongoDB User entity
|
||||||
*/
|
*/
|
||||||
public User getUser() {
|
public User getUser() {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return user.getEmail();
|
return user.getEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
return user.getPassword();
|
return user.getPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return user.getIsActivated() == 1;
|
return user.getIsActivated() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
Set<String> roles = user.getRoles();
|
Set<String> roles = user.getRoles();
|
||||||
if (roles != null && !roles.isEmpty()) {
|
if (roles != null && !roles.isEmpty()) {
|
||||||
return roles.stream()
|
return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toList());
|
||||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
// Default role if no roles are set
|
// Default role if no roles are set
|
||||||
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
|
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAccountNonExpired() {
|
public boolean isAccountNonExpired() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAccountNonLocked() {
|
public boolean isAccountNonLocked() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCredentialsNonExpired() {
|
public boolean isCredentialsNonExpired() {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -24,33 +24,20 @@ public class SecurityConfig extends VaadinWebSecurity {
|
|||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
// Konfiguriere zusätzliche öffentliche Endpunkte vor der Basis-Konfiguration
|
// Konfiguriere zusätzliche öffentliche Endpunkte vor der Basis-Konfiguration
|
||||||
http.authorizeHttpRequests(auth -> auth
|
http.authorizeHttpRequests(auth -> auth
|
||||||
// Öffentliche Endpunkte
|
// Öffentliche Endpunkte
|
||||||
.requestMatchers(
|
.requestMatchers(new AntPathRequestMatcher("/"), new AntPathRequestMatcher("/register"),
|
||||||
new AntPathRequestMatcher("/"),
|
new AntPathRequestMatcher("/login"), new AntPathRequestMatcher("/forget-password"),
|
||||||
new AntPathRequestMatcher("/register"),
|
new AntPathRequestMatcher("/forgot-password-request"), new AntPathRequestMatcher("/images/**"),
|
||||||
new AntPathRequestMatcher("/login"),
|
new AntPathRequestMatcher("/icons/**"), new AntPathRequestMatcher("/favicon.ico"),
|
||||||
new AntPathRequestMatcher("/forget-password"),
|
new AntPathRequestMatcher("/robots.txt"), new AntPathRequestMatcher("/manifest.webmanifest"),
|
||||||
new AntPathRequestMatcher("/forgot-password-request"),
|
new AntPathRequestMatcher("/sw.js"), new AntPathRequestMatcher("/offline.html"),
|
||||||
new AntPathRequestMatcher("/images/**"),
|
new AntPathRequestMatcher("/frontend/**"), new AntPathRequestMatcher("/webjars/**"),
|
||||||
new AntPathRequestMatcher("/icons/**"),
|
new AntPathRequestMatcher("/h2-console/**"),
|
||||||
new AntPathRequestMatcher("/favicon.ico"),
|
new AntPathRequestMatcher("/frontend-es5/**", "/frontend-es6/**"))
|
||||||
new AntPathRequestMatcher("/robots.txt"),
|
.permitAll());
|
||||||
new AntPathRequestMatcher("/manifest.webmanifest"),
|
|
||||||
new AntPathRequestMatcher("/sw.js"),
|
|
||||||
new AntPathRequestMatcher("/offline.html"),
|
|
||||||
new AntPathRequestMatcher("/frontend/**"),
|
|
||||||
new AntPathRequestMatcher("/webjars/**"),
|
|
||||||
new AntPathRequestMatcher("/h2-console/**"),
|
|
||||||
new AntPathRequestMatcher("/frontend-es5/**", "/frontend-es6/**")
|
|
||||||
).permitAll()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Standard-CSRF-Konfiguration
|
// Standard-CSRF-Konfiguration
|
||||||
http.csrf(csrf -> csrf
|
http.csrf(csrf -> csrf.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")));
|
||||||
.ignoringRequestMatchers(
|
|
||||||
new AntPathRequestMatcher("/h2-console/**")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delegiere die Basis-Konfiguration an VaadinWebSecurity
|
// Delegiere die Basis-Konfiguration an VaadinWebSecurity
|
||||||
// Dies fügt automatisch .anyRequest().authenticated() hinzu
|
// Dies fügt automatisch .anyRequest().authenticated() hinzu
|
||||||
@@ -60,12 +47,8 @@ public class SecurityConfig extends VaadinWebSecurity {
|
|||||||
setLoginView(http, "/login");
|
setLoginView(http, "/login");
|
||||||
|
|
||||||
// Logout-Konfiguration
|
// Logout-Konfiguration
|
||||||
http.logout(logout -> logout
|
http.logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/").invalidateHttpSession(true)
|
||||||
.logoutUrl("/logout")
|
.deleteCookies("JSESSIONID"));
|
||||||
.logoutSuccessUrl("/")
|
|
||||||
.invalidateHttpSession(true)
|
|
||||||
.deleteCookies("JSESSIONID")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ public class SecurityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUserLoggedIn() {
|
public boolean isUserLoggedIn() {
|
||||||
if (authenticationContext.isAuthenticated()) return true;
|
if (authenticationContext.isAuthenticated())
|
||||||
|
return true;
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
return auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken);
|
return auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken);
|
||||||
}
|
}
|
||||||
@@ -39,8 +40,10 @@ public class SecurityService {
|
|||||||
de.assecutor.votianlt.model.User u = cup.getUser();
|
de.assecutor.votianlt.model.User u = cup.getUser();
|
||||||
if (u != null) {
|
if (u != null) {
|
||||||
String namePart = (nullToEmpty(u.getFirstname()) + " " + nullToEmpty(u.getName())).trim();
|
String namePart = (nullToEmpty(u.getFirstname()) + " " + nullToEmpty(u.getName())).trim();
|
||||||
if (!namePart.isBlank()) return namePart;
|
if (!namePart.isBlank())
|
||||||
if (u.getEmail() != null && !u.getEmail().isBlank()) return u.getEmail();
|
return namePart;
|
||||||
|
if (u.getEmail() != null && !u.getEmail().isBlank())
|
||||||
|
return u.getEmail();
|
||||||
}
|
}
|
||||||
return cup.getUsername();
|
return cup.getUsername();
|
||||||
}
|
}
|
||||||
@@ -53,19 +56,18 @@ public class SecurityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2) Fallback: Vaadin AuthenticationContext
|
// 2) Fallback: Vaadin AuthenticationContext
|
||||||
return getAuthenticatedUser()
|
return getAuthenticatedUser().map(UserDetails::getUsername).orElse("Anonymous");
|
||||||
.map(UserDetails::getUsername)
|
|
||||||
.orElse("Anonymous");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String nullToEmpty(String s) { return s == null ? "" : s; }
|
private String nullToEmpty(String s) {
|
||||||
|
return s == null ? "" : s;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the complete MongoDB User entity from the session
|
* Get the complete MongoDB User entity from the session
|
||||||
*/
|
*/
|
||||||
public de.assecutor.votianlt.model.User getCurrentDatabaseUser() {
|
public de.assecutor.votianlt.model.User getCurrentDatabaseUser() {
|
||||||
return getAuthenticatedUser()
|
return getAuthenticatedUser().filter(userDetails -> userDetails instanceof CustomUserPrincipal)
|
||||||
.filter(userDetails -> userDetails instanceof CustomUserPrincipal)
|
|
||||||
.map(userDetails -> ((CustomUserPrincipal) userDetails).getUser())
|
.map(userDetails -> ((CustomUserPrincipal) userDetails).getUser())
|
||||||
.orElseThrow(() -> new RuntimeException("No user logged in"));
|
.orElseThrow(() -> new RuntimeException("No user logged in"));
|
||||||
}
|
}
|
||||||
@@ -101,9 +103,7 @@ public class SecurityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasRole(String role) {
|
public boolean hasRole(String role) {
|
||||||
return getAuthenticatedUser()
|
return getAuthenticatedUser().map(user -> user.getAuthorities().stream()
|
||||||
.map(user -> user.getAuthorities().stream()
|
.anyMatch(authority -> authority.getAuthority().equals("ROLE_" + role))).orElse(false);
|
||||||
.anyMatch(authority -> authority.getAuthority().equals("ROLE_" + role)))
|
|
||||||
.orElse(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,5 +25,4 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
|||||||
return new CustomUserPrincipal(user);
|
return new CustomUserPrincipal(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package de.assecutor.votianlt.security.totp;
|
|||||||
|
|
||||||
import de.assecutor.votianlt.model.User;
|
import de.assecutor.votianlt.model.User;
|
||||||
import de.assecutor.votianlt.repository.UserRepository;
|
import de.assecutor.votianlt.repository.UserRepository;
|
||||||
import de.assecutor.votianlt.util.MailUtil;
|
import de.assecutor.votianlt.service.EmailService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -13,35 +13,42 @@ import java.util.Optional;
|
|||||||
public class TwoFactorService {
|
public class TwoFactorService {
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final MailUtil mailUtil;
|
private final EmailService emailService;
|
||||||
private final SecureRandom random = new SecureRandom();
|
private final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
public TwoFactorService(UserRepository userRepository, MailUtil mailUtil) {
|
public TwoFactorService(UserRepository userRepository, EmailService emailService) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.mailUtil = mailUtil;
|
this.emailService = emailService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initiateTwoFactorFor(String email) {
|
public void initiateTwoFactorFor(String email) {
|
||||||
Optional<User> userOpt = userRepository.findByEmail(email);
|
Optional<User> userOpt = userRepository.findByEmail(email);
|
||||||
if (userOpt.isEmpty()) return;
|
if (userOpt.isEmpty())
|
||||||
|
return;
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
String code = generateSixDigitCode();
|
String code = generateSixDigitCode();
|
||||||
user.setPasswordCode(code);
|
user.setPasswordCode(code);
|
||||||
user.setPasswordTimestamp(LocalDateTime.now());
|
user.setPasswordTimestamp(LocalDateTime.now());
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
try {
|
try {
|
||||||
mailUtil.sendMail(email, "Ihr Anmeldecode (2FA)", "Ihr 2FA-Code lautet: " + code + "\nGültig für 10 Minuten.");
|
emailService.sendSimpleEmail(email, "Ihr Anmeldecode (2FA)",
|
||||||
} catch (Exception ignored) { }
|
"Ihr 2FA-Code lautet: " + code + "\nGültig für 10 Minuten.");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean verifyTwoFactorCode(String email, String code) {
|
public boolean verifyTwoFactorCode(String email, String code) {
|
||||||
Optional<User> userOpt = userRepository.findByEmail(email);
|
Optional<User> userOpt = userRepository.findByEmail(email);
|
||||||
if (userOpt.isEmpty()) return false;
|
if (userOpt.isEmpty())
|
||||||
|
return false;
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
if (user.getPasswordCode() == null || !user.getPasswordCode().equals(code)) return false;
|
if (user.getPasswordCode() == null || !user.getPasswordCode().equals(code))
|
||||||
if (user.getPasswordTimestamp() == null) return false;
|
return false;
|
||||||
|
if (user.getPasswordTimestamp() == null)
|
||||||
|
return false;
|
||||||
// Gültigkeit 10 Minuten
|
// Gültigkeit 10 Minuten
|
||||||
if (user.getPasswordTimestamp().isBefore(LocalDateTime.now().minusMinutes(10))) return false;
|
if (user.getPasswordTimestamp().isBefore(LocalDateTime.now().minusMinutes(10)))
|
||||||
|
return false;
|
||||||
// Code verbrauchen
|
// Code verbrauchen
|
||||||
user.setPasswordCode(null);
|
user.setPasswordCode(null);
|
||||||
user.setPasswordTimestamp(null);
|
user.setPasswordTimestamp(null);
|
||||||
@@ -54,5 +61,3 @@ public class TwoFactorService {
|
|||||||
return String.format("%06d", n);
|
return String.format("%06d", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,12 @@ public class EmailService {
|
|||||||
|
|
||||||
// Send email
|
// Send email
|
||||||
sendEmail(user, job, taskType, taskId, appUser);
|
sendEmail(user, job, taskType, taskId, appUser);
|
||||||
log.info("Task completion notification sent to {} for job {} task {}", user.getEmail(), job.getJobNumber(), taskId);
|
log.info("Task completion notification sent to {} for job {} task {}", user.getEmail(), job.getJobNumber(),
|
||||||
|
taskId);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to send task completion notification for job {} task {}: {}", jobId, taskId, e.getMessage(), e);
|
log.error("Failed to send task completion notification for job {} task {}: {}", jobId, taskId,
|
||||||
|
e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +84,8 @@ public class EmailService {
|
|||||||
SimpleMailMessage message = new SimpleMailMessage();
|
SimpleMailMessage message = new SimpleMailMessage();
|
||||||
message.setFrom(smtpUsername);
|
message.setFrom(smtpUsername);
|
||||||
message.setTo(user.getEmail());
|
message.setTo(user.getEmail());
|
||||||
message.setSubject("Aufgabe abgeschlossen - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
message.setSubject(
|
||||||
|
"Aufgabe abgeschlossen - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
||||||
|
|
||||||
String fullName = buildFullName(user);
|
String fullName = buildFullName(user);
|
||||||
String appUserName = buildAppUserName(appUser);
|
String appUserName = buildAppUserName(appUser);
|
||||||
@@ -150,18 +153,37 @@ public class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getTaskTypeDisplayName(String taskType) {
|
private String getTaskTypeDisplayName(String taskType) {
|
||||||
if (taskType == null) return "Unbekannte Aufgabe";
|
if (taskType == null)
|
||||||
|
return "Unbekannte Aufgabe";
|
||||||
|
|
||||||
return switch (taskType.toUpperCase()) {
|
return switch (taskType.toUpperCase()) {
|
||||||
case "PHOTO" -> "Foto-Aufgabe";
|
case "PHOTO" -> "Foto-Aufgabe";
|
||||||
case "SIGNATURE" -> "Unterschrift";
|
case "SIGNATURE" -> "Unterschrift";
|
||||||
case "BARCODE" -> "Barcode scannen";
|
case "BARCODE" -> "Barcode scannen";
|
||||||
case "CONFIRMATION" -> "Bestätigung";
|
case "CONFIRMATION" -> "Bestätigung";
|
||||||
case "TODO_LIST" -> "Checkliste";
|
case "TODO_LIST" -> "Checkliste";
|
||||||
default -> taskType;
|
default -> taskType;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildRouteString(Job job) {
|
||||||
|
if (job.getPickupCity() == null && job.getDeliveryCity() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder route = new StringBuilder();
|
||||||
|
if (job.getPickupCity() != null) {
|
||||||
|
route.append(job.getPickupCity());
|
||||||
|
}
|
||||||
|
if (job.getPickupCity() != null && job.getDeliveryCity() != null) {
|
||||||
|
route.append(" → ");
|
||||||
|
}
|
||||||
|
if (job.getDeliveryCity() != null) {
|
||||||
|
route.append(job.getDeliveryCity());
|
||||||
|
}
|
||||||
|
return route.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public void checkAndSendJobCompletionNotification(ObjectId jobId, String completedBy) {
|
public void checkAndSendJobCompletionNotification(ObjectId jobId, String completedBy) {
|
||||||
try {
|
try {
|
||||||
// Check if all tasks for this job are completed
|
// Check if all tasks for this job are completed
|
||||||
@@ -174,7 +196,8 @@ public class EmailService {
|
|||||||
boolean allCompleted = allTasks.stream().allMatch(task -> task.isCompleted());
|
boolean allCompleted = allTasks.stream().allMatch(task -> task.isCompleted());
|
||||||
|
|
||||||
if (allCompleted) {
|
if (allCompleted) {
|
||||||
log.info("All tasks completed for job {}, updating job status and sending completion notification", jobId);
|
log.info("All tasks completed for job {}, updating job status and sending completion notification",
|
||||||
|
jobId);
|
||||||
|
|
||||||
// Update job status to COMPLETED
|
// Update job status to COMPLETED
|
||||||
updateJobStatusToCompleted(jobId);
|
updateJobStatusToCompleted(jobId);
|
||||||
@@ -240,7 +263,8 @@ public class EmailService {
|
|||||||
SimpleMailMessage message = new SimpleMailMessage();
|
SimpleMailMessage message = new SimpleMailMessage();
|
||||||
message.setFrom(smtpUsername);
|
message.setFrom(smtpUsername);
|
||||||
message.setTo(user.getEmail());
|
message.setTo(user.getEmail());
|
||||||
message.setSubject("Job abgeschlossen - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
message.setSubject(
|
||||||
|
"Job abgeschlossen - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
||||||
|
|
||||||
String fullName = buildFullName(user);
|
String fullName = buildFullName(user);
|
||||||
String appUserName = buildAppUserName(appUser);
|
String appUserName = buildAppUserName(appUser);
|
||||||
@@ -299,8 +323,8 @@ public class EmailService {
|
|||||||
job.setUpdatedAt(java.time.LocalDateTime.now());
|
job.setUpdatedAt(java.time.LocalDateTime.now());
|
||||||
jobRepository.save(job);
|
jobRepository.save(job);
|
||||||
|
|
||||||
log.info("Job status updated from {} to COMPLETED for job {}",
|
log.info("Job status updated from {} to COMPLETED for job {}", oldStatus != null ? oldStatus : "null",
|
||||||
oldStatus != null ? oldStatus : "null", job.getJobNumber());
|
job.getJobNumber());
|
||||||
} else {
|
} else {
|
||||||
log.debug("Job {} already has COMPLETED status", job.getJobNumber());
|
log.debug("Job {} already has COMPLETED status", job.getJobNumber());
|
||||||
}
|
}
|
||||||
@@ -354,7 +378,8 @@ public class EmailService {
|
|||||||
SimpleMailMessage message = new SimpleMailMessage();
|
SimpleMailMessage message = new SimpleMailMessage();
|
||||||
message.setFrom(smtpUsername);
|
message.setFrom(smtpUsername);
|
||||||
message.setTo(user.getEmail());
|
message.setTo(user.getEmail());
|
||||||
message.setSubject("Neuer Job erstellt - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
message.setSubject(
|
||||||
|
"Neuer Job erstellt - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
||||||
|
|
||||||
String fullName = buildFullName(user);
|
String fullName = buildFullName(user);
|
||||||
|
|
||||||
@@ -389,14 +414,18 @@ public class EmailService {
|
|||||||
body.append("Anzahl Aufgaben: ").append(taskCount).append("\n");
|
body.append("Anzahl Aufgaben: ").append(taskCount).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
body.append("Status: ").append(job.getStatus() != null ? job.getStatus().getDisplayName() : "Unbekannt").append("\n");
|
body.append("Status: ").append(job.getStatus() != null ? job.getStatus().getDisplayName() : "Unbekannt")
|
||||||
|
.append("\n");
|
||||||
|
|
||||||
if (job.getRemark() != null && !job.getRemark().isBlank()) {
|
if (job.getRemark() != null && !job.getRemark().isBlank()) {
|
||||||
body.append("Bemerkung: ").append(job.getRemark()).append("\n");
|
body.append("Bemerkung: ").append(job.getRemark()).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
body.append("Erstellt am: ").append(job.getCreatedAt() != null ?
|
body.append("Erstellt am: ")
|
||||||
job.getCreatedAt().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")) : "Unbekannt").append("\n\n");
|
.append(job.getCreatedAt() != null
|
||||||
|
? job.getCreatedAt().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
|
||||||
|
: "Unbekannt")
|
||||||
|
.append("\n\n");
|
||||||
|
|
||||||
body.append("Der Job ist nun im System verfügbar und kann bearbeitet werden.\n\n");
|
body.append("Der Job ist nun im System verfügbar und kann bearbeitet werden.\n\n");
|
||||||
body.append("Mit freundlichen Grüßen,\n");
|
body.append("Mit freundlichen Grüßen,\n");
|
||||||
@@ -405,4 +434,24 @@ public class EmailService {
|
|||||||
message.setText(body.toString());
|
message.setText(body.toString());
|
||||||
mailSender.send(message);
|
mailSender.send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a simple text email
|
||||||
|
*/
|
||||||
|
public void sendSimpleEmail(String to, String subject, String body) {
|
||||||
|
try {
|
||||||
|
SimpleMailMessage message = new SimpleMailMessage();
|
||||||
|
message.setFrom(smtpUsername);
|
||||||
|
message.setTo(to);
|
||||||
|
message.setSubject(subject);
|
||||||
|
message.setText(body);
|
||||||
|
|
||||||
|
mailSender.send(message);
|
||||||
|
log.debug("Simple email sent to {} with subject: {}", to, subject);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to send simple email to {} with subject '{}': {}", to, subject, e.getMessage(), e);
|
||||||
|
throw new RuntimeException("Failed to send email: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,15 +29,9 @@ public class JobHistoryService {
|
|||||||
*/
|
*/
|
||||||
public void logJobCreation(Job job, String createdBy) {
|
public void logJobCreation(Job job, String createdBy) {
|
||||||
try {
|
try {
|
||||||
JobHistory history = new JobHistory(
|
JobHistory history = new JobHistory(job.getId(), "Job erstellt",
|
||||||
job.getId(),
|
"Neuer Job wurde erstellt: " + (job.getJobNumber() != null ? job.getJobNumber() : "Ohne Nummer"),
|
||||||
"Job erstellt",
|
createdBy, JobHistoryType.CREATE, null, "Job erstellt");
|
||||||
"Neuer Job wurde erstellt: " + (job.getJobNumber() != null ? job.getJobNumber() : "Ohne Nummer"),
|
|
||||||
createdBy,
|
|
||||||
JobHistoryType.CREATE,
|
|
||||||
null,
|
|
||||||
"Job erstellt"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (job.getDeliveryCompany() != null) {
|
if (job.getDeliveryCompany() != null) {
|
||||||
history.setDetails("Kunde: " + job.getDeliveryCompany());
|
history.setDetails("Kunde: " + job.getDeliveryCompany());
|
||||||
@@ -55,19 +49,12 @@ public class JobHistoryService {
|
|||||||
*/
|
*/
|
||||||
public void logStatusChange(Job job, JobStatus oldStatus, JobStatus newStatus, String changedBy) {
|
public void logStatusChange(Job job, JobStatus oldStatus, JobStatus newStatus, String changedBy) {
|
||||||
try {
|
try {
|
||||||
String description = String.format("Status geändert von %s zu %s",
|
String description = String.format("Status geändert von %s zu %s", formatStatus(oldStatus),
|
||||||
formatStatus(oldStatus),
|
formatStatus(newStatus));
|
||||||
formatStatus(newStatus));
|
|
||||||
|
|
||||||
JobHistory history = new JobHistory(
|
JobHistory history = new JobHistory(job.getId(), "Status-Änderung", description, changedBy,
|
||||||
job.getId(),
|
JobHistoryType.STATUS_CHANGE, oldStatus != null ? oldStatus.toString() : null,
|
||||||
"Status-Änderung",
|
newStatus != null ? newStatus.toString() : null);
|
||||||
description,
|
|
||||||
changedBy,
|
|
||||||
JobHistoryType.STATUS_CHANGE,
|
|
||||||
oldStatus != null ? oldStatus.toString() : null,
|
|
||||||
newStatus != null ? newStatus.toString() : null
|
|
||||||
);
|
|
||||||
|
|
||||||
jobHistoryRepository.save(history);
|
jobHistoryRepository.save(history);
|
||||||
log.debug("Status change logged for job {}: {} -> {}", job.getIdAsString(), oldStatus, newStatus);
|
log.debug("Status change logged for job {}: {} -> {}", job.getIdAsString(), oldStatus, newStatus);
|
||||||
@@ -83,15 +70,9 @@ public class JobHistoryService {
|
|||||||
try {
|
try {
|
||||||
String description = generateUpdateDescription(oldJob, newJob);
|
String description = generateUpdateDescription(oldJob, newJob);
|
||||||
|
|
||||||
JobHistory history = new JobHistory(
|
JobHistory history = new JobHistory(newJob.getId(), reason != null ? reason : "Job aktualisiert",
|
||||||
newJob.getId(),
|
description, changedBy, JobHistoryType.UPDATE, serializeJobForComparison(oldJob),
|
||||||
reason != null ? reason : "Job aktualisiert",
|
serializeJobForComparison(newJob));
|
||||||
description,
|
|
||||||
changedBy,
|
|
||||||
JobHistoryType.UPDATE,
|
|
||||||
serializeJobForComparison(oldJob),
|
|
||||||
serializeJobForComparison(newJob)
|
|
||||||
);
|
|
||||||
|
|
||||||
jobHistoryRepository.save(history);
|
jobHistoryRepository.save(history);
|
||||||
log.debug("Job update logged for job {}", newJob.getIdAsString());
|
log.debug("Job update logged for job {}", newJob.getIdAsString());
|
||||||
@@ -111,7 +92,7 @@ public class JobHistoryService {
|
|||||||
* Log task completion with detailed information and extraData
|
* Log task completion with detailed information and extraData
|
||||||
*/
|
*/
|
||||||
public void logTaskCompletion(ObjectId jobId, String taskType, String taskId, String completedBy,
|
public void logTaskCompletion(ObjectId jobId, String taskType, String taskId, String completedBy,
|
||||||
String taskDisplayName, String extraDataSummary) {
|
String taskDisplayName, String extraDataSummary) {
|
||||||
try {
|
try {
|
||||||
String taskName = taskDisplayName != null ? taskDisplayName : taskType;
|
String taskName = taskDisplayName != null ? taskDisplayName : taskType;
|
||||||
String description = String.format("Aufgabe abgeschlossen: %s", taskName);
|
String description = String.format("Aufgabe abgeschlossen: %s", taskName);
|
||||||
@@ -120,15 +101,8 @@ public class JobHistoryService {
|
|||||||
description += " - " + extraDataSummary;
|
description += " - " + extraDataSummary;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobHistory history = new JobHistory(
|
JobHistory history = new JobHistory(jobId, "Aufgabe abgeschlossen", description, completedBy,
|
||||||
jobId,
|
JobHistoryType.TASK_COMPLETED, "In Bearbeitung", "Abgeschlossen");
|
||||||
"Aufgabe abgeschlossen",
|
|
||||||
description,
|
|
||||||
completedBy,
|
|
||||||
JobHistoryType.TASK_COMPLETED,
|
|
||||||
"In Bearbeitung",
|
|
||||||
"Abgeschlossen"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Detaillierte Informationen in details speichern
|
// Detaillierte Informationen in details speichern
|
||||||
StringBuilder details = new StringBuilder();
|
StringBuilder details = new StringBuilder();
|
||||||
@@ -165,15 +139,8 @@ public class JobHistoryService {
|
|||||||
description = String.format("Job-Zuweisung geändert von %s zu %s", oldAssignee, newAssignee);
|
description = String.format("Job-Zuweisung geändert von %s zu %s", oldAssignee, newAssignee);
|
||||||
}
|
}
|
||||||
|
|
||||||
JobHistory history = new JobHistory(
|
JobHistory history = new JobHistory(job.getId(), "Zuweisung geändert", description, changedBy,
|
||||||
job.getId(),
|
JobHistoryType.ASSIGNMENT, oldAssignee, newAssignee);
|
||||||
"Zuweisung geändert",
|
|
||||||
description,
|
|
||||||
changedBy,
|
|
||||||
JobHistoryType.ASSIGNMENT,
|
|
||||||
oldAssignee,
|
|
||||||
newAssignee
|
|
||||||
);
|
|
||||||
|
|
||||||
jobHistoryRepository.save(history);
|
jobHistoryRepository.save(history);
|
||||||
log.debug("Job assignment logged for job {}", job.getIdAsString());
|
log.debug("Job assignment logged for job {}", job.getIdAsString());
|
||||||
@@ -186,7 +153,7 @@ public class JobHistoryService {
|
|||||||
* Log custom event
|
* Log custom event
|
||||||
*/
|
*/
|
||||||
public void logCustomEvent(ObjectId jobId, String reason, String description, String changedBy,
|
public void logCustomEvent(ObjectId jobId, String reason, String description, String changedBy,
|
||||||
JobHistoryType type) {
|
JobHistoryType type) {
|
||||||
try {
|
try {
|
||||||
JobHistory history = new JobHistory(jobId, reason, description, changedBy, type, null, null);
|
JobHistory history = new JobHistory(jobId, reason, description, changedBy, type, null, null);
|
||||||
jobHistoryRepository.save(history);
|
jobHistoryRepository.save(history);
|
||||||
@@ -213,18 +180,19 @@ public class JobHistoryService {
|
|||||||
// Helper methods
|
// Helper methods
|
||||||
|
|
||||||
private String formatStatus(JobStatus status) {
|
private String formatStatus(JobStatus status) {
|
||||||
if (status == null) return "Unbekannt";
|
if (status == null)
|
||||||
|
return "Unbekannt";
|
||||||
|
|
||||||
return switch (status) {
|
return switch (status) {
|
||||||
case CREATED -> "Erstellt";
|
case CREATED -> "Erstellt";
|
||||||
case IN_PROGRESS -> "In Bearbeitung";
|
case IN_PROGRESS -> "In Bearbeitung";
|
||||||
case PICKUP_SCHEDULED -> "Abholung geplant";
|
case PICKUP_SCHEDULED -> "Abholung geplant";
|
||||||
case PICKED_UP -> "Abgeholt";
|
case PICKED_UP -> "Abgeholt";
|
||||||
case IN_TRANSIT -> "Unterwegs";
|
case IN_TRANSIT -> "Unterwegs";
|
||||||
case DELIVERED -> "Zugestellt";
|
case DELIVERED -> "Zugestellt";
|
||||||
case COMPLETED -> "Abgeschlossen";
|
case COMPLETED -> "Abgeschlossen";
|
||||||
case CANCELLED -> "Storniert";
|
case CANCELLED -> "Storniert";
|
||||||
default -> status.toString();
|
default -> status.toString();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,15 +211,17 @@ public class JobHistoryService {
|
|||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!equals(oldJob.getPickupCity(), newJob.getPickupCity()) ||
|
if (!equals(oldJob.getPickupCity(), newJob.getPickupCity())
|
||||||
!equals(oldJob.getDeliveryCity(), newJob.getDeliveryCity())) {
|
|| !equals(oldJob.getDeliveryCity(), newJob.getDeliveryCity())) {
|
||||||
if (hasChanges) description.append(",");
|
if (hasChanges)
|
||||||
|
description.append(",");
|
||||||
description.append(" - Orte");
|
description.append(" - Orte");
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!equals(oldJob.getRemark(), newJob.getRemark())) {
|
if (!equals(oldJob.getRemark(), newJob.getRemark())) {
|
||||||
if (hasChanges) description.append(",");
|
if (hasChanges)
|
||||||
|
description.append(",");
|
||||||
description.append(" - Bemerkung");
|
description.append(" - Bemerkung");
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package de.assecutor.votianlt.util;
|
|
||||||
|
|
||||||
import de.assecutor.votianlt.config.MailConfig;
|
|
||||||
import jakarta.mail.Message;
|
|
||||||
import jakarta.mail.MessagingException;
|
|
||||||
import jakarta.mail.PasswordAuthentication;
|
|
||||||
import jakarta.mail.Session;
|
|
||||||
import jakarta.mail.Transport;
|
|
||||||
import jakarta.mail.internet.InternetAddress;
|
|
||||||
import jakarta.mail.internet.MimeMessage;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class MailUtil {
|
|
||||||
|
|
||||||
private final MailConfig mailConfig;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public MailUtil(MailConfig mailConfig) {
|
|
||||||
this.mailConfig = mailConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMail(String to, String subject, String body) throws MessagingException {
|
|
||||||
// SMTP-Konfiguration aus externalisierter Konfiguration
|
|
||||||
final String username = mailConfig.getUsername();
|
|
||||||
final String password = mailConfig.getPassword();
|
|
||||||
final String host = mailConfig.getHost();
|
|
||||||
final int port = mailConfig.getPort();
|
|
||||||
|
|
||||||
Properties props = new Properties();
|
|
||||||
props.put("mail.smtp.auth", "true");
|
|
||||||
props.put("mail.smtp.starttls.enable", "true");
|
|
||||||
props.put("mail.smtp.starttls.required", "true");
|
|
||||||
props.put("mail.smtp.host", host);
|
|
||||||
props.put("mail.smtp.port", String.valueOf(port));
|
|
||||||
|
|
||||||
Session session = Session.getInstance(props, new jakarta.mail.Authenticator() {
|
|
||||||
@Override
|
|
||||||
protected PasswordAuthentication getPasswordAuthentication() {
|
|
||||||
return new PasswordAuthentication(username, password);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Message message = new MimeMessage(session);
|
|
||||||
message.setFrom(new InternetAddress(username));
|
|
||||||
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
|
|
||||||
message.setSubject(subject);
|
|
||||||
message.setText(body);
|
|
||||||
|
|
||||||
Transport.send(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,8 @@ import com.vaadin.flow.component.applayout.AppLayout;
|
|||||||
|
|
||||||
public class Util {
|
public class Util {
|
||||||
public static void changeDrawerState(boolean drawerState) {
|
public static void changeDrawerState(boolean drawerState) {
|
||||||
AppLayout appLayout = (AppLayout) UI.getCurrent().getChildren()
|
AppLayout appLayout = (AppLayout) UI.getCurrent().getChildren().filter(AppLayout.class::isInstance).findFirst()
|
||||||
.filter(AppLayout.class::isInstance)
|
.orElse(null);
|
||||||
.findFirst().orElse(null);
|
|
||||||
|
|
||||||
if (appLayout != null) {
|
if (appLayout != null) {
|
||||||
appLayout.setDrawerOpened(drawerState);
|
appLayout.setDrawerOpened(drawerState);
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
// Zeroconf removed from project.
|
// Zeroconf removed from project.
|
||||||
// This file intentionally left without any classes to eliminate any zeroconf-related beans or code.
|
// This file intentionally left without any classes to eliminate any
|
||||||
|
// zeroconf-related beans or code.
|
||||||
|
|||||||
Reference in New Issue
Block a user