copy record to clipboard as csv
This commit is contained in:
4
public/icons/check.svg
Normal file
4
public/icons/check.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-circle-check-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="#4d70cb" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-1.293 5.953a1 1 0 0 0 -1.32 -.083l-.094 .083l-3.293 3.292l-1.293 -1.292l-.094 -.083a1 1 0 0 0 -1.403 1.403l.083 .094l2 2l.094 .083a1 1 0 0 0 1.226 0l.094 -.083l4 -4l.083 -.094a1 1 0 0 0 -.083 -1.32z" stroke-width="0" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 633 B |
8
public/icons/share.svg
Normal file
8
public/icons/share.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-share" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="#4d70cb" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M6 12m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
|
||||||
|
<path d="M18 6m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
|
||||||
|
<path d="M18 18m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
|
||||||
|
<path d="M8.7 10.7l6.6 -3.4" />
|
||||||
|
<path d="M8.7 13.3l6.6 3.4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 515 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-trash" width="22" height="22" viewBox="0 0 24 24" stroke-width="2" stroke="#4d70cb" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-trash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="#4d70cb" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<line x1="4" y1="7" x2="20" y2="7" />
|
<line x1="4" y1="7" x2="20" y2="7" />
|
||||||
<line x1="10" y1="11" x2="10" y2="17" />
|
<line x1="10" y1="11" x2="10" y2="17" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 524 B |
49
src/modules/record/hooks/useCopyRecord.hook.ts
Normal file
49
src/modules/record/hooks/useCopyRecord.hook.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { useTaskRecordStore } from '@/modules/record/stores/useTaskRecordStore'
|
||||||
|
import type { Task } from '@/modules/task/models/task'
|
||||||
|
import { formatDiffInMinutes, formatToShortDate } from '@/shared/format-date'
|
||||||
|
import { toValue } from '@vueuse/core'
|
||||||
|
import { ref, type ComputedRef } from 'vue'
|
||||||
|
|
||||||
|
export const useCopyRecord = (task: ComputedRef<Task | null>) => {
|
||||||
|
const recordStore = useTaskRecordStore()
|
||||||
|
|
||||||
|
const canShareTask = !!navigator.clipboard
|
||||||
|
const taskCopied = ref(false)
|
||||||
|
|
||||||
|
const shareTask = async () => {
|
||||||
|
const t = toValue(task)
|
||||||
|
|
||||||
|
if (!t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = recordStore.getTaskRecord(t.id)
|
||||||
|
|
||||||
|
let clipboardText = `${t.title};${formatToShortDate(t.date)}\n
|
||||||
|
Step;Estimation;Duration\n`
|
||||||
|
|
||||||
|
t.steps.forEach((step) => {
|
||||||
|
const recordStep = record?.stepRecords[step.id]
|
||||||
|
const duration =
|
||||||
|
recordStep && recordStep.end
|
||||||
|
? formatDiffInMinutes(recordStep.start, recordStep.end)
|
||||||
|
: '-'
|
||||||
|
const analyze = duration <= step.estimation ? '✅' : '⚠️'
|
||||||
|
|
||||||
|
clipboardText += `"${analyze} ${step.title}";${step.estimation};${duration}\n`
|
||||||
|
})
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(clipboardText)
|
||||||
|
taskCopied.value = true
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
taskCopied.value = false
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
canShareTask,
|
||||||
|
taskCopied,
|
||||||
|
shareTask
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ export const formatLongDate = (date: Date | ISODate) =>
|
|||||||
minute: 'numeric'
|
minute: 'numeric'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const formatDiffInMinutes = (date1: ISODate, date2: ISODate) => {
|
export const formatDiffInMinutes = (start: ISODate, end: ISODate) => {
|
||||||
const diffInMs = new Date(date2).getTime() - new Date(date1).getTime()
|
const diffInMs = new Date(end).getTime() - new Date(start).getTime()
|
||||||
return Math.max(0, Math.round(diffInMs / (1000 * (isTimeSpeedUp() ? 1 : 60))))
|
return Math.max(0, Math.round(diffInMs / (1000 * (isTimeSpeedUp() ? 1 : 60))))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import EstimationTimeArrival from '@/components/EstimationTimeArrival.vue'
|
import EstimationTimeArrival from '@/components/EstimationTimeArrival.vue'
|
||||||
import TaskRecordPreview from '@/modules/record/components/TaskRecordPreview.vue'
|
import TaskRecordPreview from '@/modules/record/components/TaskRecordPreview.vue'
|
||||||
import TaskNotFound from '@/modules/task/components/TaskNotFound.vue'
|
import TaskNotFound from '@/modules/task/components/TaskNotFound.vue'
|
||||||
|
import { useCopyRecord } from '@/modules/record/hooks/useCopyRecord.hook'
|
||||||
import { useTaskStore } from '@/modules/task/stores/useTask.store'
|
import { useTaskStore } from '@/modules/task/stores/useTask.store'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@@ -23,6 +24,8 @@ const deleteTask = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { canShareTask, taskCopied, shareTask } = useCopyRecord(task)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -39,6 +42,10 @@ const deleteTask = () => {
|
|||||||
>
|
>
|
||||||
<img src="/icons/edit.svg" alt="edit task" />
|
<img src="/icons/edit.svg" alt="edit task" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<button v-if="canShareTask" class="share-task button" @click="shareTask">
|
||||||
|
<img v-if="taskCopied" src="/icons/check.svg" alt="task copied!" />
|
||||||
|
<img v-else src="/icons/share.svg" alt="share task" />
|
||||||
|
</button>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'duplicate-task',
|
name: 'duplicate-task',
|
||||||
|
|||||||
Reference in New Issue
Block a user