feat: entry template management in settings
This commit is contained in:
@@ -799,6 +799,35 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Divider -->
|
||||||
|
<div class="border-t border-border-subtle" />
|
||||||
|
|
||||||
|
<!-- Entry Templates -->
|
||||||
|
<h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Entry Templates</h3>
|
||||||
|
|
||||||
|
<div v-if="entryTemplatesStore.templates.length > 0" class="space-y-2">
|
||||||
|
<div
|
||||||
|
v-for="tpl in entryTemplatesStore.templates"
|
||||||
|
:key="tpl.id"
|
||||||
|
class="flex items-center justify-between py-2 px-3 bg-bg-inset rounded-lg"
|
||||||
|
>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-[0.8125rem] text-text-primary truncate">{{ tpl.name }}</p>
|
||||||
|
<p class="text-[0.6875rem] text-text-tertiary">
|
||||||
|
{{ getTemplateProjectName(tpl.project_id) }}
|
||||||
|
<span v-if="tpl.duration"> - {{ formatTemplateDuration(tpl.duration) }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="deleteEntryTemplate(tpl.id!)"
|
||||||
|
class="text-text-tertiary hover:text-status-error transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||||
|
:aria-label="'Delete template ' + tpl.name"
|
||||||
|
>
|
||||||
|
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else class="text-[0.75rem] text-text-tertiary">No saved templates. Use "Save as Template" in the Entries view.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1279,6 +1308,7 @@ import { useToastStore } from '../stores/toast'
|
|||||||
import { useOnboardingStore } from '../stores/onboarding'
|
import { useOnboardingStore } from '../stores/onboarding'
|
||||||
import { useRecurringStore } from '../stores/recurring'
|
import { useRecurringStore } from '../stores/recurring'
|
||||||
import type { RecurringEntry } from '../stores/recurring'
|
import type { RecurringEntry } from '../stores/recurring'
|
||||||
|
import { useEntryTemplatesStore } from '../stores/entryTemplates'
|
||||||
import AppNumberInput from '../components/AppNumberInput.vue'
|
import AppNumberInput from '../components/AppNumberInput.vue'
|
||||||
import AppSelect from '../components/AppSelect.vue'
|
import AppSelect from '../components/AppSelect.vue'
|
||||||
import AppShortcutRecorder from '../components/AppShortcutRecorder.vue'
|
import AppShortcutRecorder from '../components/AppShortcutRecorder.vue'
|
||||||
@@ -1293,6 +1323,7 @@ import type { SoundEvent } from '../utils/audio'
|
|||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
const toastStore = useToastStore()
|
const toastStore = useToastStore()
|
||||||
const recurringStore = useRecurringStore()
|
const recurringStore = useRecurringStore()
|
||||||
|
const entryTemplatesStore = useEntryTemplatesStore()
|
||||||
const onboardingStore = useOnboardingStore()
|
const onboardingStore = useOnboardingStore()
|
||||||
const recurringEntries = computed(() => recurringStore.entries)
|
const recurringEntries = computed(() => recurringStore.entries)
|
||||||
|
|
||||||
@@ -1835,6 +1866,24 @@ function formatDuration(seconds: number): string {
|
|||||||
return `${m}m`
|
return `${m}m`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Entry template helpers
|
||||||
|
function getTemplateProjectName(projectId: number): string {
|
||||||
|
return recProjects.value.find(p => p.id === projectId)?.name || 'Unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTemplateDuration(seconds: number): string {
|
||||||
|
const h = Math.floor(seconds / 3600)
|
||||||
|
const m = Math.floor((seconds % 3600) / 60)
|
||||||
|
if (h > 0 && m > 0) return `${h}h ${m}m`
|
||||||
|
if (h > 0) return `${h}h`
|
||||||
|
if (m > 0) return `${m}m`
|
||||||
|
return '0m'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteEntryTemplate(id: number) {
|
||||||
|
await entryTemplatesStore.deleteTemplate(id)
|
||||||
|
}
|
||||||
|
|
||||||
function buildRecurrenceRule(): string {
|
function buildRecurrenceRule(): string {
|
||||||
if (recPattern.value === 'weekly') {
|
if (recPattern.value === 'weekly') {
|
||||||
return 'weekly:' + recWeeklyDays.value.join(',')
|
return 'weekly:' + recWeeklyDays.value.join(',')
|
||||||
@@ -2113,6 +2162,9 @@ onMounted(async () => {
|
|||||||
await recurringStore.fetchEntries()
|
await recurringStore.fetchEntries()
|
||||||
recProjects.value = await invoke<Array<{ id: number; name: string; color: string }>>('get_projects')
|
recProjects.value = await invoke<Array<{ id: number; name: string; color: string }>>('get_projects')
|
||||||
|
|
||||||
|
// Load entry templates
|
||||||
|
await entryTemplatesStore.fetchTemplates()
|
||||||
|
|
||||||
// Load calendar sources
|
// Load calendar sources
|
||||||
await fetchCalendarSources()
|
await fetchCalendarSources()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user