Skip to content

Commit f8bef51

Browse files
committed
#178: start sync to heimat dialog
1 parent 03610d9 commit f8bef51

File tree

8 files changed

+361
-29
lines changed

8 files changed

+361
-29
lines changed

src/main/java/de/doubleslash/keeptime/common/Resources.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public enum RESOURCE {
4141
FXML_MANAGE_PROJECT("/layouts/manage-project.fxml"),
4242
FXML_MANAGE_WORK("/layouts/manage-work.fxml"),
4343
FXML_EXT_PROJECT_MAPPING("/layouts/externalProjectMapping.fxml"),
44+
FXML_EXT_PROJECT_SYNC("/layouts/externalProjectSync.fxml"),
4445

4546
SVG_CALENDAR_DAYS_ICON("/svgs/calendar-days.svg"),
4647

src/main/java/de/doubleslash/keeptime/common/SvgNodeProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static String getSvgPathWithXMl(Resources.RESOURCE resource){
6565
return svgPath;
6666
}
6767

68-
public static SVGPath getSvgNodeWithScale(Resources.RESOURCE resource, Double scaleX, Double scaleY) {
68+
public static SVGPath getSvgNodeWithScale(Resources.RESOURCE resource, double scaleX, double scaleY) {
6969
SVGPath iconSvg = new SVGPath();
7070
iconSvg.setContent(getSvgPathWithXMl(resource));
7171
iconSvg.setScaleX(scaleX);
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package de.doubleslash.keeptime.view;
2+
3+
import de.doubleslash.keeptime.model.*;
4+
import de.doubleslash.keeptime.model.repos.ExternalProjectsMappingsRepository;
5+
import de.doubleslash.keeptime.model.settings.HeimatSettings;
6+
import de.doubleslash.keeptime.rest.integration.heimat.HeimatAPI;
7+
import javafx.beans.binding.Bindings;
8+
import javafx.beans.binding.StringBinding;
9+
import javafx.beans.property.*;
10+
import javafx.collections.FXCollections;
11+
import javafx.collections.ObservableList;
12+
import javafx.fxml.FXML;
13+
import javafx.scene.control.*;
14+
import javafx.scene.control.cell.CheckBoxTableCell;
15+
import javafx.scene.layout.VBox;
16+
import javafx.util.converter.LocalTimeStringConverter;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
import org.springframework.stereotype.Component;
20+
21+
import java.time.LocalDate;
22+
import java.time.LocalTime;
23+
import java.time.format.DateTimeParseException;
24+
import java.time.format.FormatStyle;
25+
import java.util.List;
26+
27+
@Component
28+
public class ExternalProjectsSyncController {
29+
30+
private static final Logger LOG = LoggerFactory.getLogger(MapExternalProjectsController.class);
31+
32+
@FXML
33+
private TableView<TableRow> mappingTableView;
34+
35+
@FXML
36+
private Button saveButton;
37+
38+
@FXML
39+
private Button cancelButton;
40+
41+
@FXML
42+
private Label dayOfSyncLabel;
43+
44+
@FXML
45+
private Label sumTimeLabel;
46+
47+
@FXML
48+
private Hyperlink externalSystemLink;
49+
50+
private final Model model;
51+
private final HeimatSettings heimatSettings;
52+
private final ExternalProjectsMappingsRepository externalProjectsMappingsRepository;
53+
54+
private final LocalTimeStringConverter localTimeStringConverter = new LocalTimeStringConverter(FormatStyle.SHORT);
55+
56+
public ExternalProjectsSyncController(final Model model, HeimatSettings heimatSettings,
57+
ExternalProjectsMappingsRepository externalProjectsMappingsRepository) {
58+
this.model = model;
59+
this.heimatSettings = heimatSettings;
60+
this.externalProjectsMappingsRepository = externalProjectsMappingsRepository;
61+
}
62+
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);
69+
70+
final HeimatAPI heimatAPI = new HeimatAPI(heimatSettings.getHeimatUrl(), heimatSettings.getHeimatPat());
71+
//final List<HeimatTask> externalProjects = heimatAPI.getMyTasks(currentReportDate);
72+
final List<ExternalProjectMapping> mappedProjects = externalProjectsMappingsRepository.findByExternalSystemId(
73+
ExternalSystem.Heimat);
74+
// TODO check if external projects are available for the currentDay
75+
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);
86+
mappingTableView.setItems(items);
87+
88+
ObservableList<TableRow> items2 = FXCollections.observableArrayList(
89+
item -> new javafx.beans.Observable[] { item.userTimeMinutes, item.shouldSyncCheckBox });
90+
items2.addAll(items);
91+
StringBinding totalSum = Bindings.createStringBinding(() -> localTimeStringConverter.toString(
92+
LocalTime.ofSecondOfDay(items.stream()
93+
.filter(item -> item.shouldSyncCheckBox.get())
94+
.mapToInt(item -> item.userTimeMinutes.getValue())
95+
.sum() * 60)), items2);
96+
sumTimeLabel.textProperty().bind(Bindings.concat("Total Sum: ", totalSum));
97+
98+
TableColumn<TableRow, Boolean> shouldSyncColumn = new TableColumn<>("Sync");
99+
shouldSyncColumn.setCellValueFactory(data -> data.getValue().shouldSyncCheckBox);
100+
shouldSyncColumn.setCellFactory(CheckBoxTableCell.forTableColumn(shouldSyncColumn));
101+
mappingTableView.setEditable(true);
102+
shouldSyncColumn.setEditable(true);
103+
shouldSyncColumn.setPrefWidth(50);
104+
105+
TableColumn<TableRow, String> projectColumn = new TableColumn<>("Project");
106+
projectColumn.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().project.getName()));
107+
projectColumn.setPrefWidth(100);
108+
TableColumn<TableRow, TableRow> timeColumn = new TableColumn<>("Time");
109+
timeColumn.setCellValueFactory(data -> new SimpleObjectProperty<>(data.getValue())); // Placeholder property
110+
111+
timeColumn.setCellFactory(column -> new TableCell<>() {
112+
private final Spinner<LocalTime> timeSpinner = new Spinner<>();
113+
private final Label keeptimeLabel = new Label();
114+
private final Label heimatLabel = new Label();
115+
116+
private final VBox container = new VBox(5); // Space between TextArea and Label
117+
118+
{
119+
setUpTimeSpinner(timeSpinner);
120+
container.getChildren().addAll(timeSpinner, keeptimeLabel, heimatLabel);
121+
}
122+
123+
@Override
124+
protected void updateItem(TableRow item, boolean empty) {
125+
super.updateItem(item, empty);
126+
127+
if (empty || item == null) {
128+
setGraphic(null);
129+
} else {
130+
keeptimeLabel.setText("KeepTime: " + localTimeStringConverter.toString(
131+
LocalTime.ofSecondOfDay(item.keeptimeTimeMinutes.get() * 60)));
132+
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+
});
138+
setGraphic(container);
139+
}
140+
}
141+
});
142+
timeColumn.setPrefWidth(125);
143+
144+
TableColumn<TableRow, TableRow> notesColumn = new TableColumn<>("Notes");
145+
notesColumn.setCellValueFactory(data -> new SimpleObjectProperty<>(data.getValue())); // Placeholder property
146+
147+
notesColumn.setCellFactory(column -> new TableCell<>() {
148+
private final TextArea textArea = new TextArea();
149+
private final Label label = new Label();
150+
private final VBox container = new VBox(5); // Space between TextArea and Label
151+
152+
{
153+
textArea.setPrefHeight(50);
154+
textArea.setPrefWidth(100);
155+
textArea.setWrapText(true);
156+
container.getChildren().addAll(textArea, label);
157+
}
158+
159+
@Override
160+
protected void updateItem(TableRow item, boolean empty) {
161+
super.updateItem(item, empty);
162+
163+
if (empty || item == null) {
164+
setGraphic(null);
165+
} else {
166+
textArea.setText(item.keeptimeNotes.get());
167+
textArea.textProperty().addListener((obs, oldText, newText) -> item.keeptimeNotes.set(newText));
168+
label.setText(item.heimatNotes.get());
169+
setGraphic(container);
170+
}
171+
}
172+
});
173+
notesColumn.setPrefWidth(250);
174+
175+
TableColumn<TableRow, String> syncColumn = new TableColumn<>("Sync Status");
176+
syncColumn.setCellValueFactory(data -> data.getValue().syncStatus);
177+
syncColumn.setPrefWidth(100);
178+
mappingTableView.getColumns().addAll(shouldSyncColumn, projectColumn, timeColumn, notesColumn, syncColumn);
179+
180+
saveButton.setOnAction((ae) -> {
181+
LOG.debug("New mappings to be synced '{}'.", "TODO");
182+
// TODO close
183+
});
184+
185+
cancelButton.setOnAction(ae -> {
186+
// TODO Close
187+
});
188+
}
189+
190+
private void setUpTimeSpinner(final Spinner<LocalTime> spinner) {
191+
spinner.focusedProperty().addListener((e) -> {
192+
final LocalTimeStringConverter stringConverter = new LocalTimeStringConverter(FormatStyle.MEDIUM);
193+
final StringProperty text = spinner.getEditor().textProperty();
194+
try {
195+
stringConverter.fromString(text.get());
196+
// needed to log in value from editor to spinner
197+
spinner.increment(0); // TODO find better Solution
198+
} catch (final DateTimeParseException ex) {
199+
text.setValue(spinner.getValue().toString());
200+
}
201+
});
202+
203+
spinner.setValueFactory(new SpinnerValueFactory<LocalTime>() {
204+
205+
@Override
206+
public void decrement(final int steps) {
207+
if (getValue() == null) {
208+
setValue(LocalTime.now());
209+
} else {
210+
final LocalTime time = getValue();
211+
setValue(time.minusMinutes(15 * steps));
212+
}
213+
214+
}
215+
216+
@Override
217+
public void increment(final int steps) {
218+
if (getValue() == null) {
219+
setValue(LocalTime.now());
220+
} else {
221+
final LocalTime time = getValue();
222+
setValue(time.plusMinutes(15 * steps));
223+
}
224+
225+
}
226+
227+
});
228+
229+
spinner.getValueFactory().setConverter(new LocalTimeStringConverter(FormatStyle.SHORT));
230+
231+
}
232+
233+
class TableRow {
234+
public BooleanProperty shouldSyncCheckBox;
235+
public Project project;
236+
237+
public StringProperty keeptimeNotes;
238+
public StringProperty userNotes;
239+
public StringProperty heimatNotes;
240+
241+
public IntegerProperty keeptimeTimeMinutes;
242+
public IntegerProperty userTimeMinutes;
243+
public IntegerProperty heimatTimeMinutes;
244+
245+
public StringProperty syncStatus;
246+
247+
}
248+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ protected void updateItem(HeimatTask item, boolean empty) {
140140
setGraphic(null);
141141
setText(null);
142142
} else {
143+
// TODO maybe show if the project was already mapped
143144
setText(item.projectName() + " - " + item.name());
144145
}
145146
}
@@ -190,6 +191,7 @@ protected void updateItem(HeimatTask item, boolean empty) {
190191

191192
externalProjectsMappingsRepository.saveAll(list);
192193
// TODO remove mappings which were removed also from database
194+
// TODO close
193195
});
194196

195197
cancelButton.setOnAction(ae -> {

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
import java.util.*;
2222
import java.util.stream.Collectors;
2323

24+
import javafx.scene.Parent;
25+
import javafx.scene.Scene;
26+
import javafx.scene.image.Image;
27+
import javafx.scene.input.KeyCode;
28+
import javafx.stage.Modality;
2429
import org.slf4j.Logger;
2530
import org.slf4j.LoggerFactory;
2631
import org.springframework.stereotype.Component;
@@ -58,6 +63,8 @@
5863
import javafx.stage.Stage;
5964
import javafx.util.Callback;
6065

66+
import static de.doubleslash.keeptime.view.ViewController.createFXMLLoader;
67+
6168
@Component
6269
public class ReportController {
6370

@@ -93,6 +100,9 @@ public class ReportController {
93100
@FXML
94101
private Button expandCollapseButton;
95102

103+
@FXML
104+
private Button heimatSyncButton;
105+
96106
private static final Logger LOG = LoggerFactory.getLogger(ReportController.class);
97107

98108
private final Model model;
@@ -123,8 +133,52 @@ private void initialize() {
123133

124134
expandCollapseButton.setOnMouseClicked(event ->toggleCollapseExpandReport());
125135
initTableView();
136+
initHeimatIntegration();
137+
}
138+
139+
private void initHeimatIntegration() {
140+
// TODO if heimat active show or hide button
141+
heimatSyncButton.setOnAction(ae-> {
142+
try {
143+
showSyncStage();
144+
} catch (IOException e) {
145+
throw new RuntimeException(e);
146+
}
147+
});
126148
}
127149

150+
private void showSyncStage() throws IOException {
151+
try{
152+
// Settings stage
153+
final FXMLLoader fxmlLoader2 = createFXMLLoader(RESOURCE.FXML_EXT_PROJECT_SYNC);
154+
fxmlLoader2.setControllerFactory(model.getSpringContext()::getBean);
155+
final Parent settingsRoot = fxmlLoader2.load();
156+
ExternalProjectsSyncController settingsController = fxmlLoader2.getController();
157+
Stage settingsStage = new Stage();
158+
//settingsController.setStage(settingsStage);
159+
settingsStage.initModality(Modality.APPLICATION_MODAL);
160+
settingsStage.setTitle("External Project Sync");
161+
settingsStage.setResizable(false);
162+
settingsStage.getIcons().add(new Image(Resources.getResource(RESOURCE.ICON_MAIN).toString()));
163+
164+
final Scene settingsScene = new Scene(settingsRoot);
165+
settingsScene.setOnKeyPressed(ke -> {
166+
if (ke.getCode() == KeyCode.ESCAPE) {
167+
LOG.info("pressed ESCAPE");
168+
settingsStage.close();
169+
}
170+
});
171+
172+
settingsStage.setScene(settingsScene);
173+
settingsStage.showAndWait();
174+
//settingsStage.setOnHiding(e -> this.mainStage.setAlwaysOnTop(true));
175+
} catch (final IOException e) {
176+
LOG.error("Error while loading sub stage");
177+
throw new FXMLLoaderException(e);
178+
}
179+
}
180+
181+
128182
private void initTableView() {
129183
final TreeTableColumn<TableRow, TableRow> noteColumn = new TreeTableColumn<>("Notes");
130184
noteColumn.setCellFactory(new Callback<TreeTableColumn<TableRow, TableRow>, TreeTableCell<TableRow, TableRow>>() {

0 commit comments

Comments
 (0)