feat: allow to edit existing relative reminders
This commit is contained in:
parent
f747d5b2fc
commit
5d38b8327f
211
src/components/tasks/partials/reminder-detail.vue
Normal file
211
src/components/tasks/partials/reminder-detail.vue
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
<template>
|
||||||
|
<div class="reminder-detail">
|
||||||
|
<BaseButton class="show" v-if="!!reminder?.relativePeriod" @click.stop="togglePeriodPopup"
|
||||||
|
:disabled="disabled || undefined">
|
||||||
|
{{ formatDuration(reminder.relativePeriod) }} <span v-html="formatBeforeAfter(reminder.relativePeriod)"></span>
|
||||||
|
{{ formatRelativeTo(reminder.relativeTo) }}
|
||||||
|
</BaseButton>
|
||||||
|
<CustomTransition name="fade">
|
||||||
|
<div v-if="show" class="control is-flex is-align-items-center mb-2">
|
||||||
|
<input
|
||||||
|
:disabled="disabled || undefined"
|
||||||
|
class="input"
|
||||||
|
placeholder="d"
|
||||||
|
v-model="periodInput.duration.days"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
/> d
|
||||||
|
<input
|
||||||
|
:disabled="disabled || undefined"
|
||||||
|
class="input"
|
||||||
|
placeholder="HH"
|
||||||
|
v-model="periodInput.duration.hours"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
/>:
|
||||||
|
<input
|
||||||
|
:disabled="disabled || undefined"
|
||||||
|
class="input"
|
||||||
|
placeholder="MM"
|
||||||
|
v-model="periodInput.duration.minutes"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
<div class="select">
|
||||||
|
<select v-model="periodInput.sign" id="sign">
|
||||||
|
<option value="-1">≤</option>
|
||||||
|
<option value="1">></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select">
|
||||||
|
<select v-model="periodInput.relativeTo" id="relativeTo">
|
||||||
|
<option :value="REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE">{{ $t('task.attributes.dueDate') }}</option>
|
||||||
|
<option :value="REMINDER_PERIOD_RELATIVE_TO_TYPES.STARTDATE">{{ $t('task.attributes.startDate') }}</option>
|
||||||
|
<option :value="REMINDER_PERIOD_RELATIVE_TO_TYPES.ENDDATE">{{ $t('task.attributes.endDate') }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<x-button
|
||||||
|
class="datepicker__close-button"
|
||||||
|
:shadow="false"
|
||||||
|
@click="close"
|
||||||
|
v-cy="'closeDatepicker'"
|
||||||
|
>
|
||||||
|
{{ $t('misc.confirm') }}
|
||||||
|
</x-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</CustomTransition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {reactive, ref, watch, type PropType} from 'vue'
|
||||||
|
|
||||||
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||||
|
import {periodToSeconds, secondsToPeriod} from '@/helpers/time/period'
|
||||||
|
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
|
||||||
|
import {REMINDER_PERIOD_RELATIVE_TO_TYPES, type IReminderPeriodRelativeTo} from '@/types/IReminderPeriodRelativeTo'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object as PropType<ITaskReminder>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'close', 'close-on-change'])
|
||||||
|
|
||||||
|
const reminder = ref<ITaskReminder>()
|
||||||
|
const show = ref(false)
|
||||||
|
|
||||||
|
const periodInput = reactive({
|
||||||
|
duration: {days: 0, hours: 0, minutes: 0, seconds: 0},
|
||||||
|
relativeTo: REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE,
|
||||||
|
sign: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
reminder.value = value
|
||||||
|
if (value.relativePeriod != 0) {
|
||||||
|
Object.assign(periodInput.duration, secondsToPeriod(Math.abs(value.relativePeriod)))
|
||||||
|
periodInput.relativeTo = value.relativeTo
|
||||||
|
periodInput.sign = value.relativePeriod <= 0 ? -1 : 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{immediate: true},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
function updateData() {
|
||||||
|
changed.value = true
|
||||||
|
console.log('updateData', periodInput)
|
||||||
|
reminder.value.relativePeriod = parseInt(periodInput.sign) * periodToSeconds(periodInput.duration.days, periodInput.duration.hours, periodInput.duration.minutes, 0)
|
||||||
|
reminder.value.relativeTo = periodInput.relativeTo
|
||||||
|
reminder.value.reminder = null
|
||||||
|
emit('update:modelValue', reminder.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePeriodPopup() {
|
||||||
|
if (props.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
show.value = !show.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const changed = ref(false)
|
||||||
|
function close() {
|
||||||
|
// Kind of dirty, but the timeout allows us to enter a time and click on "confirm" without
|
||||||
|
// having to click on another input field before it is actually used.
|
||||||
|
updateData()
|
||||||
|
setTimeout(() => {
|
||||||
|
show.value = false
|
||||||
|
emit('close', changed.value)
|
||||||
|
if (changed.value) {
|
||||||
|
changed.value = false
|
||||||
|
emit('close-on-change', changed.value)
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function formatDuration(reminderPeriod: number): string {
|
||||||
|
if (Math.abs(reminderPeriod) < 60) {
|
||||||
|
return '00:00'
|
||||||
|
}
|
||||||
|
const duration = secondsToPeriod(Math.abs(reminderPeriod))
|
||||||
|
console.log('formatDuration', duration, reminderPeriod)
|
||||||
|
return (duration.days > 0 ? duration.days + ' d ' : '') +
|
||||||
|
('' + duration.hours).padStart(2, '0') + ':' +
|
||||||
|
('' + duration.minutes).padStart(2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBeforeAfter(reminderPeriod: number): string {
|
||||||
|
if (reminderPeriod <= 0) {
|
||||||
|
return '≤'
|
||||||
|
}
|
||||||
|
return '>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRelativeTo(relativeTo: IReminderPeriodRelativeTo | null): string | null {
|
||||||
|
switch (relativeTo) {
|
||||||
|
case REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE:
|
||||||
|
return t('task.attributes.dueDate')
|
||||||
|
case REMINDER_PERIOD_RELATIVE_TO_TYPES.STARTDATE:
|
||||||
|
return t('task.attributes.startDate')
|
||||||
|
case REMINDER_PERIOD_RELATIVE_TO_TYPES.ENDDATE:
|
||||||
|
return t('task.attributes.endDate')
|
||||||
|
default:
|
||||||
|
return relativeTo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.reminders {
|
||||||
|
.reminder-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.overdue :deep(.datepicker .show) {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove {
|
||||||
|
color: var(--danger);
|
||||||
|
padding-left: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
max-width: 70px;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker__close-button {
|
||||||
|
margin: 1rem;
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
@ -6,7 +6,10 @@
|
|||||||
:class="{ 'overdue': r.reminder < new Date()}"
|
:class="{ 'overdue': r.reminder < new Date()}"
|
||||||
class="reminder-input"
|
class="reminder-input"
|
||||||
>
|
>
|
||||||
|
<ReminderDetail v-if="!!r.relativePeriod" v-model="reminders[index]" @close-on-change="() => addReminder(index)"
|
||||||
|
/>
|
||||||
<Datepicker
|
<Datepicker
|
||||||
|
v-if="!r.relativePeriod"
|
||||||
v-model="reminders[index].reminder"
|
v-model="reminders[index].reminder"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@close-on-change="() => addReminderDate(index)"
|
@close-on-change="() => addReminderDate(index)"
|
||||||
@ -26,10 +29,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {type PropType, ref, onMounted, watch} from 'vue'
|
import { onMounted, ref, watch, type PropType } from 'vue'
|
||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
import Datepicker from '@/components/input/datepicker.vue'
|
import Datepicker from '@/components/input/datepicker.vue'
|
||||||
|
import ReminderDetail from '@/components/tasks/partials/reminder-detail.vue'
|
||||||
import TaskReminderModel from '@/models/taskReminder'
|
import TaskReminderModel from '@/models/taskReminder'
|
||||||
import type { ITaskReminder } from '@/modelTypes/ITaskReminder'
|
import type { ITaskReminder } from '@/modelTypes/ITaskReminder'
|
||||||
|
|
||||||
@ -39,11 +43,7 @@ const props = defineProps({
|
|||||||
default: () => [],
|
default: () => [],
|
||||||
validator(prop) {
|
validator(prop) {
|
||||||
// This allows arrays
|
// This allows arrays
|
||||||
if (!(prop instanceof Array)) {
|
return prop instanceof Array
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
@ -78,7 +78,11 @@ function updateData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newReminder = ref(null)
|
const newReminder = ref(null)
|
||||||
function addReminderDate(index : number | null = null) {
|
function addReminder(index: number | null = null) {
|
||||||
|
updateData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addReminderDate(index: number | null = null) {
|
||||||
// New Date
|
// New Date
|
||||||
if (index === null) {
|
if (index === null) {
|
||||||
if (newReminder.value === null) {
|
if (newReminder.value === null) {
|
||||||
|
19
src/helpers/time/period.ts
Normal file
19
src/helpers/time/period.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import {SECONDS_A_DAY, SECONDS_A_HOUR, SECONDS_A_MINUTE} from '@/constants/date'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert time period given as seconds to days, hour, minutes, seconds
|
||||||
|
*/
|
||||||
|
export function secondsToPeriod(seconds: number) {
|
||||||
|
const d = Math.floor(seconds / SECONDS_A_DAY)
|
||||||
|
const h = Math.floor(seconds % SECONDS_A_DAY / 3600)
|
||||||
|
const m = Math.floor(seconds % SECONDS_A_HOUR / 60)
|
||||||
|
const s = Math.floor(seconds % 60)
|
||||||
|
return {days: d, hours: h, minutes: m, seconds: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert time period of days, hour, minutes, seconds to duration in seconds
|
||||||
|
*/
|
||||||
|
export function periodToSeconds(days: number, hours: number, minutes: number, seconds: number) : number{
|
||||||
|
return days * SECONDS_A_DAY + hours * SECONDS_A_HOUR + minutes * SECONDS_A_MINUTE + seconds
|
||||||
|
}
|
@ -2,7 +2,7 @@ import type { IAbstract } from './IAbstract'
|
|||||||
import type { IReminderPeriodRelativeTo } from '@/types/IReminderPeriodRelativeTo'
|
import type { IReminderPeriodRelativeTo } from '@/types/IReminderPeriodRelativeTo'
|
||||||
|
|
||||||
export interface ITaskReminder extends IAbstract {
|
export interface ITaskReminder extends IAbstract {
|
||||||
reminder: Date
|
reminder: Date | null
|
||||||
relativePeriod: number
|
relativePeriod: number
|
||||||
relativeTo: IReminderPeriodRelativeTo
|
relativeTo: IReminderPeriodRelativeTo | null
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user