1212import javafx .animation .RotateTransition ;
1313import javafx .application .Platform ;
1414import javafx .beans .binding .Bindings ;
15+ import javafx .beans .binding .BooleanBinding ;
1516import javafx .beans .binding .StringBinding ;
1617import javafx .beans .property .*;
1718import javafx .beans .value .ChangeListener ;
@@ -121,7 +122,7 @@ public void initForDate(LocalDate currentReportDate, List<Work> currentWorkItems
121122 mappingTableView .setItems (items );
122123
123124 ObservableList <TableRow > items2 = FXCollections .observableArrayList (
124- item -> new javafx .beans .Observable [] { item .userTimeSeconds , item .shouldSyncCheckBox });
125+ item -> new javafx .beans .Observable [] { item .userTimeSeconds , item .shouldSyncCheckBox , item . userNotes });
125126 items2 .addAll (items );
126127 StringBinding totalSum = Bindings .createStringBinding (() -> localTimeStringConverter .toString (
127128 LocalTime .ofSecondOfDay (
@@ -139,6 +140,15 @@ public void initForDate(LocalDate currentReportDate, List<Work> currentWorkItems
139140 heimatTimeLabel .setText (localTimeStringConverter .toString (
140141 LocalTime .ofSecondOfDay (tableRows .stream ().mapToLong (HeimatController .Mapping ::heimatSeconds ).sum ())));
141142
143+ BooleanBinding projectsValidProperty = Bindings .createBooleanBinding (() -> items .stream ().anyMatch (item -> {
144+ boolean shouldSync = item .shouldSyncCheckBox .get ();
145+ boolean hasNote = !item .userNotes .get ().isBlank ();
146+ boolean hasTime = areSecondsOfDayValid (item .userTimeSeconds .get ());
147+ return shouldSync && !(hasNote && hasTime );
148+ }), items2 );
149+
150+ saveButton .disableProperty ().bind (projectsValidProperty );
151+
142152 externalSystemLink .setOnAction (ae -> BrowserHelper .openURL (heimatController .getUrlForDay (currentReportDate )));
143153 }
144154
@@ -201,10 +211,9 @@ private HBox createRow(Color color, String text) {
201211 TableColumn <TableRow , TableRow > timeColumn = new TableColumn <>("Time" );
202212 timeColumn .setCellValueFactory (data -> new SimpleObjectProperty <>(data .getValue ())); // Placeholder property
203213
204- Consumer <Spinner <LocalTime >> spinnerValid = (Spinner <LocalTime > spinner ) -> {
205- int seconds = spinner .getValue ().toSecondOfDay ();
206- int minutes = (seconds / 60 );
207- if (seconds % 60 != 0 || minutes % 15 != 0 || minutes <= 0 ) {
214+ Consumer <Spinner <LocalTime >> spinnerValidConsumer = (Spinner <LocalTime > spinner ) -> {
215+ final boolean isValid = areSecondsOfDayValid (spinner .getValue ().toSecondOfDay ());
216+ if (!isValid ) {
208217 spinner .setStyle ("-fx-border-color: red; -fx-border-width: 2px; -fx-border-radius: 4px;" );
209218 } else {
210219 spinner .setStyle ("" ); // Reset to default style
@@ -240,9 +249,9 @@ protected void updateItem(TableRow item, boolean empty) {
240249 timeSpinner .getValueFactory ().setValue (LocalTime .ofSecondOfDay (item .userTimeSeconds .get ()));
241250 localTimeChangeListener = (observable , oldValue , newValue ) -> {
242251 item .userTimeSeconds .set (newValue .toSecondOfDay ());
243- spinnerValid .accept (timeSpinner );
252+ spinnerValidConsumer .accept (timeSpinner );
244253 };
245- spinnerValid .accept (timeSpinner );
254+ spinnerValidConsumer .accept (timeSpinner );
246255 timeSpinner .valueProperty ().addListener (localTimeChangeListener );
247256 }
248257 setGraphic (container );
@@ -395,6 +404,12 @@ protected List<HeimatController.HeimatErrors> call() {
395404 // TODO offer some way to book time to an additional project?
396405 }
397406
407+ private static boolean areSecondsOfDayValid (final long seconds ) {
408+ long minutes = (seconds / 60 );
409+ final boolean isInvalid = seconds % 60 != 0 || minutes % 15 != 0 || minutes <= 0 ;
410+ return !isInvalid ;
411+ }
412+
398413 private void initializeLoadingScreen () {
399414 loadingScreen .getChildren ().add (0 , loadingSpinner );
400415
0 commit comments