From 5f917e93eb301c90aac35eb27d62e1b1cfa01ea3 Mon Sep 17 00:00:00 2001 From: Twyco Date: Sat, 18 Jul 2026 18:49:23 +0200 Subject: [PATCH] fix: polish project workspace interactions --- .../components/projects/ProjectPageHeader.vue | 34 ++++ .../js/components/projects/ProjectTabs.vue | 36 ++--- resources/js/pages/projects/Backlog.vue | 48 +++--- resources/js/pages/projects/Board.vue | 146 ++++++++++++------ resources/js/pages/projects/Settings.vue | 25 +-- resources/js/types/project-navigation.ts | 6 + 6 files changed, 185 insertions(+), 110 deletions(-) create mode 100644 resources/js/components/projects/ProjectPageHeader.vue diff --git a/resources/js/components/projects/ProjectPageHeader.vue b/resources/js/components/projects/ProjectPageHeader.vue new file mode 100644 index 0000000..6e275dc --- /dev/null +++ b/resources/js/components/projects/ProjectPageHeader.vue @@ -0,0 +1,34 @@ + + + diff --git a/resources/js/components/projects/ProjectTabs.vue b/resources/js/components/projects/ProjectTabs.vue index a5fd6b6..9308b9c 100644 --- a/resources/js/components/projects/ProjectTabs.vue +++ b/resources/js/components/projects/ProjectTabs.vue @@ -14,24 +14,6 @@ defineProps(); aria-label="Project sections" class="border-outline-variant/70 flex min-w-max items-center gap-1 border-b" > - - - Board - - - (); /> + + + Board + + + import { Form, Head, Link } from '@inertiajs/vue3'; +import { Plus } from 'lucide-vue-next'; import ProjectBoardController from '@/actions/App/Http/Controllers/ProjectBoardController'; -import Heading from '@/components/Heading.vue'; +import ProjectPageHeader from '@/components/projects/ProjectPageHeader.vue'; import ProjectTabs from '@/components/projects/ProjectTabs.vue'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -72,34 +73,25 @@ const allowedTargetStatuses = (ticket: TicketModel): TicketStatusModel[] => {
-
-
- - {{ project.key }} - - -
- -
- -
-
+ +
import { Head, Link, router } from '@inertiajs/vue3'; import { CircleUserRound, GripVertical, Plus } from 'lucide-vue-next'; -import { onBeforeUnmount, ref } from 'vue'; +import { onBeforeUnmount, onMounted, ref } from 'vue'; import ProjectBoardController from '@/actions/App/Http/Controllers/ProjectBoardController'; +import ProjectPageHeader from '@/components/projects/ProjectPageHeader.vue'; import ProjectTabs from '@/components/projects/ProjectTabs.vue'; import UserAvatar from '@/components/ui/avatar/UserAvatar.vue'; -import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import AppLayout from '@/layouts/AppLayout.vue'; import { index as projectIndex } from '@/routes/projects'; @@ -53,6 +53,7 @@ const pointerX = ref(0); const pointerY = ref(0); let autoScrollFrame: number | undefined; +let initialScrollFrame: number | undefined; let longPressTimer: number | undefined; let suppressClickTicketId: number | null = null; @@ -96,6 +97,9 @@ const isActiveDropTarget = ( ); }; +const dropZoneRows = (ticket: TicketModel, boardColumnId: number): string => + `repeat(${allowedStatusesForColumn(ticket, boardColumnId).length}, minmax(0, 1fr))`; + const clearLongPressTimer = (): void => { if (longPressTimer !== undefined) { window.clearTimeout(longPressTimer); @@ -221,7 +225,11 @@ const handlePointerDown = ( startY: event.clientY, }; - (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + window.addEventListener('pointermove', handlePointerMove, { + passive: false, + }); + window.addEventListener('pointerup', finishPointer); + window.addEventListener('pointercancel', cancelPointer); if (event.pointerType === 'touch') { longPressTimer = window.setTimeout(startDragging, 240); @@ -268,6 +276,9 @@ const handlePointerMove = (event: PointerEvent): void => { const resetDragState = (): void => { clearLongPressTimer(); stopAutoScroll(); + window.removeEventListener('pointermove', handlePointerMove); + window.removeEventListener('pointerup', finishPointer); + window.removeEventListener('pointercancel', cancelPointer); pendingDragTicket.value = null; draggedTicket.value = null; dragPointer.value = null; @@ -311,11 +322,6 @@ const finishPointer = (event: PointerEvent): void => { return; } - const targetElement = event.currentTarget as HTMLElement; - if (targetElement.hasPointerCapture(event.pointerId)) { - targetElement.releasePointerCapture(event.pointerId); - } - resetDragState(); if (drag !== null) { @@ -347,7 +353,21 @@ const handleTicketClick = (event: MouseEvent, ticketId: number): void => { event.stopPropagation(); }; +onMounted(() => { + initialScrollFrame = window.requestAnimationFrame(() => { + if (boardScroller.value !== null) { + boardScroller.value.scrollLeft = 0; + } + + initialScrollFrame = undefined; + }); +}); + onBeforeUnmount(() => { + if (initialScrollFrame !== undefined) { + window.cancelAnimationFrame(initialScrollFrame); + } + resetDragState(); }); @@ -357,23 +377,13 @@ onBeforeUnmount(() => {
-
-
-
- - {{ project.key }} - - - Project board - -
-

- {{ project.name }} -

-
- +
+
Drop as {{ ticketStatus.name }}
- -
{ ? 'pointer-events-none animate-pulse opacity-55' : '', ]" - @pointerdown=" - handlePointerDown( - $event, - ticket, - boardColumn, - ) - " - @pointermove="handlePointerMove" - @pointerup="finishPointer" - @pointercancel="cancelPointer" > { + + diff --git a/resources/js/pages/projects/Settings.vue b/resources/js/pages/projects/Settings.vue index 46e54e7..10bc04e 100644 --- a/resources/js/pages/projects/Settings.vue +++ b/resources/js/pages/projects/Settings.vue @@ -10,8 +10,8 @@ import { Tags, } from 'lucide-vue-next'; import { computed } from 'vue'; +import ProjectPageHeader from '@/components/projects/ProjectPageHeader.vue'; import ProjectTabs from '@/components/projects/ProjectTabs.vue'; -import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import AppLayout from '@/layouts/AppLayout.vue'; import { index as projectsIndex } from '@/routes/projects'; @@ -134,29 +134,18 @@ const sectionHref = (section: ProjectSettingsSection) =>
-
-
- - {{ project.key }} - -
-

- {{ project.name }} -

-

- Project configuration and access controls. -

-
-
- -
+
= Omit< T, 'embedded' | 'project' | 'status'