From e18ff30ca3c9e52b4aedc0734713742f91da6298 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 21 May 2026 09:22:40 +0200 Subject: [PATCH] =?UTF-8?q?fix(my-tasks)=20:=20drag=20&=20drop=20par=20wor?= =?UTF-8?q?kflow=20(popover=20si=20ambigu)=20+=20ent=C3=AAtes=20de=20colon?= =?UTF-8?q?nes=20teint=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/task/StatusPickerPopover.vue | 35 +++++++++++ frontend/i18n/locales/fr.json | 3 +- frontend/pages/my-tasks.vue | 63 ++++++++++++++++++- 3 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 frontend/components/task/StatusPickerPopover.vue diff --git a/frontend/components/task/StatusPickerPopover.vue b/frontend/components/task/StatusPickerPopover.vue new file mode 100644 index 0000000..a37e074 --- /dev/null +++ b/frontend/components/task/StatusPickerPopover.vue @@ -0,0 +1,35 @@ + + + diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index 7bd9f6c..c38f0d6 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -236,7 +236,8 @@ "sortBy": "Trier par", "sortDefault": "Par défaut", "sortDeadline": "Échéance", - "sortScheduledStart": "Date planifiée" + "sortScheduledStart": "Date planifiée", + "dropRefused": "Aucun statut de cette colonne dans le workflow de ce projet" }, "dashboard": { "title": "Tableau de bord", diff --git a/frontend/pages/my-tasks.vue b/frontend/pages/my-tasks.vue index cece4ab..937167e 100644 --- a/frontend/pages/my-tasks.vue +++ b/frontend/pages/my-tasks.vue @@ -8,7 +8,7 @@ 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 { STATUS_CATEGORY_LABEL, STATUS_CATEGORY_COLOR, contrastText } from '~/services/dto/workflow' import { useTaskService } from '~/services/tasks' import { useTaskStatusService } from '~/services/task-statuses' import { useTaskEffortService } from '~/services/task-efforts' @@ -71,6 +71,46 @@ const selectedTask = ref(null) // Timer const timerStore = useTimerStore() +// Toast +const toast = useToast() + +// Drag & drop +const dragOverCategory = ref(null) +const pendingPicker = ref<{ statuses: TaskStatus[], task: Task, x: number, y: number } | null>(null) + +function statusesForTaskCategory(task: Task, category: StatusCategory): TaskStatus[] { + const wf = task.project?.workflow + if (!wf) return [] + return wf.statuses.filter(s => s.category === category) +} + +async function applyStatus(task: Task, status: TaskStatus): Promise { + await taskService.update(task.id, { status: `/api/task_statuses/${status.id}` }) + await loadTasks() +} + +function onDrop(category: StatusCategory, event: DragEvent): void { + dragOverCategory.value = null + const taskId = Number(event.dataTransfer?.getData('text/plain')) + const task = tasks.value.find(t => t.id === taskId) + if (!task) return + const candidates = statusesForTaskCategory(task, category) + if (candidates.length === 0) { + toast.error({ message: t('myTasks.dropRefused') }) + return + } + if (candidates.length === 1) { + void applyStatus(task, candidates[0]) + return + } + pendingPicker.value = { statuses: candidates, task, x: event.clientX, y: event.clientY } +} + +function onPickerChoice(status: TaskStatus): void { + if (pendingPicker.value) void applyStatus(pendingPicker.value.task, status) + pendingPicker.value = null +} + function isTimerOnTask(task: Task): boolean { const entry = timerStore.activeEntry if (!entry?.task) return false @@ -397,9 +437,16 @@ onMounted(async () => {
-
+
{{ STATUS_CATEGORY_LABEL[cat] }} ({{ tasksByCategory(cat).length }})
@@ -481,6 +528,16 @@ onMounted(async () => {

+ + +