From 3fb5f32ac30995d280712da0c83812d730ac165d Mon Sep 17 00:00:00 2001 From: Mahdi Date: Sat, 17 Oct 2026 18:56:44 +0330 Subject: [PATCH] fix: align activity badge and person profile with prior lens Count nav badge for desk=mine, always apply activity prior filter on person records, and stop loading prior=all deals on profiles. Co-authored-by: Cursor --- .../src/activities/activities.controller.ts | 8 +- apps/api/src/activities/activities.service.ts | 17 ++- apps/web/src/layouts/AppLayout.vue | 4 +- apps/web/src/views/PersonProfileView.vue | 109 ++++++++---------- 4 files changed, 72 insertions(+), 66 deletions(-) diff --git a/apps/api/src/activities/activities.controller.ts b/apps/api/src/activities/activities.controller.ts index b74f764..7c76807 100644 --- a/apps/api/src/activities/activities.controller.ts +++ b/apps/api/src/activities/activities.controller.ts @@ -57,8 +57,12 @@ export class ActivitiesController { ) {} @Get('summary') - summary(@CurrentUser() user: Actor) { - return this.activitiesService.summary(user.organizationId, user); + summary( + @CurrentUser() user: Actor, + @Query('desk') desk?: string, + ) { + const scope = desk === 'mine' || desk === 'team' ? desk : undefined; + return this.activitiesService.summary(user.organizationId, user, scope); } @Get('stages') diff --git a/apps/api/src/activities/activities.service.ts b/apps/api/src/activities/activities.service.ts index 83fbd9b..ba44480 100644 --- a/apps/api/src/activities/activities.service.ts +++ b/apps/api/src/activities/activities.service.ts @@ -118,9 +118,19 @@ export class ActivitiesService { async summary( organizationId: string, actor: ActivityActor, + desk?: 'mine' | 'team', ): Promise { const { start, end } = dayBounds(); - const scope = activityAccessWhere(actor); + const scope: Prisma.ActivityWhereInput = + desk === 'mine' + ? { + OR: [ + { assigneeId: actor.userId }, + { assignees: { some: { id: actor.userId } } }, + { participantIds: { has: actor.userId } }, + ], + } + : activityAccessWhere(actor); const prior = activityPriorWhere(actor.priorLens, actor.priorCutoff); const [todayCount, overdueCount] = await Promise.all([ this.prisma.activity.count({ @@ -733,10 +743,7 @@ export class ActivitiesService { ...this.types.searchByName(typeRows, t), ]); if (search) andParts.push(search); - const onRecord = Boolean(entity?.personId || entity?.companyId || entity?.dealId); - if (!onRecord) { - andParts.push(activityPriorWhere(actor.priorLens, actor.priorCutoff)); - } + andParts.push(activityPriorWhere(actor.priorLens, actor.priorCutoff)); const base: Prisma.ActivityWhereInput = { organizationId, AND: andParts, diff --git a/apps/web/src/layouts/AppLayout.vue b/apps/web/src/layouts/AppLayout.vue index 3d0f9c2..88c400e 100644 --- a/apps/web/src/layouts/AppLayout.vue +++ b/apps/web/src/layouts/AppLayout.vue @@ -305,7 +305,9 @@ async function refreshNotifs() { async function loadActivityBadge() { try { - const { data } = await api.get('/activities/summary'); + const { data } = await api.get('/activities/summary', { + params: { desk: 'mine' }, + }); activityBadge.value = data.todayCount; } catch { activityBadge.value = 0; diff --git a/apps/web/src/views/PersonProfileView.vue b/apps/web/src/views/PersonProfileView.vue index 32b9b80..4576640 100644 --- a/apps/web/src/views/PersonProfileView.vue +++ b/apps/web/src/views/PersonProfileView.vue @@ -184,6 +184,7 @@ const previousSourceLabel = computed(() => { const auth = useAuthStore(); const priorArchiveOn = computed(() => Boolean(auth.user?.priorArchive)); +const priorLens = computed(() => auth.user?.priorArchive?.lens ?? null); function isPriorActivity(row: ActivityDto) { const cutoff = auth.user?.priorArchive?.cutoffAt; @@ -199,8 +200,24 @@ function isPriorDeal(row: DealDto) { return new Date(row.createdAt).getTime() < new Date(cutoff).getTime(); } +/** با لنز کار جاری فقط جاری؛ با بایگانی فقط قبلی؛ بدون قابلیت همه. */ +function inActiveLensActivity(row: ActivityDto) { + if (!priorArchiveOn.value || !auth.user?.priorArchive?.cutoffAt) return true; + const prior = isPriorActivity(row); + return priorLens.value === 'archive' ? prior : !prior; +} + +function inActiveLensDeal(row: DealDto) { + if (!priorArchiveOn.value || !auth.user?.priorArchive?.cutoffAt) return true; + const prior = isPriorDeal(row); + return priorLens.value === 'archive' ? prior : !prior; +} + +const lensActivities = computed(() => activities.value.filter(inActiveLensActivity)); +const lensDeals = computed(() => deals.value.filter(inActiveLensDeal)); + const lastCallAt = computed(() => { - const calls = activities.value + const calls = lensActivities.value .filter((a) => a.type === 'call') .map((a) => a.status === 'done' ? a.completedAt || a.dueAt || a.createdAt : a.createdAt, @@ -217,23 +234,23 @@ const daysSinceCall = computed(() => const nextActivity = computed(() => { const now = Date.now(); return ( - activities.value + lensActivities.value .filter((a) => a.status === 'open' && a.dueAt) .sort((a, b) => new Date(a.dueAt!).getTime() - new Date(b.dueAt!).getTime()) .find((a) => new Date(a.dueAt!).getTime() >= now - 60 * 60 * 1000) ?? - activities.value + lensActivities.value .filter((a) => a.status === 'open' && a.dueAt) .sort((a, b) => new Date(a.dueAt!).getTime() - new Date(b.dueAt!).getTime())[0] ?? null ); }); -const openDeals = computed(() => deals.value.filter((d) => d.status === 'open')); +const openDeals = computed(() => lensDeals.value.filter((d) => d.status === 'open')); const currentDeals = computed(() => - priorArchiveOn.value ? deals.value.filter((d) => !isPriorDeal(d)) : deals.value, + priorLens.value === 'archive' ? [] : lensDeals.value, ); const priorDeals = computed(() => - priorArchiveOn.value ? deals.value.filter((d) => isPriorDeal(d)) : [], + priorLens.value === 'archive' ? lensDeals.value : [], ); const lastQuote = computed( @@ -323,7 +340,7 @@ function timelineIcon(tone: TimelineTone) { const timeline = computed(() => { const rows: TimelineRow[] = []; - for (const a of activities.value) { + for (const a of lensActivities.value) { const at = new Date(a.dueAt || a.createdAt).getTime(); rows.push({ key: `a-${a.id}`, @@ -336,27 +353,30 @@ const timeline = computed(() => { prior: isPriorActivity(a), }); } - for (const n of notes.value) { - rows.push({ - key: `n-${n.id}`, - title: n.title?.trim() || 'یادداشت', - body: n.body, - when: historyWhen(n.createdAt), - tone: 'note', - href: '/notes', - at: new Date(n.createdAt).getTime(), - }); - } - for (const q of quotes.value) { - rows.push({ - key: `q-${q.id}`, - title: 'ایمیل ارسال شد', - body: `پیشنهاد شماره ${q.number} با مبلغ ${formatMoney(q.totalAmount, q.currency)} ارسال شد`, - when: historyWhen(q.createdAt), - tone: 'quote', - href: `/quotes/${q.id}`, - at: new Date(q.createdAt).getTime(), - }); + // یادداشت و پیشنهاد در هر دو لنز می‌مانند (فقط معامله/فعالیت جدا می‌شوند) + if (priorLens.value !== 'archive') { + for (const n of notes.value) { + rows.push({ + key: `n-${n.id}`, + title: n.title?.trim() || 'یادداشت', + body: n.body, + when: historyWhen(n.createdAt), + tone: 'note', + href: '/notes', + at: new Date(n.createdAt).getTime(), + }); + } + for (const q of quotes.value) { + rows.push({ + key: `q-${q.id}`, + title: 'ایمیل ارسال شد', + body: `پیشنهاد شماره ${q.number} با مبلغ ${formatMoney(q.totalAmount, q.currency)} ارسال شد`, + when: historyWhen(q.createdAt), + tone: 'quote', + href: `/quotes/${q.id}`, + at: new Date(q.createdAt).getTime(), + }); + } } return rows.sort((a, b) => b.at - a.at); }); @@ -366,17 +386,7 @@ const visibleTimeline = computed(() => { historyFilter.value === 'all' ? timeline.value : timeline.value.filter((row) => row.tone === historyFilter.value); - const current = priorArchiveOn.value ? rows.filter((row) => !row.prior) : rows; - return current.slice(0, 8); -}); - -const priorTimeline = computed(() => { - if (!priorArchiveOn.value) return []; - const rows = - historyFilter.value === 'all' - ? timeline.value - : timeline.value.filter((row) => row.tone === historyFilter.value); - return rows.filter((row) => row.prior).slice(0, 8); + return rows.slice(0, 8); }); function fileKind(name: string, mime: string): AttachmentRow['kind'] { @@ -478,7 +488,7 @@ async function load() { api.get>('/memory', { params: { personId: id.value, limit: 50 }, }), - api.get('/pipelines/board', { params: { prior: 'all' } }), + api.get('/pipelines/board'), ]); person.value = personRes.data; activities.value = actRes.data.items; @@ -868,24 +878,7 @@ watch(() => route.query.tab, readTab, { immediate: true }); -

موردی با این فیلتر نیست.

- +

موردی با این فیلتر نیست.

مشاهده همه فعالیت‌ها