feat(activities): filter the desk from each column
Assignees are searched instead of listed in full, and start time only filters whether a time is set. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -193,6 +193,9 @@ export class ActivitiesController {
|
||||
typeDefId: query.typeDefId,
|
||||
status: query.status,
|
||||
assigneeId: query.assigneeId,
|
||||
title: query.title,
|
||||
customer: query.customer,
|
||||
due: query.due,
|
||||
boardId: query.boardId,
|
||||
},
|
||||
);
|
||||
@@ -226,6 +229,9 @@ export class ActivitiesController {
|
||||
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,
|
||||
|
||||
@@ -53,6 +53,10 @@ type ListFacets = {
|
||||
typeDefId?: string;
|
||||
status?: ActivityDto['status'];
|
||||
assigneeId?: string;
|
||||
title?: string;
|
||||
customer?: string;
|
||||
/** set = زمان شروع دارد، unset = ندارد */
|
||||
due?: 'set' | 'unset';
|
||||
/** گروه فعالیت؛ خالی = همهٔ گروهها */
|
||||
boardId?: string;
|
||||
};
|
||||
@@ -864,11 +868,15 @@ export class ActivitiesService {
|
||||
: activityAccessWhere(actor);
|
||||
const facetAssignee = facets?.assigneeId?.trim();
|
||||
const facetTypeDef = facets?.typeDefId?.trim();
|
||||
const facetTitle = facets?.title?.trim();
|
||||
const facetCustomer = facets?.customer?.trim();
|
||||
const andParts: Prisma.ActivityWhereInput[] = [
|
||||
scope,
|
||||
...(facets?.type ? [{ type: facets.type }] : []),
|
||||
...(facetTypeDef ? [this.types.whereForType(typeRows, facetTypeDef)] : []),
|
||||
...(facets?.status ? [{ status: facets.status }] : []),
|
||||
...(facetTitle ? [{ title: ci(facetTitle) }] : []),
|
||||
...(facetCustomer ? [this.customerWhere(facetCustomer)] : []),
|
||||
...(facetAssignee
|
||||
? [
|
||||
{
|
||||
@@ -915,15 +923,18 @@ export class ActivitiesService {
|
||||
const fromDate = range?.from ? new Date(range.from) : null;
|
||||
const toDate = range?.to ? new Date(range.to) : null;
|
||||
if (fromDate || toDate) {
|
||||
return {
|
||||
...base,
|
||||
dueAt: {
|
||||
...(fromDate && !Number.isNaN(fromDate.getTime())
|
||||
? { gte: fromDate }
|
||||
: {}),
|
||||
...(toDate && !Number.isNaN(toDate.getTime()) ? { lt: toDate } : {}),
|
||||
return this.withDue(
|
||||
{
|
||||
...base,
|
||||
dueAt: {
|
||||
...(fromDate && !Number.isNaN(fromDate.getTime())
|
||||
? { gte: fromDate }
|
||||
: {}),
|
||||
...(toDate && !Number.isNaN(toDate.getTime()) ? { lt: toDate } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
facets?.due,
|
||||
);
|
||||
}
|
||||
|
||||
// Explicit status facet overrides the status implied by time tabs.
|
||||
@@ -937,28 +948,62 @@ export class ActivitiesService {
|
||||
|
||||
switch (filter) {
|
||||
case 'today':
|
||||
return { ...base, ...impliedOpen, dueAt: { gte: start, lt: end } };
|
||||
return this.withDue(
|
||||
{ ...base, ...impliedOpen, dueAt: { gte: start, lt: end } },
|
||||
facets?.due,
|
||||
);
|
||||
case 'overdue':
|
||||
// عقبافتاده ذاتاً یعنی باز و موعدگذشته
|
||||
return {
|
||||
...base,
|
||||
...(facets?.status ? { status: facets.status } : { status: 'open' as const }),
|
||||
dueAt: { lt: start },
|
||||
};
|
||||
return this.withDue(
|
||||
{
|
||||
...base,
|
||||
...(facets?.status ? { status: facets.status } : { status: 'open' as const }),
|
||||
dueAt: { lt: start },
|
||||
},
|
||||
facets?.due,
|
||||
);
|
||||
case 'upcoming':
|
||||
return {
|
||||
...base,
|
||||
...impliedOpen,
|
||||
OR: [{ dueAt: { gte: end } }, { dueAt: null }],
|
||||
};
|
||||
return this.withDue(
|
||||
{
|
||||
...base,
|
||||
...impliedOpen,
|
||||
OR: [{ dueAt: { gte: end } }, { dueAt: null }],
|
||||
},
|
||||
facets?.due,
|
||||
);
|
||||
case 'done':
|
||||
return { ...base, ...impliedDone };
|
||||
return this.withDue({ ...base, ...impliedDone }, facets?.due);
|
||||
case 'all':
|
||||
default:
|
||||
return base;
|
||||
return this.withDue(base, facets?.due);
|
||||
}
|
||||
}
|
||||
|
||||
/** نام شخص (چندکلمهای) یا نام شرکت */
|
||||
private customerWhere(term: string): Prisma.ActivityWhereInput {
|
||||
const parts = term.split(/\s+/).filter(Boolean);
|
||||
const person: Prisma.PersonWhereInput =
|
||||
parts.length <= 1
|
||||
? { OR: [{ firstName: ci(term) }, { lastName: ci(term) }] }
|
||||
: {
|
||||
AND: parts.map((part) => ({
|
||||
OR: [{ firstName: ci(part) }, { lastName: ci(part) }],
|
||||
})),
|
||||
};
|
||||
return {
|
||||
OR: [{ person }, { company: { name: ci(term) } }],
|
||||
};
|
||||
}
|
||||
|
||||
/** وجود یا نبود زمان شروع، روی هر فیلتر زمانی */
|
||||
private withDue(
|
||||
where: Prisma.ActivityWhereInput,
|
||||
due?: 'set' | 'unset',
|
||||
): Prisma.ActivityWhereInput {
|
||||
if (due !== 'set' && due !== 'unset') return where;
|
||||
return { AND: [where, due === 'set' ? { dueAt: { not: null } } : { dueAt: null }] };
|
||||
}
|
||||
|
||||
private async fireDone(
|
||||
organizationId: string,
|
||||
row: {
|
||||
|
||||
@@ -69,6 +69,23 @@ export class ListActivitiesQueryDto {
|
||||
@IsString()
|
||||
assigneeId?: string;
|
||||
|
||||
/** فیلتر عنوان فعالیت، جدا از جستجوی کلی */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
title?: string;
|
||||
|
||||
/** نام شخص یا شرکتِ مشتری */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
customer?: string;
|
||||
|
||||
/** زمان شروع: set = دارد، unset = ندارد */
|
||||
@IsOptional()
|
||||
@IsIn(['set', 'unset'])
|
||||
due?: 'set' | 'unset';
|
||||
|
||||
/** نوع فعالیت (تعریفشده در سازمان) */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
Reference in New Issue
Block a user