add duplicate, copy previous day/week, and repeat entry

This commit is contained in:
Your Name
2026-02-18 10:35:06 +02:00
parent f4d8dcd9c7
commit 3501d1e7d8
2 changed files with 247 additions and 20 deletions

View File

@@ -41,6 +41,20 @@
>
Clear
</button>
<div class="ml-auto flex gap-2">
<button
@click="copyPreviousDay"
class="text-text-secondary text-xs hover:text-text-primary transition-colors border border-border-subtle rounded-lg px-3 py-1.5"
>
Copy Yesterday
</button>
<button
@click="copyPreviousWeek"
class="text-text-secondary text-xs hover:text-text-primary transition-colors border border-border-subtle rounded-lg px-3 py-1.5"
>
Copy Last Week
</button>
</div>
</div>
<!-- Entries Table -->
@@ -78,13 +92,21 @@
{{ getTaskName(entry.task_id) || '-' }}
</td>
<td class="px-4 py-3 text-[0.75rem] text-text-secondary">
{{ entry.description || '-' }}
<span v-if="entry.description" v-html="renderMarkdown(entry.description)" class="markdown-inline" />
<span v-else>-</span>
</td>
<td class="px-4 py-3 text-right text-[0.75rem] font-mono text-accent-text">
{{ formatDuration(entry.duration) }}
</td>
<td class="px-4 py-3">
<div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-100">
<button
@click="duplicateEntry(entry)"
class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
title="Duplicate"
>
<Copy class="h-3.5 w-3.5" :stroke-width="2" />
</button>
<button
@click="openEditDialog(entry)"
class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
@@ -123,7 +145,7 @@
<div
v-if="showEditDialog"
class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
@click.self="closeEditDialog"
@click.self="tryCloseEditDialog"
>
<div class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-md p-6 animate-modal-enter max-h-[calc(100vh-2rem)] overflow-y-auto">
<h2 class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-4">Edit Entry</h2>
@@ -222,22 +244,38 @@
</div>
</div>
</div>
<AppDiscardDialog :show="showDiscardDialog" @cancel="cancelDiscard" @discard="confirmDiscard" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { List as ListIcon } from 'lucide-vue-next'
import { List as ListIcon, Copy } from 'lucide-vue-next'
import { invoke } from '@tauri-apps/api/core'
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 { useEntriesStore, type TimeEntry } from '../stores/entries'
import { useProjectsStore } from '../stores/projects'
import { formatDate } from '../utils/locale'
import { useFormGuard } from '../utils/formGuard'
import { renderMarkdown } from '../utils/markdown'
const entriesStore = useEntriesStore()
const projectsStore = useProjectsStore()
const { showDiscardDialog, snapshot: snapshotForm, tryClose: tryCloseForm, confirmDiscard, cancelDiscard } = useFormGuard()
function getEditFormData() {
return { project_id: editForm.project_id, description: editForm.description, duration: editForm.duration, date: editDate.value, hour: editHour.value, minute: editMinute.value }
}
function tryCloseEditDialog() {
tryCloseForm(getEditFormData(), closeEditDialog)
}
// Filter state
const startDate = ref('')
const endDate = ref('')
@@ -324,6 +362,74 @@ function formatDuration(seconds: number): string {
return `${minutes}m`
}
// Duplicate an entry with current timestamp
async function duplicateEntry(entry: TimeEntry) {
const now = new Date()
const newEntry: TimeEntry = {
project_id: entry.project_id,
task_id: entry.task_id,
description: entry.description,
start_time: now.toISOString(),
end_time: new Date(now.getTime() + entry.duration * 1000).toISOString(),
duration: entry.duration,
}
await entriesStore.createEntry(newEntry)
await entriesStore.fetchEntries()
}
// Copy yesterday's entries to today
async function copyPreviousDay() {
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
const yStr = yesterday.toISOString().split('T')[0]
const prevEntries = await invoke<TimeEntry[]>('get_time_entries', {
startDate: yStr, endDate: yStr
})
const now = new Date()
const todayStr = now.toISOString().split('T')[0]
for (const e of prevEntries) {
const startHour = new Date(e.start_time).getHours()
const startMin = new Date(e.start_time).getMinutes()
const newStart = new Date(`${todayStr}T${String(startHour).padStart(2,'0')}:${String(startMin).padStart(2,'0')}:00`)
await entriesStore.createEntry({
project_id: e.project_id,
task_id: e.task_id,
description: e.description,
start_time: newStart.toISOString(),
end_time: new Date(newStart.getTime() + e.duration * 1000).toISOString(),
duration: e.duration,
})
}
await entriesStore.fetchEntries()
}
// Copy last week's entries shifted forward 7 days
async function copyPreviousWeek() {
const now = new Date()
const prevWeekStart = new Date(now)
prevWeekStart.setDate(now.getDate() - now.getDay() + 1 - 7)
const prevWeekEnd = new Date(prevWeekStart)
prevWeekEnd.setDate(prevWeekStart.getDate() + 6)
const prevEntries = await invoke<TimeEntry[]>('get_time_entries', {
startDate: prevWeekStart.toISOString().split('T')[0],
endDate: prevWeekEnd.toISOString().split('T')[0],
})
for (const e of prevEntries) {
const entryDate = new Date(e.start_time)
const newDate = new Date(entryDate)
newDate.setDate(entryDate.getDate() + 7)
await entriesStore.createEntry({
project_id: e.project_id,
task_id: e.task_id,
description: e.description,
start_time: newDate.toISOString(),
end_time: new Date(newDate.getTime() + e.duration * 1000).toISOString(),
duration: e.duration,
})
}
await entriesStore.fetchEntries()
}
// Apply filters
function applyFilters() {
entriesStore.fetchEntries(startDate.value || undefined, endDate.value || undefined)
@@ -354,6 +460,7 @@ function openEditDialog(entry: TimeEntry) {
editHour.value = dt.getHours()
editMinute.value = dt.getMinutes()
snapshotForm(getEditFormData())
showEditDialog.value = true
}