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 @@
+
+
+
+
+
+
+
+
+ {{ selectedCount }}/{{ totalCount }}
+
+
+
+
+ v && emit('bulk-update', 'status', v)"
+ />
+
+ v && emit('bulk-update', 'assignee', v)"
+ />
+
+ v && emit('bulk-update', 'priority', v)"
+ />
+
+ v && emit('bulk-update', 'effort', v)"
+ />
+
+ v && emit('bulk-update', 'group', v)"
+ />
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+ {{ task.project.code }}-{{ task.number }}
+
+
+
+
+
{{ task.title }}
+
+
+
+ {{ tag.label }}
+
+
+ {{ task.status.label }}
+
+
+ Backlog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+
-
+
+
+
+
(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()
}