Skip to content

Commit ac171dc

Browse files
committed
#188: Add feature to add work
1 parent fd527bd commit ac171dc

File tree

6 files changed

+199
-80
lines changed

6 files changed

+199
-80
lines changed

src/main/java/de/doubleslash/keeptime/controller/Controller.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,17 @@ public void deleteWork(final Work workToBeDeleted) {
272272
model.getWorkRepository().delete(workToBeDeleted);
273273
}
274274

275+
public Work addWork(final Work work) {
276+
LOG.info("Adding work '{}'", work);
277+
final Work saved = model.getWorkRepository().save(work);
278+
// show in report if it belongs to today
279+
final LocalDate today = dateProvider.dateTimeNow().toLocalDate();
280+
if (today.equals(saved.getStartTime().toLocalDate())) {
281+
model.getPastWorkItems().add(saved);
282+
}
283+
return saved;
284+
}
285+
275286
/**
276287
* Changes the indexes of the originalList parameter to have a consistent order.
277288
*

src/main/java/de/doubleslash/keeptime/view/ManageWorkController.java

Lines changed: 10 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
package de.doubleslash.keeptime.view;
1818

1919
import java.time.LocalDateTime;
20-
import java.time.LocalTime;
21-
import java.time.format.DateTimeParseException;
22-
import java.time.format.FormatStyle;
2320

2421
import javafx.scene.control.skin.ComboBoxListViewSkin;
2522
import org.slf4j.Logger;
@@ -35,7 +32,6 @@
3532
import javafx.beans.binding.BooleanBinding;
3633
import javafx.beans.property.BooleanProperty;
3734
import javafx.beans.property.SimpleBooleanProperty;
38-
import javafx.beans.property.StringProperty;
3935
import javafx.beans.value.ChangeListener;
4036
import javafx.beans.value.ObservableValue;
4137
import javafx.collections.transformation.FilteredList;
@@ -46,16 +42,13 @@
4642
import javafx.scene.control.Label;
4743
import javafx.scene.control.ListCell;
4844
import javafx.scene.control.ListView;
49-
import javafx.scene.control.Spinner;
50-
import javafx.scene.control.SpinnerValueFactory;
5145
import javafx.scene.control.TextArea;
5246
import javafx.scene.input.KeyCode;
5347
import javafx.scene.input.KeyCodeCombination;
5448
import javafx.scene.input.KeyEvent;
5549
import javafx.scene.layout.GridPane;
5650
import javafx.scene.paint.Color;
5751
import javafx.util.StringConverter;
58-
import javafx.util.converter.LocalTimeStringConverter;
5952

6053
public class ManageWorkController {
6154

@@ -70,10 +63,10 @@ public class ManageWorkController {
7063
private DatePicker startDatePicker;
7164

7265
@FXML
73-
private Spinner<LocalTime> startTimeSpinner;
66+
private TimePickerComboBox startTimePicker;
7467

7568
@FXML
76-
private Spinner<LocalTime> endTimeSpinner;
69+
private TimePickerComboBox endTimePicker;
7770

7871
@FXML
7972
private DatePicker endDatePicker;
@@ -102,10 +95,6 @@ public void setModel(final Model model) {
10295
@FXML
10396
private void initialize() {
10497

105-
setUpTimeSpinner(startTimeSpinner);
106-
107-
setUpTimeSpinner(endTimeSpinner);
108-
10998
setUpTimeRestriction();
11099

111100
setProjectUpComboBox();
@@ -121,20 +110,20 @@ private void initialize() {
121110
private void setUpTimeRestriction() {
122111

123112
BooleanBinding isValidBinding = Bindings.createBooleanBinding(() -> {
124-
if (startTimeSpinner.getValue() == null || endTimeSpinner.getValue() == null
113+
if (startTimePicker.getValue() == null || endTimePicker.getValue() == null
125114
|| startDatePicker.getValue() == null || endDatePicker.getValue() == null) {
126115
return false;
127116
}
128117

129-
LocalDateTime start = LocalDateTime.of(startDatePicker.getValue(), startTimeSpinner.getValue());
130-
LocalDateTime end = LocalDateTime.of(endDatePicker.getValue(), endTimeSpinner.getValue());
118+
LocalDateTime start = LocalDateTime.of(startDatePicker.getValue(), startTimePicker.getValue());
119+
LocalDateTime end = LocalDateTime.of(endDatePicker.getValue(), endTimePicker.getValue());
131120

132121
if (start.isAfter(end)) {
133122
return false;
134123
} else {
135124
return true;
136125
}
137-
}, startTimeSpinner.valueProperty(), endTimeSpinner.valueProperty(), endDatePicker.valueProperty(),
126+
}, startTimePicker.valueProperty(), endTimePicker.valueProperty(), endDatePicker.valueProperty(),
138127
startDatePicker.valueProperty());
139128

140129
this.isValidProperty.bind(isValidBinding);
@@ -149,48 +138,6 @@ private void setUpTimeRestriction() {
149138
}, isValidProperty));
150139
}
151140

152-
private void setUpTimeSpinner(final Spinner<LocalTime> spinner) {
153-
spinner.focusedProperty().addListener((e) -> {
154-
final LocalTimeStringConverter stringConverter = new LocalTimeStringConverter(FormatStyle.MEDIUM);
155-
final StringProperty text = spinner.getEditor().textProperty();
156-
try {
157-
stringConverter.fromString(text.get());
158-
// needed to log in value from editor to spinner
159-
spinner.increment(0); // TODO find better Solution
160-
} catch (final DateTimeParseException ex) {
161-
text.setValue(spinner.getValue().toString());
162-
}
163-
});
164-
165-
spinner.setValueFactory(new SpinnerValueFactory<LocalTime>() {
166-
167-
@Override
168-
public void decrement(final int steps) {
169-
if (getValue() == null) {
170-
setValue(LocalTime.now());
171-
} else {
172-
final LocalTime time = getValue();
173-
setValue(time.minusMinutes(steps));
174-
}
175-
176-
}
177-
178-
@Override
179-
public void increment(final int steps) {
180-
if (getValue() == null) {
181-
setValue(LocalTime.now());
182-
} else {
183-
final LocalTime time = getValue();
184-
setValue(time.plusMinutes(steps));
185-
}
186-
187-
}
188-
189-
});
190-
191-
spinner.getValueFactory().setConverter(new LocalTimeStringConverter(FormatStyle.MEDIUM));
192-
193-
}
194141

195142
private void setProjectUpComboBox() {
196143
// color Dropdown Options
@@ -330,8 +277,8 @@ public void initializeWith(final Work work) {
330277
startDatePicker.setValue(work.getStartTime().toLocalDate());
331278
endDatePicker.setValue(work.getEndTime().toLocalDate());
332279

333-
startTimeSpinner.getValueFactory().setValue(work.getStartTime().toLocalTime());
334-
endTimeSpinner.getValueFactory().setValue(work.getEndTime().toLocalTime());
280+
startTimePicker.setValue(work.getStartTime().toLocalTime());
281+
endTimePicker.setValue(work.getEndTime().toLocalTime());
335282

336283
noteTextArea.setText(work.getNotes());
337284

@@ -366,8 +313,8 @@ private void setColor(final Node object, final Color color) {
366313
}
367314

368315
public Work getWorkFromUserInput() {
369-
return new Work(LocalDateTime.of(startDatePicker.getValue(), startTimeSpinner.getValue()),
370-
LocalDateTime.of(endDatePicker.getValue(), endTimeSpinner.getValue()), selectedProject,
316+
return new Work(LocalDateTime.of(startDatePicker.getValue(), startTimePicker.getValue()),
317+
LocalDateTime.of(endDatePicker.getValue(), endTimePicker.getValue()), selectedProject,
371318
noteTextArea.getText());
372319
}
373320

src/main/java/de/doubleslash/keeptime/view/ReportController.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.io.IOException;
2020
import java.time.LocalDate;
21+
import java.time.LocalDateTime;
22+
import java.time.LocalTime;
2123
import java.util.*;
2224
import java.util.stream.Collectors;
2325

@@ -102,6 +104,9 @@ public class ReportController {
102104
@FXML
103105
private Button heimatSyncButton;
104106

107+
@FXML
108+
private Button addWorkButton;
109+
105110
private static final Logger LOG = LoggerFactory.getLogger(ReportController.class);
106111

107112
private final Model model;
@@ -132,10 +137,34 @@ private void initialize() {
132137
colorTimeLine = new ColorTimeLine(colorTimeLineCanvas);
133138

134139
expandCollapseButton.setOnMouseClicked(event ->toggleCollapseExpandReport());
140+
if (addWorkButton != null) {
141+
addWorkButton.setOnAction(e -> onAddWork());
142+
}
135143
initTableView();
136144
initHeimatIntegration();
137145
}
138146

147+
private void onAddWork() {
148+
// Default values: current report date window or now if today
149+
final boolean isToday = LocalDate.now().equals(currentReportDate);
150+
final LocalDateTime now = LocalDateTime.now();
151+
final LocalDateTime defaultStart = isToday ? now.minusMinutes(15) : currentReportDate.atTime(LocalTime.of(9, 0));
152+
final LocalDateTime defaultEnd = isToday ? now : currentReportDate.atTime(LocalTime.of(10, 0));
153+
154+
final Project defaultProject = model.activeWorkItem.get() != null
155+
? model.activeWorkItem.get().getProject()
156+
: model.getIdleProject();
157+
158+
final Work newWorkDefaults = new Work(defaultStart, defaultEnd, defaultProject, "");
159+
final Dialog<Work> dialog = setupAddWorkDialog(newWorkDefaults);
160+
161+
final Optional<Work> result = dialog.showAndWait();
162+
result.ifPresent(createdWork -> {
163+
controller.addWork(createdWork);
164+
this.update();
165+
});
166+
}
167+
139168
private void initHeimatIntegration() {
140169
heimatSyncButton.setVisible(model.getHeimatSettings().isHeimatActive());
141170
final SVGPath svgNodeWithScale = SvgNodeProvider.getSvgNodeWithScale(RESOURCE.SVG_ROTATE_ICON, 0.03, 0.03);
@@ -448,6 +477,19 @@ private Dialog<Work> setupEditWorkDialog(final Work work) {
448477
return dialog;
449478
}
450479

480+
private Dialog<Work> setupAddWorkDialog(final Work work) {
481+
final Dialog<Work> dialog = new Dialog<>();
482+
dialog.initOwner(stage);
483+
dialog.setTitle("Add work");
484+
dialog.setHeaderText("Add work");
485+
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
486+
487+
final GridPane grid = setUpEditWorkGridPane(work, dialog);
488+
dialog.getDialogPane().setContent(grid);
489+
490+
return dialog;
491+
}
492+
451493
private GridPane setUpEditWorkGridPane(final Work work, final Dialog<Work> dialog) {
452494
final GridPane grid;
453495
final FXMLLoader loader = new FXMLLoader(Resources.getResource(RESOURCE.FXML_MANAGE_WORK));
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package de.doubleslash.keeptime.view;
2+
3+
import javafx.application.Platform;
4+
import javafx.beans.property.ObjectProperty;
5+
import javafx.beans.property.SimpleObjectProperty;
6+
import javafx.scene.control.ComboBox;
7+
import javafx.scene.control.Label;
8+
import javafx.scene.layout.HBox;
9+
import javafx.util.StringConverter;
10+
11+
import java.time.LocalTime;
12+
import java.util.stream.IntStream;
13+
14+
public class TimePickerComboBox extends HBox {
15+
16+
private final ComboBox<Integer> hourBox = new ComboBox<>();
17+
private final ComboBox<Integer> minuteBox = new ComboBox<>();
18+
19+
private final ObjectProperty<LocalTime> value = new SimpleObjectProperty<>(this, "value");
20+
21+
private boolean updating = false;
22+
23+
public TimePickerComboBox() {
24+
init24HourControls();
25+
attachListeners();
26+
setSpacing(4);
27+
}
28+
29+
public ObjectProperty<LocalTime> valueProperty() {
30+
return value;
31+
}
32+
33+
public LocalTime getValue() {
34+
return value.get();
35+
}
36+
37+
public void setValue(LocalTime time) {
38+
value.set(time);
39+
}
40+
41+
private void attachListeners() {
42+
hourBox.valueProperty().addListener((obs, ov, nv) -> updateValueFromUi());
43+
minuteBox.valueProperty().addListener((obs, ov, nv) -> updateValueFromUi());
44+
value.addListener((obs, ov, nv) -> updateUiFromValue(nv));
45+
}
46+
47+
private void init24HourControls() {
48+
// Populate hours 0-23
49+
hourBox.getItems().setAll(IntStream.range(0, 24).boxed().toList());
50+
// Populate minutes 0-59
51+
minuteBox.getItems().setAll(IntStream.range(0, 60).boxed().toList());
52+
53+
// Two-digit formatting for hour/minute
54+
StringConverter<Integer> twoDigitConverter = new StringConverter<>() {
55+
@Override public String toString(Integer object) {
56+
return object == null ? "" : String.format("%02d", object);
57+
}
58+
@Override public Integer fromString(String string) {
59+
try { return Integer.valueOf(string); } catch (Exception e) { return null; }
60+
}
61+
};
62+
hourBox.setConverter(twoDigitConverter);
63+
minuteBox.setConverter(twoDigitConverter);
64+
// Ensure editors exist for formatting updates
65+
hourBox.setEditable(true);
66+
minuteBox.setEditable(true);
67+
68+
getChildren().clear();
69+
getChildren().addAll(hourBox, new Label(":"), minuteBox);
70+
}
71+
72+
private void updateValueFromUi() {
73+
if (updating) return;
74+
75+
Integer hSel = hourBox.getValue();
76+
Integer mSel = minuteBox.getValue();
77+
if (hSel == null || mSel == null) {
78+
value.set(null);
79+
return;
80+
}
81+
82+
int hour24 = hSel;
83+
84+
updating = true;
85+
try {
86+
value.set(LocalTime.of(hour24, mSel));
87+
} finally {
88+
updating = false;
89+
}
90+
}
91+
92+
private void updateUiFromValue(LocalTime t) {
93+
if (updating) return;
94+
95+
updating = true;
96+
try {
97+
if (t == null) {
98+
hourBox.getSelectionModel().clearSelection();
99+
minuteBox.getSelectionModel().clearSelection();
100+
return;
101+
}
102+
103+
int h24 = t.getHour();
104+
int m = t.getMinute();
105+
hourBox.setValue(h24);
106+
minuteBox.setValue(m);
107+
108+
Platform.runLater(() -> {
109+
minuteBox.getEditor().setText(minuteBox.getConverter().toString(minuteBox.getValue()));
110+
hourBox.getEditor().setText(hourBox.getConverter().toString(hourBox.getValue()));
111+
});
112+
} finally {
113+
updating = false;
114+
}
115+
}
116+
}

src/main/resources/layouts/manage-work.fxml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
<?import javafx.scene.control.ComboBox?>
55
<?import javafx.scene.control.DatePicker?>
66
<?import javafx.scene.control.Label?>
7-
<?import javafx.scene.control.Spinner?>
87
<?import javafx.scene.control.TextArea?>
98
<?import javafx.scene.layout.ColumnConstraints?>
109
<?import javafx.scene.layout.GridPane?>
1110
<?import javafx.scene.layout.RowConstraints?>
1211
<?import javafx.scene.text.Font?>
12+
<?import de.doubleslash.keeptime.view.TimePickerComboBox?>
1313

1414
<GridPane fx:id="grid" hgap="5.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="214.0" prefWidth="371.0" vgap="5.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.doubleslash.keeptime.view.ManageWorkController">
1515
<columnConstraints>
@@ -31,15 +31,15 @@
3131
<Font name="Open Sans Regular" size="12.0" />
3232
</font>
3333
</Label>
34-
<DatePicker fx:id="startDatePicker" GridPane.columnIndex="1" />
35-
<Spinner fx:id="startTimeSpinner" editable="true" GridPane.columnIndex="2" />
34+
<DatePicker fx:id="startDatePicker" GridPane.columnIndex="1" />
35+
<TimePickerComboBox fx:id="startTimePicker" GridPane.columnIndex="2" />
3636
<Label text="End" GridPane.rowIndex="1">
3737
<font>
3838
<Font name="Open Sans Regular" size="12.0" />
3939
</font>
4040
</Label>
41-
<DatePicker fx:id="endDatePicker" GridPane.columnIndex="1" GridPane.rowIndex="1" />
42-
<Spinner fx:id="endTimeSpinner" editable="true" GridPane.columnIndex="2" GridPane.rowIndex="1" />
41+
<DatePicker fx:id="endDatePicker" GridPane.columnIndex="1" GridPane.rowIndex="1" />
42+
<TimePickerComboBox fx:id="endTimePicker" GridPane.columnIndex="2" GridPane.rowIndex="1" />
4343
<Label text="Project" GridPane.rowIndex="3">
4444
<font>
4545
<Font name="Open Sans Regular" size="12.0" />

0 commit comments

Comments
 (0)