From 55505b2b6b3855103e9ea92a960474d413c6bdcd Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Feb 2026 10:51:56 +0200 Subject: [PATCH] feat: add daily/weekly goals, streaks, and time rounding Settings Timer tab now has daily/weekly goal hour inputs. Dashboard shows goal progress bars and streak counter. Settings Billing tab has rounding toggle with increment and method selectors. New rounding.ts utility for nearest/up/down time rounding. --- src/utils/rounding.ts | 10 +++ src/views/Settings.vue | 144 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/utils/rounding.ts diff --git a/src/utils/rounding.ts b/src/utils/rounding.ts new file mode 100644 index 0000000..1d8ced9 --- /dev/null +++ b/src/utils/rounding.ts @@ -0,0 +1,10 @@ +export function roundDuration(seconds: number, incrementMinutes: number, method: 'nearest' | 'up' | 'down'): number { + if (incrementMinutes <= 0) return seconds + const incSeconds = incrementMinutes * 60 + switch (method) { + case 'up': return Math.ceil(seconds / incSeconds) * incSeconds + case 'down': return Math.floor(seconds / incSeconds) * incSeconds + case 'nearest': return Math.round(seconds / incSeconds) * incSeconds + default: return seconds + } +} diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 61dbff2..a2b8b12 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -264,6 +264,44 @@ @change="saveShortcutSettings" /> + + +
+ + +

Goals

+ +
+
+

Daily Goal

+

Target hours per day

+
+ +
+ +
+
+

Weekly Goal

+

Target hours per week

+
+ +
@@ -285,6 +323,63 @@ @update:model-value="saveSettings" /> + + +
+ +
+
+

Time Rounding

+

Round time entries for billing

+
+ +
+ +
+
+
+

Increment

+

Round to nearest increment

+
+
+ +
+
+
+
+

Method

+

Rounding direction

+
+
+ +
+
+
@@ -448,6 +543,31 @@ const accentColor = ref('amber') const shortcutToggleTimer = ref('CmdOrCtrl+Shift+T') const shortcutShowApp = ref('CmdOrCtrl+Shift+Z') +// Goal settings +const dailyGoalHours = ref(8) +const weeklyGoalHours = ref(40) + +// Rounding settings +const roundingEnabled = ref(false) +const roundingIncrement = ref(15) +const roundingMethod = ref('nearest') + +const roundingIncrements = [ + { value: 1, label: '1 minute' }, + { value: 5, label: '5 minutes' }, + { value: 6, label: '6 minutes' }, + { value: 10, label: '10 minutes' }, + { value: 15, label: '15 minutes' }, + { value: 30, label: '30 minutes' }, + { value: 60, label: '1 hour' }, +] + +const roundingMethods = [ + { value: 'nearest', label: 'Nearest' }, + { value: 'up', label: 'Round up' }, + { value: 'down', label: 'Round down' }, +] + const themeModes = [ { value: 'dark', label: 'Dark' }, { value: 'light', label: 'Light' }, @@ -560,6 +680,25 @@ async function saveShortcutSettings() { await settingsStore.updateSetting('shortcut_show_app', shortcutShowApp.value) } +// Save goal settings +async function saveGoalSettings() { + await settingsStore.updateSetting('daily_goal_hours', dailyGoalHours.value.toString()) + await settingsStore.updateSetting('weekly_goal_hours', weeklyGoalHours.value.toString()) +} + +// Toggle rounding +function toggleRounding() { + roundingEnabled.value = !roundingEnabled.value + saveRoundingSettings() +} + +// Save rounding settings +async function saveRoundingSettings() { + await settingsStore.updateSetting('rounding_enabled', roundingEnabled.value.toString()) + await settingsStore.updateSetting('rounding_increment', roundingIncrement.value.toString()) + await settingsStore.updateSetting('rounding_method', roundingMethod.value) +} + // Import file handling async function handleImportFile() { try { @@ -670,5 +809,10 @@ onMounted(async () => { accentColor.value = settingsStore.settings.accent_color || 'amber' shortcutToggleTimer.value = settingsStore.settings.shortcut_toggle_timer || 'CmdOrCtrl+Shift+T' shortcutShowApp.value = settingsStore.settings.shortcut_show_app || 'CmdOrCtrl+Shift+Z' + dailyGoalHours.value = parseFloat(settingsStore.settings.daily_goal_hours) || 8 + weeklyGoalHours.value = parseFloat(settingsStore.settings.weekly_goal_hours) || 40 + roundingEnabled.value = settingsStore.settings.rounding_enabled === 'true' + roundingIncrement.value = parseInt(settingsStore.settings.rounding_increment) || 15 + roundingMethod.value = settingsStore.settings.rounding_method || 'nearest' })