Add activity detail, Jalali pickers, and customer/user profiles.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 21:29:00 +03:30
co-authored by Cursor
parent 574feedfa5
commit f884c60cab
39 changed files with 8255 additions and 441 deletions
@@ -22,9 +22,12 @@ type ActivityRow = {
id: string;
type: ActivityDto['type'];
status: ActivityDto['status'];
priority: ActivityDto['priority'];
title: string;
description: string | null;
dueAt: Date | null;
durationMinutes: number | null;
reminderAt: Date | null;
completedAt: Date | null;
createdAt: Date;
updatedAt: Date;
@@ -32,6 +35,7 @@ type ActivityRow = {
company: { id: string; name: string } | null;
deal: { id: string; title: string } | null;
assignee: { id: string; fullName: string } | null;
createdBy: { id: string; fullName: string } | null;
};
const include = {
@@ -39,6 +43,7 @@ const include = {
company: { select: { id: true, name: true } },
deal: { select: { id: true, title: true } },
assignee: { select: { id: true, fullName: true } },
createdBy: { select: { id: true, fullName: true } },
} satisfies Prisma.ActivityInclude;
@Injectable()
@@ -173,12 +178,16 @@ export class ActivitiesService {
organizationId,
title: dto.title.trim(),
type: dto.type ?? 'task',
priority: dto.priority ?? 'medium',
description: emptyToNull(dto.description),
dueAt: dto.dueAt ? new Date(dto.dueAt) : null,
durationMinutes: dto.durationMinutes ?? null,
reminderAt: dto.reminderAt ? new Date(dto.reminderAt) : null,
personId: emptyToNull(dto.personId ?? null),
companyId: emptyToNull(dto.companyId ?? null),
dealId: emptyToNull(dto.dealId ?? null),
assigneeId,
createdById: userId,
},
include,
});
@@ -211,8 +220,11 @@ export class ActivitiesService {
const data: Prisma.ActivityUncheckedUpdateInput = {};
if (dto.title !== undefined) data.title = dto.title.trim();
if (dto.type !== undefined) data.type = dto.type;
if (dto.priority !== undefined) data.priority = dto.priority;
if ('description' in dto) data.description = emptyToNull(dto.description);
if ('dueAt' in dto) data.dueAt = dto.dueAt ? new Date(dto.dueAt) : null;
if ('durationMinutes' in dto) data.durationMinutes = dto.durationMinutes ?? null;
if ('reminderAt' in dto) data.reminderAt = dto.reminderAt ? new Date(dto.reminderAt) : null;
if ('personId' in dto) data.personId = emptyToNull(dto.personId ?? null);
if ('companyId' in dto) data.companyId = emptyToNull(dto.companyId ?? null);
if ('dealId' in dto) data.dealId = emptyToNull(dto.dealId ?? null);
@@ -394,9 +406,12 @@ export class ActivitiesService {
id: row.id,
type: row.type,
status: row.status,
priority: row.priority,
title: row.title,
description: row.description,
dueAt: row.dueAt?.toISOString() ?? null,
durationMinutes: row.durationMinutes,
reminderAt: row.reminderAt?.toISOString() ?? null,
completedAt: row.completedAt?.toISOString() ?? null,
person: row.person
? {
@@ -407,6 +422,7 @@ export class ActivitiesService {
company: row.company,
deal: row.deal,
assignee: row.assignee,
createdBy: row.createdBy,
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
};
+33
View File
@@ -14,6 +14,7 @@ import {
const ACTIVITY_TYPES = ['call', 'meeting', 'task', 'note'] as const;
const ACTIVITY_STATUSES = ['open', 'done', 'canceled'] as const;
const ACTIVITY_PRIORITIES = ['very_low', 'low', 'medium', 'high', 'very_high'] as const;
const ACTIVITY_FILTERS = ['today', 'overdue', 'upcoming', 'done', 'all'] as const;
export class ListActivitiesQueryDto {
@@ -71,6 +72,10 @@ export class CreateActivityDto {
@IsIn(ACTIVITY_TYPES)
type?: (typeof ACTIVITY_TYPES)[number];
@IsOptional()
@IsIn(ACTIVITY_PRIORITIES)
priority?: (typeof ACTIVITY_PRIORITIES)[number];
@IsOptional()
@IsString()
@MaxLength(2000)
@@ -81,6 +86,18 @@ export class CreateActivityDto {
@IsDateString()
dueAt?: string | null;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(24 * 60)
durationMinutes?: number | null;
@IsOptional()
@ValidateIf((_, v) => v !== '' && v != null)
@IsDateString()
reminderAt?: string | null;
@IsOptional()
@ValidateIf((_, v) => v !== '' && v != null)
@IsString()
@@ -117,6 +134,10 @@ export class UpdateActivityDto {
@IsIn(ACTIVITY_STATUSES)
status?: (typeof ACTIVITY_STATUSES)[number];
@IsOptional()
@IsIn(ACTIVITY_PRIORITIES)
priority?: (typeof ACTIVITY_PRIORITIES)[number];
@IsOptional()
@IsString()
@MaxLength(2000)
@@ -127,6 +148,18 @@ export class UpdateActivityDto {
@IsDateString()
dueAt?: string | null;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(24 * 60)
durationMinutes?: number | null;
@IsOptional()
@ValidateIf((_, v) => v !== '' && v != null)
@IsDateString()
reminderAt?: string | null;
@IsOptional()
@ValidateIf((_, v) => v !== '' && v != null)
@IsString()