feat: edit relative reminders

This commit is contained in:
cernst 2023-03-16 09:54:18 +01:00 committed by kolaente
parent 0d6c0c8399
commit 14e2698833
Signed by untrusted user: konrad
GPG Key ID: F40E70337AB24C9B
4 changed files with 114 additions and 161 deletions

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="reminder-detail"> <div>
<ReminderPeriod v-if="showRelativeReminder()" v-model="reminder" @update:modelValue="() => updateData()"></ReminderPeriod> <ReminderPeriod v-if="showRelativeReminder()" v-model="reminder" :disabled="disabled" @update:modelValue="() => updateData()"></ReminderPeriod>
<Datepicker <Datepicker
v-if="showAbsoluteReminder()" v-if="showAbsoluteReminder()"
v-model="reminderDate" v-model="reminderDate"
@ -11,12 +11,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, type PropType } from 'vue'
import Datepicker from '@/components/input/datepicker.vue' import Datepicker from '@/components/input/datepicker.vue'
import ReminderPeriod from '@/components/tasks/partials/reminder-period.vue' import ReminderPeriod from '@/components/tasks/partials/reminder-period.vue'
import TaskReminderModel from '@/models/taskReminder' import TaskReminderModel from '@/models/taskReminder'
import type { ITaskReminder } from '@/modelTypes/ITaskReminder' import type { ITaskReminder } from '@/modelTypes/ITaskReminder'
import { ref, watch, type PropType } from 'vue'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -28,16 +27,14 @@ const props = defineProps({
}, },
}) })
const emit = defineEmits(['update:modelValue', 'update:Reminder', 'close', 'close-on-change']) const emit = defineEmits(['update:modelValue'])
const reminder = ref<ITaskReminder>() const reminder = ref<ITaskReminder>()
const reminderDate = ref() const reminderDate = ref()
watch( watch(
() => props.modelValue, () => props.modelValue,
(value) => { (value) => {
console.log('reminder-detail.watch', value)
reminder.value = value reminder.value = value
if (reminder.value && reminder.value.reminder) { if (reminder.value && reminder.value.reminder) {
reminderDate.value = new Date(reminder.value.reminder) reminderDate.value = new Date(reminder.value.reminder)
@ -46,9 +43,19 @@ watch(
{immediate: true}, {immediate: true},
) )
function updateData() {
emit('update:modelValue', reminder.value)
}
function showAbsoluteReminder() {
return !reminder.value || !reminder.value?.relativeTo
}
function showRelativeReminder() {
return !reminder.value || reminder.value?.relativeTo
}
function setReminderDate() { function setReminderDate() {
console.log('reminder-detail.setReminderDate', reminderDate.value)
console.log('reminder-detail.setReminderDate.reminder', reminder.value)
if (!reminderDate.value) { if (!reminderDate.value) {
return return
} }
@ -58,43 +65,4 @@ function setReminderDate() {
reminder.value.reminder = new Date(reminderDate.value) reminder.value.reminder = new Date(reminderDate.value)
updateData() updateData()
} }
function updateData() {
console.log('reminder-detail.updateData', reminder.value)
emit('update:modelValue', reminder.value)
}
function showAbsoluteReminder() {
return !reminder.value || !reminder.value?.relativeTo
}
function showRelativeReminder() {
console.log('showRelativeReminder', reminder.value)
return !reminder.value || reminder.value?.relativeTo
}
</script> </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;
}
}
}
</style>

View File

@ -1,78 +1,79 @@
<template> <template>
<div class="datepicker"> <div class="datepicker">
<BaseButton class="show" v-if="!!reminder?.relativeTo" @click.stop="togglePeriodPopup" <BaseButton :disabled="disabled" class="show" v-if="!!reminder?.relativeTo" @click.stop="togglePeriodPopup">
:disabled="disabled || undefined">
{{ formatDuration(reminder.relativePeriod) }} <span v-html="formatBeforeAfter(reminder.relativePeriod)"></span> {{ formatDuration(reminder.relativePeriod) }} <span v-html="formatBeforeAfter(reminder.relativePeriod)"></span>
{{ formatRelativeTo(reminder.relativeTo) }} {{ formatRelativeTo(reminder.relativeTo) }}
</BaseButton> </BaseButton>
<CustomTransition name="fade"> <CustomTransition name="fade">
<div v-if="show" class="control is-flex is-align-items-center mb-2"> <div v-if="isShowForm" class="mt-2" ref="periodPopup">
<input <div class="control is-flex is-align-items-center">
:disabled="disabled || undefined" <input
class="input" :disabled="disabled"
placeholder="d" class="input"
v-model="periodInput.duration.days" placeholder="d"
type="number" v-model="periodInput.duration.days"
min="0" type="number"
/> d min="0"
<input /> d
:disabled="disabled || undefined" <input
class="input" :disabled="disabled"
placeholder="HH" class="input"
v-model="periodInput.duration.hours" placeholder="HH"
type="number" v-model="periodInput.duration.hours"
min="0" type="number"
/>: min="0"
<input />:
:disabled="disabled || undefined" <input
class="input" :disabled="disabled"
placeholder="MM" class="input"
v-model="periodInput.duration.minutes" placeholder="MM"
type="number" v-model="periodInput.duration.minutes"
min="0" type="number"
/> min="0"
<div class="select"> />
<select v-model="periodInput.sign" id="sign">
<option value="-1">&le;</option>
<option value="1">&gt;</option>
</select>
</div>
<div class="control">
<div class="select"> <div class="select">
<select v-model="periodInput.relativeTo" id="relativeTo"> <select :disabled="disabled" v-model="periodInput.sign" id="sign">
<option :value="REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE">{{ $t('task.attributes.dueDate') }}</option> <option value="-1">&le;</option>
<option :value="REMINDER_PERIOD_RELATIVE_TO_TYPES.STARTDATE">{{ <option value="1">&gt;</option>
$t('task.attributes.startDate')
}}
</option>
<option :value="REMINDER_PERIOD_RELATIVE_TO_TYPES.ENDDATE">{{ $t('task.attributes.endDate') }}</option>
</select> </select>
</div> </div>
<div class="control">
<div class="select">
<select :disabled="disabled" 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>
</div>
<div class="control">
<x-button
:disabled="disabled"
class="close-button"
:shadow="false"
@click="submitForm"
>
{{ $t('misc.confirm') }}
</x-button>
</div> </div>
<x-button
class="datepicker__close-button"
:shadow="false"
@click="close"
v-cy="'closeDatepicker'"
>
{{ $t('misc.confirm') }}
</x-button>
</div> </div>
</CustomTransition> </CustomTransition>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch, type PropType } from 'vue'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue' import CustomTransition from '@/components/misc/CustomTransition.vue'
import { closeWhenClickedOutside } from '@/helpers/closeWhenClickedOutside'
import { periodToSeconds, secondsToPeriod } from '@/helpers/time/period' import { periodToSeconds, secondsToPeriod } from '@/helpers/time/period'
import TaskReminderModel from '@/models/taskReminder' import TaskReminderModel from '@/models/taskReminder'
import type { ITaskReminder } from '@/modelTypes/ITaskReminder' import type { ITaskReminder } from '@/modelTypes/ITaskReminder'
import { REMINDER_PERIOD_RELATIVE_TO_TYPES, type IReminderPeriodRelativeTo } from '@/types/IReminderPeriodRelativeTo' import { REMINDER_PERIOD_RELATIVE_TO_TYPES, type IReminderPeriodRelativeTo } from '@/types/IReminderPeriodRelativeTo'
import { onMounted, onBeforeUnmount, reactive, ref, watch, type PropType } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const {t} = useI18n({useScope: 'global'}) const {t} = useI18n({useScope: 'global'})
@ -91,7 +92,7 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue', 'close', 'close-on-change']) const emit = defineEmits(['update:modelValue', 'close', 'close-on-change'])
const reminder = ref<ITaskReminder>() const reminder = ref<ITaskReminder>()
const show = ref(false) const isShowForm = ref(false)
const periodInput = reactive({ const periodInput = reactive({
duration: {days: 0, hours: 0, minutes: 0, seconds: 0}, duration: {days: 0, hours: 0, minutes: 0, seconds: 0},
@ -99,11 +100,12 @@ const periodInput = reactive({
sign: -1, sign: -1,
}) })
onMounted(() => document.addEventListener('click', hidePeriodPopup))
onBeforeUnmount(() =>document.removeEventListener('click', hidePeriodPopup))
watch( watch(
() => props.modelValue, () => props.modelValue,
(value) => { (value) => {
console.log('reminders-period.watch', value)
reminder.value = value reminder.value = value
if (value && value.relativeTo != null) { if (value && value.relativeTo != null) {
Object.assign(periodInput.duration, secondsToPeriod(Math.abs(value.relativePeriod))) Object.assign(periodInput.duration, secondsToPeriod(Math.abs(value.relativePeriod)))
@ -111,7 +113,7 @@ watch(
periodInput.sign = value.relativePeriod <= 0 ? -1 : 1 periodInput.sign = value.relativePeriod <= 0 ? -1 : 1
} else { } else {
reminder.value = new TaskReminderModel() reminder.value = new TaskReminderModel()
show.value = true isShowForm.value = true
} }
}, },
{immediate: true}, {immediate: true},
@ -120,10 +122,11 @@ watch(
function updateData() { function updateData() {
changed.value = true changed.value = true
reminder.value.relativePeriod = parseInt(periodInput.sign) * periodToSeconds(periodInput.duration.days, periodInput.duration.hours, periodInput.duration.minutes, 0) if (reminder.value) {
reminder.value.relativeTo = periodInput.relativeTo reminder.value.relativePeriod = parseInt(periodInput.sign) * periodToSeconds(periodInput.duration.days, periodInput.duration.hours, periodInput.duration.minutes, 0)
reminder.value.reminder = null reminder.value.relativeTo = periodInput.relativeTo
console.log('reminders-period.updateData', reminder.value) reminder.value.reminder = null
}
emit('update:modelValue', reminder.value) emit('update:modelValue', reminder.value)
} }
@ -131,18 +134,25 @@ function togglePeriodPopup() {
if (props.disabled) { if (props.disabled) {
return return
} }
isShowForm.value = !isShowForm.value
}
show.value = !show.value const periodPopup = ref<HTMLElement | null>(null)
function hidePeriodPopup(e: MouseEvent) {
if (isShowForm.value) {
closeWhenClickedOutside(e, periodPopup.value, close)
}
}
function submitForm() {
updateData()
close()
} }
const changed = ref(false) const changed = ref(false)
function close() { 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(() => { setTimeout(() => {
show.value = false isShowForm.value = false
emit('close', changed.value) emit('close', changed.value)
if (changed.value) { if (changed.value) {
changed.value = false changed.value = false
@ -151,7 +161,6 @@ function close() {
}, 200) }, 200)
} }
function formatDuration(reminderPeriod: number): string { function formatDuration(reminderPeriod: number): string {
if (Math.abs(reminderPeriod) < 60) { if (Math.abs(reminderPeriod) < 60) {
return '00:00' return '00:00'
@ -181,38 +190,18 @@ function formatRelativeTo(relativeTo: IReminderPeriodRelativeTo | null): string
return relativeTo return relativeTo
} }
} }
</script> </script>
<style lang="scss" scoped> <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 { .input {
max-width: 70px; max-width: 70px;
width: 70px; width: 70px;
} }
.datepicker__close-button { .close-button {
margin: 1rem; margin: 0.5rem;
width: calc(100% - 2rem); width: calc(100% - 1rem);
} }
</style> </style>

View File

@ -1,9 +1,8 @@
<template> <template>
<div class="reminders"> <div class="reminders">
<div v-for="(r, index) in reminders" :key="index" :class="{ 'overdue': r.reminder < new Date() }" class="reminder-input"> <div v-for="(r, index) in reminders" :key="index" :class="{ 'overdue': r.reminder < new Date() }" class="reminder-input">
<div> <div class="reminder-detail">
<ReminderDetail :disabled="disabled" v-model="reminders[index]" mode="edit" <ReminderDetail :disabled="disabled" v-model="reminders[index]" @update:modelValue="() => editReminder(index)"/>
@update:modelValue="() => editReminder(index)"/>
</div> </div>
<div> <div>
<BaseButton @click="removeReminderByIndex(index)" v-if="!disabled" class="remove"> <BaseButton @click="removeReminderByIndex(index)" v-if="!disabled" class="remove">
@ -11,26 +10,23 @@
</BaseButton> </BaseButton>
</div> </div>
</div> </div>
<div class="reminder-input" v-if="!disabled"> <div class="reminder-input">
<BaseButton @click.stop="toggleAddReminder" class="show" :disabled="disabled || undefined"> <BaseButton @click.stop="toggleAddReminder" v-if="!disabled">
{{ $t('task.addReminder') }} {{ $t('task.addReminder') }}
</BaseButton> </BaseButton>
</div>
<CustomTransition name="fade"> <div class="reminder-input">
<ReminderDetail v-if="isAddReminder" :disabled="disabled" mode="add" @update:modelValue="addNewReminder"/> <ReminderDetail v-if="isAddReminder" :disabled="disabled" @update:modelValue="addNewReminder"/>
</CustomTransition>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, reactive, ref, watch, type PropType} from 'vue'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import ReminderDetail from '@/components/tasks/partials/reminder-detail.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'
import { onMounted, reactive, ref, watch, type PropType } from 'vue'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -63,21 +59,18 @@ function toggleAddReminder() {
} }
function updateData() { function updateData() {
console.log('reminders.updateData', reminders.value)
emit('update:modelValue', reminders.value)
isAddReminder.value = false isAddReminder.value = false
emit('update:modelValue', reminders.value)
} }
function editReminder(index: number) { function editReminder(index: number) {
console.log('reminders.editReminder', reminders.value[index])
if (reminders.value[index] === null) { if (reminders.value[index] === null) {
return return
} }
updateData() updateData()
} }
function addNewReminder(newReminder) { function addNewReminder(newReminder : ITaskReminder) {
console.log('reminders.addNewReminder to old reminders', newReminder)
if (newReminder == null) { if (newReminder == null) {
return return
} }
@ -86,7 +79,6 @@ function addNewReminder(newReminder) {
updateData() updateData()
} }
function removeReminderByIndex(index: number) { function removeReminderByIndex(index: number) {
reminders.value.splice(index, 1) reminders.value.splice(index, 1)
updateData() updateData()
@ -107,11 +99,15 @@ function removeReminderByIndex(index: number) {
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
.reminder-detail {
width: 100%;
}
.remove { .remove {
color: var(--danger); color: var(--danger);
padding-left: 2rem; vertical-align: top;
padding-left: .5rem;
line-height: 1;
} }
} }
} }
</style> </style>

View File

@ -5,7 +5,7 @@ import type {IReminderPeriodRelativeTo} from '@/types/IReminderPeriodRelativeTo'
export default class TaskReminderModel extends AbstractModel<ITaskReminder> implements ITaskReminder { export default class TaskReminderModel extends AbstractModel<ITaskReminder> implements ITaskReminder {
reminder: Date | null reminder: Date | null
relativePeriod: number = 0 relativePeriod = 0
relativeTo: IReminderPeriodRelativeTo | null = null relativeTo: IReminderPeriodRelativeTo | null = null
constructor(data: Partial<ITaskReminder> = {}) { constructor(data: Partial<ITaskReminder> = {}) {