From fdf1fbe4aa997488375442494a6df7b472e1287b Mon Sep 17 00:00:00 2001 From: Mahdi Date: Tue, 10 Nov 2026 20:10:16 +0330 Subject: [PATCH] feat(activities): peek at a card from the board Let people read notes, postpone, and mark an activity done beside the card instead of opening the full page. Co-authored-by: Cursor --- apps/web/src/components/JalaliDateInput.vue | 2 +- .../activities/ActivitiesKanban.vue | 36 +- .../activities/ActivityCommentSheet.vue | 4 +- .../activities/ActivityPostponeSheet.vue | 4 +- .../activities/ActivityQuickView.vue | 538 ++++++++++++++++++ apps/web/src/components/pipeline/DealCard.vue | 61 +- .../src/components/pipeline/PipelineBoard.vue | 4 +- .../components/pipeline/PipelineColumn.vue | 4 +- apps/web/src/lib/pipeline-board.ts | 3 + 9 files changed, 630 insertions(+), 26 deletions(-) create mode 100644 apps/web/src/components/activities/ActivityQuickView.vue diff --git a/apps/web/src/components/JalaliDateInput.vue b/apps/web/src/components/JalaliDateInput.vue index d34fcbd..6b165f6 100644 --- a/apps/web/src/components/JalaliDateInput.vue +++ b/apps/web/src/components/JalaliDateInput.vue @@ -360,7 +360,7 @@ function place() { function liftAboveOverlays() { let z = 120; - for (const el of document.querySelectorAll('.ant-modal-wrap, .ant-drawer, .ant-popover, [data-lead-filter]')) { + for (const el of document.querySelectorAll('.ant-modal-wrap, .ant-drawer, .ant-popover, [role="dialog"], [data-lead-filter]')) { const zi = Number.parseInt(window.getComputedStyle(el).zIndex, 10); if (Number.isFinite(zi)) z = Math.max(z, zi + 20); } diff --git a/apps/web/src/components/activities/ActivitiesKanban.vue b/apps/web/src/components/activities/ActivitiesKanban.vue index 20d7d11..cbeb57f 100644 --- a/apps/web/src/components/activities/ActivitiesKanban.vue +++ b/apps/web/src/components/activities/ActivitiesKanban.vue @@ -12,6 +12,7 @@ import { activityTypeView } from '@/lib/activity-types'; import { calendarDaysBetween, formatClockFa, formatJalaliDay, startOfLocalDay, toFaDigits } from '@/lib/jalali'; import PipelineBoard from '@/components/pipeline/PipelineBoard.vue'; import DealCardOverlay from '@/components/pipeline/DealCardOverlay.vue'; +import ActivityQuickView from '@/components/activities/ActivityQuickView.vue'; import { ACTIVITY_BOARD_COPY, initialsFromTitle, @@ -64,6 +65,9 @@ const draggingColumnId = ref(null); const overlayDeal = ref(null); const overlayAccent = ref('green'); const overlayPos = ref({ x: 0, y: 0 }); +const quickId = ref(null); +const peekId = ref(null); +let peekLinger = 0; const moving = ref(false); const stageBusy = ref(false); const localOrder = ref(null); @@ -223,6 +227,7 @@ function onMenuActivity(id: string) { } function onDragStart(deal: PipelineDeal, event: DragEvent) { + if (quickId.value) closeQuick(); draggingId.value = deal.id; overlayDeal.value = deal; const sid = findStageOf(deal.id); @@ -420,7 +425,7 @@ async function onAddStage(name: string) { } } -function openActivity(id: string) { +function activityQuery(id: string) { const query: Record = { from: 'desk', nav: crypto.randomUUID(), @@ -440,7 +445,27 @@ function openActivity(id: string) { if (source.due) query.due = source.due; const columnId = findStageOf(id); if (columnId) query.stage = columnId; - void router.push({ name: 'activity-detail', params: { id }, query }); + return query; +} + +function openActivity(id: string) { + window.clearTimeout(peekLinger); + quickId.value = id; + peekId.value = id; +} + +function closeQuick() { + quickId.value = null; + const id = peekId.value; + window.clearTimeout(peekLinger); + peekLinger = window.setTimeout(() => { + if (!quickId.value && peekId.value === id) peekId.value = null; + }, 650); +} + +function openFullActivity(id: string) { + quickId.value = null; + void router.push({ name: 'activity-detail', params: { id }, query: activityQuery(id) }); } @@ -456,6 +481,7 @@ function openActivity(id: string) { :copy="ACTIVITY_BOARD_COPY" :select-mode="selectMode" :selected-ids="selectedIds" + :peek-id="peekId" @drag-start="onDragStart" @drag-end="onDragEnd" @drag-over="onDragOver" @@ -473,6 +499,12 @@ function openActivity(id: string) { @add-stage="onAddStage" @load-more="loadMore" /> + Promise; + /** بالاتر از مودال خلاصهٔ فعالیت */ + overlayZIndex?: number; }>(); const emit = defineEmits<{ @@ -355,7 +357,7 @@ onBeforeUnmount(() => {