fix(boards): keep card order inside a column and show activity priority
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -120,6 +120,7 @@ function toCard(item: ActivityDto, stageId: string): PipelineDeal {
|
||||
assigneeNames,
|
||||
ownerId: item.assignee?.id ?? null,
|
||||
assigneeIds: (item.assignees ?? []).map((a) => a.id),
|
||||
priority: item.priority,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -253,40 +254,54 @@ function onDragOver(stageId: string, event: DragEvent) {
|
||||
|
||||
async function moveCard(activityId: string, toStageId: string, index?: number) {
|
||||
const fromStageId = findStageOf(activityId);
|
||||
if (!fromStageId || fromStageId === toStageId) return;
|
||||
if (!fromStageId) return;
|
||||
const from = columns.value[fromStageId]!;
|
||||
const to = columns.value[toStageId];
|
||||
if (!to) return;
|
||||
const item = from.items.find((i) => i.id === activityId)!;
|
||||
const fromIndex = from.items.findIndex((i) => i.id === activityId);
|
||||
const item = from.items[fromIndex];
|
||||
if (!item) return;
|
||||
const toStage = props.stages.find((s) => s.id === toStageId)!;
|
||||
const same = fromStageId === toStageId;
|
||||
const without = (same ? from.items : to.items).filter((i) => i.id !== activityId);
|
||||
if (same && index == null) return;
|
||||
let insertAt = index == null || index < 0 ? 0 : index;
|
||||
if (same && index != null && index > fromIndex) insertAt -= 1;
|
||||
insertAt = Math.max(0, Math.min(insertAt, without.length));
|
||||
if (same && insertAt === fromIndex) return;
|
||||
|
||||
const snapshot = {
|
||||
fromItems: from.items,
|
||||
fromTotal: from.total,
|
||||
toItems: to.items,
|
||||
toTotal: to.total,
|
||||
};
|
||||
const prevFrom = from.items;
|
||||
const prevTo = to.items;
|
||||
const prevFromTotal = from.total;
|
||||
const prevToTotal = to.total;
|
||||
const optimistic: ActivityDto = { ...item, status: toStage.kind, stageId: toStageId };
|
||||
from.items = from.items.filter((i) => i.id !== activityId);
|
||||
from.total = Math.max(0, from.total - 1);
|
||||
const nextTo = [...to.items];
|
||||
const at = index == null || index < 0 || index > nextTo.length ? 0 : index;
|
||||
nextTo.splice(at, 0, optimistic);
|
||||
to.items = nextTo;
|
||||
to.total += 1;
|
||||
const nextTo = [...without];
|
||||
nextTo.splice(insertAt, 0, optimistic);
|
||||
if (same) {
|
||||
from.items = nextTo;
|
||||
} else {
|
||||
from.items = from.items.filter((i) => i.id !== activityId);
|
||||
from.total = Math.max(0, from.total - 1);
|
||||
to.items = nextTo;
|
||||
to.total += 1;
|
||||
}
|
||||
|
||||
moving.value = true;
|
||||
try {
|
||||
const { data } = await api.patch<ActivityDto>(`/activities/${activityId}/stage`, {
|
||||
stageId: toStageId,
|
||||
position: insertAt,
|
||||
});
|
||||
to.items = to.items.map((i) => (i.id === activityId ? data : i));
|
||||
const column = same ? from : to;
|
||||
column.items = column.items.map((i) => (i.id === activityId ? data : i));
|
||||
emit('changed');
|
||||
} catch (e) {
|
||||
from.items = snapshot.fromItems;
|
||||
from.total = snapshot.fromTotal;
|
||||
to.items = snapshot.toItems;
|
||||
to.total = snapshot.toTotal;
|
||||
from.items = prevFrom;
|
||||
from.total = prevFromTotal;
|
||||
if (!same) {
|
||||
to.items = prevTo;
|
||||
to.total = prevToTotal;
|
||||
}
|
||||
emit('error', apiMessage(e, 'جابهجایی فعالیت ناموفق بود.'));
|
||||
} finally {
|
||||
moving.value = false;
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
CalendarOutlined,
|
||||
CheckOutlined,
|
||||
DeleteOutlined,
|
||||
FlagFilled,
|
||||
MoreOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
@@ -13,10 +14,12 @@ import {
|
||||
Flex as AFlex,
|
||||
Space as ASpace,
|
||||
Tag as ATag,
|
||||
Tooltip as ATooltip,
|
||||
Typography,
|
||||
theme,
|
||||
} from 'ant-design-vue';
|
||||
import { userAvatarSrc } from '@/lib/userAvatars';
|
||||
import type { ActivityPriority } from '@mento/shared';
|
||||
import type { PipelineAccent, PipelineDeal, PipelineUiCopy } from '@/lib/pipeline-board';
|
||||
import { DEAL_BOARD_COPY, formatCurrencyParts } from '@/lib/pipeline-board';
|
||||
|
||||
@@ -60,6 +63,16 @@ const extraAssignees = computed(() =>
|
||||
Math.max(0, (props.deal.assigneeNames?.length ?? 0) - 1),
|
||||
);
|
||||
const showTrash = computed(() => props.selectMode && props.selected);
|
||||
const PRIORITY_FLAG: Record<ActivityPriority, { label: string; color: string }> = {
|
||||
very_high: { label: 'خیلی زیاد', color: '#dc2626' },
|
||||
high: { label: 'زیاد', color: '#ea580c' },
|
||||
medium: { label: 'متوسط', color: '#0d9488' },
|
||||
low: { label: 'کم', color: '#2563eb' },
|
||||
very_low: { label: 'خیلی کم', color: '#94a3b8' },
|
||||
};
|
||||
const priorityFlag = computed(() =>
|
||||
props.deal.priority ? PRIORITY_FLAG[props.deal.priority] : null,
|
||||
);
|
||||
|
||||
const cardStyle = computed(() => ({
|
||||
cursor: props.selectMode ? 'pointer' : 'grab',
|
||||
@@ -140,6 +153,11 @@ const cardStyle = computed(() => ({
|
||||
</a-typography-text>
|
||||
</div>
|
||||
|
||||
<a-tooltip v-if="priorityFlag" :title="`اولویت: ${priorityFlag.label}`">
|
||||
<span :style="{ display: 'inline-flex', flexShrink: 0, lineHeight: 1 }">
|
||||
<FlagFilled :style="{ color: priorityFlag.color, fontSize: '16px' }" />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<span :data-card-id="deal.id">
|
||||
<a-button
|
||||
v-if="showTrash"
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import AddDealButton from './AddDealButton.vue';
|
||||
import DealCard from './DealCard.vue';
|
||||
import type { PipelineDeal, PipelineStage, PipelineUiCopy } from '@/lib/pipeline-board';
|
||||
import { DEAL_BOARD_COPY, PIPELINE_ACCENT } from '@/lib/pipeline-board';
|
||||
import { DEAL_BOARD_COPY, PIPELINE_ACCENT, pointerDropIndex } from '@/lib/pipeline-board';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -115,7 +115,7 @@ function onCardDragOver(stageId: string, event: DragEvent) {
|
||||
function onCardDrop(stageId: string, index: number, event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
emit('drop', stageId, event, index);
|
||||
emit('drop', stageId, event, pointerDropIndex(index, event));
|
||||
}
|
||||
|
||||
function startRename() {
|
||||
|
||||
Reference in New Issue
Block a user