Redesign sales goals and forecast with typed targets and live progress.

Match desktop/mobile mockups and compute current amounts, status, and KPIs from real CRM data.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-05 18:38:11 +03:30
co-authored by Cursor
parent 1c1b77e74a
commit 32af147237
8 changed files with 1287 additions and 385 deletions
+67
View File
@@ -1,13 +1,30 @@
import {
IsIn,
IsNumber,
IsOptional,
IsString,
Max,
MaxLength,
Min,
MinLength,
} from 'class-validator';
import { Type } from 'class-transformer';
const SALES_GOAL_TYPES = [
'sales',
'customer',
'revenue',
'deal',
'activity',
'quality',
] as const;
const SALES_GOAL_STATUSES = [
'in_progress',
'completed',
'needs_attention',
] as const;
export class CreateSalesGoalDto {
@IsString()
@MinLength(2)
@@ -25,6 +42,24 @@ export class CreateSalesGoalDto {
@Min(0)
targetAmount!: number;
@IsOptional()
@IsString()
@IsIn(SALES_GOAL_TYPES)
type?: string;
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(0)
forecastAmount?: number | null;
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(0)
@Max(100)
probability?: number | null;
@IsOptional()
@IsString()
@MaxLength(8)
@@ -61,6 +96,24 @@ export class UpdateSalesGoalDto {
@Min(0)
targetAmount?: number;
@IsOptional()
@IsString()
@IsIn(SALES_GOAL_TYPES)
type?: string;
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(0)
forecastAmount?: number | null;
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(0)
@Max(100)
probability?: number | null;
@IsOptional()
@IsString()
@MaxLength(8)
@@ -102,4 +155,18 @@ export class ListGoalsQueryDto {
@IsOptional()
@IsString()
userId?: string;
@IsOptional()
@IsString()
q?: string;
@IsOptional()
@IsString()
@IsIn(SALES_GOAL_STATUSES)
status?: string;
@IsOptional()
@IsString()
@IsIn(SALES_GOAL_TYPES)
type?: string;
}