Erweiterungen

This commit is contained in:
2026-02-06 20:06:22 +01:00
parent e50958dbf5
commit 1a866d6083
5 changed files with 82 additions and 10 deletions

View File

@@ -10,6 +10,7 @@ import org.springframework.data.mongodb.core.mapping.Field;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime;
import java.math.BigDecimal; import java.math.BigDecimal;
@Data @Data
@@ -115,9 +116,15 @@ public class Job {
@Field("pickup_date") @Field("pickup_date")
private LocalDate pickupDate; private LocalDate pickupDate;
@Field("pickup_time")
private LocalTime pickupTime;
@Field("delivery_date") @Field("delivery_date")
private LocalDate deliveryDate; private LocalDate deliveryDate;
@Field("delivery_time")
private LocalTime deliveryTime;
// Bemerkung // Bemerkung
@Field("remark") @Field("remark")
private String remark; private String remark;

View File

@@ -112,6 +112,10 @@ public class AddJobView extends Main {
private DatePicker pickupDate; private DatePicker pickupDate;
private DatePicker deliveryDate; private DatePicker deliveryDate;
// Time picker fields for appointments
private TimePicker pickupTime;
private TimePicker deliveryTime;
private com.vaadin.flow.component.tabs.Tab addressesTab; private com.vaadin.flow.component.tabs.Tab addressesTab;
private com.vaadin.flow.component.tabs.Tab appointmentsTab; private com.vaadin.flow.component.tabs.Tab appointmentsTab;
private com.vaadin.flow.component.tabs.Tab cargoTab; private com.vaadin.flow.component.tabs.Tab cargoTab;
@@ -510,7 +514,7 @@ public class AddJobView extends Main {
// Appointment (Pickup) // Appointment (Pickup)
H3 pickupApptTitle = new H3("Termin (Abholung)"); H3 pickupApptTitle = new H3("Termin (Abholung)");
pickupApptTitle.getStyle().set("margin", "0"); pickupApptTitle.getStyle().set("margin", "0");
TimePicker pickupTime = new TimePicker("Uhrzeit"); pickupTime = new TimePicker("Uhrzeit");
HorizontalLayout pickupApptRow = new HorizontalLayout(pickupDate, pickupTime); HorizontalLayout pickupApptRow = new HorizontalLayout(pickupDate, pickupTime);
pickupApptRow.setWidthFull(); pickupApptRow.setWidthFull();
pickupApptRow.setSpacing(true); pickupApptRow.setSpacing(true);
@@ -521,7 +525,7 @@ public class AddJobView extends Main {
// Appointment (Delivery) // Appointment (Delivery)
H3 deliveryApptTitle = new H3("Termin (Lieferung)"); H3 deliveryApptTitle = new H3("Termin (Lieferung)");
deliveryApptTitle.getStyle().set("margin", "0"); deliveryApptTitle.getStyle().set("margin", "0");
TimePicker deliveryTime = new TimePicker("Uhrzeit"); deliveryTime = new TimePicker("Uhrzeit");
HorizontalLayout deliveryApptRow = new HorizontalLayout(deliveryDate, deliveryTime); HorizontalLayout deliveryApptRow = new HorizontalLayout(deliveryDate, deliveryTime);
deliveryApptRow.setWidthFull(); deliveryApptRow.setWidthFull();
deliveryApptRow.setSpacing(true); deliveryApptRow.setSpacing(true);
@@ -843,6 +847,10 @@ public class AddJobView extends Main {
binder.forField(deliveryDate).asRequired("").bind(Job::getDeliveryDate, Job::setDeliveryDate); binder.forField(deliveryDate).asRequired("").bind(Job::getDeliveryDate, Job::setDeliveryDate);
// Bind time picker fields (optional)
binder.bind(pickupTime, Job::getPickupTime, Job::setPickupTime);
binder.bind(deliveryTime, Job::getDeliveryTime, Job::setDeliveryTime);
// Bind customerSelection field with validation // Bind customerSelection field with validation
binder.forField(customerSelection).asRequired("").bind(Job::getCustomerSelection, Job::setCustomerSelection); binder.forField(customerSelection).asRequired("").bind(Job::getCustomerSelection, Job::setCustomerSelection);
@@ -1082,12 +1090,16 @@ public class AddJobView extends Main {
private boolean hasTasksValidationErrors() { private boolean hasTasksValidationErrors() {
for (BaseTask task : tasksState) { for (BaseTask task : tasksState) {
// Check if any ConfirmationTask has an empty description (required field) // Check if any ConfirmationTask has an empty description or buttonText (required fields)
if (task instanceof ConfirmationTask) { if (task instanceof ConfirmationTask confirmationTask) {
String description = task.getDescription(); String description = task.getDescription();
if (description == null || description.trim().isEmpty()) { if (description == null || description.trim().isEmpty()) {
return true; return true;
} }
String buttonText = confirmationTask.getButtonText();
if (buttonText == null || buttonText.trim().isEmpty()) {
return true;
}
} }
// Check if any TodoListTask has at least one non-empty todo item // Check if any TodoListTask has at least one non-empty todo item
if (task instanceof TodoListTask todoListTask) { if (task instanceof TodoListTask todoListTask) {
@@ -1120,7 +1132,9 @@ public class AddJobView extends Main {
// Zusätzliche Felder, die nicht über den Binder gebunden sind, manuell setzen // Zusätzliche Felder, die nicht über den Binder gebunden sind, manuell setzen
job.setPickupDate(pickupDate.getValue()); job.setPickupDate(pickupDate.getValue());
job.setPickupTime(pickupTime.getValue());
job.setDeliveryDate(deliveryDate.getValue()); job.setDeliveryDate(deliveryDate.getValue());
job.setDeliveryTime(deliveryTime.getValue());
if (remarkArea != null) if (remarkArea != null)
job.setRemark(remarkArea.getValue()); job.setRemark(remarkArea.getValue());
@@ -1815,10 +1829,11 @@ public class AddJobView extends Main {
descriptionField.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)"); descriptionField.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
} }
// Button text field // Button text field (required)
TextField buttonTextField = new TextField("Button-Text"); TextField buttonTextField = new TextField("Button-Text");
buttonTextField.setPlaceholder("z.B. 'Bestätigen', 'Abgeschlossen'"); buttonTextField.setPlaceholder("z.B. 'Bestätigen', 'Abgeschlossen'");
buttonTextField.setWidthFull(); buttonTextField.setWidthFull();
buttonTextField.setRequiredIndicatorVisible(true);
ConfirmationTask confirmationTask = (ConfirmationTask) task; ConfirmationTask confirmationTask = (ConfirmationTask) task;
buttonTextField.setValue(confirmationTask.getButtonText() != null ? confirmationTask.getButtonText() : ""); buttonTextField.setValue(confirmationTask.getButtonText() != null ? confirmationTask.getButtonText() : "");
buttonTextField.addValueChangeListener(ev -> { buttonTextField.addValueChangeListener(ev -> {
@@ -1828,7 +1843,23 @@ public class AddJobView extends Main {
((ConfirmationTask) stateTask).setButtonText(ev.getValue()); ((ConfirmationTask) stateTask).setButtonText(ev.getValue());
} }
} }
// Update field styling based on value
boolean isEmpty = ev.getValue() == null || ev.getValue().trim().isEmpty();
if (isEmpty) {
buttonTextField.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
buttonTextField.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
} else {
buttonTextField.getStyle().remove("--vaadin-input-field-background");
buttonTextField.getStyle().remove("--vaadin-input-field-border-color");
}
triggerValidation();
updateTabLabels();
}); });
// Initial styling for empty field
if (confirmationTask.getButtonText() == null || confirmationTask.getButtonText().trim().isEmpty()) {
buttonTextField.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
buttonTextField.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
}
configContainer.add(descriptionField, buttonTextField); configContainer.add(descriptionField, buttonTextField);
break; break;

View File

@@ -180,7 +180,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
topRow.setSpacing(true); topRow.setSpacing(true);
VerticalLayout pickupBox = borderedBox(); VerticalLayout pickupBox = borderedBox();
pickupBox.add(new H3("Abholung " + (job.getPickupDate() != null ? formatLocalDate(job.getPickupDate()) : ""))); pickupBox.add(new H3("Abholung " + formatDateWithTime(job.getPickupDate(), job.getPickupTime())));
pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany()))); pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany())));
pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation()) + (job.getPickupSalutation() != null ? " " : "") pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation()) + (job.getPickupSalutation() != null ? " " : "")
+ valueOrEmpty(job.getPickupFirstName()) + (job.getPickupFirstName() != null ? " " : "") + valueOrEmpty(job.getPickupFirstName()) + (job.getPickupFirstName() != null ? " " : "")
@@ -189,8 +189,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
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( deliveryBox.add(new H3("Lieferung " + formatDateWithTime(job.getDeliveryDate(), job.getDeliveryTime())));
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 ? " " : "") + valueOrEmpty(job.getDeliveryFirstName()) + (job.getDeliverySalutation() != null ? " " : "") + valueOrEmpty(job.getDeliveryFirstName())
@@ -340,6 +339,25 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
} }
} }
private String formatLocalTime(java.time.LocalTime time) {
try {
return DateTimeFormatUtil.formatTime(time);
} catch (Exception e) {
return "";
}
}
private String formatDateWithTime(java.time.LocalDate date, java.time.LocalTime time) {
StringBuilder sb = new StringBuilder();
if (date != null) {
sb.append(formatLocalDate(date));
if (time != null) {
sb.append(", ").append(formatLocalTime(time));
}
}
return sb.toString();
}
private String valueOrEmpty(String v) { private String valueOrEmpty(String v) {
return v == null ? "" : v; return v == null ? "" : v;
} }

View File

@@ -21,6 +21,7 @@ import com.vaadin.flow.router.Route;
import de.assecutor.votianlt.model.Job; import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.JobStatus; import de.assecutor.votianlt.model.JobStatus;
import de.assecutor.votianlt.mqtt.MqttPublisher; import de.assecutor.votianlt.mqtt.MqttPublisher;
import de.assecutor.votianlt.util.DateTimeFormatUtil;
import de.assecutor.votianlt.repository.JobRepository; import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.security.SecurityService; import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.service.ClientConnectionService; import de.assecutor.votianlt.service.ClientConnectionService;
@@ -106,7 +107,7 @@ public class ShowJobsView extends VerticalLayout {
grid.addColumn(job -> extractCompanyName(job.getCustomerSelection())).setHeader("Auftraggeber") grid.addColumn(job -> extractCompanyName(job.getCustomerSelection())).setHeader("Auftraggeber")
.setAutoWidth(true).setFlexGrow(1).setSortable(true); .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);
grid.addColumn(Job::getCreatedAt).setHeader("Auftragsdatum").setAutoWidth(true).setSortable(true); grid.addColumn(job -> DateTimeFormatUtil.formatDateTime(job.getCreatedAt())).setHeader("Auftragsdatum").setAutoWidth(true).setSortable(true);
grid.addColumn(Job::getDeliveryCity).setHeader("Zielort").setAutoWidth(true).setFlexGrow(1).setSortable(true); grid.addColumn(Job::getDeliveryCity).setHeader("Zielort").setAutoWidth(true).setFlexGrow(1).setSortable(true);
// Action column: manual completion for jobs without digital processing // Action column: manual completion for jobs without digital processing
@@ -299,7 +300,7 @@ public class ShowJobsView extends VerticalLayout {
for (Job job : jobs) { for (Job job : jobs) {
csv.append(escapeCsv(extractCompanyName(job.getCustomerSelection()))).append(","); csv.append(escapeCsv(extractCompanyName(job.getCustomerSelection()))).append(",");
csv.append(escapeCsv(job.getJobNumber())).append(","); csv.append(escapeCsv(job.getJobNumber())).append(",");
csv.append(job.getCreatedAt() != null ? job.getCreatedAt().toString() : "").append(","); csv.append(DateTimeFormatUtil.formatDateTime(job.getCreatedAt())).append(",");
csv.append(escapeCsv(job.getDeliveryCity())).append("\n"); csv.append(escapeCsv(job.getDeliveryCity())).append("\n");
} }

View File

@@ -2,6 +2,7 @@ package de.assecutor.votianlt.util;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
/** /**
@@ -60,6 +61,20 @@ public class DateTimeFormatUtil {
return dateTime.format(TIME_FORMATTER); return dateTime.format(TIME_FORMATTER);
} }
/**
* Formats a LocalTime to German format: "HH:MM Uhr"
*
* @param time
* the LocalTime to format
* @return formatted time string or empty string if time is null
*/
public static String formatTime(LocalTime time) {
if (time == null) {
return "";
}
return time.format(TIME_FORMATTER);
}
/** /**
* Returns the date time formatter for direct use * Returns the date time formatter for direct use
* *