forked from vikunja/frontend
193 lines
5.1 KiB
Vue
193 lines
5.1 KiB
Vue
<template>
|
|
<CreateEdit
|
|
:padding="false"
|
|
:title="heading"
|
|
@primary="create"
|
|
:loading="taskService.loading"
|
|
>
|
|
<input
|
|
:placeholder="$t('task.create.title')"
|
|
class="task-title input"
|
|
@keyup.enter="create"
|
|
v-model="newTask.title"
|
|
v-focus
|
|
/>
|
|
<p class="help is-danger ml-4" v-if="errorMessage !== ''">
|
|
{{ errorMessage }}
|
|
</p>
|
|
<QuickAddMagic class="ml-4" v-else/>
|
|
<BaseButton
|
|
v-if="!descriptionFormVisible"
|
|
@click="() => descriptionFormVisible = true"
|
|
class="toggle-description-button"
|
|
>
|
|
{{ $t('task.create.description') }}
|
|
</BaseButton>
|
|
<editor
|
|
v-if="descriptionFormVisible"
|
|
v-model="newTask.description"
|
|
:placeholder="$t('task.create.descriptionPlaceholder')"
|
|
:preview-is-default="false"
|
|
class="m-4"
|
|
/>
|
|
<div class="px-4 pb-4 task-attributes">
|
|
<Datepicker
|
|
v-model="newTask.dueDate"
|
|
v-slot="{ date, openPopup }"
|
|
>
|
|
<XButton variant="secondary" @click.stop="openPopup()" class="datepicker-button">
|
|
{{ date ? formatDateShort(date) : t('task.attributes.dueDate') }}
|
|
</XButton>
|
|
</Datepicker>
|
|
<XButton variant="secondary" class="ml-2">
|
|
{{ t('task.detail.actions.label') }}
|
|
</XButton>
|
|
<div class="is-flex pl-2">
|
|
<span
|
|
v-for="label in realLabels"
|
|
:style="{'background': label.hexColor, 'color': label.textColor}"
|
|
class="tag mr-2">
|
|
<span>{{ label.title }}</span>
|
|
<BaseButton @click="removeLabel(label)" class="delete is-small"/>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</CreateEdit>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import {computed, ref, watch} from 'vue'
|
|
import {useI18n} from 'vue-i18n'
|
|
import {formatISO} from 'date-fns'
|
|
|
|
import CreateEdit from '@/components/misc/create-edit.vue'
|
|
import Editor from '@/components/input/AsyncEditor'
|
|
import BaseButton from '@/components/base/BaseButton.vue'
|
|
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
|
import XButton from '@/components/input/button.vue'
|
|
import Datepicker from '@/components/input/datepicker.vue'
|
|
|
|
import type {ITask} from '@/modelTypes/ITask'
|
|
import TaskModel from '@/models/task'
|
|
import TaskService from '@/services/task'
|
|
import {useRouter} from 'vue-router'
|
|
import {useListStore} from '@/stores/lists'
|
|
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
|
|
import {parseTaskText} from '@/modules/parseTaskText'
|
|
import {findAssignees} from '@/helpers/findAssignees'
|
|
import {formatDateShort} from '@/helpers/time/formatDate'
|
|
import {useLabelStore} from '@/stores/labels'
|
|
import {useStore} from '@/store'
|
|
import type {ILabel} from '@/modelTypes/ILabel'
|
|
import LabelModel from '@/models/label'
|
|
|
|
const listStore = useListStore()
|
|
const labelStore = useLabelStore()
|
|
const store = useStore()
|
|
const router = useRouter()
|
|
const {t} = useI18n()
|
|
const props = defineProps<{
|
|
listId: number,
|
|
}>()
|
|
|
|
const heading = computed(() => {
|
|
const listTitle = listStore.getListById(props.listId)?.title || ''
|
|
return listTitle !== ''
|
|
? t('task.create.heading', {list: listTitle})
|
|
: t('task.new')
|
|
})
|
|
|
|
const errorMessage = ref('')
|
|
const taskService = ref(new TaskService())
|
|
const descriptionFormVisible = ref(false)
|
|
const newTask = ref<ITask>(new TaskModel({}))
|
|
|
|
const parsedTask = computed(() => parseTaskText(newTask.value.title, getQuickAddMagicMode()))
|
|
watch(
|
|
() => parsedTask.value.date,
|
|
date => newTask.value.dueDate = date,
|
|
)
|
|
|
|
const labels = ref<string[]>([])
|
|
watch(
|
|
() => parsedTask.value.labels,
|
|
labelTitles => labels.value = labelTitles,
|
|
)
|
|
const realLabels = computed<ILabel[]>(() => {
|
|
const existingLabels = labelStore.getLabelsByExactTitles(labels.value)
|
|
|
|
const newLabels = labels.value
|
|
.filter(l => l !== '' && !(existingLabels.map(le => le.title).includes(l)))
|
|
.map(newLabel => new LabelModel({title: newLabel}))
|
|
|
|
return [
|
|
...existingLabels,
|
|
...newLabels,
|
|
]
|
|
})
|
|
|
|
function removeLabel(label: ILabel) {
|
|
while (true) { // Using a loop to remove all labels, including possible duplicates added via quick add magic
|
|
const index = labels.value.findIndex(el => el.toLowerCase() === label.title.toLowerCase())
|
|
if (index === -1) {
|
|
break
|
|
}
|
|
labels.value.splice(index, 1)
|
|
}
|
|
}
|
|
|
|
async function create() {
|
|
if (newTask.value.title === '') {
|
|
errorMessage.value = t('list.create.addTitleRequired')
|
|
return
|
|
}
|
|
errorMessage.value = ''
|
|
|
|
const assignees = await findAssignees(parsedTask.value.assignees)
|
|
|
|
const finalTask = new TaskModel({
|
|
...newTask.value,
|
|
listId: props.listId,
|
|
title: parsedTask.value.text,
|
|
dueDate: newTask.value.dueDate !== null ? formatISO(newTask.value.dueDate) : null,
|
|
priority: parsedTask.value.priority,
|
|
assignees: parsedTask.value.assignees,
|
|
})
|
|
|
|
const task = await taskService.value.create(finalTask)
|
|
|
|
await store.dispatch('tasks/addLabelsToTask', {
|
|
task,
|
|
parsedLabels: labels.value,
|
|
})
|
|
|
|
return router.push({name: 'task.detail', params: {id: task.id}})
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.task-title {
|
|
width: 100%;
|
|
font-size: 1.5rem;
|
|
border: 0;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.toggle-description-button {
|
|
padding: 1rem;
|
|
color: var(--grey-400);
|
|
width: 100%;
|
|
text-align: left;
|
|
}
|
|
|
|
.datepicker-button {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.task-attributes {
|
|
display: flex;
|
|
align-items: center;
|
|
overflow-y: auto;
|
|
}
|
|
</style>
|