Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,48 @@ Create projects and choose if they are counted as 'work time'. Select the projec
+ Ask for notes when switching project (if empty): Pops up a dialog to add notes if no notes are given and you try to switch projects
+ Export: export database for backup and later import (import currently not yet implemented)

#### Heimat Integration

KeepTime can be integrated with [Heimat](https://doubleslash.heimat.software), allowing you to map and import your Heimat projects directly into KeepTime.

To enable Heimat integration:

1. In the Settings, switch to the "Heimat" section.
![Settings Heimat](readme/images/settingsHeimat.png?raw=true "Settings Heimat")
1. Enter the following details:
- **URL:** `https://your-heimat-domain-com`
- **Access Token:** Retrieve your access token from Heimat and paste it here.
![Heimat Website](readme/images/heimatWebsite.png?raw=true "Heimat Website")
1. Click on **"Validate connection"**. If the connection is valid, you can proceed.

Once the connection is validated, you can use the **"Map projects"** feature to map your Heimat projects to KeepTime projects or import Heimat projects directly into KeepTime.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in report controller click the sync button to sync the current day to heimat. (maybe with a screenshot - as the button is somewhat "hidden". ) you can adapt the time and description per task in an extra dialog before syncing to heimat.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done



### Reports:
![Report Screen](readme/images/reportDescription.png?raw=true "Report")

+ the report screen gives you a summary for every day
KeepTime’s reporting workflow works by tracking daily work and synchronizing project activities with external task management. Here’s how you use it:

1. **Track Your Work**
Throughout your workday, log activities and assign them to projects. Each entry includes a timeslot, duration, and optional notes.

2. **Review Your Daily Report**
At any time, open the report view to see an overview of your day, including all logged activities, their durations, and project associations.

3. **Edit and Manage Entries**
Use the controls in the report to copy, edit, or delete individual work entries as needed. You can also copy notes or summaries for external use.

4. **Select the Day to View**
Use the calendar widget to select which day’s report you want to review. Only days with recorded work are available.

![Report Dialog](readme/images/reportDescription.png)

5. **Synchronize Projects with Heimat**
When ready to sync your work with Heimat:
- Open the project synchronization view.
- Select which projects to include in the synchronization.
- Press the Sync button to transfer your tracked work to Heimat.

![External Project Dialog](readme/images/externalProjectDialog.png)

## Install

Expand Down
Binary file added readme/images/externalProjectDialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added readme/images/heimatWebsite.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified readme/images/reportDescription.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added readme/images/settingsHeimat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public void tryLogin() {
try {
heimatAPI.isLoginValid();
} catch (Exception e) {
throw new SecurityException("Could not connect to HEIMAT API. Maybe wrong configuration?", e);
throw new SecurityException("Could not connect to Heimat API. Maybe wrong configuration?", e);
}
}

Expand Down Expand Up @@ -222,7 +222,7 @@ public List<Mapping> getTableRows(final LocalDate currentReportDate, final List<
long heimatTimeSeconds = addHeimatTimes(times);

final Mapping mapping2 = new Mapping(id, true, false,
"Present in HEIMAT but not KeepTime\n\nSync to " + externalProjectMapping.getExternalTaskName() + "\n("
"Present in Heimat but not KeepTime\n\nSync to " + externalProjectMapping.getExternalTaskName() + "\n("
+ externalProjectMapping.getExternalProjectName() + ")", times, mappedProjects.stream()
.filter(
mp -> mp.getExternalTaskId()
Expand Down Expand Up @@ -390,7 +390,7 @@ public ExistingAndInvalidMappings getExistingProjectMappings(List<HeimatTask> ex
.filter(ep -> ep.id() == mapping.get().getExternalTaskId())
.findAny();
if (any.isEmpty()) {
LOG.warn("A mapping exists but task does not exist anymore in HEIMAT! '{}'->'{}'.",
LOG.warn("A mapping exists but task does not exist anymore in Heimat! '{}'->'{}'.",
mapping.get().getProject(), mapping.get().getExternalTaskId());
invalidExternalMappings.add(mapping.get());
return new ProjectMapping(p, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private void initialize() {
externalProjects);
externalProjectsObservableList.add(0, null); // option to clear selection

TableColumn<HeimatController.ProjectMapping, HeimatTask> externalColumn = new TableColumn<>("HEIMAT project");
TableColumn<HeimatController.ProjectMapping, HeimatTask> externalColumn = new TableColumn<>("Heimat project");
externalColumn.setCellValueFactory(data -> new SimpleObjectProperty<>(data.getValue().getHeimatTask()));
externalColumn.setCellFactory(col -> new TableCell<>() {
// TODO search in box would be nice
Expand Down Expand Up @@ -199,7 +199,7 @@ protected void updateItem(HeimatTask item, boolean empty) {
private List<HeimatTask> showMultiSelectDialog(final List<HeimatTask> externalProjects,
List<HeimatTask> unmappedHeimatTasks) {
Dialog<List<HeimatTask>> dialog = new Dialog<>();
dialog.setTitle("Import HEIMAT projects");
dialog.setTitle("Import Heimat projects");
dialog.setHeaderText("You can select mutliple items");
dialog.initOwner(this.thisStage);
dialog.setWidth(600);
Expand All @@ -211,7 +211,7 @@ private List<HeimatTask> showMultiSelectDialog(final List<HeimatTask> externalPr
dialog.getDialogPane().getButtonTypes().addAll(okButtonType, cancelButtonType);

TableView<HeimatTask> tableView = new TableView<>();
TableColumn<HeimatTask, HeimatTask> nameColumn = new TableColumn<>("HEIMAT project");
TableColumn<HeimatTask, HeimatTask> nameColumn = new TableColumn<>("Heimat project");
nameColumn.setCellValueFactory(data -> new SimpleObjectProperty<>(data.getValue()));
nameColumn.setCellFactory(param -> new TableCell<>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ protected void updateItem(TableRow item, boolean empty) {
} else {
keeptimeLabel.setText("KeepTime: " + localTimeStringConverter.toString(
LocalTime.ofSecondOfDay(item.keeptimeTimeSeconds.get())));
heimatLabel.setText("HEIMAT: " + localTimeStringConverter.toString(
heimatLabel.setText("Heimat: " + localTimeStringConverter.toString(
LocalTime.ofSecondOfDay(item.heimatTimeSeconds.get())));
timeSpinner.setDisable(!item.mapping.canBeSynced());
timeSpinner.getValueFactory().setValue(LocalTime.ofSecondOfDay(0));
Expand Down Expand Up @@ -379,7 +379,7 @@ protected void updateItem(TableRow item, boolean empty) {
final Label keeptimeLabel = new Label("KeepTime:");
keeptimeLabel.setMinWidth(60);
hbox.getChildren().addAll(copyKeepTimeNotes, keeptimeLabel, keepTimeNotesLabel);
final Label heimatLabel = new Label("HEIMAT:");
final Label heimatLabel = new Label("Heimat:");
heimatLabel.setMinWidth(60);
hbox2.getChildren().addAll(copyHeimatNotes, heimatLabel, heimatNotesLabel);
container.getChildren().addAll(textArea, hbox, hbox2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private void initHeimatIntegration() {
heimatSyncButton.setMinSize(25, 25);
heimatSyncButton.setGraphic(svgNodeWithScale);
heimatSyncButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
heimatSyncButton.setTooltip(new Tooltip("Synchronize to HEIMAT..."));
heimatSyncButton.setTooltip(new Tooltip("Synchronize to Heimat..."));
heimatSyncButton.setOnAction(ae-> {
try {
showSyncStage();
Expand Down
24 changes: 8 additions & 16 deletions src/main/resources/layouts/externalProjectSync.fxml
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<StackPane style="-fx-background-color: white;" stylesheets="@../css/dsStyles.css" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.doubleslash.keeptime.view.ExternalProjectsSyncController">
<StackPane style="-fx-background-color: white;" stylesheets="@../css/dsStyles.css" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.doubleslash.keeptime.view.ExternalProjectsSyncController">
<children>
<AnchorPane fx:id="pane" prefHeight="600.0" prefWidth="1000.0">
<children>
<TableView fx:id="mappingTableView" layoutY="50.0" prefHeight="603.0" prefWidth="1112.0" AnchorPane.bottomAnchor="80.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="35.0" />
<Hyperlink fx:id="externalSystemLink" layoutX="29.0" layoutY="723.0" text="Open day in HEIMAT" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0" />
<Hyperlink fx:id="externalSystemLink" layoutX="29.0" layoutY="723.0" text="Open day in Heimat" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0" />
<HBox spacing="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0">
<children>
<Label text="Day to sync" />
Expand All @@ -30,13 +22,13 @@
<children>
<HBox spacing="5.0">
<children>
<Label text="New HEIMAT time:" />
<Label text="New Heimat time:" />
<Label fx:id="sumTimeLabel" alignment="CENTER_RIGHT" contentDisplay="RIGHT" text="00:00" />
</children>
</HBox>
<HBox spacing="5.0">
<children>
<Label text="Current HEIMAT time:" />
<Label text="Current Heimat time:" />
<Label fx:id="heimatTimeLabel" text="00:00" />
</children>
</HBox>
Expand Down Expand Up @@ -76,7 +68,7 @@
<Font size="18.0" />
</font>
</Label>
<Hyperlink fx:id="externalSystemLinkLoadingScreen" text="Open day in HEIMAT" />
<Hyperlink fx:id="externalSystemLinkLoadingScreen" text="Open day in Heimat" />
<Label fx:id="loadingClosingMessage" alignment="CENTER" contentDisplay="CENTER" layoutX="473.0" layoutY="307.0" text="Closing in..." textAlignment="CENTER">
<font>
<Font size="18.0" />
Expand Down
34 changes: 10 additions & 24 deletions src/main/resources/layouts/settings.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,15 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. -->

<?import java.lang.String?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.Group?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ColorPicker?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Line?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>

<AnchorPane fx:id="settingsRoot" focusTraversable="true" prefHeight="285.0" prefWidth="514.0" style="-fx-background-color: white;" stylesheets="@../css/dsStyles.css" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.doubleslash.keeptime.view.SettingsController">
<AnchorPane fx:id="settingsRoot" focusTraversable="true" prefHeight="285.0" prefWidth="514.0" style="-fx-background-color: white;" stylesheets="@../css/dsStyles.css" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.doubleslash.keeptime.view.SettingsController">
<children>
<TabPane layoutX="-50.0" layoutY="3.0" prefHeight="383.0" prefWidth="608.0" rotateGraphic="true" side="LEFT" stylesheets="@../css/settingsv2.css" tabClosingPolicy="UNAVAILABLE">
<tabs>
Expand Down Expand Up @@ -464,7 +450,7 @@
<children>
<VBox styleClass="settingsBorder" stylesheets="@../css/settingsv2.css">
<children>
<Label prefHeight="28.0" prefWidth="279.0" text="HEIMAT Integration">
<Label prefHeight="28.0" prefWidth="279.0" text="Heimat Integration">
<font>
<Font name="System Bold" size="13.0" />
</font>
Expand All @@ -488,7 +474,7 @@
<children>
<VBox styleClass="settingsBorder" stylesheets="@../css/settingsv2.css">
<children>
<CheckBox fx:id="heimatActivationCheckbox" mnemonicParsing="false" text="Use HEIMAT">
<CheckBox fx:id="heimatActivationCheckbox" mnemonicParsing="false" text="Use Heimat">
<font>
<Font name="Open Sans Regular" size="12.0" />
</font>
Expand Down
5 changes: 3 additions & 2 deletions src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
<appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/log-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<maxHistory>7</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>

<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="rollingFile" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ void shouldShowHeimatTimeWhenProjectIsMappedInKeeptimeButNoWorkAtThatDay() {
// ASSERT
assertAll(() -> assertThat(tableRows.size(), Matchers.is(1)), () -> assertTrue(mapping.canBeSynced()),
() -> assertTrue(mapping.canBeSynced()), () -> assertFalse(mapping.shouldBeSynced()),
() -> assertThat(mapping.syncMessage(), Matchers.containsString("Present in HEIMAT but not KeepTime")),
() -> assertThat(mapping.syncMessage(), Matchers.containsString("Present in Heimat but not KeepTime")),
() -> assertThat(mapping.syncMessage(), Matchers.containsString(project1To1Mapping.getExternalTaskName())),
() -> assertThat(mapping.keeptimeSeconds(), Matchers.is(0L)),
() -> assertThat(mapping.keeptimeNotes(), Matchers.is("")),
Expand Down