Skip to content
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Application to track your time spent on different projects each day. Aim was to

Create projects and choose if they are counted as 'work time'. Select the project you work on. Before you switch the project, write a comment on what u did. Change the project. Repeat.

KeepTime can connect to [Heimat](https://heimat-software.com/) so you can import Heimat projects and sync your tracked time back to Heimat. Details: [heimat.md](readme/heimat.md).

## Usage

### Main view:
Expand All @@ -29,9 +31,25 @@ Create projects and choose if they are counted as 'work time'. Select the projec
+ Export: export database for backup and later import (import currently not yet implemented)

### 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.

5. **Synchronize Projects with Heimat**
If you want you can syncronize the current date with Heimat. See [heimat.md](readme/heimat.md)

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

## Install

Expand Down
53 changes: 53 additions & 0 deletions readme/heimat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Heimat integration

[Heimat](https://heimat-software.com/) is external project and time-tracking software. KeepTime can connect to it so you can:

- **Import** projects from Heimat into KeepTime, or **link** Heimat projects to projects you already have in KeepTime.
- **Push** the time you logged in KeepTime for a given day into Heimat when you choose to sync.

**Important:** KeepTime never writes to Heimat on its own. Data is sent to Heimat only when you run the sync from the report view.

---

## 1. Connect KeepTime to Heimat

1. Open **Settings** and go to the **HEIMAT** section.

![Settings Heimat](images/settingsHeimat.png "Settings Heimat")

2. Fill in:

| Field | What to enter |
| ---------------- | -------------------------------------------------------------------------------------------------------------------- |
| **URL** | Your Heimat instance URL (the same base URL you use in the browser), for example `https://your-company.example.com`. |
| **Access token** | Create or copy a token in Heimat (under your account or API settings) and paste it here. |

![Heimat Website](images/heimatWebsite.png "Heimat Website")

3. Click **Validate connection**. If it succeeds, the integration is ready to use.

---

## 2. Match Heimat projects to KeepTime

After a successful connection, use **Map projects** (button in Heimat settings dialog) to either:

- Map each Heimat project to an existing KeepTime project, or
- Import Heimat projects as new KeepTime projects.

Do this once (or whenever your project lists change) so KeepTime knows where each Heimat project should go.

---

## 3. Sync a day’s work to Heimat

When you want to upload time for the **currently selected day** in the report:

1. Open the **report** view for that day.
2. Click the **sync** button (Heimat / external sync).
3. In the dialog, choose which projects to include and add any **note** you want stored with the sync.
4. Click **Sync** to send that day’s tracked time to Heimat.

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

Until you complete step 4, nothing is written to Heimat.
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 @@ -237,7 +237,7 @@ public List<Mapping> getTableRows(final LocalDate currentReportDate, final List<
long heimatTimeSeconds = addHeimatTimes(times);

StyledMessage syncMessage = StyledMessage.of(
new StyledMessage.TextSegment("Present in HEIMAT but not KeepTime\n\nSync to "),
new StyledMessage.TextSegment("Present in Heimat but not KeepTime\n\nSync to "),
new StyledMessage.TextSegment(externalProjectMapping.getExternalTaskName(), true),
new StyledMessage.TextSegment("\n(" + externalProjectMapping.getExternalProjectName() + ")"));

Expand Down Expand Up @@ -408,7 +408,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 @@ -325,7 +325,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 @@ -384,7 +384,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 @@ -144,7 +144,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
8 changes: 4 additions & 4 deletions src/main/resources/layouts/externalProjectSync.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<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 @@ -22,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 @@ -63,7 +63,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
7 changes: 4 additions & 3 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>
<totalSizeCap>3GB</totalSizeCap>
<maxHistory>7</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<totalSizeCap>10MB</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().toPlainText(), Matchers.containsString("Present in HEIMAT but not KeepTime")),
() -> assertThat(mapping.syncMessage().toPlainText(), Matchers.containsString("Present in Heimat but not KeepTime")),
() -> assertThat(mapping.syncMessage().toPlainText(), Matchers.containsString(project1To1Mapping.getExternalTaskName())),
() -> assertThat(mapping.keeptimeSeconds(), Matchers.is(0L)),
() -> assertThat(mapping.keeptimeNotes(), Matchers.is("")),
Expand Down