11package de .doubleslash .keeptime .view ;
22
3+ import de .doubleslash .keeptime .controller .Controller ;
34import de .doubleslash .keeptime .model .*;
45import de .doubleslash .keeptime .model .repos .ExternalProjectsMappingsRepository ;
56import de .doubleslash .keeptime .model .settings .HeimatSettings ;
67import de .doubleslash .keeptime .rest .integration .heimat .HeimatAPI ;
8+ import de .doubleslash .keeptime .rest .integration .heimat .model .HeimatTime ;
79import javafx .beans .binding .Bindings ;
810import javafx .beans .binding .StringBinding ;
911import javafx .beans .property .*;
12+ import javafx .beans .value .ChangeListener ;
1013import javafx .collections .FXCollections ;
1114import javafx .collections .ObservableList ;
1215import javafx .fxml .FXML ;
1316import javafx .scene .control .*;
1417import javafx .scene .control .cell .CheckBoxTableCell ;
18+ import javafx .scene .layout .HBox ;
1519import javafx .scene .layout .VBox ;
1620import javafx .util .converter .LocalTimeStringConverter ;
1721import org .slf4j .Logger ;
2024
2125import java .time .LocalDate ;
2226import java .time .LocalTime ;
27+ import java .time .format .DateTimeFormatter ;
2328import java .time .format .DateTimeParseException ;
2429import java .time .format .FormatStyle ;
25- import java .util .List ;
30+ import java .util .*;
31+ import java .util .stream .Collectors ;
2632
2733@ Component
2834public class ExternalProjectsSyncController {
@@ -47,54 +53,94 @@ public class ExternalProjectsSyncController {
4753 @ FXML
4854 private Hyperlink externalSystemLink ;
4955
56+ private final Controller controller ;
5057 private final Model model ;
5158 private final HeimatSettings heimatSettings ;
5259 private final ExternalProjectsMappingsRepository externalProjectsMappingsRepository ;
5360
54- private final LocalTimeStringConverter localTimeStringConverter = new LocalTimeStringConverter (FormatStyle .SHORT );
61+ private final LocalTimeStringConverter localTimeStringConverter = new LocalTimeStringConverter (FormatStyle .MEDIUM );
5562
56- public ExternalProjectsSyncController (final Model model , HeimatSettings heimatSettings ,
63+ public ExternalProjectsSyncController (final Controller controller , final Model model , HeimatSettings heimatSettings ,
5764 ExternalProjectsMappingsRepository externalProjectsMappingsRepository ) {
65+ this .controller = controller ;
5866 this .model = model ;
5967 this .heimatSettings = heimatSettings ;
6068 this .externalProjectsMappingsRepository = externalProjectsMappingsRepository ;
6169 }
6270
63- @ FXML
64- private void initialize () {
65- // TODO set this from ReportController
66- final LocalDate currentReportDate = LocalDate .now ();
67- final List <Work > currentWorkItems = model .getWorkRepository ()
68- .findByStartDateOrderByStartTimeAsc (currentReportDate );
71+ public void initForDate (LocalDate currentReportDate , List <Work > currentWorkItems ) {
72+ dayOfSyncLabel .setText (currentReportDate .format (DateTimeFormatter .BASIC_ISO_DATE ));
6973
7074 final HeimatAPI heimatAPI = new HeimatAPI (heimatSettings .getHeimatUrl (), heimatSettings .getHeimatPat ());
71- //final List<HeimatTask> externalProjects = heimatAPI.getMyTasks(currentReportDate);
75+ // TODO check if external projects are available for the currentDay
76+ // final List<HeimatTask> heimatTasks = heimatAPI.getMyTasks(currentReportDate);
77+ final List <HeimatTime > heimatTimes = heimatAPI .getMyTimes (currentReportDate );
7278 final List <ExternalProjectMapping > mappedProjects = externalProjectsMappingsRepository .findByExternalSystemId (
7379 ExternalSystem .Heimat );
74- // TODO check if external projects are available for the currentDay
7580
76- final TableRow tableRow = new TableRow ();
77- tableRow .project = model .activeWorkItem .get ().getProject ();
78- tableRow .shouldSyncCheckBox = new SimpleBooleanProperty (true );
79- tableRow .syncStatus = new SimpleStringProperty ("Can be synced" );
80- tableRow .heimatNotes = new SimpleStringProperty ("Heimat notes" );
81- tableRow .keeptimeNotes = new SimpleStringProperty ("Current notes" );
82- tableRow .heimatTimeMinutes = new SimpleIntegerProperty (90 );
83- tableRow .userTimeMinutes = new SimpleIntegerProperty (90 );
84- tableRow .keeptimeTimeMinutes = new SimpleIntegerProperty (90 );
85- final ObservableList <TableRow > items = FXCollections .observableArrayList (tableRow );
81+ final List <TableRow > list = new ArrayList <>();
82+
83+ final SortedSet <Project > workedProjectsSet = currentWorkItems .stream ()
84+ .map (Work ::getProject )
85+ .filter (Project ::isWork )
86+ .collect (Collectors .toCollection (() -> new TreeSet <>(
87+ Comparator .comparing (Project ::getIndex ))));
88+ for (final Project project : workedProjectsSet ) {
89+ String heimatNotes = "" ;
90+ long heimatTimeSeconds = 0 ;
91+ boolean isMappedInHeimat = false ;
92+ final Optional <ExternalProjectMapping > optionalHeimatMapping = mappedProjects .stream ()
93+ .filter (mp -> mp .getProject ()
94+ .getId ()
95+ == project .getId ())
96+ .findAny ();
97+ if (optionalHeimatMapping .isPresent ()) {
98+ isMappedInHeimat = true ;
99+ final Optional <HeimatTime > optionalAlreadyBookedTime = heimatTimes .stream ()
100+ .filter (heimatTime -> heimatTime .taskId ()
101+ == optionalHeimatMapping .get ()
102+ .getExternalTaskId ())
103+ .findAny ();
104+ if (optionalAlreadyBookedTime .isPresent ()) {
105+ heimatNotes = optionalAlreadyBookedTime .get ().note ();
106+ heimatTimeSeconds = optionalAlreadyBookedTime .get ().durationInMinutes () * 60L ;
107+ }
108+ }
109+ final List <Work > onlyCurrentProjectWork = currentWorkItems .stream ()
110+ .filter (w -> w .getProject () == project )
111+ .toList ();
112+
113+ final long projectWorkSeconds = controller .calcSeconds (onlyCurrentProjectWork );
114+
115+ final ProjectReport pr = new ProjectReport ();
116+ for (final Work work : onlyCurrentProjectWork ) {
117+ final String currentWorkNote = work .getNotes ();
118+ pr .appendToWorkNotes (currentWorkNote );
119+ }
120+ final String keeptimeNotes = pr .getNotes ();
121+ String canBeSynced = "Can be synced" ;
122+ if (!isMappedInHeimat ) {
123+ canBeSynced = "Not mapped in Heimat" ;
124+ }
125+ list .add (new TableRow (project , isMappedInHeimat , canBeSynced , heimatNotes , keeptimeNotes , keeptimeNotes , heimatTimeSeconds ,
126+ projectWorkSeconds , projectWorkSeconds ));
127+ }
128+ final ObservableList <TableRow > items = FXCollections .observableArrayList (list );
86129 mappingTableView .setItems (items );
87130
88131 ObservableList <TableRow > items2 = FXCollections .observableArrayList (
89- item -> new javafx .beans .Observable [] { item .userTimeMinutes , item .shouldSyncCheckBox });
132+ item -> new javafx .beans .Observable [] { item .userTimeSeconds , item .shouldSyncCheckBox });
90133 items2 .addAll (items );
91134 StringBinding totalSum = Bindings .createStringBinding (() -> localTimeStringConverter .toString (
92135 LocalTime .ofSecondOfDay (items .stream ()
93136 .filter (item -> item .shouldSyncCheckBox .get ())
94- .mapToInt (item -> item .userTimeMinutes .getValue ())
95- .sum () * 60 )), items2 );
137+ .mapToLong (item -> item .userTimeSeconds .getValue ())
138+ .sum ())), items2 );
96139 sumTimeLabel .textProperty ().bind (Bindings .concat ("Total Sum: " , totalSum ));
140+ }
97141
142+ @ FXML
143+ private void initialize () {
98144 TableColumn <TableRow , Boolean > shouldSyncColumn = new TableColumn <>("Sync" );
99145 shouldSyncColumn .setCellValueFactory (data -> data .getValue ().shouldSyncCheckBox );
100146 shouldSyncColumn .setCellFactory (CheckBoxTableCell .forTableColumn (shouldSyncColumn ));
@@ -105,14 +151,15 @@ private void initialize() {
105151 TableColumn <TableRow , String > projectColumn = new TableColumn <>("Project" );
106152 projectColumn .setCellValueFactory (data -> new SimpleStringProperty (data .getValue ().project .getName ()));
107153 projectColumn .setPrefWidth (100 );
154+ // TODO set color
108155 TableColumn <TableRow , TableRow > timeColumn = new TableColumn <>("Time" );
109156 timeColumn .setCellValueFactory (data -> new SimpleObjectProperty <>(data .getValue ())); // Placeholder property
110157
111158 timeColumn .setCellFactory (column -> new TableCell <>() {
112159 private final Spinner <LocalTime > timeSpinner = new Spinner <>();
113160 private final Label keeptimeLabel = new Label ();
114161 private final Label heimatLabel = new Label ();
115-
162+ private ChangeListener < LocalTime > localTimeChangeListener ;
116163 private final VBox container = new VBox (5 ); // Space between TextArea and Label
117164
118165 {
@@ -123,18 +170,20 @@ private void initialize() {
123170 @ Override
124171 protected void updateItem (TableRow item , boolean empty ) {
125172 super .updateItem (item , empty );
126-
173+ if (localTimeChangeListener != null )
174+ timeSpinner .valueProperty ().removeListener (localTimeChangeListener );
127175 if (empty || item == null ) {
128176 setGraphic (null );
129177 } else {
130178 keeptimeLabel .setText ("KeepTime: " + localTimeStringConverter .toString (
131- LocalTime .ofSecondOfDay (item .keeptimeTimeMinutes .get () * 60 )));
179+ LocalTime .ofSecondOfDay (item .keeptimeTimeSeconds .get ())));
132180 heimatLabel .setText ("Heimat: " + localTimeStringConverter .toString (
133- LocalTime .ofSecondOfDay (item .heimatTimeMinutes .get () * 60 )));
134- timeSpinner .getValueFactory ().setValue (LocalTime .ofSecondOfDay (item .userTimeMinutes .get () * 60 ));
135- timeSpinner .valueProperty ().addListener ((observable , oldValue , newValue ) -> {
136- item .userTimeMinutes .set (newValue .getHour () * 60 + newValue .getMinute ());
137- });
181+ LocalTime .ofSecondOfDay (item .heimatTimeSeconds .get ())));
182+ timeSpinner .getValueFactory ().setValue (LocalTime .ofSecondOfDay (item .userTimeSeconds .get ()));
183+ localTimeChangeListener = (observable , oldValue , newValue ) -> {
184+ item .userTimeSeconds .set (newValue .toSecondOfDay ());
185+ };
186+ timeSpinner .valueProperty ().addListener (localTimeChangeListener );
138187 setGraphic (container );
139188 }
140189 }
@@ -145,27 +194,32 @@ protected void updateItem(TableRow item, boolean empty) {
145194 notesColumn .setCellValueFactory (data -> new SimpleObjectProperty <>(data .getValue ())); // Placeholder property
146195
147196 notesColumn .setCellFactory (column -> new TableCell <>() {
197+ private ChangeListener <String > stringChangeListener ;
148198 private final TextArea textArea = new TextArea ();
149- private final Label label = new Label ();
199+ private final Label heimatNotesLabel = new Label ();
200+ private final HBox hbox = new HBox (5 );
150201 private final VBox container = new VBox (5 ); // Space between TextArea and Label
151202
152203 {
153204 textArea .setPrefHeight (50 );
154205 textArea .setPrefWidth (100 );
155206 textArea .setWrapText (true );
156- container .getChildren ().addAll (textArea , label );
207+ hbox .getChildren ().addAll (new Label ("Heimat:" ), heimatNotesLabel );
208+ container .getChildren ().addAll (textArea , hbox );
157209 }
158210
159211 @ Override
160212 protected void updateItem (TableRow item , boolean empty ) {
161213 super .updateItem (item , empty );
162-
214+ if (stringChangeListener != null )
215+ textArea .textProperty ().removeListener (stringChangeListener );
163216 if (empty || item == null ) {
164217 setGraphic (null );
165218 } else {
166219 textArea .setText (item .keeptimeNotes .get ());
167- textArea .textProperty ().addListener ((obs , oldText , newText ) -> item .keeptimeNotes .set (newText ));
168- label .setText (item .heimatNotes .get ());
220+ stringChangeListener = (obs , oldText , newText ) -> item .keeptimeNotes .set (newText );
221+ textArea .textProperty ().addListener (stringChangeListener );
222+ heimatNotesLabel .setText (item .heimatNotes .get ());
169223 setGraphic (container );
170224 }
171225 }
@@ -207,8 +261,10 @@ public void decrement(final int steps) {
207261 if (getValue () == null ) {
208262 setValue (LocalTime .now ());
209263 } else {
264+ if (steps == 0 )
265+ return ;
210266 final LocalTime time = getValue ();
211- setValue (time . minusMinutes ( 15 * steps ));
267+ setValue (decrementToLastFullQuarter ( time ));
212268 }
213269
214270 }
@@ -218,31 +274,59 @@ public void increment(final int steps) {
218274 if (getValue () == null ) {
219275 setValue (LocalTime .now ());
220276 } else {
277+ if (steps == 0 )
278+ return ;
221279 final LocalTime time = getValue ();
222- setValue (time . plusMinutes ( 15 * steps ));
280+ setValue (incrementToNextFullQuarter ( time ));
223281 }
224282
225283 }
226284
227285 });
228286
229- spinner .getValueFactory ().setConverter (new LocalTimeStringConverter (FormatStyle .SHORT ));
287+ spinner .getValueFactory ().setConverter (new LocalTimeStringConverter (FormatStyle .MEDIUM ));
288+
289+ // TODO mark red if not a 15 minute slot
290+ }
291+
292+ public static LocalTime decrementToLastFullQuarter (LocalTime time ) {
293+ int minutes = time .getMinute ();
294+ int decrement = (minutes % 15 == 0 && time .getSecond () == 0 ) ? 15 : minutes % 15 ;
295+ return time .minusMinutes (decrement ).withSecond (0 ).withNano (0 );
296+ }
230297
298+ public static LocalTime incrementToNextFullQuarter (LocalTime time ) {
299+ int minutes = time .getMinute ();
300+ int increment = (minutes % 15 == 0 && time .getSecond () == 0 ) ? 15 : 15 - (minutes % 15 );
301+ return time .plusMinutes (increment ).withSecond (0 ).withNano (0 );
231302 }
232303
233- class TableRow {
304+ static class TableRow {
234305 public BooleanProperty shouldSyncCheckBox ;
235306 public Project project ;
236307
237308 public StringProperty keeptimeNotes ;
238309 public StringProperty userNotes ;
239310 public StringProperty heimatNotes ;
240311
241- public IntegerProperty keeptimeTimeMinutes ;
242- public IntegerProperty userTimeMinutes ;
243- public IntegerProperty heimatTimeMinutes ;
312+ public LongProperty keeptimeTimeSeconds ;
313+ public LongProperty userTimeSeconds ;
314+ public LongProperty heimatTimeSeconds ;
244315
245316 public StringProperty syncStatus ;
246317
318+ public TableRow (final Project project , final boolean shouldSync , final String syncStatus ,
319+ final String heimatNotes , final String keeptimeNotes , String userNotes , final long heimatTimeSeconds ,
320+ final long keeptimeSeconds , final long userSeconds ) {
321+ this .project = project ;
322+ this .shouldSyncCheckBox = new SimpleBooleanProperty (shouldSync );
323+ this .syncStatus = new SimpleStringProperty (syncStatus );
324+ this .heimatNotes = new SimpleStringProperty (heimatNotes );
325+ this .keeptimeNotes = new SimpleStringProperty (keeptimeNotes );
326+ this .userNotes = new SimpleStringProperty (userNotes );
327+ this .heimatTimeSeconds = new SimpleLongProperty (heimatTimeSeconds );
328+ this .userTimeSeconds = new SimpleLongProperty (userSeconds );
329+ this .keeptimeTimeSeconds = new SimpleLongProperty (keeptimeSeconds );
330+ }
247331 }
248332}
0 commit comments