Erweiterungen

This commit is contained in:
2025-09-14 21:20:02 +02:00
parent 47c663c31a
commit 99355039ed
3 changed files with 337 additions and 4 deletions

View File

@@ -24,11 +24,18 @@ import de.assecutor.votianlt.model.task.TodoListTask;
import de.assecutor.votianlt.model.task.PhotoTask;
import de.assecutor.votianlt.model.task.SignatureTask;
import de.assecutor.votianlt.model.task.ConfirmationTask;
import de.assecutor.votianlt.model.task.BarcodeTask;
import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import de.assecutor.votianlt.repository.CargoItemRepository;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.repository.TaskRepository;
import de.assecutor.votianlt.repository.SignatureRepository;
import de.assecutor.votianlt.repository.BarcodeRepository;
import de.assecutor.votianlt.repository.PhotoRepository;
import de.assecutor.votianlt.model.Signature;
import de.assecutor.votianlt.model.Barcode;
import de.assecutor.votianlt.model.Photo;
import de.assecutor.votianlt.pages.service.AppUserService;
import jakarta.annotation.security.RolesAllowed;
import org.bson.types.ObjectId;
@@ -46,6 +53,9 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
private final JobRepository jobRepository;
private final CargoItemRepository cargoItemRepository;
private final TaskRepository taskRepository;
private final SignatureRepository signatureRepository;
private final BarcodeRepository barcodeRepository;
private final PhotoRepository photoRepository;
private final AppUserService appUserService;
private final VerticalLayout content;
@@ -54,10 +64,16 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
public JobSummaryView(JobRepository jobRepository,
CargoItemRepository cargoItemRepository,
TaskRepository taskRepository,
SignatureRepository signatureRepository,
BarcodeRepository barcodeRepository,
PhotoRepository photoRepository,
AppUserService appUserService) {
this.jobRepository = jobRepository;
this.cargoItemRepository = cargoItemRepository;
this.taskRepository = taskRepository;
this.signatureRepository = signatureRepository;
this.barcodeRepository = barcodeRepository;
this.photoRepository = photoRepository;
this.appUserService = appUserService;
setSizeFull();
@@ -75,12 +91,27 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
@Override
public void setParameter(BeforeEvent event, String parameter) {
if (parameter == null || parameter.isBlank()) return;
content.removeAll();
if (parameter == null || parameter.isBlank()) {
content.add(new Span("Fehler: Keine Job-ID angegeben"));
return;
}
ObjectId jobId;
try { jobId = new ObjectId(parameter); } catch (Exception e) { return; }
try {
jobId = new ObjectId(parameter);
} catch (Exception e) {
content.add(new Span("Fehler: Ungültige Job-ID Format: " + parameter));
return;
}
Job job = jobRepository.findById(jobId).orElse(null);
if (job == null) return;
if (job == null) {
content.add(new Span("Fehler: Job mit ID " + parameter + " nicht gefunden"));
return;
}
List<CargoItem> cargo = cargoItemRepository.findByJobId(jobId);
List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(jobId);
@@ -429,12 +460,123 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
}
content.add(new Span(photoInfo));
}
// Show photos if task is completed
if (task.isCompleted()) {
try {
ObjectId taskId = new ObjectId(task.getIdAsString());
List<Photo> photos = photoRepository.findByTaskId(taskId);
if (!photos.isEmpty()) {
content.add(new Span("")); // Spacer
// Collect all photos from all Photo entries
List<String> allPhotos = new ArrayList<>();
for (Photo photo : photos) {
if (photo.getPhoto() != null && !photo.getPhoto().isBlank()) {
allPhotos.add(photo.getPhoto());
}
}
if (!allPhotos.isEmpty()) {
content.add(new Span("Aufgenommene Fotos (" + allPhotos.size() + "):"));
// Create photo gallery container
Div photoGallery = createPhotoGallery(allPhotos);
content.add(photoGallery);
}
}
} catch (Exception e) {
// Ignore errors when loading photos
}
}
} else if (task instanceof ConfirmationTask confirmationTask) {
if (confirmationTask.getButtonText() != null && !confirmationTask.getButtonText().isBlank()) {
content.add(new Span("Button-Text: " + confirmationTask.getButtonText()));
}
} else if (task instanceof SignatureTask) {
} else if (task instanceof SignatureTask signatureTask) {
content.add(new Span("Unterschrift erforderlich"));
// Show signature if task is completed
if (task.isCompleted()) {
try {
ObjectId taskId = new ObjectId(task.getIdAsString());
List<Signature> signatures = signatureRepository.findByTaskId(taskId);
if (!signatures.isEmpty()) {
content.add(new Span("")); // Spacer
content.add(new Span("Gespeicherte Unterschrift:"));
// Display the latest signature (assuming one signature per task)
Signature signature = signatures.get(signatures.size() - 1);
String svgContent = signature.getSignatureSvg();
if (svgContent != null && !svgContent.isBlank()) {
// Create a div to hold the SVG
Div svgContainer = new Div();
svgContainer.getStyle()
.set("border", "1px solid var(--lumo-contrast-20pct)")
.set("border-radius", "var(--lumo-border-radius-m)")
.set("padding", "var(--lumo-space-s)")
.set("background-color", "white")
.set("width", "100%")
.set("max-width", "450px")
.set("overflow", "hidden")
.set("display", "flex")
.set("align-items", "center")
.set("justify-content", "center");
// Process SVG to make it responsive
String responsiveSvg = makeResponsiveSvg(svgContent);
svgContainer.getElement().setProperty("innerHTML", responsiveSvg);
content.add(svgContainer);
}
}
} catch (Exception e) {
// Ignore errors when loading signature
}
}
} else if (task instanceof BarcodeTask barcodeTask) {
content.add(new Span("Barcode-Scan erforderlich"));
// Show barcodes if task is completed
if (task.isCompleted()) {
try {
ObjectId taskId = new ObjectId(task.getIdAsString());
List<Barcode> barcodes = barcodeRepository.findByTaskId(taskId);
if (!barcodes.isEmpty()) {
content.add(new Span("")); // Spacer
content.add(new Span("Gescannte Barcodes (" + barcodes.size() + "):"));
// Display all scanned barcodes
for (int i = 0; i < barcodes.size(); i++) {
Barcode barcode = barcodes.get(i);
String barcodeValue = barcode.getBarcode();
if (barcodeValue != null && !barcodeValue.isBlank()) {
// Create a styled container for each barcode
Div barcodeContainer = new Div();
barcodeContainer.getStyle()
.set("border", "1px solid var(--lumo-contrast-20pct)")
.set("border-radius", "var(--lumo-border-radius-s)")
.set("padding", "var(--lumo-space-s)")
.set("margin", "var(--lumo-space-xs) 0")
.set("background-color", "var(--lumo-contrast-5pct)")
.set("font-family", "monospace")
.set("font-size", "var(--lumo-font-size-s)")
.set("word-break", "break-all");
Span barcodeSpan = new Span((i + 1) + ". " + barcodeValue);
barcodeContainer.add(barcodeSpan);
content.add(barcodeContainer);
}
}
}
} catch (Exception e) {
// Ignore errors when loading barcodes
}
}
}
}
@@ -540,6 +682,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
return new Icon(VaadinIcon.EDIT);
} else if (task instanceof ConfirmationTask) {
return new Icon(VaadinIcon.CHECK_CIRCLE);
} else if (task instanceof BarcodeTask) {
return new Icon(VaadinIcon.BARCODE);
} else {
return new Icon(VaadinIcon.TASKS);
}
@@ -571,11 +715,194 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
} else {
return "Bestätigung erforderlich";
}
} else if (task instanceof BarcodeTask) {
return "Barcode-Scan erforderlich";
}
return "Aufgabe offen";
}
private Div createPhotoGallery(List<String> photos) {
Div galleryContainer = new Div();
galleryContainer.getStyle()
.set("border", "1px solid var(--lumo-contrast-20pct)")
.set("border-radius", "var(--lumo-border-radius-m)")
.set("padding", "var(--lumo-space-m)")
.set("background-color", "white")
.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) {
// Single photo - no navigation needed
Div photoContainer = createPhotoContainer(photos.get(0));
photoContainer.getStyle()
.set("flex", "1")
.set("display", "flex")
.set("align-items", "center")
.set("justify-content", "center");
galleryContainer.add(photoContainer);
} else {
// Multiple photos - add navigation
final int[] currentIndex = {0}; // Use array to make it effectively final
// Photo counter
Span photoCounter = new Span((currentIndex[0] + 1) + " / " + photos.size());
photoCounter.getStyle()
.set("position", "absolute")
.set("top", "var(--lumo-space-s)")
.set("right", "var(--lumo-space-s)")
.set("background-color", "rgba(0, 0, 0, 0.6)")
.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
Div photoContainer = createPhotoContainer(photos.get(0));
photoContainer.getStyle()
.set("margin", "0 40px") // Space for buttons
.set("flex", "1")
.set("display", "flex")
.set("align-items", "center")
.set("justify-content", "center");
// Previous button
Button prevButton = new Button(new Icon(VaadinIcon.CHEVRON_LEFT));
prevButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ICON);
prevButton.getStyle()
.set("position", "absolute")
.set("left", "var(--lumo-space-s)")
.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
Button nextButton = new Button(new Icon(VaadinIcon.CHEVRON_RIGHT));
nextButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ICON);
nextButton.getStyle()
.set("position", "absolute")
.set("right", "var(--lumo-space-s)")
.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
prevButton.addClickListener(e -> {
if (currentIndex[0] > 0) {
currentIndex[0]--;
updatePhotoDisplay(photoContainer, photos.get(currentIndex[0]), photoCounter, currentIndex[0] + 1, photos.size());
}
prevButton.setEnabled(currentIndex[0] > 0);
nextButton.setEnabled(currentIndex[0] < photos.size() - 1);
});
nextButton.addClickListener(e -> {
if (currentIndex[0] < photos.size() - 1) {
currentIndex[0]++;
updatePhotoDisplay(photoContainer, photos.get(currentIndex[0]), photoCounter, currentIndex[0] + 1, photos.size());
}
prevButton.setEnabled(currentIndex[0] > 0);
nextButton.setEnabled(currentIndex[0] < photos.size() - 1);
});
// Initial button states
prevButton.setEnabled(false);
nextButton.setEnabled(photos.size() > 1);
galleryContainer.add(photoCounter, photoContainer, prevButton, nextButton);
}
return galleryContainer;
}
private Div createPhotoContainer(String base64Photo) {
Div photoContainer = new Div();
photoContainer.getStyle()
.set("width", "100%")
.set("height", "100%")
.set("display", "flex")
.set("align-items", "center")
.set("justify-content", "center")
.set("overflow", "hidden");
// Create image element
String imgSrc = base64Photo.startsWith("data:") ? base64Photo : "data:image/jpeg;base64," + base64Photo;
photoContainer.getElement().setProperty("innerHTML",
"<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);' />");
return photoContainer;
}
private void updatePhotoDisplay(Div photoContainer, String base64Photo, Span counter, int current, int total) {
String imgSrc = base64Photo.startsWith("data:") ? base64Photo : "data:image/jpeg;base64," + base64Photo;
photoContainer.getElement().setProperty("innerHTML",
"<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);' />");
counter.setText(current + " / " + total);
}
private String makeResponsiveSvg(String svgContent) {
if (svgContent == null || svgContent.isBlank()) {
return svgContent;
}
// Remove any existing width and height attributes and add responsive styling
String responsiveSvg = svgContent
.replaceAll("width\\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
if (!responsiveSvg.contains("viewBox")) {
// Try to extract original dimensions for viewBox
String widthMatch = extractAttribute(svgContent, "width");
String heightMatch = extractAttribute(svgContent, "height");
if (widthMatch != null && heightMatch != null) {
try {
// Clean numbers (remove px, pt, etc.)
String cleanWidth = widthMatch.replaceAll("[^0-9.]", "");
String cleanHeight = heightMatch.replaceAll("[^0-9.]", "");
if (!cleanWidth.isEmpty() && !cleanHeight.isEmpty()) {
responsiveSvg = responsiveSvg.replaceFirst("<svg",
"<svg viewBox=\"0 0 " + cleanWidth + " " + cleanHeight + "\"");
}
} catch (Exception e) {
// Ignore extraction errors
}
}
}
// Add responsive styling
responsiveSvg = responsiveSvg.replaceFirst("<svg",
"<svg style=\"max-width: 100%; max-height: 200px; width: auto; height: auto;\"");
return responsiveSvg;
}
private String extractAttribute(String svg, String attributeName) {
String pattern = attributeName + "\\s*=\\s*[\"']([^\"']*)[\"']";
java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern, java.util.regex.Pattern.CASE_INSENSITIVE);
java.util.regex.Matcher matcher = p.matcher(svg);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
private void resetAllTaskCardHoverStates() {
// Reset hover state for all task cards
for (Div taskCard : taskCards) {

View File

@@ -5,6 +5,9 @@ import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface BarcodeRepository extends MongoRepository<Barcode, ObjectId> {
List<Barcode> findByTaskId(ObjectId taskId);
}

View File

@@ -5,6 +5,9 @@ import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface SignatureRepository extends MongoRepository<Signature, ObjectId> {
List<Signature> findByTaskId(ObjectId taskId);
}