feat(activities): step through the same desk list from a detail

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-02 14:30:17 +03:30
co-authored by Cursor
parent a0936bf19f
commit e6e90aabf3
5 changed files with 318 additions and 36 deletions
@@ -238,6 +238,39 @@ export class ActivitiesController {
);
}
/** ترتیب همان لیست میز، برای فعالیت قبلی/بعدی در جزئیات */
@Get('nav-ids')
navIds(@CurrentUser() user: Actor, @Query() query: ListActivitiesQueryDto) {
return this.activitiesService.orderedIds(
user.organizationId,
user,
query.filter ?? 'today',
query.q,
{
personId: query.personId,
companyId: query.companyId,
dealId: query.dealId,
leadId: query.leadId,
},
{
from: query.from,
to: query.to,
},
query.desk,
{
type: query.type,
typeDefId: query.typeDefId,
status: query.status,
assigneeId: query.assigneeId,
title: query.title,
customer: query.customer,
due: query.due,
boardId: query.boardId,
},
query.stageId ? { stageId: query.stageId } : undefined,
);
}
@Get('export')
async export(
@CurrentUser() user: Actor,
+98 -20
View File
@@ -265,6 +265,102 @@ export class ActivitiesService {
facets?: ListFacets,
stageScope?: StageScope,
): Promise<PaginatedResponse<ActivityDto>> {
const { where, ctx } = await this.listScope(
organizationId,
actor,
filter,
q,
entity,
range,
desk,
facets,
stageScope,
);
const take = Math.min(Math.max(limit, 1), 200);
const [rows, total] = await this.prisma.$transaction([
this.prisma.activity.findMany({
where,
include,
orderBy: [{ dueAt: 'asc' }, { createdAt: 'desc' }],
skip: (page - 1) * take,
take,
}),
this.prisma.activity.count({ where }),
]);
return {
items: rows.map((row) => this.toDto(row, ctx)),
total,
page,
limit: take,
};
}
/**
* شناسه‌ها با همان فیلتر و ترتیب لیست (زمان شروع صعودی).
* برای ورق‌زدن جزئیات؛ سقف دارد تا یک زبانهٔ خیلی شلوغ کل پاسخ را بزرگ نکند.
*/
async orderedIds(
organizationId: string,
actor: ActivityActor,
filter: ActivityFilter = 'today',
q?: string,
entity?: {
personId?: string;
companyId?: string;
dealId?: string;
leadId?: string;
},
range?: {
from?: string;
to?: string;
},
desk?: 'mine' | 'team',
facets?: ListFacets,
stageScope?: StageScope,
): Promise<{ ids: string[]; total: number; truncated: boolean }> {
const cap = 2000;
const { where } = await this.listScope(
organizationId,
actor,
filter,
q,
entity,
range,
desk,
facets,
stageScope,
);
const [rows, total] = await this.prisma.$transaction([
this.prisma.activity.findMany({
where,
select: { id: true },
orderBy: [{ dueAt: 'asc' }, { createdAt: 'desc' }],
take: cap,
}),
this.prisma.activity.count({ where }),
]);
return { ids: rows.map((row) => row.id), total, truncated: total > rows.length };
}
private async listScope(
organizationId: string,
actor: ActivityActor,
filter: ActivityFilter,
q: string | undefined,
entity:
| {
personId?: string;
companyId?: string;
dealId?: string;
leadId?: string;
}
| undefined,
range: { from?: string; to?: string } | undefined,
desk: 'mine' | 'team' | undefined,
facets: ListFacets | undefined,
stageScope: StageScope | undefined,
): Promise<{ where: Prisma.ActivityWhereInput; ctx: DtoContext }> {
const typeRows = await this.types.rows(organizationId);
const where = this.buildWhere(
organizationId,
@@ -291,27 +387,9 @@ export class ActivitiesService {
await this.stages.whereForStage(organizationId, stageScope.stageId),
);
}
const ctx: DtoContext = {
stages: boardCtx,
types: this.types.systemOf(typeRows),
};
const take = Math.min(Math.max(limit, 1), 200);
const [rows, total] = await this.prisma.$transaction([
this.prisma.activity.findMany({
where,
include,
orderBy: [{ dueAt: 'asc' }, { createdAt: 'desc' }],
skip: (page - 1) * take,
take,
}),
this.prisma.activity.count({ where }),
]);
return {
items: rows.map((row) => this.toDto(row, ctx)),
total,
page,
limit: take,
where,
ctx: { stages: boardCtx, types: this.types.systemOf(typeRows) },
};
}