feat: edit mode

This commit is contained in:
Julien Calixte
2026-01-24 14:31:40 +01:00
parent b2068865e8
commit 67d65b54f4
4 changed files with 109 additions and 3 deletions

View File

@@ -2,7 +2,7 @@
import { useTaskStore } from '@/modules/task/stores/useTask.store'
import { formatDiffInMinutes } from '@/shared/format-date'
import { toISODate } from '@/shared/types/date'
import { computed, onUnmounted, ref } from 'vue'
import { computed, nextTick, onUnmounted, ref } from 'vue'
import { useTaskRecordStore } from '../stores/useTaskRecordStore'
import { is10PercentOffThanEstimation } from '@/modules/record/services/compare-with-estimation'
@@ -10,6 +10,7 @@ const props = defineProps<{
taskId: string
stepId: string
stepNumber: number
isLastCompletedStep?: boolean
}>()
const taskStore = useTaskStore()
@@ -67,6 +68,39 @@ const isOffEstimation = computed(() => {
duration: duration.value
})
})
// Duration editing
const isEditing = ref(false)
const editedDuration = ref(0)
const durationInputRef = ref<HTMLInputElement | null>(null)
const isEditable = computed(
() => props.isLastCompletedStep && record.value && !record.value.end
)
function startEditing() {
editedDuration.value = duration.value ?? 0
isEditing.value = true
nextTick(() => {
durationInputRef.value?.focus()
durationInputRef.value?.select()
})
}
function confirmEdit() {
if (editedDuration.value >= 1) {
recordStore.updateStepDuration({
taskId: props.taskId,
stepId: props.stepId,
newDurationMinutes: editedDuration.value
})
}
isEditing.value = false
}
function cancelEdit() {
isEditing.value = false
}
</script>
<template>
@@ -96,7 +130,28 @@ const isOffEstimation = computed(() => {
{{ step.title }}
</td>
<td class="estimation minutes">{{ step.estimation }} min</td>
<td class="minutes" v-if="stepRecord">{{ duration }} min</td>
<td class="minutes" v-if="stepRecord">
<template v-if="isEditing">
<input
ref="durationInputRef"
type="number"
min="1"
v-model.number="editedDuration"
@blur="confirmEdit"
@keydown.enter="confirmEdit"
@keydown.escape="cancelEdit"
class="input is-small duration-input"
/>
min
</template>
<span
v-else
:class="{ 'is-editable': isEditable }"
@click="isEditable && startEditing()"
>
{{ duration }} min
</span>
</td>
<td v-else></td>
</tr>
</template>
@@ -145,6 +200,19 @@ $blob-color: $link;
.minutes {
text-align: right;
}
.is-editable {
cursor: pointer;
text-decoration: underline dotted;
&:hover {
opacity: 0.7;
}
}
.duration-input {
width: 60px;
display: inline-block;
}
}
@keyframes pulse {

View File

@@ -32,6 +32,16 @@ useAppTitle(task.value?.title ?? '')
const record = computed(() => recordStore.getTaskRecord(props.taskId))
const recordNotes = computed(() => recordStore.getRecordNotes(props.taskId))
const lastCompletedStepId = computed(() => {
if (!record.value || !task.value || !record.value.currentStepId) return null
const currentIndex = task.value.steps.findIndex(
(s) => s.id === record.value?.currentStepId
)
return currentIndex > 0 ? task.value.steps[currentIndex - 1].id : null
})
</script>
<template>
@@ -65,6 +75,7 @@ const recordNotes = computed(() => recordStore.getRecordNotes(props.taskId))
:key="step.id"
:step-id="step.id"
:step-number="key + 1"
:is-last-completed-step="step.id === lastCompletedStepId"
/>
</tbody>
</table>

View File

@@ -5,6 +5,7 @@ import type { Recordable } from '../interfaces/recordable'
import type { TimeRange } from '../interfaces/time-range'
import { TaskRecord } from '../models/task-record'
import { addBreakTimeToStepRecords } from '../services/breaktime-service'
import { isTimeSpeedUp } from '@/shared/format-date'
export interface TaskRecordStoreState {
records: { [recordId: string]: Recordable }
@@ -221,6 +222,32 @@ export const useTaskRecordStore = defineStore('task-record-store', {
start: toISODate(new Date(latestStartDate))
})
}
},
updateStepDuration(params: {
taskId: string
stepId: string
newDurationMinutes: number
}) {
const record = this.records[params.taskId]
if (!record) return
const stepRecord = record.stepRecords[params.stepId]
if (!stepRecord?.end) return // Only completed steps
// Calculate new end time (in dev mode, units are seconds for faster testing)
const unitMultiplier = isTimeSpeedUp() ? 1 : 60
const startMs = new Date(stepRecord.start).getTime()
const newEndMs =
startMs + params.newDurationMinutes * unitMultiplier * 1000
const newEnd = toISODate(new Date(newEndMs))
// Adjust current step's start time for continuity
const currentStepId = record.currentStepId
if (currentStepId && record.stepRecords[currentStepId]) {
record.stepRecords[currentStepId].start = newEnd
}
stepRecord.end = newEnd
}
},
getters: {

View File

@@ -1,6 +1,6 @@
import type { ISODate } from './types/date'
const isTimeSpeedUp = () => process.env.NODE_ENV === 'development'
export const isTimeSpeedUp = () => process.env.NODE_ENV === 'development'
export const formatDate = (date: Date | string) =>
new Date(date).toLocaleString()