feat: rounding visibility indicators on entry rows
This commit is contained in:
@@ -172,6 +172,15 @@
|
|||||||
title="Non-billable"
|
title="Non-billable"
|
||||||
>NB</span>
|
>NB</span>
|
||||||
{{ formatDuration(entry.duration) }}
|
{{ formatDuration(entry.duration) }}
|
||||||
|
<span
|
||||||
|
v-if="getRoundedDuration(entry.duration) !== null"
|
||||||
|
class="ml-1 text-[0.5625rem] font-sans text-text-tertiary"
|
||||||
|
:title="'Actual: ' + formatDuration(entry.duration) + ' - Rounded: ' + formatDuration(getRoundedDuration(entry.duration)!)"
|
||||||
|
>
|
||||||
|
<Clock class="inline w-3 h-3 mr-0.5" aria-hidden="true" :stroke-width="1.5" />
|
||||||
|
<span class="sr-only">Duration rounded from {{ formatDuration(entry.duration) }} to {{ formatDuration(getRoundedDuration(entry.duration)!) }}</span>
|
||||||
|
Rounded
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
@@ -734,7 +743,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
import { List as ListIcon, Copy, DollarSign, Receipt, Plus, Pencil, Trash2, Lock, Image, Upload } from 'lucide-vue-next'
|
import { List as ListIcon, Copy, DollarSign, Receipt, Plus, Pencil, Trash2, Lock, Image, Upload, Clock } from 'lucide-vue-next'
|
||||||
import { invoke, convertFileSrc } from '@tauri-apps/api/core'
|
import { invoke, convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import AppNumberInput from '../components/AppNumberInput.vue'
|
import AppNumberInput from '../components/AppNumberInput.vue'
|
||||||
import AppSelect from '../components/AppSelect.vue'
|
import AppSelect from '../components/AppSelect.vue'
|
||||||
@@ -751,6 +760,7 @@ import { useTagsStore } from '../stores/tags'
|
|||||||
import { useToastStore } from '../stores/toast'
|
import { useToastStore } from '../stores/toast'
|
||||||
import { useEntryTemplatesStore } from '../stores/entryTemplates'
|
import { useEntryTemplatesStore } from '../stores/entryTemplates'
|
||||||
import { formatDate, formatCurrency, getCurrencySymbol } from '../utils/locale'
|
import { formatDate, formatCurrency, getCurrencySymbol } from '../utils/locale'
|
||||||
|
import { useSettingsStore } from '../stores/settings'
|
||||||
import { useFormGuard } from '../utils/formGuard'
|
import { useFormGuard } from '../utils/formGuard'
|
||||||
import { renderMarkdown } from '../utils/markdown'
|
import { renderMarkdown } from '../utils/markdown'
|
||||||
|
|
||||||
@@ -760,6 +770,7 @@ const projectsStore = useProjectsStore()
|
|||||||
const tagsStore = useTagsStore()
|
const tagsStore = useTagsStore()
|
||||||
const toast = useToastStore()
|
const toast = useToastStore()
|
||||||
const entryTemplatesStore = useEntryTemplatesStore()
|
const entryTemplatesStore = useEntryTemplatesStore()
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
const showTemplatePicker = ref(false)
|
const showTemplatePicker = ref(false)
|
||||||
const showReceiptLightbox = ref(false)
|
const showReceiptLightbox = ref(false)
|
||||||
const receiptLightboxData = ref({ imageUrl: '', description: '', category: '', date: '', amount: '' })
|
const receiptLightboxData = ref({ imageUrl: '', description: '', category: '', date: '', amount: '' })
|
||||||
@@ -1005,6 +1016,24 @@ function formatDuration(seconds: number): string {
|
|||||||
return `${minutes}m`
|
return `${minutes}m`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rounding helpers
|
||||||
|
function roundDuration(seconds: number, increment: number, method: string): number {
|
||||||
|
const incrementSeconds = increment * 60
|
||||||
|
if (incrementSeconds <= 0) return seconds
|
||||||
|
if (method === 'up') return Math.ceil(seconds / incrementSeconds) * incrementSeconds
|
||||||
|
if (method === 'down') return Math.floor(seconds / incrementSeconds) * incrementSeconds
|
||||||
|
return Math.round(seconds / incrementSeconds) * incrementSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoundedDuration(seconds: number): number | null {
|
||||||
|
if (settingsStore.settings.rounding_enabled !== 'true') return null
|
||||||
|
const increment = parseInt(settingsStore.settings.rounding_increment) || 0
|
||||||
|
const method = settingsStore.settings.rounding_method || 'nearest'
|
||||||
|
if (increment <= 0) return null
|
||||||
|
const rounded = roundDuration(seconds, increment, method)
|
||||||
|
return rounded !== seconds ? rounded : null
|
||||||
|
}
|
||||||
|
|
||||||
// Tag helper functions
|
// Tag helper functions
|
||||||
function getTagName(tagId: number): string {
|
function getTagName(tagId: number): string {
|
||||||
const tag = tagsStore.tags.find(t => t.id === tagId)
|
const tag = tagsStore.tags.find(t => t.id === tagId)
|
||||||
@@ -1553,7 +1582,8 @@ onMounted(async () => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
entriesStore.fetchEntriesPaginated(),
|
entriesStore.fetchEntriesPaginated(),
|
||||||
projectsStore.fetchProjects(),
|
projectsStore.fetchProjects(),
|
||||||
tagsStore.fetchTags()
|
tagsStore.fetchTags(),
|
||||||
|
settingsStore.fetchSettings()
|
||||||
])
|
])
|
||||||
|
|
||||||
await loadEntryTags()
|
await loadEntryTags()
|
||||||
|
|||||||
Reference in New Issue
Block a user