feat(workflow) : my-tasks - kanban groupé par catégorie avec badge statut, suppression drag-to-status
This commit is contained in:
@@ -40,6 +40,13 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex items-center gap-1.5">
|
||||
<span
|
||||
v-if="showStatusBadge && task.status"
|
||||
class="rounded-full px-2 py-0.5 text-xs font-semibold text-white"
|
||||
:style="{ backgroundColor: task.status.color }"
|
||||
>
|
||||
{{ task.status.label }}
|
||||
</span>
|
||||
<span
|
||||
v-if="task.priority"
|
||||
class="rounded-full px-2 py-0.5 text-xs font-semibold text-white"
|
||||
@@ -106,8 +113,10 @@ import type { Task } from '~/services/dto/task'
|
||||
const props = withDefaults(defineProps<{
|
||||
task: Task
|
||||
showProjectColor?: boolean
|
||||
showStatusBadge?: boolean
|
||||
}>(), {
|
||||
showProjectColor: false,
|
||||
showStatusBadge: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -7,6 +7,8 @@ import type { TaskTag } from '~/services/dto/task-tag'
|
||||
import type { TaskGroup } from '~/services/dto/task-group'
|
||||
import type { UserData } from '~/services/dto/user-data'
|
||||
import type { Project } from '~/services/dto/project'
|
||||
import type { StatusCategory } from '~/services/dto/workflow'
|
||||
import { STATUS_CATEGORY_LABEL } from '~/services/dto/workflow'
|
||||
import { useTaskService } from '~/services/tasks'
|
||||
import { useTaskStatusService } from '~/services/task-statuses'
|
||||
import { useTaskEffortService } from '~/services/task-efforts'
|
||||
@@ -112,13 +114,11 @@ const sortOptions = computed(() => [
|
||||
{ label: t('myTasks.sortScheduledStart'), value: SORT_SCHEDULED },
|
||||
])
|
||||
|
||||
// Kanban helpers
|
||||
const sortedStatuses = computed(() =>
|
||||
[...statuses.value].sort((a, b) => a.position - b.position)
|
||||
)
|
||||
// Kanban helpers (grouped by canonical status category)
|
||||
const CATEGORIES: StatusCategory[] = ['todo', 'in_progress', 'blocked', 'review', 'done']
|
||||
|
||||
function tasksByStatus(statusId: number): Task[] {
|
||||
return tasks.value.filter(t => t.status?.id === statusId)
|
||||
function tasksByCategory(category: StatusCategory): Task[] {
|
||||
return tasks.value.filter(t => t.status?.category === category)
|
||||
}
|
||||
|
||||
const backlogTasks = computed(() =>
|
||||
@@ -205,44 +205,6 @@ watch(selectedProjectId, () => {
|
||||
selectedGroupId.value = null
|
||||
}, { flush: 'sync' })
|
||||
|
||||
// Drag & drop
|
||||
const dragOverStatusId = ref<number | null>(null)
|
||||
const dragCounter = ref(0)
|
||||
|
||||
function onDragEnter(id: number) {
|
||||
dragCounter.value++
|
||||
dragOverStatusId.value = id
|
||||
}
|
||||
|
||||
function onDragLeave() {
|
||||
dragCounter.value--
|
||||
if (dragCounter.value === 0) {
|
||||
dragOverStatusId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function onDrop(event: DragEvent) {
|
||||
dragCounter.value = 0
|
||||
dragOverStatusId.value = null
|
||||
return Number(event.dataTransfer!.getData('text/plain'))
|
||||
}
|
||||
|
||||
async function onDropStatus(event: DragEvent, status: TaskStatus) {
|
||||
const taskId = onDrop(event)
|
||||
const task = tasks.value.find(t => t.id === taskId)
|
||||
if (!task || task.status?.id === status.id) return
|
||||
task.status = status
|
||||
await taskService.update(taskId, { status: `/api/task_statuses/${status.id}` })
|
||||
}
|
||||
|
||||
async function onDropBacklog(event: DragEvent) {
|
||||
const taskId = onDrop(event)
|
||||
const task = tasks.value.find(t => t.id === taskId)
|
||||
if (!task || !task.status) return
|
||||
task.status = null
|
||||
await taskService.update(taskId, { status: null })
|
||||
}
|
||||
|
||||
// Modal
|
||||
function openTaskCreate() {
|
||||
selectedTask.value = null
|
||||
@@ -428,36 +390,29 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kanban View -->
|
||||
<!-- Kanban View — grouped by canonical category -->
|
||||
<div v-if="viewMode === 'kanban'">
|
||||
<div class="mt-6 flex h-[calc(100vh-260px)] gap-3 overflow-x-auto pb-4">
|
||||
<div
|
||||
v-for="status in sortedStatuses"
|
||||
:key="status.id"
|
||||
class="flex min-w-36 flex-1 flex-col rounded-lg transition-colors"
|
||||
:class="dragOverStatusId === status.id ? 'bg-neutral-200' : 'bg-neutral-50'"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent="onDragEnter(status.id)"
|
||||
@dragleave="onDragLeave"
|
||||
@drop.prevent="onDropStatus($event, status)"
|
||||
v-for="cat in CATEGORIES"
|
||||
:key="cat"
|
||||
class="flex min-w-40 flex-1 flex-col rounded-lg bg-neutral-50"
|
||||
>
|
||||
<div
|
||||
class="shrink-0 rounded-t-lg px-4 py-3 text-sm font-bold text-white"
|
||||
:style="{ backgroundColor: status.color }"
|
||||
>
|
||||
{{ status.label }} ({{ tasksByStatus(status.id).length }})
|
||||
<div class="shrink-0 rounded-t-lg bg-neutral-200 px-4 py-3 text-sm font-bold text-neutral-800">
|
||||
{{ STATUS_CATEGORY_LABEL[cat] }} ({{ tasksByCategory(cat).length }})
|
||||
</div>
|
||||
<div class="min-h-0 flex-1 overflow-y-auto p-3">
|
||||
<div class="flex flex-col gap-3">
|
||||
<TaskCard
|
||||
v-for="task in tasksByStatus(status.id)"
|
||||
v-for="task in tasksByCategory(cat)"
|
||||
:key="task.id"
|
||||
:task="task"
|
||||
show-project-color
|
||||
show-status-badge
|
||||
@click="openTaskEdit(task)"
|
||||
/>
|
||||
<p
|
||||
v-if="tasksByStatus(status.id).length === 0"
|
||||
v-if="tasksByCategory(cat).length === 0"
|
||||
class="py-4 text-center text-xs text-neutral-400"
|
||||
>
|
||||
{{ $t('myTasks.noTasks') }}
|
||||
@@ -467,15 +422,8 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backlog below kanban -->
|
||||
<div
|
||||
class="mt-8 rounded-lg p-4 transition-colors"
|
||||
:class="dragOverStatusId === 0 ? 'bg-neutral-200' : 'bg-neutral-50'"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent="onDragEnter(0)"
|
||||
@dragleave="onDragLeave"
|
||||
@drop.prevent="onDropBacklog($event)"
|
||||
>
|
||||
<!-- Backlog below kanban (no drag/drop — status change goes through TaskModal) -->
|
||||
<div class="mt-8 rounded-lg bg-neutral-50 p-4">
|
||||
<h2 class="text-lg font-bold text-neutral-900">{{ $t('myTasks.backlog') }} ({{ backlogTasks.length }})</h2>
|
||||
<div class="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<TaskCard
|
||||
@@ -483,6 +431,7 @@ onMounted(async () => {
|
||||
:key="task.id"
|
||||
:task="task"
|
||||
show-project-color
|
||||
show-status-badge
|
||||
@click="openTaskEdit(task)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user