From 787f8bbacfada1afb4a57e6fe4bd4127534c9b66 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Feb 2026 10:51:39 +0200 Subject: [PATCH] feat: integrate tags in Timer and Entries views Timer shows tag selector below description, saves tags on stop. Entries table displays tag chips per row with color coding. Tags loaded from store on mount. --- src/views/Entries.vue | 78 +++++++++++++++++++++++++++++++++++++++---- src/views/Timer.vue | 24 +++++++++++-- 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/src/views/Entries.vue b/src/views/Entries.vue index b0a93d5..b2c6ecd 100644 --- a/src/views/Entries.vue +++ b/src/views/Entries.vue @@ -94,6 +94,16 @@ - +
+ + {{ getTagName(tagId) }} + +
{{ formatDuration(entry.duration) }} @@ -174,6 +184,12 @@ /> + +
+ + +
+
@@ -257,14 +273,19 @@ import AppNumberInput from '../components/AppNumberInput.vue' import AppSelect from '../components/AppSelect.vue' import AppDatePicker from '../components/AppDatePicker.vue' import AppDiscardDialog from '../components/AppDiscardDialog.vue' +import AppTagInput from '../components/AppTagInput.vue' import { useEntriesStore, type TimeEntry } from '../stores/entries' import { useProjectsStore } from '../stores/projects' +import { useTagsStore } from '../stores/tags' import { formatDate } from '../utils/locale' import { useFormGuard } from '../utils/formGuard' import { renderMarkdown } from '../utils/markdown' const entriesStore = useEntriesStore() const projectsStore = useProjectsStore() +const tagsStore = useTagsStore() +const entryTags = ref>({}) +const editEntryTags = ref([]) const { showDiscardDialog, snapshot: snapshotForm, tryClose: tryCloseForm, confirmDiscard, cancelDiscard } = useFormGuard() @@ -362,6 +383,28 @@ function formatDuration(seconds: number): string { return `${minutes}m` } +// Tag helper functions +function getTagName(tagId: number): string { + const tag = tagsStore.tags.find(t => t.id === tagId) + return tag?.name || '' +} + +function getTagColor(tagId: number): string { + const tag = tagsStore.tags.find(t => t.id === tagId) + return tag?.color || '#6B7280' +} + +async function loadEntryTags() { + const tags: Record = {} + for (const entry of entriesStore.entries) { + if (entry.id) { + const entryTagList = await tagsStore.getEntryTags(entry.id) + tags[entry.id] = entryTagList.map(t => t.id!).filter(id => id != null) + } + } + entryTags.value = tags +} + // Duplicate an entry with current timestamp async function duplicateEntry(entry: TimeEntry) { const now = new Date() @@ -375,6 +418,7 @@ async function duplicateEntry(entry: TimeEntry) { } await entriesStore.createEntry(newEntry) await entriesStore.fetchEntries() + await loadEntryTags() } // Copy yesterday's entries to today @@ -401,6 +445,7 @@ async function copyPreviousDay() { }) } await entriesStore.fetchEntries() + await loadEntryTags() } // Copy last week's entries shifted forward 7 days @@ -428,23 +473,26 @@ async function copyPreviousWeek() { }) } await entriesStore.fetchEntries() + await loadEntryTags() } // Apply filters -function applyFilters() { - entriesStore.fetchEntries(startDate.value || undefined, endDate.value || undefined) +async function applyFilters() { + await entriesStore.fetchEntries(startDate.value || undefined, endDate.value || undefined) + await loadEntryTags() } // Clear filters -function clearFilters() { +async function clearFilters() { startDate.value = '' endDate.value = '' filterProject.value = null - entriesStore.fetchEntries() + await entriesStore.fetchEntries() + await loadEntryTags() } // Open edit dialog -function openEditDialog(entry: TimeEntry) { +async function openEditDialog(entry: TimeEntry) { editingEntry.value = entry editForm.id = entry.id || 0 editForm.project_id = entry.project_id @@ -460,6 +508,14 @@ function openEditDialog(entry: TimeEntry) { editHour.value = dt.getHours() editMinute.value = dt.getMinutes() + // Load tags for this entry + if (entry.id) { + const tags = await tagsStore.getEntryTags(entry.id) + editEntryTags.value = tags.map(t => t.id!).filter(id => id != null) + } else { + editEntryTags.value = [] + } + snapshotForm(getEditFormData()) showEditDialog.value = true } @@ -488,6 +544,13 @@ async function handleEdit() { } await entriesStore.updateEntry(updatedEntry) + + // Save tags for the edited entry + if (editForm.id) { + await tagsStore.setEntryTags(editForm.id, editEntryTags.value) + await loadEntryTags() + } + closeEditDialog() } } @@ -516,9 +579,12 @@ async function handleDelete() { onMounted(async () => { await Promise.all([ entriesStore.fetchEntries(), - projectsStore.fetchProjects() + projectsStore.fetchProjects(), + tagsStore.fetchTags() ]) + await loadEntryTags() + // Set default date range to this week const now = new Date() const weekStart = new Date(now) diff --git a/src/views/Timer.vue b/src/views/Timer.vue index c4d6b51..c625cc5 100644 --- a/src/views/Timer.vue +++ b/src/views/Timer.vue @@ -104,6 +104,10 @@
+
+ + +
@@ -183,10 +187,12 @@ import { useToastStore } from '../stores/toast' import { Timer as TimerIcon, RotateCcw, ExternalLink, Star } from 'lucide-vue-next' import { invoke } from '@tauri-apps/api/core' import AppSelect from '../components/AppSelect.vue' +import AppTagInput from '../components/AppTagInput.vue' import IdlePromptDialog from '../components/IdlePromptDialog.vue' import AppTrackingPromptDialog from '../components/AppTrackingPromptDialog.vue' import { formatDateTime } from '../utils/locale' import { useFavoritesStore, type Favorite } from '../stores/favorites' +import { useTagsStore } from '../stores/tags' const timerStore = useTimerStore() const projectsStore = useProjectsStore() @@ -194,12 +200,14 @@ const entriesStore = useEntriesStore() const settingsStore = useSettingsStore() const toastStore = useToastStore() const favoritesStore = useFavoritesStore() +const tagsStore = useTagsStore() const favorites = computed(() => favoritesStore.favorites) // Local state for inputs const selectedProject = ref(timerStore.selectedProjectId) const selectedTask = ref(timerStore.selectedTaskId) const description = ref(timerStore.description) +const selectedTags = ref([]) const projectTasks = ref([]) // Split timer into parts for colon animation @@ -300,7 +308,7 @@ async function openMiniTimer() { } // Toggle timer -function toggleTimer() { +async function toggleTimer() { if (timerStore.isStopped) { if (!selectedProject.value) { toastStore.info('Please select a project before starting the timer') @@ -309,7 +317,16 @@ function toggleTimer() { timerStore.start() } else { timerStore.stop() - entriesStore.fetchEntries() + await entriesStore.fetchEntries() + + // Save tags for the new entry if any were selected + if (selectedTags.value.length > 0) { + const entries = entriesStore.entries + if (entries.length > 0 && entries[0].id) { + await tagsStore.setEntryTags(entries[0].id, selectedTags.value) + } + } + selectedTags.value = [] } } @@ -390,7 +407,8 @@ onMounted(async () => { projectsStore.fetchProjects(), entriesStore.fetchEntries(), settingsStore.fetchSettings(), - favoritesStore.fetchFavorites() + favoritesStore.fetchFavorites(), + tagsStore.fetchTags() ]) // Restore timer state