diff --git a/src/components/TimesheetStart.tsx b/src/components/TimesheetStart.tsx
index 8261903..b189e6e 100644
--- a/src/components/TimesheetStart.tsx
+++ b/src/components/TimesheetStart.tsx
@@ -7,6 +7,7 @@ import { useTimekeepStore } from "@/contexts/use-timekeep-store";
import {
withEntry,
isKeepRunning,
+ getPathToEntry,
getRunningEntry,
stopRunningEntries,
} from "@/timekeep";
@@ -29,6 +30,14 @@ export default function TimekeepStart() {
[timekeep]
);
+ const pathToEntry = useMemo(
+ () =>
+ currentEntry
+ ? getPathToEntry(timekeep.entries, currentEntry)
+ : null,
+ [timekeep, currentEntry]
+ );
+
const isTimekeepRunning = currentEntry !== null;
const onStart = (event: FormEvent) => {
@@ -86,7 +95,20 @@ export default function TimekeepStart() {
- Name: {currentEntry.name}
+ Name: {" "}
+ {pathToEntry && pathToEntry.length > 0 && (
+
+ {pathToEntry.map((path, index) => (
+
+ {path.name}
+
+ {index <
+ pathToEntry.length - 1 &&
+ " >"}
+
+ ))}
+
+ )}
{"Started at: "}
diff --git a/src/styles.css b/src/styles.css
index 97820fc..54ea10f 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -224,3 +224,8 @@
color: #777;
margin-left: 0.5rem;
}
+
+.timekeep-path-to-entry {
+ display: inline-flex;
+ gap: 0.5rem;
+}
diff --git a/src/timekeep/__fixtures__/checking/findRunningEntryPath.ts b/src/timekeep/__fixtures__/checking/findRunningEntryPath.ts
new file mode 100644
index 0000000..87588b8
--- /dev/null
+++ b/src/timekeep/__fixtures__/checking/findRunningEntryPath.ts
@@ -0,0 +1,51 @@
+import moment from "moment";
+
+export const currentTime = moment();
+
+export const runningEntry = {
+ id: "9054dee3-8c15-493b-ad31-f070e08c2699",
+ name: "Running Entry",
+ startTime: currentTime,
+ endTime: null,
+ subEntries: null,
+};
+
+export const input = [
+ {
+ id: "8054dee3-8c15-493b-ad31-f070e08c2699",
+ name: "Block 1",
+ startTime: null,
+ endTime: null,
+ subEntries: [
+ {
+ id: "7054dee3-8c15-493b-ad31-f070e08c2699",
+ name: "Part 1",
+ startTime: currentTime,
+ endTime: currentTime,
+ subEntries: null,
+ },
+ {
+ id: "7054dee3-8c15-493b-ad31-f070e08c269a",
+ name: "Part 2",
+ startTime: null,
+ endTime: null,
+ subEntries: [runningEntry],
+ },
+ ],
+ },
+];
+
+export const path = [
+ {
+ id: "8054dee3-8c15-493b-ad31-f070e08c2699",
+ name: "Block 1",
+ },
+ {
+ id: "7054dee3-8c15-493b-ad31-f070e08c269a",
+ name: "Part 2",
+ },
+ {
+ id: "9054dee3-8c15-493b-ad31-f070e08c2699",
+ name: "Running Entry",
+ },
+];
diff --git a/src/timekeep/index.test.ts b/src/timekeep/index.test.ts
index f17223f..df9a5e0 100644
--- a/src/timekeep/index.test.ts
+++ b/src/timekeep/index.test.ts
@@ -23,6 +23,7 @@ import {
isKeepRunning,
isEntryRunning,
removeSubEntry,
+ getPathToEntry,
getRunningEntry,
getEntryDuration,
getTotalDuration,
@@ -300,6 +301,16 @@ describe("checking entries", () => {
expect(output).toBe(runningEntry);
});
+ it("should find running entry path", async () => {
+ const { input, runningEntry, path } = await import(
+ "./__fixtures__/checking/findRunningEntryPath"
+ );
+
+ const output = getPathToEntry(input, runningEntry);
+
+ expect(output).toEqual(path);
+ });
+
it("should not find running entry", async () => {
const { input } = await import(
"./__fixtures__/checking/shouldNotFindRunningEntry"
diff --git a/src/timekeep/index.ts b/src/timekeep/index.ts
index d84ece0..1020d59 100644
--- a/src/timekeep/index.ts
+++ b/src/timekeep/index.ts
@@ -288,6 +288,47 @@ export function isEntryRunning(entry: TimeEntry) {
return entry.startTime !== null && entry.endTime === null;
}
+/**
+ * Get the path to the provided target entry
+ *
+ * @param entries The collection of entries
+ * @param target The target entry
+ * @returns The path to the entry
+ */
+export function getPathToEntry(
+ entries: TimeEntry[],
+ target: TimeEntry
+): { id: string; name: string }[] {
+ const path: { id: string; name: string }[] = [];
+
+ function dfs(entry: TimeEntry): boolean {
+ path.push({ id: entry.id, name: entry.name });
+
+ if (entry.id === target.id) {
+ return true;
+ }
+
+ if (entry.subEntries) {
+ for (const sub of entry.subEntries) {
+ if (dfs(sub)) {
+ return true;
+ }
+ }
+ }
+
+ path.pop();
+ return false;
+ }
+
+ for (const entry of entries) {
+ if (dfs(entry)) {
+ return path;
+ }
+ }
+
+ return [];
+}
+
/**
* Searches through the nested list of time entries using
* a "stack" depth-first search approach attempting to