@hed-hog/contact 0.0.293 → 0.0.295

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/person/dto/account.dto.d.ts +28 -0
  2. package/dist/person/dto/account.dto.d.ts.map +1 -0
  3. package/dist/person/dto/account.dto.js +123 -0
  4. package/dist/person/dto/account.dto.js.map +1 -0
  5. package/dist/person/dto/activity.dto.d.ts +15 -0
  6. package/dist/person/dto/activity.dto.d.ts.map +1 -0
  7. package/dist/person/dto/activity.dto.js +65 -0
  8. package/dist/person/dto/activity.dto.js.map +1 -0
  9. package/dist/person/dto/dashboard-query.dto.d.ts +9 -0
  10. package/dist/person/dto/dashboard-query.dto.d.ts.map +1 -0
  11. package/dist/person/dto/dashboard-query.dto.js +40 -0
  12. package/dist/person/dto/dashboard-query.dto.js.map +1 -0
  13. package/dist/person/dto/followup-query.dto.d.ts +10 -0
  14. package/dist/person/dto/followup-query.dto.d.ts.map +1 -0
  15. package/dist/person/dto/followup-query.dto.js +45 -0
  16. package/dist/person/dto/followup-query.dto.js.map +1 -0
  17. package/dist/person/person.controller.d.ts +204 -0
  18. package/dist/person/person.controller.d.ts.map +1 -1
  19. package/dist/person/person.controller.js +138 -0
  20. package/dist/person/person.controller.js.map +1 -1
  21. package/dist/person/person.service.d.ts +234 -0
  22. package/dist/person/person.service.d.ts.map +1 -1
  23. package/dist/person/person.service.js +1367 -0
  24. package/dist/person/person.service.js.map +1 -1
  25. package/hedhog/data/menu.yaml +163 -163
  26. package/hedhog/data/route.yaml +41 -0
  27. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +210 -114
  28. package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +3 -0
  29. package/hedhog/frontend/app/accounts/page.tsx.ejs +323 -245
  30. package/hedhog/frontend/app/activities/_components/activity-detail-sheet.tsx.ejs +240 -0
  31. package/hedhog/frontend/app/activities/_components/activity-types.ts.ejs +66 -0
  32. package/hedhog/frontend/app/activities/page.tsx.ejs +165 -517
  33. package/hedhog/frontend/app/dashboard/_components/dashboard-types.ts.ejs +70 -0
  34. package/hedhog/frontend/app/dashboard/page.tsx.ejs +504 -356
  35. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +242 -153
  36. package/hedhog/frontend/messages/en.json +91 -6
  37. package/hedhog/frontend/messages/pt.json +91 -6
  38. package/hedhog/table/crm_activity.yaml +68 -0
  39. package/hedhog/table/person_company.yaml +22 -0
  40. package/package.json +5 -5
  41. package/src/person/dto/account.dto.ts +100 -0
  42. package/src/person/dto/activity.dto.ts +54 -0
  43. package/src/person/dto/dashboard-query.dto.ts +25 -0
  44. package/src/person/dto/followup-query.dto.ts +25 -0
  45. package/src/person/person.controller.ts +116 -0
  46. package/src/person/person.service.ts +2139 -77
@@ -451,7 +451,7 @@
451
451
  },
452
452
  "CrmDashboard": {
453
453
  "title": "CRM Dashboard",
454
- "subtitle": "Executive and operational CRM panel with mock data aligned to the current module model.",
454
+ "subtitle": "Executive and operational CRM panel connected to the real data of the current module.",
455
455
  "breadcrumbs": {
456
456
  "home": "Home",
457
457
  "crm": "CRM"
@@ -462,6 +462,17 @@
462
462
  "primaryAction": "Open pipeline",
463
463
  "secondaryAction": "View leads and contacts",
464
464
  "refresh": "Refresh",
465
+ "filters": {
466
+ "ownerPlaceholder": "Owner",
467
+ "ownerAll": "All owners",
468
+ "periodPlaceholder": "Period",
469
+ "period7d": "Last 7 days",
470
+ "period30d": "Last 30 days",
471
+ "period90d": "Last 90 days",
472
+ "periodCustom": "Custom range",
473
+ "dateFrom": "Start date",
474
+ "dateTo": "End date"
475
+ },
465
476
  "summary": {
466
477
  "pipelineValue": "Pipeline value",
467
478
  "conversionRate": "Overall conversion",
@@ -508,7 +519,7 @@
508
519
  },
509
520
  "source": {
510
521
  "title": "Lead source mix",
511
- "description": "Share of the main sources inside the mocked base."
522
+ "description": "Share of the main sources inside the filtered CRM base."
512
523
  },
513
524
  "owner": {
514
525
  "title": "Owner performance",
@@ -554,7 +565,8 @@
554
565
  "other": "Other"
555
566
  },
556
567
  "common": {
557
- "unassigned": "Unassigned"
568
+ "unassigned": "Unassigned",
569
+ "noData": "No data for the current filters."
558
570
  }
559
571
  },
560
572
  "CrmPipeline": {
@@ -686,6 +698,9 @@
686
698
  "form": {
687
699
  "person": "Person",
688
700
  "personPlaceholder": "Select a person",
701
+ "personSearchPlaceholder": "Search person...",
702
+ "personLoading": "Loading people...",
703
+ "personEmpty": "No people found.",
689
704
  "date": "Follow-up date",
690
705
  "notes": "Notes",
691
706
  "notesPlaceholder": "Add context for the next interaction...",
@@ -766,9 +781,36 @@
766
781
  "description": "Adjust filters to find activities or clear filters to return to the full queue.",
767
782
  "resetFilters": "Clear filters"
768
783
  },
784
+ "detail": {
785
+ "title": "Activity details",
786
+ "description": "Review the full operational context before acting on this activity.",
787
+ "person": "Person",
788
+ "personType": "Person type",
789
+ "personTypeValue": {
790
+ "individual": "Individual",
791
+ "company": "Company"
792
+ },
793
+ "tradeName": "Trade name",
794
+ "owner": "Owner",
795
+ "type": "Type",
796
+ "source": "Source",
797
+ "dueAt": "Due at",
798
+ "createdAt": "Created at",
799
+ "completedAt": "Completed at",
800
+ "createdBy": "Created by",
801
+ "completedBy": "Completed by",
802
+ "close": "Close",
803
+ "emptyNotes": "No notes provided.",
804
+ "notCompleted": "Not completed",
805
+ "sourceKind": {
806
+ "manual": "Manual",
807
+ "followup": "Follow-up",
808
+ "interaction": "Interaction"
809
+ }
810
+ },
769
811
  "toasts": {
770
812
  "markedAsCompleted": "Activity marked as completed.",
771
- "openDetails": "Opening activity: {subject}"
813
+ "completeError": "Failed to complete activity."
772
814
  }
773
815
  },
774
816
  "CrmFuturePage": {
@@ -835,6 +877,49 @@
835
877
  "email": "Email",
836
878
  "phone": "Phone",
837
879
  "website": "Website",
838
- "industry": "Industry"
880
+ "industry": "Industry",
881
+ "saveError": "Failed to save account",
882
+ "deleteError": "Failed to delete account",
883
+ "form": {
884
+ "createTitle": "New Account",
885
+ "editTitle": "Edit Account",
886
+ "createDescription": "Create a new account record in the CRM.",
887
+ "editDescription": "Update the account information and relationship details.",
888
+ "companyName": "Company Name",
889
+ "tradeName": "Trade Name",
890
+ "status": "Status",
891
+ "lifecycleStage": "Stage",
892
+ "email": "Email",
893
+ "phone": "Phone",
894
+ "website": "Website",
895
+ "industry": "Industry",
896
+ "annualRevenue": "Annual Revenue",
897
+ "employeeCount": "Employees",
898
+ "city": "City",
899
+ "state": "State",
900
+ "owner": "Owner",
901
+ "createSubmit": "Create Account",
902
+ "updateSubmit": "Save Changes",
903
+ "saving": "Saving...",
904
+ "placeholders": {
905
+ "companyName": "Company name",
906
+ "tradeName": "Trade name or brand",
907
+ "email": "contact@company.com",
908
+ "phone": "(11) 99999-9999",
909
+ "website": "www.company.com",
910
+ "industry": "Technology, Healthcare...",
911
+ "annualRevenue": "0.00",
912
+ "employeeCount": "0",
913
+ "city": "Sao Paulo",
914
+ "state": "SP"
915
+ },
916
+ "validation": {
917
+ "nameMinLength": "Name must be at least 2 characters",
918
+ "invalidEmail": "Enter a valid email address",
919
+ "nonNegativeNumber": "Enter zero or a positive number",
920
+ "integerRequired": "Enter a whole number",
921
+ "stateMaxLength": "State must be at most 2 characters"
922
+ }
923
+ }
924
+ }
839
925
  }
840
- }
@@ -450,7 +450,7 @@
450
450
  },
451
451
  "CrmDashboard": {
452
452
  "title": "Dashboard CRM",
453
- "subtitle": "Painel executivo e operacional do funil comercial com dados mockados alinhados ao módulo atual.",
453
+ "subtitle": "Painel executivo e operacional do funil comercial conectado aos dados reais do módulo atual.",
454
454
  "breadcrumbs": {
455
455
  "home": "Home",
456
456
  "crm": "CRM"
@@ -461,6 +461,17 @@
461
461
  "primaryAction": "Abrir pipeline",
462
462
  "secondaryAction": "Ver leads e contatos",
463
463
  "refresh": "Atualizar",
464
+ "filters": {
465
+ "ownerPlaceholder": "Responsável",
466
+ "ownerAll": "Todos os responsáveis",
467
+ "periodPlaceholder": "Período",
468
+ "period7d": "Últimos 7 dias",
469
+ "period30d": "Últimos 30 dias",
470
+ "period90d": "Últimos 90 dias",
471
+ "periodCustom": "Intervalo customizado",
472
+ "dateFrom": "Data inicial",
473
+ "dateTo": "Data final"
474
+ },
464
475
  "summary": {
465
476
  "pipelineValue": "Valor em pipeline",
466
477
  "conversionRate": "Conversão geral",
@@ -507,7 +518,7 @@
507
518
  },
508
519
  "source": {
509
520
  "title": "Origem dos leads",
510
- "description": "Participação das principais origens dentro da base mockada."
521
+ "description": "Participação das principais origens dentro da base filtrada do CRM."
511
522
  },
512
523
  "owner": {
513
524
  "title": "Performance por owner",
@@ -553,7 +564,8 @@
553
564
  "other": "Outro"
554
565
  },
555
566
  "common": {
556
- "unassigned": "Não atribuído"
567
+ "unassigned": "Não atribuído",
568
+ "noData": "Não há dados para os filtros atuais."
557
569
  }
558
570
  },
559
571
  "CrmPipeline": {
@@ -685,6 +697,9 @@
685
697
  "form": {
686
698
  "person": "Pessoa",
687
699
  "personPlaceholder": "Selecione uma pessoa",
700
+ "personSearchPlaceholder": "Buscar pessoa...",
701
+ "personLoading": "Carregando pessoas...",
702
+ "personEmpty": "Nenhuma pessoa encontrada.",
688
703
  "date": "Data do follow-up",
689
704
  "notes": "Notas",
690
705
  "notesPlaceholder": "Adicione contexto para a próxima interação...",
@@ -765,9 +780,36 @@
765
780
  "description": "Ajuste os filtros para encontrar atividades ou limpe os filtros para voltar à fila completa.",
766
781
  "resetFilters": "Limpar filtros"
767
782
  },
783
+ "detail": {
784
+ "title": "Detalhes da atividade",
785
+ "description": "Revise todo o contexto operacional antes de agir sobre esta atividade.",
786
+ "person": "Pessoa",
787
+ "personType": "Tipo de pessoa",
788
+ "personTypeValue": {
789
+ "individual": "Pessoa física",
790
+ "company": "Empresa"
791
+ },
792
+ "tradeName": "Nome fantasia",
793
+ "owner": "Responsável",
794
+ "type": "Tipo",
795
+ "source": "Origem",
796
+ "dueAt": "Prazo",
797
+ "createdAt": "Criada em",
798
+ "completedAt": "Concluída em",
799
+ "createdBy": "Criada por",
800
+ "completedBy": "Concluída por",
801
+ "close": "Fechar",
802
+ "emptyNotes": "Nenhuma nota informada.",
803
+ "notCompleted": "Não concluída",
804
+ "sourceKind": {
805
+ "manual": "Manual",
806
+ "followup": "Follow-up",
807
+ "interaction": "Interação"
808
+ }
809
+ },
768
810
  "toasts": {
769
811
  "markedAsCompleted": "Atividade marcada como concluída.",
770
- "openDetails": "Abrindo atividade: {subject}"
812
+ "completeError": "Falha ao concluir atividade."
771
813
  }
772
814
  },
773
815
  "CrmFuturePage": {
@@ -834,6 +876,49 @@
834
876
  "email": "Email",
835
877
  "phone": "Telefone",
836
878
  "website": "Site",
837
- "industry": "Setor"
879
+ "industry": "Setor",
880
+ "saveError": "Nao foi possivel salvar a conta",
881
+ "deleteError": "Nao foi possivel excluir a conta",
882
+ "form": {
883
+ "createTitle": "Nova Conta",
884
+ "editTitle": "Editar Conta",
885
+ "createDescription": "Crie um novo registro de conta no CRM.",
886
+ "editDescription": "Atualize as informacoes da conta e do relacionamento.",
887
+ "companyName": "Nome da empresa",
888
+ "tradeName": "Nome fantasia",
889
+ "status": "Status",
890
+ "lifecycleStage": "Estagio",
891
+ "email": "Email",
892
+ "phone": "Telefone",
893
+ "website": "Site",
894
+ "industry": "Setor",
895
+ "annualRevenue": "Receita anual",
896
+ "employeeCount": "Colaboradores",
897
+ "city": "Cidade",
898
+ "state": "Estado",
899
+ "owner": "Responsavel",
900
+ "createSubmit": "Criar conta",
901
+ "updateSubmit": "Salvar alteracoes",
902
+ "saving": "Salvando...",
903
+ "placeholders": {
904
+ "companyName": "Nome da empresa",
905
+ "tradeName": "Nome fantasia ou marca",
906
+ "email": "contato@empresa.com",
907
+ "phone": "(11) 99999-9999",
908
+ "website": "www.empresa.com",
909
+ "industry": "Tecnologia, Saude...",
910
+ "annualRevenue": "0,00",
911
+ "employeeCount": "0",
912
+ "city": "Sao Paulo",
913
+ "state": "SP"
914
+ },
915
+ "validation": {
916
+ "nameMinLength": "O nome deve ter pelo menos 2 caracteres",
917
+ "invalidEmail": "Informe um email valido",
918
+ "nonNegativeNumber": "Informe zero ou um numero positivo",
919
+ "integerRequired": "Informe um numero inteiro",
920
+ "stateMaxLength": "O estado deve ter no maximo 2 caracteres"
921
+ }
922
+ }
923
+ }
838
924
  }
839
- }
@@ -0,0 +1,68 @@
1
+ table: crm_activity
2
+ columns:
3
+ - type: pk
4
+ - name: person_id
5
+ type: fk
6
+ references:
7
+ table: person
8
+ column: id
9
+ onDelete: CASCADE
10
+ onUpdate: CASCADE
11
+ - name: owner_user_id
12
+ type: fk
13
+ isNullable: true
14
+ references:
15
+ table: user
16
+ column: id
17
+ onDelete: SET NULL
18
+ onUpdate: CASCADE
19
+ - name: created_by_user_id
20
+ type: fk
21
+ isNullable: true
22
+ references:
23
+ table: user
24
+ column: id
25
+ onDelete: SET NULL
26
+ onUpdate: CASCADE
27
+ - name: completed_by_user_id
28
+ type: fk
29
+ isNullable: true
30
+ references:
31
+ table: user
32
+ column: id
33
+ onDelete: SET NULL
34
+ onUpdate: CASCADE
35
+ - name: type
36
+ type: enum
37
+ values: [call, email, meeting, whatsapp, task, note]
38
+ - name: subject
39
+ type: varchar
40
+ length: 255
41
+ - name: notes
42
+ type: text
43
+ isNullable: true
44
+ - name: due_at
45
+ type: datetime
46
+ - name: completed_at
47
+ type: datetime
48
+ isNullable: true
49
+ - name: priority
50
+ type: enum
51
+ values: [high, medium, low]
52
+ default: medium
53
+ - name: source_kind
54
+ type: enum
55
+ values: [manual, followup, interaction]
56
+ default: manual
57
+ - type: created_at
58
+ - type: updated_at
59
+
60
+ indices:
61
+ - columns: [person_id]
62
+ - columns: [owner_user_id]
63
+ - columns: [due_at]
64
+ - columns: [completed_at]
65
+ - columns: [completed_at, due_at]
66
+ - columns: [person_id, source_kind]
67
+ isUnique: true
68
+ where: source_kind = 'followup' AND completed_at IS NULL
@@ -6,6 +6,28 @@ columns:
6
6
  column: id
7
7
  - name: trade_name
8
8
  isNullable: true
9
+ - name: industry
10
+ isNullable: true
11
+ - name: website
12
+ isNullable: true
13
+ - name: annual_revenue
14
+ type: decimal
15
+ precision: 14
16
+ scale: 2
17
+ isNullable: true
18
+ - name: employee_count
19
+ type: int
20
+ isNullable: true
21
+ - name: account_lifecycle_stage
22
+ type: enum
23
+ values: [prospect, customer, churned, inactive]
24
+ isNullable: true
25
+ - name: city
26
+ isNullable: true
27
+ - name: state
28
+ type: varchar
29
+ length: 2
30
+ isNullable: true
9
31
  - name: foundation_date
10
32
  type: date
11
33
  isNullable: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/contact",
3
- "version": "0.0.293",
3
+ "version": "0.0.295",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,11 +9,11 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/core": "0.0.293",
13
- "@hed-hog/api-locale": "0.0.13",
14
- "@hed-hog/api-prisma": "0.0.5",
15
- "@hed-hog/address": "0.0.293",
12
+ "@hed-hog/core": "0.0.295",
16
13
  "@hed-hog/api-mail": "0.0.8",
14
+ "@hed-hog/api-prisma": "0.0.5",
15
+ "@hed-hog/api-locale": "0.0.13",
16
+ "@hed-hog/address": "0.0.295",
17
17
  "@hed-hog/api": "0.0.4",
18
18
  "@hed-hog/api-pagination": "0.0.6"
19
19
  },
@@ -0,0 +1,100 @@
1
+ import { Type } from 'class-transformer';
2
+ import {
3
+ IsEmail,
4
+ IsIn,
5
+ IsInt,
6
+ IsNumber,
7
+ IsOptional,
8
+ IsString,
9
+ MaxLength,
10
+ } from 'class-validator';
11
+ import { PersonStatus } from './create.dto';
12
+
13
+ export const ACCOUNT_LIFECYCLE_STAGES = [
14
+ 'prospect',
15
+ 'customer',
16
+ 'churned',
17
+ 'inactive',
18
+ ] as const;
19
+
20
+ export type AccountLifecycleStage = (typeof ACCOUNT_LIFECYCLE_STAGES)[number];
21
+
22
+ export class AccountListQueryDTO {
23
+ @IsOptional()
24
+ @IsString()
25
+ search?: string;
26
+
27
+ @IsOptional()
28
+ @IsIn(['all', PersonStatus.ACTIVE, PersonStatus.INACTIVE])
29
+ status?: 'all' | PersonStatus;
30
+
31
+ @IsOptional()
32
+ @IsIn(['all', ...ACCOUNT_LIFECYCLE_STAGES])
33
+ lifecycle_stage?: 'all' | AccountLifecycleStage;
34
+
35
+ @IsOptional()
36
+ @IsIn(['name', 'created_at'])
37
+ sortField?: 'name' | 'created_at';
38
+
39
+ @IsOptional()
40
+ @IsIn(['asc', 'desc'])
41
+ sortOrder?: 'asc' | 'desc';
42
+ }
43
+
44
+ export class CreateAccountDTO {
45
+ @IsString()
46
+ name: string;
47
+
48
+ @IsOptional()
49
+ @IsString()
50
+ trade_name?: string | null;
51
+
52
+ @IsIn([PersonStatus.ACTIVE, PersonStatus.INACTIVE])
53
+ status: PersonStatus;
54
+
55
+ @IsOptional()
56
+ @IsString()
57
+ industry?: string | null;
58
+
59
+ @IsOptional()
60
+ @IsString()
61
+ website?: string | null;
62
+
63
+ @IsOptional()
64
+ @IsEmail()
65
+ email?: string | null;
66
+
67
+ @IsOptional()
68
+ @IsString()
69
+ phone?: string | null;
70
+
71
+ @IsOptional()
72
+ @Type(() => Number)
73
+ @IsInt()
74
+ owner_user_id?: number | null;
75
+
76
+ @IsOptional()
77
+ @Type(() => Number)
78
+ @IsNumber()
79
+ annual_revenue?: number | null;
80
+
81
+ @IsOptional()
82
+ @Type(() => Number)
83
+ @IsInt()
84
+ employee_count?: number | null;
85
+
86
+ @IsOptional()
87
+ @IsIn(ACCOUNT_LIFECYCLE_STAGES)
88
+ lifecycle_stage?: AccountLifecycleStage | null;
89
+
90
+ @IsOptional()
91
+ @IsString()
92
+ city?: string | null;
93
+
94
+ @IsOptional()
95
+ @IsString()
96
+ @MaxLength(2)
97
+ state?: string | null;
98
+ }
99
+
100
+ export class UpdateAccountDTO extends CreateAccountDTO {}
@@ -0,0 +1,54 @@
1
+ import { getLocaleText } from '@hed-hog/api-locale';
2
+ import { IsIn, IsOptional, IsString } from 'class-validator';
3
+
4
+ export const CRM_ACTIVITY_TYPES = [
5
+ 'call',
6
+ 'email',
7
+ 'meeting',
8
+ 'whatsapp',
9
+ 'task',
10
+ 'note',
11
+ ] as const;
12
+
13
+ export const CRM_ACTIVITY_PRIORITIES = ['high', 'medium', 'low'] as const;
14
+ export const CRM_ACTIVITY_STATUSES = [
15
+ 'pending',
16
+ 'overdue',
17
+ 'completed',
18
+ ] as const;
19
+ export const CRM_ACTIVITY_SOURCE_KINDS = [
20
+ 'manual',
21
+ 'followup',
22
+ 'interaction',
23
+ ] as const;
24
+
25
+ export type CrmActivityType = (typeof CRM_ACTIVITY_TYPES)[number];
26
+ export type CrmActivityPriority = (typeof CRM_ACTIVITY_PRIORITIES)[number];
27
+ export type CrmActivityStatus = (typeof CRM_ACTIVITY_STATUSES)[number];
28
+ export type CrmActivitySourceKind = (typeof CRM_ACTIVITY_SOURCE_KINDS)[number];
29
+
30
+ export class ActivityListQueryDTO {
31
+ @IsOptional()
32
+ @IsString({
33
+ message: (args) => getLocaleText('validation.stringRequired', args.value),
34
+ })
35
+ search?: string;
36
+
37
+ @IsOptional()
38
+ @IsIn(['all', ...CRM_ACTIVITY_STATUSES], {
39
+ message: (args) => getLocaleText('validation.typeMustBeEnum', args.value),
40
+ })
41
+ status?: 'all' | CrmActivityStatus;
42
+
43
+ @IsOptional()
44
+ @IsIn(['all', ...CRM_ACTIVITY_TYPES], {
45
+ message: (args) => getLocaleText('validation.typeMustBeEnum', args.value),
46
+ })
47
+ type?: 'all' | CrmActivityType;
48
+
49
+ @IsOptional()
50
+ @IsIn(['all', ...CRM_ACTIVITY_PRIORITIES], {
51
+ message: (args) => getLocaleText('validation.typeMustBeEnum', args.value),
52
+ })
53
+ priority?: 'all' | CrmActivityPriority;
54
+ }
@@ -0,0 +1,25 @@
1
+ import { Type } from 'class-transformer';
2
+ import { IsDateString, IsIn, IsInt, IsOptional } from 'class-validator';
3
+
4
+ export const CRM_DASHBOARD_PERIODS = ['7d', '30d', '90d', 'custom'] as const;
5
+
6
+ export type CrmDashboardPeriod = (typeof CRM_DASHBOARD_PERIODS)[number];
7
+
8
+ export class DashboardQueryDTO {
9
+ @IsOptional()
10
+ @Type(() => Number)
11
+ @IsInt()
12
+ owner_user_id?: number;
13
+
14
+ @IsOptional()
15
+ @IsIn(CRM_DASHBOARD_PERIODS)
16
+ period?: CrmDashboardPeriod;
17
+
18
+ @IsOptional()
19
+ @IsDateString()
20
+ date_from?: string;
21
+
22
+ @IsOptional()
23
+ @IsDateString()
24
+ date_to?: string;
25
+ }
@@ -0,0 +1,25 @@
1
+ import { IsIn, IsOptional, IsString, Matches } from 'class-validator';
2
+
3
+ export class FollowupListQueryDTO {
4
+ @IsOptional()
5
+ @IsString()
6
+ search?: string;
7
+
8
+ @IsOptional()
9
+ @IsIn(['all', 'today', 'upcoming', 'overdue'])
10
+ status?: 'all' | 'today' | 'upcoming' | 'overdue';
11
+
12
+ @IsOptional()
13
+ @Matches(/^\d{4}-\d{2}-\d{2}$/)
14
+ date_from?: string;
15
+
16
+ @IsOptional()
17
+ @Matches(/^\d{4}-\d{2}-\d{2}$/)
18
+ date_to?: string;
19
+ }
20
+
21
+ export class FollowupStatsQueryDTO {
22
+ @IsOptional()
23
+ @IsString()
24
+ search?: string;
25
+ }