From 8c346769464e586f194d8dfff1e655ec941c3590 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 8 Jun 2026 15:16:46 +0200 Subject: [PATCH] feat(time-tracking): extract the smart-fill start computation --- .../src/helpers/time/smartFillStart.test.ts | 47 +++++++++++++++++++ frontend/src/helpers/time/smartFillStart.ts | 20 ++++++++ 2 files changed, 67 insertions(+) create mode 100644 frontend/src/helpers/time/smartFillStart.test.ts create mode 100644 frontend/src/helpers/time/smartFillStart.ts diff --git a/frontend/src/helpers/time/smartFillStart.test.ts b/frontend/src/helpers/time/smartFillStart.test.ts new file mode 100644 index 000000000..e623125cf --- /dev/null +++ b/frontend/src/helpers/time/smartFillStart.test.ts @@ -0,0 +1,47 @@ +import {describe, it, expect} from 'vitest' + +import {smartFillStart} from './smartFillStart' +import type {ITimeEntry} from '@/modelTypes/ITimeEntry' + +function entry(startTime: Date, endTime: Date | null): ITimeEntry { + return { + id: 1, + userId: 1, + taskId: 0, + projectId: 0, + startTime, + endTime, + comment: '', + created: startTime, + updated: startTime, + maxPermission: null, + } +} + +describe('smartFillStart', () => { + const now = new Date('2026-06-07T15:30:00') + + it('continues from the latest entry end time', () => { + const entries = [ + entry(new Date('2026-06-07T09:00:00'), new Date('2026-06-07T10:00:00')), + entry(new Date('2026-06-07T11:00:00'), new Date('2026-06-07T12:30:00')), + ] + expect(smartFillStart(entries, '09:00', now)).toEqual(new Date('2026-06-07T12:30:00')) + }) + + it('ignores still-running entries (no end) when picking the latest end', () => { + const entries = [ + entry(new Date('2026-06-07T09:00:00'), new Date('2026-06-07T10:00:00')), + entry(new Date('2026-06-07T13:00:00'), null), + ] + expect(smartFillStart(entries, '09:00', now)).toEqual(new Date('2026-06-07T10:00:00')) + }) + + it('falls back to the default start time on the current day when there are no entries', () => { + expect(smartFillStart([], '08:15', now)).toEqual(new Date('2026-06-07T08:15:00')) + }) + + it('falls back to 09:00 when no default is configured', () => { + expect(smartFillStart([], '', now)).toEqual(new Date('2026-06-07T09:00:00')) + }) +}) diff --git a/frontend/src/helpers/time/smartFillStart.ts b/frontend/src/helpers/time/smartFillStart.ts new file mode 100644 index 000000000..c131c1d0e --- /dev/null +++ b/frontend/src/helpers/time/smartFillStart.ts @@ -0,0 +1,20 @@ +import type {ITimeEntry} from '@/modelTypes/ITimeEntry' + +// The smart-clock start time: continue from the most recent entry's end so +// consecutive entries don't overlap or leave gaps; with no completed entry to +// continue from, fall back to the user's configured default start (HH:MM) on +// the given day. +export function smartFillStart(recentEntries: ITimeEntry[], defaultStart: string, now: Date): Date { + const lastEnd = recentEntries + .map(entry => entry.endTime) + .filter((end): end is Date => end !== null) + .sort((a, b) => b.getTime() - a.getTime())[0] + if (lastEnd !== undefined) { + return new Date(lastEnd) + } + + const [hours, minutes] = (defaultStart || '09:00').split(':').map(Number) + const start = new Date(now) + start.setHours(hours || 0, minutes || 0, 0, 0) + return start +}