From c5898fbf74d3774c18c74a5f21ed57ec65a7a571 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 16 Mar 2026 16:35:02 +0100 Subject: [PATCH] feat(ui) : add create task button on my-tasks and responsive kanban columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add "Créer une tâche" button on my-tasks page with mandatory project selector - TaskModal now accepts optional projects prop for project selection in create mode - Replace fixed-width kanban columns (w-72 shrink-0) with flexible layout (min-w-36 flex-1) - Add min-w-0 and overflow-x-hidden on default layout to properly contain content - Kanban now adapts to screen size from 1024px to 1920px+ Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/components/task/TaskModal.vue | 70 +++++++++++++++++++++++--- frontend/i18n/locales/fr.json | 3 +- frontend/layouts/default.vue | 4 +- frontend/pages/my-tasks.vue | 49 +++++++++++------- frontend/pages/projects/[id]/index.vue | 6 +-- 5 files changed, 103 insertions(+), 29 deletions(-) diff --git a/frontend/components/task/TaskModal.vue b/frontend/components/task/TaskModal.vue index 3f3ceeb..e9f45e7 100644 --- a/frontend/components/task/TaskModal.vue +++ b/frontend/components/task/TaskModal.vue @@ -65,6 +65,20 @@ @blur="touched.title = true" /> + +
+ +

+ Le projet est requis +

+
+
() const emit = defineEmits<{ @@ -318,10 +335,12 @@ const form = reactive({ groupId: null as number | null, tagIds: [] as number[], clientTicketId: null as number | null, + projectId: null as number | null, }) const touched = reactive({ title: false, + project: false, }) const statusOptions = computed(() => @@ -340,8 +359,22 @@ const userOptions = computed(() => props.users.map(u => ({ label: u.username, value: u.id })) ) -const groupOptions = computed(() => - props.groups.map(g => ({ label: g.title, value: g.id })) +const groupOptions = computed(() => { + let filtered = props.groups + if (showProjectSelect.value && form.projectId) { + filtered = filtered.filter(g => g.project?.id === form.projectId) + } + return filtered.map(g => ({ label: g.title, value: g.id })) +}) + +const showProjectSelect = computed(() => !!props.projects?.length && !isEditing.value) + +const projectOptions = computed(() => + (props.projects ?? []).map(p => ({ label: p.name, value: p.id })) +) + +const resolvedProjectId = computed(() => + showProjectSelect.value ? form.projectId : props.projectId ) const canArchive = computed(() => { @@ -385,8 +418,10 @@ function populateForm(task: Task | null) { form.groupId = null form.tagIds = [] form.clientTicketId = null + form.projectId = null } touched.title = false + touched.project = false } watch(() => props.modelValue, async (open) => { @@ -394,9 +429,14 @@ watch(() => props.modelValue, async (open) => { confirmDeleteDocOpen.value = false documentToDelete.value = null populateForm(props.task) - try { - clientTickets.value = await clientTicketService.getAll({ project: props.projectId }) - } catch { + const pid = resolvedProjectId.value + if (pid) { + try { + clientTickets.value = await clientTicketService.getAll({ project: pid }) + } catch { + clientTickets.value = [] + } + } else { clientTickets.value = [] } if (props.task?.project?.giteaOwner && props.task?.project?.giteaRepo && !giteaUrl.value) { @@ -426,6 +466,22 @@ const clientTicketOptions = computed(() => clientTickets.value.map(ct => ({ label: `CT-${String(ct.number).padStart(3, '0')} — ${ct.title}`, value: ct.id })) ) +// Reset group and reload client tickets when project changes in create mode +watch(() => form.projectId, async (pid) => { + if (!showProjectSelect.value) return + form.groupId = null + form.clientTicketId = null + if (pid) { + try { + clientTickets.value = await clientTicketService.getAll({ project: pid }) + } catch { + clientTickets.value = [] + } + } else { + clientTickets.value = [] + } +}) + const authStore = useAuthStore() const isAdmin = computed(() => authStore.user?.roles?.includes('ROLE_ADMIN') ?? false) @@ -541,7 +597,9 @@ async function handleUnarchive() { async function handleSubmit() { touched.title = true + touched.project = true if (!form.title.trim()) return + if (showProjectSelect.value && !form.projectId) return isSubmitting.value = true try { @@ -553,7 +611,7 @@ async function handleSubmit() { priority: form.priorityId ? `/api/task_priorities/${form.priorityId}` : null, assignee: form.assigneeId ? `/api/users/${form.assigneeId}` : null, group: form.groupId ? `/api/task_groups/${form.groupId}` : null, - project: `/api/projects/${props.projectId}`, + project: `/api/projects/${resolvedProjectId.value}`, tags: form.tagIds.map(id => `/api/task_tags/${id}`), clientTicket: form.clientTicketId ? `/api/client_tickets/${form.clientTicketId}` : null, } diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index 1044ec1..859a668 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -112,7 +112,8 @@ "allEfforts": "Tous les efforts", "allAssignees": "Tous", "noTasks": "Aucune tâche", - "backlog": "Backlog" + "backlog": "Backlog", + "createTask": "Créer une tâche" }, "dashboard": { "title": "Tableau de bord", diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index bbf64ae..3c6f7fb 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -123,9 +123,9 @@
-
+
-
+
diff --git a/frontend/pages/my-tasks.vue b/frontend/pages/my-tasks.vue index bb28a10..dabc8b3 100644 --- a/frontend/pages/my-tasks.vue +++ b/frontend/pages/my-tasks.vue @@ -214,6 +214,11 @@ async function onDropBacklog(event: DragEvent) { } // Modal +function openTaskCreate() { + selectedTask.value = null + taskModalOpen.value = true +} + function openTaskEdit(task: Task) { selectedTask.value = task taskModalOpen.value = true @@ -229,28 +234,37 @@ onMounted(() => {