Add desk-style task workflow on Activities and Project boards.

Managers can switch my desk vs team, reassign activities, and filter project cards to their work; assignees can see and move cards.
This commit is contained in:
2026-09-28 12:28:14 +03:30
parent 8effd4cbc6
commit 32e27d45f7
13 changed files with 303 additions and 50 deletions
+41 -16
View File
@@ -12,6 +12,8 @@ export type PipelineDeal = {
currencyUnit?: string;
/** نام مسئولان برای نمایش روی کارت */
assigneeNames?: string[];
ownerId?: string | null;
assigneeIds?: string[];
};
export type PipelineUiCopy = {
@@ -206,6 +208,8 @@ export function mapProjectBoardToStages(
person: { fullName: string } | null;
company: { name: string } | null;
deal: { title: string } | null;
owner?: { id: string; fullName: string } | null;
assignees?: Array<{ id: string; fullName: string }>;
}>;
}>;
},
@@ -215,22 +219,33 @@ export function mapProjectBoardToStages(
id: stage.id,
title: stage.name,
color: ACCENT_CYCLE[index % ACCENT_CYCLE.length]!,
deals: stage.cards.map((card) => ({
id: card.id,
title: card.title,
description:
card.company?.name ||
card.person?.fullName ||
card.deal?.title ||
card.notes?.trim() ||
'',
amount: null,
date: formatDate(card.createdAt),
initials: initialsFromTitle(
card.company?.name || card.person?.fullName || card.title,
),
stageId: card.stageId,
})),
deals: stage.cards.map((card) => {
const assigneeNames = [
...(card.owner?.fullName ? [card.owner.fullName] : []),
...(card.assignees ?? [])
.map((a) => a.fullName)
.filter((n) => n && n !== card.owner?.fullName),
];
return {
id: card.id,
title: card.title,
description:
card.company?.name ||
card.person?.fullName ||
card.deal?.title ||
card.notes?.trim() ||
'',
amount: null,
date: formatDate(card.createdAt),
initials: initialsFromTitle(
card.company?.name || card.person?.fullName || card.title,
),
stageId: card.stageId,
assigneeNames,
ownerId: card.owner?.id ?? null,
assigneeIds: (card.assignees ?? []).map((a) => a.id),
};
}),
}));
}
@@ -292,6 +307,8 @@ export type PipelineBoardFilter = {
query?: string;
amountMin?: number | null;
amountMax?: number | null;
/** اگر ست باشد فقط کارت‌های مربوط به این کاربر (مالک یا مسئول) */
mineUserId?: string | null;
};
export function filterStagesByQuery(
@@ -308,14 +325,22 @@ export function filterStages(
const q = filter.query?.trim().toLowerCase() ?? '';
const min = filter.amountMin;
const max = filter.amountMax;
const mineUserId = filter.mineUserId?.trim() || null;
const hasFilter =
Boolean(q) ||
Boolean(mineUserId) ||
(min != null && !Number.isNaN(min)) ||
(max != null && !Number.isNaN(max));
if (!hasFilter) return stages;
return stages.map((stage) => ({
...stage,
deals: stage.deals.filter((d) => {
if (mineUserId) {
const isMine =
d.ownerId === mineUserId ||
(d.assigneeIds ?? []).includes(mineUserId);
if (!isMine) return false;
}
if (
q &&
!d.title.toLowerCase().includes(q) &&