@nextsparkjs/theme-crm 0.1.0-beta.1

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 (140) hide show
  1. package/CRM_PLAN.md +343 -0
  2. package/about.md +122 -0
  3. package/config/app.config.ts +185 -0
  4. package/config/billing.config.ts +187 -0
  5. package/config/dashboard.config.ts +372 -0
  6. package/config/dev.config.ts +55 -0
  7. package/config/features.config.ts +336 -0
  8. package/config/flows.config.ts +511 -0
  9. package/config/permissions.config.ts +297 -0
  10. package/config/theme.config.ts +111 -0
  11. package/entities/activities/activities.config.ts +61 -0
  12. package/entities/activities/activities.fields.ts +362 -0
  13. package/entities/activities/activities.service.ts +503 -0
  14. package/entities/activities/activities.types.ts +117 -0
  15. package/entities/activities/messages/en.json +123 -0
  16. package/entities/activities/messages/es.json +123 -0
  17. package/entities/activities/migrations/020_activities_table.sql +123 -0
  18. package/entities/activities/migrations/021_activities_metas.sql +114 -0
  19. package/entities/activities/migrations/022_activities_sample_data.sql +420 -0
  20. package/entities/campaigns/campaigns.config.ts +61 -0
  21. package/entities/campaigns/campaigns.fields.ts +413 -0
  22. package/entities/campaigns/campaigns.service.ts +426 -0
  23. package/entities/campaigns/campaigns.types.ts +124 -0
  24. package/entities/campaigns/messages/en.json +145 -0
  25. package/entities/campaigns/messages/es.json +145 -0
  26. package/entities/campaigns/migrations/001_campaigns_table.sql +127 -0
  27. package/entities/campaigns/migrations/002_campaigns_metas.sql +114 -0
  28. package/entities/campaigns/migrations/003_campaigns_sample_data.sql +364 -0
  29. package/entities/companies/companies.config.ts +61 -0
  30. package/entities/companies/companies.fields.ts +429 -0
  31. package/entities/companies/companies.service.ts +566 -0
  32. package/entities/companies/companies.types.ts +125 -0
  33. package/entities/companies/messages/en.json +146 -0
  34. package/entities/companies/messages/es.json +146 -0
  35. package/entities/companies/migrations/001_companies_table.sql +150 -0
  36. package/entities/companies/migrations/002_companies_metas.sql +114 -0
  37. package/entities/companies/migrations/003_companies_sample_data.sql +246 -0
  38. package/entities/contacts/contacts.config.ts +61 -0
  39. package/entities/contacts/contacts.fields.ts +359 -0
  40. package/entities/contacts/contacts.service.ts +509 -0
  41. package/entities/contacts/contacts.types.ts +108 -0
  42. package/entities/contacts/messages/en.json +117 -0
  43. package/entities/contacts/messages/es.json +117 -0
  44. package/entities/contacts/migrations/001_contacts_table.sql +134 -0
  45. package/entities/contacts/migrations/002_contacts_metas.sql +114 -0
  46. package/entities/contacts/migrations/003_contacts_sample_data.sql +421 -0
  47. package/entities/leads/leads.config.ts +61 -0
  48. package/entities/leads/leads.fields.ts +336 -0
  49. package/entities/leads/leads.service.ts +496 -0
  50. package/entities/leads/leads.types.ts +114 -0
  51. package/entities/leads/messages/en.json +132 -0
  52. package/entities/leads/messages/es.json +132 -0
  53. package/entities/leads/migrations/001_leads_table.sql +150 -0
  54. package/entities/leads/migrations/002_leads_metas.sql +120 -0
  55. package/entities/leads/migrations/003_leads_sample_data.sql +242 -0
  56. package/entities/notes/messages/en.json +114 -0
  57. package/entities/notes/messages/es.json +114 -0
  58. package/entities/notes/migrations/020_notes_table.sql +118 -0
  59. package/entities/notes/migrations/021_notes_metas.sql +114 -0
  60. package/entities/notes/migrations/022_notes_sample_data.sql +275 -0
  61. package/entities/notes/notes.config.ts +61 -0
  62. package/entities/notes/notes.fields.ts +283 -0
  63. package/entities/notes/notes.service.ts +320 -0
  64. package/entities/notes/notes.types.ts +102 -0
  65. package/entities/opportunities/messages/en.json +107 -0
  66. package/entities/opportunities/messages/es.json +107 -0
  67. package/entities/opportunities/migrations/010_opportunities_table.sql +145 -0
  68. package/entities/opportunities/migrations/011_opportunities_metas.sql +114 -0
  69. package/entities/opportunities/migrations/012_opportunities_sample_data.sql +438 -0
  70. package/entities/opportunities/opportunities.config.ts +61 -0
  71. package/entities/opportunities/opportunities.fields.ts +416 -0
  72. package/entities/opportunities/opportunities.service.ts +525 -0
  73. package/entities/opportunities/opportunities.types.ts +135 -0
  74. package/entities/pipelines/messages/en.json +115 -0
  75. package/entities/pipelines/messages/es.json +115 -0
  76. package/entities/pipelines/migrations/001_pipelines_table.sql +106 -0
  77. package/entities/pipelines/migrations/002_pipelines_metas.sql +114 -0
  78. package/entities/pipelines/migrations/003_pipelines_sample_data.sql +91 -0
  79. package/entities/pipelines/pipelines.config.ts +62 -0
  80. package/entities/pipelines/pipelines.fields.ts +193 -0
  81. package/entities/pipelines/pipelines.service.ts +383 -0
  82. package/entities/pipelines/pipelines.types.ts +78 -0
  83. package/entities/products/messages/en.json +135 -0
  84. package/entities/products/messages/es.json +135 -0
  85. package/entities/products/migrations/001_products_table.sql +117 -0
  86. package/entities/products/migrations/002_products_metas.sql +114 -0
  87. package/entities/products/migrations/003_products_sample_data.sql +247 -0
  88. package/entities/products/products.config.ts +62 -0
  89. package/entities/products/products.fields.ts +361 -0
  90. package/entities/products/products.service.ts +437 -0
  91. package/entities/products/products.types.ts +125 -0
  92. package/lib/crm-constants.ts +77 -0
  93. package/lib/crm-utils.ts +185 -0
  94. package/lib/selectors.ts +333 -0
  95. package/messages/en.json +131 -0
  96. package/messages/es.json +131 -0
  97. package/migrations/999_theme_sample_data.sql +473 -0
  98. package/package.json +18 -0
  99. package/pendings.md +205 -0
  100. package/permissions-matrix.md +216 -0
  101. package/styles/components.css +414 -0
  102. package/styles/crm-theme.css +358 -0
  103. package/styles/globals.css +576 -0
  104. package/styles/variables.css +111 -0
  105. package/templates/dashboard/(main)/activities/components/ActivityCard.tsx +169 -0
  106. package/templates/dashboard/(main)/activities/components/ActivityTimeline.tsx +165 -0
  107. package/templates/dashboard/(main)/activities/page.tsx +297 -0
  108. package/templates/dashboard/(main)/campaigns/page.tsx +373 -0
  109. package/templates/dashboard/(main)/companies/page.tsx +296 -0
  110. package/templates/dashboard/(main)/contacts/page.tsx +347 -0
  111. package/templates/dashboard/(main)/layout.tsx +98 -0
  112. package/templates/dashboard/(main)/leads/page.tsx +335 -0
  113. package/templates/dashboard/(main)/opportunities/[id]/edit/page.tsx +95 -0
  114. package/templates/dashboard/(main)/opportunities/create/page.tsx +94 -0
  115. package/templates/dashboard/(main)/opportunities/page.tsx +350 -0
  116. package/templates/dashboard/(main)/pipelines/[id]/edit/page.tsx +95 -0
  117. package/templates/dashboard/(main)/pipelines/[id]/page.tsx +143 -0
  118. package/templates/dashboard/(main)/pipelines/create/page.tsx +94 -0
  119. package/templates/dashboard/(main)/pipelines/page.tsx +234 -0
  120. package/templates/dashboard/(main)/products/[id]/edit/page.tsx +97 -0
  121. package/templates/dashboard/(main)/products/[id]/page.tsx +509 -0
  122. package/templates/dashboard/(main)/products/create/page.tsx +96 -0
  123. package/templates/dashboard/(main)/products/page.tsx +308 -0
  124. package/templates/shared/ActionButtons.tsx +41 -0
  125. package/templates/shared/CRMDashboard.tsx +519 -0
  126. package/templates/shared/CRMDataTable.tsx +441 -0
  127. package/templates/shared/CRMMetricCard.tsx +76 -0
  128. package/templates/shared/CRMMobileNav.tsx +172 -0
  129. package/templates/shared/CRMSidebar.tsx +346 -0
  130. package/templates/shared/CRMTopBar.tsx +265 -0
  131. package/templates/shared/DealCard.tsx +123 -0
  132. package/templates/shared/EntityCard.tsx +58 -0
  133. package/templates/shared/OpportunityForm.tsx +649 -0
  134. package/templates/shared/PipelineForm.tsx +367 -0
  135. package/templates/shared/PipelineKanban.tsx +194 -0
  136. package/templates/shared/QuickFilters.tsx +47 -0
  137. package/templates/shared/StageColumn.tsx +175 -0
  138. package/templates/shared/StageSelect.tsx +177 -0
  139. package/templates/shared/StagesRepeater.tsx +317 -0
  140. package/templates/shared/index.ts +9 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * CRM Theme Utilities
3
+ * Helper functions for the CRM theme
4
+ */
5
+
6
+ /**
7
+ * Format currency with proper symbol and decimals
8
+ */
9
+ export function formatCurrency(
10
+ amount: number,
11
+ currency: string = 'USD',
12
+ locale: string = 'en-US'
13
+ ): string {
14
+ return new Intl.NumberFormat(locale, {
15
+ style: 'currency',
16
+ currency,
17
+ minimumFractionDigits: 0,
18
+ maximumFractionDigits: 2,
19
+ }).format(amount)
20
+ }
21
+
22
+ /**
23
+ * Format large numbers with K, M, B suffixes
24
+ */
25
+ export function formatCompactNumber(num: number): string {
26
+ if (num >= 1_000_000_000) {
27
+ return `${(num / 1_000_000_000).toFixed(1)}B`
28
+ }
29
+ if (num >= 1_000_000) {
30
+ return `${(num / 1_000_000).toFixed(1)}M`
31
+ }
32
+ if (num >= 1_000) {
33
+ return `${(num / 1_000).toFixed(1)}K`
34
+ }
35
+ return num.toString()
36
+ }
37
+
38
+ /**
39
+ * Calculate percentage change between two values
40
+ */
41
+ export function calculatePercentageChange(
42
+ current: number,
43
+ previous: number
44
+ ): { value: number; isPositive: boolean } {
45
+ if (previous === 0) {
46
+ return { value: 100, isPositive: current > 0 }
47
+ }
48
+
49
+ const change = ((current - previous) / previous) * 100
50
+ return {
51
+ value: Math.abs(change),
52
+ isPositive: change >= 0,
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Get initials from a name
58
+ */
59
+ export function getInitials(name: string): string {
60
+ return name
61
+ .split(' ')
62
+ .map(part => part[0])
63
+ .join('')
64
+ .toUpperCase()
65
+ .slice(0, 2)
66
+ }
67
+
68
+ /**
69
+ * Calculate days between two dates
70
+ */
71
+ export function daysBetween(date1: Date, date2: Date): number {
72
+ const diffTime = Math.abs(date2.getTime() - date1.getTime())
73
+ return Math.ceil(diffTime / (1000 * 60 * 60 * 24))
74
+ }
75
+
76
+ /**
77
+ * Check if a deal is rotten (stale) based on last update
78
+ */
79
+ export function isDealRotten(
80
+ updatedAt: Date | string,
81
+ rottenDays: number = 30
82
+ ): boolean {
83
+ const lastUpdate = typeof updatedAt === 'string' ? new Date(updatedAt) : updatedAt
84
+ const daysSinceUpdate = daysBetween(new Date(), lastUpdate)
85
+ return daysSinceUpdate > rottenDays
86
+ }
87
+
88
+ /**
89
+ * Format date relative to now (e.g., "2 hours ago", "yesterday")
90
+ */
91
+ export function formatRelativeDate(date: Date | string | null | undefined): string {
92
+ if (!date) {
93
+ return '-'
94
+ }
95
+ const d = typeof date === 'string' ? new Date(date) : date
96
+ const now = new Date()
97
+ const diffInSeconds = Math.floor((now.getTime() - d.getTime()) / 1000)
98
+
99
+ if (diffInSeconds < 60) {
100
+ return 'just now'
101
+ }
102
+
103
+ const diffInMinutes = Math.floor(diffInSeconds / 60)
104
+ if (diffInMinutes < 60) {
105
+ return `${diffInMinutes}m ago`
106
+ }
107
+
108
+ const diffInHours = Math.floor(diffInMinutes / 60)
109
+ if (diffInHours < 24) {
110
+ return `${diffInHours}h ago`
111
+ }
112
+
113
+ const diffInDays = Math.floor(diffInHours / 24)
114
+ if (diffInDays === 1) {
115
+ return 'yesterday'
116
+ }
117
+ if (diffInDays < 7) {
118
+ return `${diffInDays}d ago`
119
+ }
120
+
121
+ return d.toLocaleDateString()
122
+ }
123
+
124
+ /**
125
+ * Get badge variant based on status
126
+ */
127
+ export function getStatusVariant(
128
+ status: string
129
+ ): 'success' | 'warning' | 'danger' | 'info' | 'neutral' {
130
+ const lowerStatus = status.toLowerCase()
131
+
132
+ if (['won', 'completed', 'active', 'converted', 'success'].includes(lowerStatus)) {
133
+ return 'success'
134
+ }
135
+
136
+ if (['pending', 'in_progress', 'scheduled', 'proposal', 'negotiation'].includes(lowerStatus)) {
137
+ return 'warning'
138
+ }
139
+
140
+ if (['lost', 'cancelled', 'overdue', 'failed'].includes(lowerStatus)) {
141
+ return 'danger'
142
+ }
143
+
144
+ if (['new', 'open', 'qualification'].includes(lowerStatus)) {
145
+ return 'info'
146
+ }
147
+
148
+ return 'neutral'
149
+ }
150
+
151
+ /**
152
+ * Calculate expected revenue from amount and probability
153
+ */
154
+ export function calculateExpectedRevenue(amount: number, probability: number): number {
155
+ return (amount * probability) / 100
156
+ }
157
+
158
+ /**
159
+ * Truncate text with ellipsis
160
+ */
161
+ export function truncate(text: string, maxLength: number): string {
162
+ if (text.length <= maxLength) {
163
+ return text
164
+ }
165
+ return text.slice(0, maxLength) + '...'
166
+ }
167
+
168
+ /**
169
+ * Format phone number to international format
170
+ */
171
+ export function formatPhoneNumber(phone: string): string {
172
+ // Remove all non-numeric characters
173
+ const cleaned = phone.replace(/\D/g, '')
174
+
175
+ // Format based on length
176
+ if (cleaned.length === 10) {
177
+ return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6)}`
178
+ }
179
+
180
+ if (cleaned.length === 11 && cleaned[0] === '1') {
181
+ return `+1 (${cleaned.slice(1, 4)}) ${cleaned.slice(4, 7)}-${cleaned.slice(7)}`
182
+ }
183
+
184
+ return phone // Return original if format is unknown
185
+ }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * CRM Theme - Block Selectors
3
+ *
4
+ * This file defines selectors for block components in the theme.
5
+ * It's placed in lib/ instead of tests/ so TypeScript can resolve imports.
6
+ *
7
+ * Used by:
8
+ * - Block components (for data-cy attributes)
9
+ * - Cypress tests (via tests/cypress/src/selectors.ts)
10
+ */
11
+
12
+ import { createSelectorHelpers } from '@nextsparkjs/core/lib/test/selector-factory'
13
+ import { CORE_SELECTORS } from '@nextsparkjs/core/lib/test/core-selectors'
14
+
15
+ // =============================================================================
16
+ // BLOCK SELECTORS
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Block-specific selectors for the CRM theme.
21
+ * Each block has at minimum a 'container' selector.
22
+ * Dynamic selectors use {index} placeholder.
23
+ */
24
+ export const BLOCK_SELECTORS = {
25
+ // CRM theme currently has no custom blocks
26
+ // Add block selectors here when blocks are created
27
+ } as const
28
+
29
+ // =============================================================================
30
+ // ENTITY SELECTORS
31
+ // =============================================================================
32
+
33
+ /**
34
+ * Entity-specific selectors for the CRM theme.
35
+ */
36
+ export const ENTITY_SELECTORS = {
37
+ leads: {
38
+ list: 'leads-list',
39
+ listItem: 'lead-item-{index}',
40
+ card: 'lead-card-{id}',
41
+ name: 'lead-name',
42
+ email: 'lead-email',
43
+ phone: 'lead-phone',
44
+ company: 'lead-company',
45
+ status: 'lead-status',
46
+ source: 'lead-source',
47
+ createButton: 'lead-create-button',
48
+ editButton: 'lead-edit-button',
49
+ deleteButton: 'lead-delete-button',
50
+ convertButton: 'lead-convert-button',
51
+ form: {
52
+ container: 'lead-form',
53
+ name: 'lead-form-name',
54
+ email: 'lead-form-email',
55
+ phone: 'lead-form-phone',
56
+ company: 'lead-form-company',
57
+ status: 'lead-form-status',
58
+ source: 'lead-form-source',
59
+ submit: 'lead-form-submit',
60
+ cancel: 'lead-form-cancel',
61
+ },
62
+ },
63
+ contacts: {
64
+ list: 'contacts-list',
65
+ listItem: 'contact-item-{index}',
66
+ card: 'contact-card-{id}',
67
+ name: 'contact-name',
68
+ email: 'contact-email',
69
+ phone: 'contact-phone',
70
+ company: 'contact-company',
71
+ createButton: 'contact-create-button',
72
+ editButton: 'contact-edit-button',
73
+ deleteButton: 'contact-delete-button',
74
+ form: {
75
+ container: 'contact-form',
76
+ firstName: 'contact-form-first-name',
77
+ lastName: 'contact-form-last-name',
78
+ email: 'contact-form-email',
79
+ phone: 'contact-form-phone',
80
+ company: 'contact-form-company',
81
+ submit: 'contact-form-submit',
82
+ cancel: 'contact-form-cancel',
83
+ },
84
+ },
85
+ companies: {
86
+ list: 'companies-list',
87
+ listItem: 'company-item-{index}',
88
+ card: 'company-card-{id}',
89
+ name: 'company-name',
90
+ website: 'company-website',
91
+ industry: 'company-industry',
92
+ size: 'company-size',
93
+ createButton: 'company-create-button',
94
+ editButton: 'company-edit-button',
95
+ deleteButton: 'company-delete-button',
96
+ form: {
97
+ container: 'company-form',
98
+ name: 'company-form-name',
99
+ website: 'company-form-website',
100
+ industry: 'company-form-industry',
101
+ size: 'company-form-size',
102
+ submit: 'company-form-submit',
103
+ cancel: 'company-form-cancel',
104
+ },
105
+ },
106
+ opportunities: {
107
+ list: 'opportunities-list',
108
+ listItem: 'opportunity-item-{index}',
109
+ card: 'opportunity-card-{id}',
110
+ name: 'opportunity-name',
111
+ value: 'opportunity-value',
112
+ stage: 'opportunity-stage',
113
+ probability: 'opportunity-probability',
114
+ closeDate: 'opportunity-close-date',
115
+ createButton: 'opportunity-create-button',
116
+ editButton: 'opportunity-edit-button',
117
+ deleteButton: 'opportunity-delete-button',
118
+ form: {
119
+ container: 'opportunity-form',
120
+ name: 'opportunity-form-name',
121
+ value: 'opportunity-form-value',
122
+ stage: 'opportunity-form-stage',
123
+ probability: 'opportunity-form-probability',
124
+ closeDate: 'opportunity-form-close-date',
125
+ submit: 'opportunity-form-submit',
126
+ cancel: 'opportunity-form-cancel',
127
+ },
128
+ },
129
+ activities: {
130
+ list: 'activities-list',
131
+ listItem: 'activity-item-{index}',
132
+ card: 'activity-card-{id}',
133
+ type: 'activity-type',
134
+ subject: 'activity-subject',
135
+ dueDate: 'activity-due-date',
136
+ status: 'activity-status',
137
+ createButton: 'activity-create-button',
138
+ editButton: 'activity-edit-button',
139
+ deleteButton: 'activity-delete-button',
140
+ completeButton: 'activity-complete-button',
141
+ form: {
142
+ container: 'activity-form',
143
+ type: 'activity-form-type',
144
+ subject: 'activity-form-subject',
145
+ description: 'activity-form-description',
146
+ dueDate: 'activity-form-due-date',
147
+ submit: 'activity-form-submit',
148
+ cancel: 'activity-form-cancel',
149
+ },
150
+ },
151
+ notes: {
152
+ list: 'notes-list',
153
+ listItem: 'note-item-{index}',
154
+ card: 'note-card-{id}',
155
+ title: 'note-title',
156
+ content: 'note-content',
157
+ createButton: 'note-create-button',
158
+ editButton: 'note-edit-button',
159
+ deleteButton: 'note-delete-button',
160
+ form: {
161
+ container: 'note-form',
162
+ title: 'note-form-title',
163
+ content: 'note-form-content',
164
+ submit: 'note-form-submit',
165
+ cancel: 'note-form-cancel',
166
+ },
167
+ },
168
+ campaigns: {
169
+ list: 'campaigns-list',
170
+ listItem: 'campaign-item-{index}',
171
+ card: 'campaign-card-{id}',
172
+ name: 'campaign-name',
173
+ type: 'campaign-type',
174
+ status: 'campaign-status',
175
+ budget: 'campaign-budget',
176
+ createButton: 'campaign-create-button',
177
+ editButton: 'campaign-edit-button',
178
+ deleteButton: 'campaign-delete-button',
179
+ form: {
180
+ container: 'campaign-form',
181
+ name: 'campaign-form-name',
182
+ type: 'campaign-form-type',
183
+ status: 'campaign-form-status',
184
+ budget: 'campaign-form-budget',
185
+ startDate: 'campaign-form-start-date',
186
+ endDate: 'campaign-form-end-date',
187
+ submit: 'campaign-form-submit',
188
+ cancel: 'campaign-form-cancel',
189
+ },
190
+ },
191
+ products: {
192
+ list: 'products-list',
193
+ listItem: 'product-item-{index}',
194
+ card: 'product-card-{id}',
195
+ name: 'product-name',
196
+ sku: 'product-sku',
197
+ price: 'product-price',
198
+ category: 'product-category',
199
+ createButton: 'product-create-button',
200
+ editButton: 'product-edit-button',
201
+ deleteButton: 'product-delete-button',
202
+ form: {
203
+ container: 'product-form',
204
+ name: 'product-form-name',
205
+ sku: 'product-form-sku',
206
+ price: 'product-form-price',
207
+ description: 'product-form-description',
208
+ category: 'product-form-category',
209
+ submit: 'product-form-submit',
210
+ cancel: 'product-form-cancel',
211
+ },
212
+ },
213
+ pipelines: {
214
+ list: 'pipelines-list',
215
+ listItem: 'pipeline-item-{index}',
216
+ card: 'pipeline-card-{id}',
217
+ name: 'pipeline-name',
218
+ stages: 'pipeline-stages',
219
+ createButton: 'pipeline-create-button',
220
+ editButton: 'pipeline-edit-button',
221
+ deleteButton: 'pipeline-delete-button',
222
+ form: {
223
+ container: 'pipeline-form',
224
+ name: 'pipeline-form-name',
225
+ description: 'pipeline-form-description',
226
+ stages: 'pipeline-form-stages',
227
+ submit: 'pipeline-form-submit',
228
+ cancel: 'pipeline-form-cancel',
229
+ },
230
+ },
231
+ } as const
232
+
233
+ // =============================================================================
234
+ // CRM-SPECIFIC SELECTORS
235
+ // =============================================================================
236
+
237
+ /**
238
+ * CRM-specific UI selectors.
239
+ */
240
+ export const CRM_SELECTORS = {
241
+ dashboard: {
242
+ container: 'crm-dashboard',
243
+ statsCards: 'dashboard-stats-cards',
244
+ recentActivities: 'dashboard-recent-activities',
245
+ pipeline: 'dashboard-pipeline',
246
+ charts: 'dashboard-charts',
247
+ },
248
+ pipelineView: {
249
+ container: 'pipeline-view',
250
+ stage: 'pipeline-stage-{index}',
251
+ stageHeader: 'pipeline-stage-header-{index}',
252
+ opportunityCard: 'pipeline-opportunity-{id}',
253
+ dropZone: 'pipeline-drop-zone-{index}',
254
+ },
255
+ reporting: {
256
+ container: 'crm-reporting',
257
+ dateRange: 'report-date-range',
258
+ filters: 'report-filters',
259
+ exportButton: 'report-export-button',
260
+ chart: 'report-chart',
261
+ },
262
+ } as const
263
+
264
+ // =============================================================================
265
+ // THEME SELECTORS (CORE + BLOCKS + ENTITIES + CRM)
266
+ // =============================================================================
267
+
268
+ /**
269
+ * Complete theme selectors merging core, blocks, and entities.
270
+ */
271
+ export const THEME_SELECTORS = {
272
+ ...CORE_SELECTORS,
273
+ blocks: BLOCK_SELECTORS,
274
+ entities: ENTITY_SELECTORS,
275
+ crm: CRM_SELECTORS,
276
+ } as const
277
+
278
+ // =============================================================================
279
+ // EXPORTS
280
+ // =============================================================================
281
+
282
+ /**
283
+ * Create helpers bound to theme selectors
284
+ */
285
+ const helpers = createSelectorHelpers(THEME_SELECTORS)
286
+
287
+ /**
288
+ * Full selectors object (core + theme extensions)
289
+ */
290
+ export const SELECTORS = helpers.SELECTORS
291
+
292
+ /**
293
+ * Get a selector value by path
294
+ *
295
+ * @example
296
+ * sel('auth.login.form') // 'login-form'
297
+ * sel('entities.leads.list') // 'leads-list'
298
+ * sel('entities.leads.listItem', { index: '0' }) // 'lead-item-0'
299
+ */
300
+ export const sel = helpers.sel
301
+
302
+ /**
303
+ * Alias for sel
304
+ */
305
+ export const s = helpers.s
306
+
307
+ /**
308
+ * Get selector only in dev/test environments
309
+ */
310
+ export const selDev = helpers.selDev
311
+
312
+ /**
313
+ * Get Cypress selector string [data-cy="..."]
314
+ *
315
+ * @example
316
+ * cySelector('entities.leads.list') // '[data-cy="leads-list"]'
317
+ */
318
+ export const cySelector = helpers.cySelector
319
+
320
+ /**
321
+ * Create entity-specific selector helpers
322
+ */
323
+ export const entitySelectors = helpers.entitySelectors
324
+
325
+ /**
326
+ * Type exports
327
+ */
328
+ export type ThemeSelectorsType = typeof THEME_SELECTORS
329
+ export type BlockSelectorsType = typeof BLOCK_SELECTORS
330
+ export type EntitySelectorsType = typeof ENTITY_SELECTORS
331
+ export type CRMSelectorsType = typeof CRM_SELECTORS
332
+ export type { Replacements } from '@nextsparkjs/core/lib/test/selector-factory'
333
+ export { CORE_SELECTORS }
@@ -0,0 +1,131 @@
1
+ {
2
+ "crm": {
3
+ "name": "CRM Enterprise",
4
+ "description": "Complete CRM solution for sales and marketing teams",
5
+ "tagline": "Close more deals, grow your business",
6
+
7
+ "navigation": {
8
+ "leads": "Leads",
9
+ "contacts": "Contacts",
10
+ "companies": "Companies",
11
+ "opportunities": "Opportunities",
12
+ "activities": "Activities",
13
+ "campaigns": "Campaigns",
14
+ "reports": "Reports",
15
+ "settings": "Settings"
16
+ },
17
+
18
+ "quickActions": {
19
+ "newLead": "New Lead",
20
+ "newContact": "New Contact",
21
+ "newCompany": "New Company",
22
+ "newOpportunity": "New Opportunity",
23
+ "scheduleActivity": "Schedule Activity",
24
+ "createCampaign": "Create Campaign"
25
+ },
26
+
27
+ "dashboard": {
28
+ "welcome": "Welcome back!",
29
+ "welcomeWithName": "Welcome back, {name}!",
30
+ "overview": "Sales Overview",
31
+ "pipelineValue": "Pipeline Value",
32
+ "openDeals": "Open Deals",
33
+ "wonThisMonth": "Won This Month",
34
+ "conversionRate": "Conversion Rate",
35
+ "upcomingActivities": "Upcoming Activities",
36
+ "recentLeads": "Recent Leads",
37
+ "topOpportunities": "Top Opportunities"
38
+ },
39
+
40
+ "pipeline": {
41
+ "title": "Sales Pipeline",
42
+ "totalValue": "Total Value",
43
+ "dealCount": "Deals",
44
+ "stages": {
45
+ "qualification": "Qualification",
46
+ "needsAnalysis": "Needs Analysis",
47
+ "proposal": "Proposal",
48
+ "negotiation": "Negotiation",
49
+ "closedWon": "Closed Won",
50
+ "closedLost": "Closed Lost"
51
+ }
52
+ },
53
+
54
+ "reports": {
55
+ "title": "Reports",
56
+ "sales": "Sales Report",
57
+ "marketing": "Marketing Report",
58
+ "pipeline": "Pipeline Report",
59
+ "activity": "Activity Report",
60
+ "forecast": "Sales Forecast"
61
+ },
62
+
63
+ "features": {
64
+ "convert": {
65
+ "title": "Convert Lead",
66
+ "description": "Convert this lead to a contact and company",
67
+ "createContact": "Create Contact",
68
+ "createCompany": "Create Company",
69
+ "success": "Lead converted successfully",
70
+ "alreadyConverted": "This lead has already been converted"
71
+ },
72
+ "bulkImport": {
73
+ "title": "Import Data",
74
+ "description": "Import data from CSV or Excel file",
75
+ "selectFile": "Select file",
76
+ "mapping": "Map columns",
77
+ "preview": "Preview",
78
+ "import": "Import"
79
+ },
80
+ "bulkExport": {
81
+ "title": "Export Data",
82
+ "description": "Export data to CSV or Excel file",
83
+ "allRecords": "All records",
84
+ "selectedRecords": "Selected records",
85
+ "filteredRecords": "Filtered records"
86
+ }
87
+ },
88
+
89
+ "roles": {
90
+ "owner": "Administrator",
91
+ "admin": "Manager",
92
+ "member": "Representative",
93
+ "viewer": "Viewer",
94
+ "descriptions": {
95
+ "owner": "Full access to all CRM features and settings",
96
+ "admin": "Can manage team, view reports, and perform bulk operations",
97
+ "member": "Can manage leads, contacts, opportunities and activities",
98
+ "viewer": "Read-only access to CRM data"
99
+ }
100
+ },
101
+
102
+ "stats": {
103
+ "totalLeads": "Total Leads",
104
+ "qualifiedLeads": "Qualified Leads",
105
+ "totalOpportunities": "Total Opportunities",
106
+ "wonOpportunities": "Won Opportunities",
107
+ "pipelineValue": "Pipeline Value",
108
+ "avgDealSize": "Avg Deal Size",
109
+ "winRate": "Win Rate",
110
+ "activeCampaigns": "Active Campaigns"
111
+ },
112
+
113
+ "empty": {
114
+ "noLeads": {
115
+ "title": "No leads yet",
116
+ "description": "Start capturing leads to grow your pipeline.",
117
+ "action": "Add your first lead"
118
+ },
119
+ "noOpportunities": {
120
+ "title": "No opportunities yet",
121
+ "description": "Create opportunities from qualified leads.",
122
+ "action": "Create opportunity"
123
+ },
124
+ "noActivities": {
125
+ "title": "No activities scheduled",
126
+ "description": "Schedule calls, meetings, and tasks.",
127
+ "action": "Schedule activity"
128
+ }
129
+ }
130
+ }
131
+ }