From 8c568677644921fddf525e638df09eceb899b3a6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Feb 2026 10:51:47 +0200 Subject: [PATCH] feat: add budget progress indicators to Projects and Dashboard Project edit dialog includes budget hours and amount fields. Project cards show progress bars with color-coded status. Dashboard displays budget alerts section for projects exceeding 75% of budget. --- src/views/Dashboard.vue | 110 ++++++++++++++++ src/views/Projects.vue | 285 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 369 insertions(+), 26 deletions(-) diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 0ac9431..1062da6 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -38,6 +38,41 @@ + +
+

Goals

+
+
+
+ Today + {{ formatGoalHours(goalProgress.today_seconds) }} +
+
+
+
+
+
+
+ This Week + {{ formatGoalHours(goalProgress.week_seconds) }} +
+
+
+
+
+
+ {{ goalProgress.streak_days }} + day streak +
+
+
+

Weekly Hours

@@ -74,6 +109,27 @@ No entries yet. Start tracking your time.

+ + +
+

Budget Alerts

+
+
+
+
+ {{ alert.name }} +
+ + {{ alert.pct.toFixed(0) }}% + +
+
+
@@ -94,6 +150,7 @@ import { import { Clock } from 'lucide-vue-next' import { useEntriesStore } from '../stores/entries' import { useProjectsStore } from '../stores/projects' +import { useSettingsStore } from '../stores/settings' import { formatDateLong } from '../utils/locale' import type { TimeEntry } from '../stores/entries' @@ -102,6 +159,28 @@ ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend) const entriesStore = useEntriesStore() const projectsStore = useProjectsStore() +const settingsStore = useSettingsStore() + +const goalProgress = ref<{ today_seconds: number; week_seconds: number; streak_days: number } | null>(null) + +async function loadGoalProgress() { + try { + goalProgress.value = await invoke('get_goal_progress') + } catch (e) { + // ignore + } +} + +const dailyGoalHours = computed(() => parseFloat(settingsStore.settings.daily_goal_hours) || 8) +const weeklyGoalHours = computed(() => parseFloat(settingsStore.settings.weekly_goal_hours) || 40) +const dailyPct = computed(() => goalProgress.value ? (goalProgress.value.today_seconds / 3600 / dailyGoalHours.value) * 100 : 0) +const weeklyPct = computed(() => goalProgress.value ? (goalProgress.value.week_seconds / 3600 / weeklyGoalHours.value) * 100 : 0) + +function formatGoalHours(seconds: number): string { + const h = Math.floor(seconds / 3600) + const m = Math.floor((seconds % 3600) / 60) + return `${h}h ${m}m` +} const todayStats = ref<{ totalSeconds: number; byProject: unknown[] }>({ totalSeconds: 0, byProject: [] }) const weekStats = ref<{ totalSeconds: number; byProject: unknown[] }>({ totalSeconds: 0, byProject: [] }) @@ -173,6 +252,34 @@ const activeProjectsCount = computed(() => { return projectsStore.projects.filter(p => !p.archived).length }) +// Budget status +const budgetStatus = ref>({}) + +async function loadBudgetStatus() { + for (const project of projectsStore.projects) { + if (project.id && project.budget_hours) { + try { + const status = await invoke<{ used_hours: number; used_amount: number }>('get_project_budget_status', { projectId: project.id }) + budgetStatus.value[project.id] = status + } catch (e) { + // ignore + } + } + } +} + +const budgetAlerts = computed(() => { + return projectsStore.projects + .filter(p => p.id && p.budget_hours) + .map(p => { + const used = budgetStatus.value[p.id!]?.used_hours || 0 + const pct = (used / p.budget_hours!) * 100 + return { id: p.id!, name: p.name, color: p.color, pct } + }) + .filter(a => a.pct > 75) + .sort((a, b) => b.pct - a.pct) +}) + // Chart data for weekly hours const chartData = computed(() => { const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] @@ -252,6 +359,9 @@ const chartOptions = { onMounted(async () => { await projectsStore.fetchProjects() await entriesStore.fetchEntries() + await settingsStore.fetchSettings() + await loadGoalProgress() + await loadBudgetStatus() try { todayStats.value = await invoke('get_reports', { diff --git a/src/views/Projects.vue b/src/views/Projects.vue index 80d7108..101ba94 100644 --- a/src/views/Projects.vue +++ b/src/views/Projects.vue @@ -47,6 +47,19 @@
+
+
+ {{ getBudgetUsed(project).toFixed(0) }}h / {{ project.budget_hours }}h + {{ getBudgetPct(project).toFixed(0) }}% +
+
+
+
+
@@ -69,7 +82,7 @@

@@ -117,28 +130,94 @@
-
-
+ + +
+
+ +
-
- + + - +
+ + +
+ +
+
+
+
+ +
+
+

{{ app.display_name || app.exe_name }}

+

{{ app.exe_name }}

+
+
+ +
+
+

No tracked apps. Timer will run without app visibility checks.

+
+ + +
@@ -189,18 +268,36 @@
+ +