|
23 | 23 | </button>
|
24 | 24 | </div>
|
25 | 25 | </Transition>
|
26 |
| - <Transition mode="out-in"> |
| 26 | + <div class="info"> |
| 27 | + <span class="time">{{ getTime() }}</span> |
27 | 28 | <button v-if="!task.project_id" class="assign-project-button" @click="enableAssignProjectMode(task)">Assign project</button>
|
28 |
| - </Transition> |
| 29 | + </div> |
29 | 30 | </div>
|
30 | 31 | </template>
|
31 | 32 | <script lang="ts" setup>
|
32 |
| -import {computed, ref} from 'vue' |
33 | 33 | import {deleteTask, putTask} from '@/api'
|
34 |
| -import {useMutation, useQueryClient} from '@tanstack/vue-query' |
35 | 34 | import {useAssignTaskProject} from '@/composables/useAssignTaskProject'
|
36 |
| -import dayjs from 'dayjs' |
37 | 35 | import {useSettings} from '@/composables/useSettings'
|
| 36 | +import {calculateTime} from '@/helpers' |
| 37 | +import {useMutation, useQueryClient} from '@tanstack/vue-query' |
| 38 | +import dayjs from 'dayjs' |
| 39 | +import {computed, ref} from 'vue' |
38 | 40 |
|
39 | 41 | const props = defineProps<{
|
40 | 42 | task: Record<any, any>
|
41 | 43 | project?: unknown | null
|
42 | 44 | }>()
|
43 | 45 |
|
44 | 46 | const visibleOverlay = ref(false)
|
| 47 | +const {statuses} = useSettings() |
45 | 48 |
|
46 |
| -const {mutate: startTask} = useMutation({ |
| 49 | +const { |
| 50 | + mutate: startTask, |
| 51 | + isError, |
| 52 | + error |
| 53 | +} = useMutation({ |
47 | 54 | mutationFn: ({task, status}: {task: Record<any, any>; status: string}) =>
|
48 | 55 | putTask(task.id, {
|
49 | 56 | action: 'start',
|
50 | 57 | data: {...task, status_id: status}
|
51 | 58 | }),
|
52 | 59 | onMutate: async ({task, status}) => {
|
53 |
| - await queryClient.cancelQueries({queryKey: ['running-task', props.project?.id ?? null]}) |
54 |
| - const previousTodos = queryClient.getQueryData(['running-task', props.project?.id ?? null]) |
55 |
| - queryClient.setQueryData(['running-task', props.project?.id ?? null], (old) => { |
56 |
| - const date = dayjs() |
57 |
| - const unixTimestamp = date.unix() |
58 |
| - const timeLogParsed = JSON.parse(task.time_log) |
59 |
| - const timeLogRunning = [...timeLogParsed, [unixTimestamp, 0]] |
| 60 | + await queryClient.cancelQueries({queryKey: ['tasks', props.project?.id ?? null, statuses.running]}) |
60 | 61 |
|
61 |
| - return { |
62 |
| - ...old, |
63 |
| - data: [ |
64 |
| - { |
65 |
| - ...task, |
66 |
| - status_id: status, |
67 |
| - time_log: JSON.stringify(timeLogRunning) |
68 |
| - } |
69 |
| - ] |
| 62 | + // Snapshot the previous value |
| 63 | + const previousTodos = queryClient.getQueryData(['tasks', props.project?.id ?? null, '']) |
| 64 | +
|
| 65 | + const date = dayjs() |
| 66 | + const unixTimestamp = date.unix() |
| 67 | + const timeLogParsed = JSON.parse(task.time_log) |
| 68 | + const timeLogRunning = [...timeLogParsed, [unixTimestamp, 0]] |
| 69 | + const optimisticTask = { |
| 70 | + data: [ |
| 71 | + { |
| 72 | + ...task, |
| 73 | + status_id: status, |
| 74 | + time_log: JSON.stringify(timeLogRunning) |
| 75 | + } |
| 76 | + ], |
| 77 | + meta: { |
| 78 | + pagination: { |
| 79 | + total: 1, |
| 80 | + count: 1, |
| 81 | + per_page: 6, |
| 82 | + current_page: 1, |
| 83 | + total_pages: 1, |
| 84 | + links: {} |
| 85 | + } |
70 | 86 | }
|
71 |
| - }) |
| 87 | + } |
72 | 88 |
|
73 |
| - return {previousTodos} |
| 89 | + queryClient.setQueryData(['tasks', props.project?.id ?? null, statuses.running], optimisticTask) |
| 90 | +
|
| 91 | + return {optimisticTask, previousTodos} |
| 92 | + }, |
| 93 | + onSuccess: (result, variables, context) => { |
| 94 | + // Replace optimistic todo in the todos list with the result |
| 95 | + queryClient.setQueryData(['tasks', props.project?.id ?? null, statuses.running], (old) => ({ |
| 96 | + data: old.data.map((task) => (task.id === context.optimisticTask.id ? result.data : task)) |
| 97 | + })) |
74 | 98 | },
|
75 | 99 | // If the mutation fails,
|
76 | 100 | // use the context returned from onMutate to roll back
|
77 | 101 | onError(_, __, context) {
|
78 |
| - queryClient.setQueryData(['running-task', props.project?.id ?? null], context.previousTodos) |
| 102 | + queryClient.setQueryData(['tasks', props.project?.id ?? null], context.previousTodos) |
79 | 103 | },
|
80 |
| - // Always refetch after error or success: |
| 104 | + /* Always refetch after error or success: */ |
81 | 105 | onSettled() {
|
82 |
| - queryClient.invalidateQueries({queryKey: ['running-task', props.project?.id ?? null]}) |
| 106 | + queryClient.invalidateQueries({queryKey: ['tasks', props.project?.id ?? null]}) |
83 | 107 | }
|
84 | 108 | })
|
85 | 109 |
|
@@ -127,7 +151,6 @@ const confirmRemoveTask = (task: unknown) => {
|
127 | 151 | }
|
128 | 152 | }
|
129 | 153 |
|
130 |
| -const {statuses} = useSettings() |
131 | 154 | const {mutate: setStatus} = useMutation({
|
132 | 155 | mutationFn: ({task, status}: {task: Record<any, any>; status: string}) => {
|
133 | 156 | return putTask(task.id, {data: {...task, status_id: status}})
|
@@ -160,6 +183,16 @@ const taskFinished = computed(() => {
|
160 | 183 | }
|
161 | 184 | return false
|
162 | 185 | })
|
| 186 | +
|
| 187 | +const getTime = () => { |
| 188 | + const time = Number( |
| 189 | + calculateTime(props.task.time_log, { |
| 190 | + inSeconds: true, |
| 191 | + calculateLastTimeLog: false |
| 192 | + }) |
| 193 | + ) |
| 194 | + return new Date(time * 1000).toISOString().slice(11, 19) |
| 195 | +} |
163 | 196 | </script>
|
164 | 197 |
|
165 | 198 | <style scoped>
|
@@ -228,5 +261,19 @@ const taskFinished = computed(() => {
|
228 | 261 | textarea {
|
229 | 262 | color: gray;
|
230 | 263 | }
|
| 264 | +
|
| 265 | + .time { |
| 266 | + color: lightgray; |
| 267 | + } |
| 268 | +} |
| 269 | +
|
| 270 | +.time { |
| 271 | + text-align: start; |
| 272 | + color: gray; |
| 273 | +} |
| 274 | +
|
| 275 | +.info { |
| 276 | + display: flex; |
| 277 | + gap: 0.5em; |
231 | 278 | }
|
232 | 279 | </style>
|
0 commit comments