diff --git a/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java b/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java index e5c8d3b..f09a0ad 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java @@ -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 { 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 { 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 { @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 cargo = cargoItemRepository.findByJobId(jobId); List tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(jobId); @@ -429,12 +460,123 @@ public class JobSummaryView extends Main implements HasUrlParameter { } content.add(new Span(photoInfo)); } + + // Show photos if task is completed + if (task.isCompleted()) { + try { + ObjectId taskId = new ObjectId(task.getIdAsString()); + List photos = photoRepository.findByTaskId(taskId); + + if (!photos.isEmpty()) { + content.add(new Span("")); // Spacer + + // Collect all photos from all Photo entries + List 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 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 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 { 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 { } else { return "Bestätigung erforderlich"; } + } else if (task instanceof BarcodeTask) { + return "Barcode-Scan erforderlich"; } return "Aufgabe offen"; } + private Div createPhotoGallery(List 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", + ""); + + 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", + ""); + + 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(" { + List findByTaskId(ObjectId taskId); } \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/repository/SignatureRepository.java b/src/main/java/de/assecutor/votianlt/repository/SignatureRepository.java index d0a4d10..f0f3635 100644 --- a/src/main/java/de/assecutor/votianlt/repository/SignatureRepository.java +++ b/src/main/java/de/assecutor/votianlt/repository/SignatureRepository.java @@ -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 { + List findByTaskId(ObjectId taskId); } \ No newline at end of file