Skip to content

Commit c4e4e67

Browse files
authored
Merge pull request #46 from doubleSlashde/feature/rework_work_items_in_report_ui
Feature/rework work items in report ui
2 parents 293f073 + 3201229 commit c4e4e67

File tree

6 files changed

+248
-148
lines changed

6 files changed

+248
-148
lines changed

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

Lines changed: 121 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -31,37 +31,42 @@
3131
import com.sun.javafx.scene.control.skin.DatePickerSkin;
3232

3333
import de.doubleslash.keeptime.common.DateFormatter;
34-
import de.doubleslash.keeptime.common.FontProvider;
3534
import de.doubleslash.keeptime.common.Resources;
3635
import de.doubleslash.keeptime.common.Resources.RESOURCE;
3736
import de.doubleslash.keeptime.controller.Controller;
3837
import de.doubleslash.keeptime.exceptions.FXMLLoaderException;
3938
import de.doubleslash.keeptime.model.Model;
4039
import de.doubleslash.keeptime.model.Project;
4140
import de.doubleslash.keeptime.model.Work;
41+
import de.doubleslash.keeptime.view.worktable.ProjectTableRow;
42+
import de.doubleslash.keeptime.view.worktable.TableRow;
43+
import de.doubleslash.keeptime.view.worktable.WorkTableRow;
44+
import javafx.beans.property.ReadOnlyObjectWrapper;
4245
import javafx.event.ActionEvent;
46+
import javafx.event.EventHandler;
4347
import javafx.fxml.FXML;
4448
import javafx.fxml.FXMLLoader;
45-
import javafx.geometry.Insets;
46-
import javafx.geometry.Pos;
4749
import javafx.scene.Node;
4850
import javafx.scene.canvas.Canvas;
4951
import javafx.scene.control.Button;
5052
import javafx.scene.control.ButtonType;
51-
import javafx.scene.control.ContextMenu;
5253
import javafx.scene.control.DateCell;
5354
import javafx.scene.control.DatePicker;
5455
import javafx.scene.control.Dialog;
5556
import javafx.scene.control.Label;
56-
import javafx.scene.control.MenuItem;
57-
import javafx.scene.control.ScrollPane;
57+
import javafx.scene.control.TreeItem;
58+
import javafx.scene.control.TreeTableCell;
59+
import javafx.scene.control.TreeTableColumn;
60+
import javafx.scene.control.TreeTableView;
61+
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
5862
import javafx.scene.input.Clipboard;
5963
import javafx.scene.input.ClipboardContent;
6064
import javafx.scene.layout.AnchorPane;
6165
import javafx.scene.layout.BorderPane;
6266
import javafx.scene.layout.GridPane;
6367
import javafx.scene.layout.HBox;
6468
import javafx.scene.shape.Circle;
69+
import javafx.scene.text.Text;
6570
import javafx.stage.Stage;
6671
import javafx.util.Callback;
6772

@@ -86,10 +91,7 @@ public class ReportController {
8691
private Label currentDayTimeLabel;
8792

8893
@FXML
89-
private GridPane gridPane;
90-
91-
@FXML
92-
private ScrollPane scrollPane;
94+
private TreeTableView<TableRow> workTableTreeView;
9395

9496
@FXML
9597
private AnchorPane reportRoot;
@@ -113,120 +115,115 @@ public class ReportController {
113115
private void initialize() {
114116
LOG.info("Init reportController");
115117
currentReportDate = LocalDate.now();
116-
118+
initTableView();
117119
colorTimeLine = new ColorTimeLine(colorTimeLineCanvas);
118120
}
119121

122+
private void initTableView() {
123+
final TreeTableColumn<TableRow, TableRow> noteColumn = new TreeTableColumn<>("Notes");
124+
noteColumn.setCellFactory(new Callback<TreeTableColumn<TableRow, TableRow>, TreeTableCell<TableRow, TableRow>>() {
125+
@Override
126+
public TreeTableCell<TableRow, TableRow> call(final TreeTableColumn<TableRow, TableRow> column) {
127+
return new TreeTableCell<TableRow, TableRow>() {
128+
@Override
129+
protected void updateItem(final TableRow item, final boolean empty) {
130+
super.updateItem(item, empty);
131+
if (item == null || empty) {
132+
setGraphic(null);
133+
setText(null);
134+
} else {
135+
final Text text = new Text(item.getNotes());
136+
text.wrappingWidthProperty().bind(noteColumn.widthProperty().subtract(35));
137+
text.setUnderline(item.isUnderlined());
138+
this.setGraphic(text);
139+
}
140+
}
141+
};
142+
}
143+
144+
});
145+
noteColumn.setCellValueFactory(
146+
(final TreeTableColumn.CellDataFeatures<TableRow, TableRow> entry) -> new ReadOnlyObjectWrapper<>(
147+
entry.getValue().getValue()));
148+
noteColumn.setMinWidth(200);
149+
noteColumn.impl_setReorderable(false);
150+
this.workTableTreeView.getColumns().add(noteColumn);
151+
152+
final TreeTableColumn<TableRow, String> timeRangeColumn = new TreeTableColumn<>("Timeslot");
153+
timeRangeColumn.setCellValueFactory(new TreeItemPropertyValueFactory<TableRow, String>("timeRange"));
154+
timeRangeColumn.setMinWidth(120);
155+
timeRangeColumn.impl_setReorderable(false);
156+
this.workTableTreeView.getColumns().add(timeRangeColumn);
157+
158+
final TreeTableColumn<TableRow, String> timeSumColumn = new TreeTableColumn<>("Duration");
159+
timeSumColumn.setCellValueFactory(new TreeItemPropertyValueFactory<TableRow, String>("timeSum"));
160+
timeSumColumn.setMinWidth(60);
161+
timeSumColumn.impl_setReorderable(false);
162+
this.workTableTreeView.getColumns().add(timeSumColumn);
163+
164+
final TreeTableColumn<TableRow, Button> buttonColumn = new TreeTableColumn<>("Controls");
165+
buttonColumn.setCellValueFactory(new TreeItemPropertyValueFactory<TableRow, Button>("buttonBox"));
166+
buttonColumn.setMinWidth(100);
167+
buttonColumn.setSortable(false);
168+
buttonColumn.impl_setReorderable(false);
169+
this.workTableTreeView.getColumns().add(buttonColumn);
170+
171+
workTableTreeView.setShowRoot(false);
172+
173+
}
174+
120175
private void updateReport(final LocalDate dateToShow) {
121176
this.currentReportDate = dateToShow;
122177
this.loadCalenderWidget();
123178
reportRoot.requestFocus();
124179

125-
this.currentDayLabel.setText(DateFormatter.toDayDateString(dateToShow));
126-
final List<Work> currentWorkItems = model.getWorkRepository().findByCreationDateOrderByStartTimeAsc(dateToShow);
180+
this.currentDayLabel.setText(DateFormatter.toDayDateString(this.currentReportDate));
181+
final List<Work> currentWorkItems = model.getWorkRepository()
182+
.findByCreationDateOrderByStartTimeAsc(this.currentReportDate);
127183

128184
colorTimeLine.update(currentWorkItems, controller.calcSeconds(currentWorkItems));
129185

130186
final SortedSet<Project> workedProjectsSet = currentWorkItems.stream().map(Work::getProject)
131-
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Project::getName))));
132-
133-
this.gridPane.getChildren().clear();
134-
this.gridPane.getRowConstraints().clear();
135-
this.gridPane.getColumnConstraints().get(0).setPrefWidth(300);
187+
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Project::getIndex))));
136188

137-
int rowIndex = 0;
138189
long currentWorkSeconds = 0;
139190
long currentSeconds = 0;
140191

141-
for (final Project project : workedProjectsSet) {
142-
final Label projectName = new Label(project.getName());
143-
projectName.setFont(FontProvider.getBoldFont());
144-
projectName.setUnderline(project.isWork());
145-
final Circle circle = new Circle(5, project.getColor());
146-
147-
final HBox projectNameHBox = new HBox();
148-
projectNameHBox.setAlignment(Pos.CENTER_LEFT);
149-
projectNameHBox.setPadding(new Insets(0, 0, 0, 5));
150-
projectNameHBox.setSpacing(5);
151-
152-
projectNameHBox.getChildren().add(circle);
153-
projectNameHBox.getChildren().add(projectName);
154-
155-
this.gridPane.add(projectNameHBox, 0, rowIndex);
192+
final TreeItem<TableRow> root = new TreeItem<>();
156193

194+
for (final Project project : workedProjectsSet) {
157195
final List<Work> onlyCurrentProjectWork = currentWorkItems.stream().filter(w -> w.getProject() == project)
158196
.collect(Collectors.toList());
159197

160-
final long todaysWorkSeconds = controller.calcSeconds(onlyCurrentProjectWork);
198+
final long projectWorkSeconds = controller.calcSeconds(onlyCurrentProjectWork);
161199

162-
currentSeconds += todaysWorkSeconds;
200+
currentSeconds += projectWorkSeconds;
163201
if (project.isWork()) {
164-
currentWorkSeconds += todaysWorkSeconds;
202+
currentWorkSeconds += projectWorkSeconds;
165203
}
166204

167-
final Label workedTimeLabel = new Label(DateFormatter.secondsToHHMMSS(todaysWorkSeconds));
168-
workedTimeLabel.setFont(FontProvider.getBoldFont());
169-
this.gridPane.add(workedTimeLabel, 2, rowIndex);
170-
171-
// text will be set later
172-
final Button bProjectReport = createProjectReport();
173-
this.gridPane.add(bProjectReport, 1, rowIndex);
174-
175-
rowIndex++;
176-
177-
final ProjectReport pr = new ProjectReport(onlyCurrentProjectWork.size());
178-
for (int j = 0; j < onlyCurrentProjectWork.size(); j++) {
179-
final Work work = onlyCurrentProjectWork.get(j);
180-
final String workedHours = DateFormatter
181-
.secondsToHHMMSS(DateFormatter.getSecondsBewtween(work.getStartTime(), work.getEndTime()));
182-
183-
final String currentWorkNote = work.getNotes();
184-
pr.appendToWorkNotes(currentWorkNote);
185-
final Label commentLabel = new Label(currentWorkNote);
186-
commentLabel.setFont(FontProvider.getDefaultFont());
187-
commentLabel.setWrapText(true);
188-
this.gridPane.add(commentLabel, 0, rowIndex);
189-
190-
final Label fromTillLabel = new Label(DateFormatter.toTimeString(work.getStartTime()) + " - "
191-
+ DateFormatter.toTimeString(work.getEndTime()));
192-
fromTillLabel.setFont(FontProvider.getDefaultFont());
193-
fromTillLabel.setWrapText(true);
194-
this.gridPane.add(fromTillLabel, 1, rowIndex);
195-
196-
final Label workedHoursLabel = new Label(workedHours);
197-
workedHoursLabel.setFont(FontProvider.getDefaultFont());
198-
this.gridPane.add(workedHoursLabel, 2, rowIndex);
205+
final HBox projectButtonBox = new HBox();
206+
projectButtonBox.getChildren().add(createProjectReportButton(onlyCurrentProjectWork));
199207

200-
final HBox clickDummy = new HBox();
201-
final ContextMenu contextMenu = new ContextMenu();
202-
final MenuItem editMenuItem = new MenuItem("edit");
208+
final Circle circle = new Circle(6, project.getColor());
203209

204-
editMenuItem.setOnAction(e -> {
205-
LOG.info(EDIT_WORK_DIALOG_TITLE);
206-
final Dialog<Work> dialog = setupEditWorkDialog(work);
210+
final TreeItem<TableRow> projectRow = new TreeItem<>(
211+
new ProjectTableRow(project, projectWorkSeconds, projectButtonBox), circle);
207212

208-
final Optional<Work> result = dialog.showAndWait();
209-
210-
result.ifPresent(editedWork -> {
211-
controller.editWork(work, editedWork);
212-
213-
this.update();
214-
});
215-
});
216-
217-
contextMenu.getItems().add(editMenuItem);
218-
219-
clickDummy.setOnContextMenuRequested(
220-
event -> contextMenu.show(clickDummy, event.getScreenX(), event.getScreenY()));
213+
for (final Work w : onlyCurrentProjectWork) {
214+
final HBox workButtonBox = new HBox();
215+
workButtonBox.getChildren().add(createEditWorkButton(w));
216+
final TreeItem<TableRow> workRow = new TreeItem<>(new WorkTableRow(w, workButtonBox));
217+
projectRow.getChildren().add(workRow);
218+
}
221219

222-
this.gridPane.add(clickDummy, 0, rowIndex, 3, 1);
220+
projectRow.setExpanded(true);
221+
root.getChildren().add(projectRow);
223222

224-
rowIndex++;
225-
}
226-
bProjectReport.setUserData(pr.getNotes(true));
227223
}
228-
this.scrollPane.setVvalue(0); // scroll to the top
229224

225+
root.setExpanded(true);
226+
workTableTreeView.setRoot(root);
230227
this.currentDayTimeLabel.setText(DateFormatter.secondsToHHMMSS(currentSeconds));
231228
this.currentDayWorkTimeLabel.setText(DateFormatter.secondsToHHMMSS(currentWorkSeconds));
232229

@@ -260,6 +257,23 @@ public void updateItem(final LocalDate item, final boolean empty) {
260257

261258
}
262259

260+
private Button createEditWorkButton(final Work work) {
261+
final Button bProjectReport = new Button("edit");
262+
bProjectReport.setOnAction(e -> {
263+
LOG.info("Edit work clicked.");
264+
final Dialog<Work> dialog = setupEditWorkDialog(work);
265+
266+
final Optional<Work> result = dialog.showAndWait();
267+
268+
result.ifPresent(editedWork -> {
269+
controller.editWork(work, editedWork);
270+
271+
this.update();
272+
});
273+
});
274+
return bProjectReport;
275+
}
276+
263277
private Dialog<Work> setupEditWorkDialog(final Work work) {
264278
final Dialog<Work> dialog = new Dialog<>();
265279
dialog.initOwner(stage);
@@ -295,27 +309,29 @@ private GridPane setUpEditWorkGridPane(final Work work, final Dialog<Work> dialo
295309
return grid;
296310
}
297311

298-
private Button createProjectReport() {
312+
private Button createProjectReportButton(final List<Work> projectWork) {
299313
final Button bProjectReport = new Button("Copy to clipboard");
300-
301-
bProjectReport.setOnAction((final ActionEvent event) -> {
302-
final Object source = event.getSource();
303-
final Button btn = (Button) source;
304-
final Object userData = btn.getUserData();
305-
final String notes = (String) userData;
306-
314+
final EventHandler<ActionEvent> eventListener = actionEvent -> {
315+
LOG.debug("Copy to Clipboard clicked.");
316+
final ProjectReport pr = new ProjectReport(projectWork.size());
317+
for (int j = 0; j < projectWork.size(); j++) {
318+
final Work work = projectWork.get(j);
319+
final String currentWorkNote = work.getNotes();
320+
pr.appendToWorkNotes(currentWorkNote);
321+
}
307322
final Clipboard clipboard = Clipboard.getSystemClipboard();
308323
final ClipboardContent content = new ClipboardContent();
309-
content.putString(notes);
324+
content.putString(pr.getNotes(true));
310325
clipboard.setContent(content);
311-
});
326+
};
327+
328+
bProjectReport.setOnAction(eventListener);
312329
return bProjectReport;
330+
313331
}
314332

315333
public void setModel(final Model model) {
316334
this.model = model;
317-
318-
this.loadCalenderWidget();
319335
}
320336

321337
public void update() {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package de.doubleslash.keeptime.view.worktable;
2+
3+
import de.doubleslash.keeptime.common.DateFormatter;
4+
import de.doubleslash.keeptime.model.Project;
5+
import javafx.scene.layout.HBox;
6+
7+
public class ProjectTableRow implements TableRow {
8+
9+
private final Project project;
10+
private final long projectWorkSeconds;
11+
private final HBox buttonBox;
12+
13+
public ProjectTableRow(final Project project, final long projectWorkSeconds, final HBox buttonBox) {
14+
this.projectWorkSeconds = projectWorkSeconds;
15+
this.project = project;
16+
this.buttonBox = buttonBox;
17+
}
18+
19+
@Override
20+
public String getNotes() {
21+
return project.getName();
22+
}
23+
24+
@Override
25+
public String getTimeRange() {
26+
return null;
27+
28+
}
29+
30+
@Override
31+
public String getTimeSum() {
32+
return DateFormatter.secondsToHHMMSS(projectWorkSeconds);
33+
34+
}
35+
36+
@Override
37+
public HBox getButtonBox() {
38+
return buttonBox;
39+
}
40+
41+
@Override
42+
public boolean isUnderlined() {
43+
return project.isWork();
44+
}
45+
46+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package de.doubleslash.keeptime.view.worktable;
2+
3+
import javafx.scene.layout.HBox;
4+
5+
public interface TableRow {
6+
7+
public String getNotes();
8+
9+
public String getTimeRange();
10+
11+
public String getTimeSum();
12+
13+
public HBox getButtonBox();
14+
15+
public boolean isUnderlined();
16+
17+
}

0 commit comments

Comments
 (0)