diff --git a/frontend/components/task/TaskBulkActions.vue b/frontend/components/task/TaskBulkActions.vue new file mode 100644 index 0000000..f88ab80 --- /dev/null +++ b/frontend/components/task/TaskBulkActions.vue @@ -0,0 +1,131 @@ + + + diff --git a/frontend/components/task/TaskCard.vue b/frontend/components/task/TaskCard.vue index 528cf2e..7f11c3b 100644 --- a/frontend/components/task/TaskCard.vue +++ b/frontend/components/task/TaskCard.vue @@ -9,7 +9,12 @@
- {{ task.project.code }}{{ task.number }} + {{ task.project.code }}{{ task.number }} import type { Task } from '~/services/dto/task' -const props = defineProps<{ +const props = withDefaults(defineProps<{ task: Task -}>() + showProjectColor?: boolean +}>(), { + showProjectColor: false, +}) const emit = defineEmits<{ (e: 'click'): void diff --git a/frontend/components/task/TaskListItem.vue b/frontend/components/task/TaskListItem.vue new file mode 100644 index 0000000..95a0e19 --- /dev/null +++ b/frontend/components/task/TaskListItem.vue @@ -0,0 +1,108 @@ + + + diff --git a/frontend/pages/my-tasks.vue b/frontend/pages/my-tasks.vue index 8af5999..d7078c3 100644 --- a/frontend/pages/my-tasks.vue +++ b/frontend/pages/my-tasks.vue @@ -51,6 +51,9 @@ const selectedAssigneeId = ref(auth.user?.id ?? null) // View toggle const viewMode = ref<'kanban' | 'list'>('kanban') +// Bulk selection +const selectedTaskIds = reactive(new Set()) + // Modal const taskModalOpen = ref(false) const selectedTask = ref(null) @@ -228,6 +231,52 @@ async function onSaved() { await loadTasks() } +function toggleTaskSelect(taskId: number) { + if (selectedTaskIds.has(taskId)) { + selectedTaskIds.delete(taskId) + } else { + selectedTaskIds.add(taskId) + } +} + +function toggleSelectAll(taskList: Task[]) { + if (selectedTaskIds.size === taskList.length) { + selectedTaskIds.clear() + } else { + taskList.forEach(t => selectedTaskIds.add(t.id)) + } +} + +async function onBulkUpdate(field: string, value: number) { + const ids = [...selectedTaskIds] + if (ids.length === 0) return + const payload: Record = {} + if (field === 'status') payload.status = `/api/task_statuses/${value}` + else if (field === 'assignee') payload.assignee = `/api/users/${value}` + else if (field === 'priority') payload.priority = `/api/task_priorities/${value}` + else if (field === 'effort') payload.effort = `/api/task_efforts/${value}` + else if (field === 'group') payload.group = `/api/task_groups/${value}` + await Promise.all(ids.map(id => taskService.update(id, payload))) + selectedTaskIds.clear() + await loadTasks() +} + +async function onBulkArchive() { + const ids = [...selectedTaskIds] + if (ids.length === 0) return + await Promise.all(ids.map(id => taskService.update(id, { archived: true }))) + selectedTaskIds.clear() + await loadTasks() +} + +async function onBulkDelete() { + const ids = [...selectedTaskIds] + if (ids.length === 0) return + await Promise.all(ids.map(id => taskService.remove(id))) + selectedTaskIds.clear() + await loadTasks() +} + onMounted(() => { loadAll() }) @@ -247,24 +296,16 @@ onMounted(() => { {{ $t('myTasks.createTask') }} -
- - -
+
@@ -351,6 +392,7 @@ onMounted(() => { v-for="task in tasksByStatus(status.id)" :key="task.id" :task="task" + show-project-color @click="openTaskEdit(task)" />

{ v-for="task in backlogTasks" :key="task.id" :task="task" + show-project-color @click="openTaskEdit(task)" />

@@ -392,63 +435,31 @@ onMounted(() => { -
-
+ + -
-

{{ task.title }}

-
- - {{ task.priority.label }} - - - {{ tag.label }} - -
-
-
- -
- - - {{ task.project.code }}-{{ task.number }} - - -
-
-
+ @toggle-select="toggleTaskSelect" + />

+ Ajouter un ticket + Ticket +

-
+
+ +
+ + +

+ Aucun ticket +

+
+ (null) const selectedTagId = ref(null) const selectedAssigneeId = ref(null) const selectedStatusId = ref(null) +const selectedPriorityId = ref(null) +const selectedEffortId = ref(null) +const viewMode = ref<'kanban' | 'list'>('kanban') +const selectedTaskIds = reactive(new Set()) const dragOverStatusId = ref(null) const dragCounter = ref(0) const taskDrawerOpen = ref(false) @@ -213,6 +279,14 @@ const statusFilterOptions = computed(() => statuses.value.map(s => ({ label: s.label, value: s.id })) ) +const priorityFilterOptions = computed(() => + priorities.value.map(p => ({ label: p.label, value: p.id })) +) + +const effortFilterOptions = computed(() => + efforts.value.map(e => ({ label: e.label, value: e.id })) +) + const filteredTasks = computed(() => { let result = tasks.value.filter(t => !t.archived) if (selectedGroupId.value) { @@ -227,6 +301,12 @@ const filteredTasks = computed(() => { if (selectedStatusId.value) { result = result.filter(t => t.status?.id === selectedStatusId.value) } + if (selectedPriorityId.value) { + result = result.filter(t => t.priority?.id === selectedPriorityId.value) + } + if (selectedEffortId.value) { + result = result.filter(t => t.effort?.id === selectedEffortId.value) + } return result }) @@ -311,6 +391,52 @@ async function onDropBacklog(event: DragEvent) { await taskService.update(taskId, { status: null }) } +function toggleTaskSelect(taskId: number) { + if (selectedTaskIds.has(taskId)) { + selectedTaskIds.delete(taskId) + } else { + selectedTaskIds.add(taskId) + } +} + +function toggleSelectAll(taskList: Task[]) { + if (selectedTaskIds.size === taskList.length) { + selectedTaskIds.clear() + } else { + taskList.forEach(t => selectedTaskIds.add(t.id)) + } +} + +async function onBulkUpdate(field: string, value: number) { + const ids = [...selectedTaskIds] + if (ids.length === 0) return + const payload: Record = {} + if (field === 'status') payload.status = `/api/task_statuses/${value}` + else if (field === 'assignee') payload.assignee = `/api/users/${value}` + else if (field === 'priority') payload.priority = `/api/task_priorities/${value}` + else if (field === 'effort') payload.effort = `/api/task_efforts/${value}` + else if (field === 'group') payload.group = `/api/task_groups/${value}` + await Promise.all(ids.map(id => taskService.update(id, payload))) + selectedTaskIds.clear() + await loadData() +} + +async function onBulkArchive() { + const ids = [...selectedTaskIds] + if (ids.length === 0) return + await Promise.all(ids.map(id => taskService.update(id, { archived: true }))) + selectedTaskIds.clear() + await loadData() +} + +async function onBulkDelete() { + const ids = [...selectedTaskIds] + if (ids.length === 0) return + await Promise.all(ids.map(id => taskService.remove(id))) + selectedTaskIds.clear() + await loadData() +} + async function onSaved() { await loadData() }