initial
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:visible="visible"
|
||||
:header="task ? 'Edit Task' : 'New Task'"
|
||||
:modal="true"
|
||||
style="width: min(520px, 92vw)"
|
||||
@update:visible="emit('update:visible', $event)"
|
||||
>
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<InputText v-model.trim="form.title" class="w-full" autofocus />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<Textarea v-model="form.description" rows="3" class="w-full" />
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Status</label>
|
||||
<Select v-model="form.status" :options="statusOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Priority</label>
|
||||
<Select v-model="form.priority" :options="priorityOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Assignee</label>
|
||||
<Select
|
||||
v-model="form.assigneeId"
|
||||
:options="assigneeOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
showClear
|
||||
placeholder="Unassigned"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Due date</label>
|
||||
<DatePicker v-model="form.dueDate" class="w-full" dateFormat="yy-mm-dd" showClear />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="savingError" class="mb-2">
|
||||
<Message severity="error" variant="simple">{{ savingError }}</Message>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<Button
|
||||
v-if="task && canDelete"
|
||||
label="Delete"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
text
|
||||
@click="onDelete"
|
||||
/>
|
||||
<div>
|
||||
<Button label="Cancel" severity="secondary" text @click="emit('update:visible', false)" />
|
||||
<Button v-if="canEdit" label="Save" :loading="saving" @click="onSave" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { createTask, updateTask, deleteTask } from '../../services/modules'
|
||||
import { errorMessage } from '../../services/api'
|
||||
import type { Task, TaskPriority, TaskStatus } from '../../types'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
task: Task | null
|
||||
projectId: string
|
||||
members: { userId: string; displayName: string }[]
|
||||
canEdit: boolean
|
||||
canDelete: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
saved: []
|
||||
deleted: []
|
||||
}>()
|
||||
|
||||
const toast = useToast()
|
||||
const saving = ref(false)
|
||||
const savingError = ref('')
|
||||
|
||||
const statusOptions = [
|
||||
{ label: 'To Do', value: 'Todo' as TaskStatus },
|
||||
{ label: 'In Progress', value: 'InProgress' as TaskStatus },
|
||||
{ label: 'Done', value: 'Done' as TaskStatus },
|
||||
{ label: 'Cancelled', value: 'Cancelled' as TaskStatus },
|
||||
]
|
||||
|
||||
const priorityOptions = [
|
||||
{ label: 'Low', value: 'Low' as TaskPriority },
|
||||
{ label: 'Medium', value: 'Medium' as TaskPriority },
|
||||
{ label: 'High', value: 'High' as TaskPriority },
|
||||
]
|
||||
|
||||
const assigneeOptions = computed(() => props.members.map((m) => ({ label: m.displayName, value: m.userId })))
|
||||
|
||||
const form = reactive({
|
||||
title: '',
|
||||
description: '',
|
||||
status: 'Todo' as TaskStatus,
|
||||
priority: 'Medium' as TaskPriority,
|
||||
assigneeId: null as string | null,
|
||||
dueDate: null as Date | null,
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
savingError.value = ''
|
||||
if (props.task) {
|
||||
form.title = props.task.title
|
||||
form.description = props.task.description ?? ''
|
||||
form.status = props.task.status
|
||||
form.priority = props.task.priority
|
||||
form.assigneeId = props.task.assigneeId
|
||||
form.dueDate = props.task.dueDate ? new Date(props.task.dueDate) : null
|
||||
} else {
|
||||
form.title = ''
|
||||
form.description = ''
|
||||
form.status = 'Todo'
|
||||
form.priority = 'Medium'
|
||||
form.assigneeId = null
|
||||
form.dueDate = null
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
async function onSave() {
|
||||
if (!form.title.trim()) {
|
||||
savingError.value = 'Title is required'
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
savingError.value = ''
|
||||
try {
|
||||
const dueDate = form.dueDate ? form.dueDate.toISOString() : null
|
||||
if (props.task) {
|
||||
await updateTask(props.task.id, {
|
||||
title: form.title.trim(),
|
||||
description: form.description || null,
|
||||
status: form.status,
|
||||
priority: form.priority,
|
||||
assigneeId: form.assigneeId,
|
||||
dueDate,
|
||||
})
|
||||
} else {
|
||||
await createTask(props.projectId, {
|
||||
title: form.title.trim(),
|
||||
description: form.description || null,
|
||||
status: form.status,
|
||||
priority: form.priority,
|
||||
assigneeId: form.assigneeId,
|
||||
dueDate,
|
||||
})
|
||||
}
|
||||
toast.add({ severity: 'success', summary: 'Saved', life: 2000 })
|
||||
emit('saved')
|
||||
} catch (e) {
|
||||
savingError.value = errorMessage(e)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onDelete() {
|
||||
if (!props.task) return
|
||||
saving.value = true
|
||||
try {
|
||||
await deleteTask(props.task.id)
|
||||
toast.add({ severity: 'success', summary: 'Task deleted', life: 2000 })
|
||||
emit('deleted')
|
||||
} catch (e) {
|
||||
savingError.value = errorMessage(e)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user