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 <cursoragent@cursor.com>
This commit is contained in:
2026-11-10 20:10:16 +03:30
co-authored by Cursor
parent 6df198321a
commit fdf1fbe4aa
9 changed files with 630 additions and 26 deletions
@@ -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<string | null>(null);
const overlayDeal = ref<PipelineDeal | null>(null);
const overlayAccent = ref<PipelineAccent>('green');
const overlayPos = ref({ x: 0, y: 0 });
const quickId = ref<string | null>(null);
const peekId = ref<string | null>(null);
let peekLinger = 0;
const moving = ref(false);
const stageBusy = ref(false);
const localOrder = ref<string[] | null>(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<string, string> = {
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) });
}
</script>
@@ -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"
/>
<ActivityQuickView
:activity-id="quickId"
@close="closeQuick"
@changed="loadAll()"
@full="openFullActivity"
/>
<Teleport to="body">
<DealCardOverlay
v-if="overlayDeal && draggingId"