@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,320 @@
1
+ /**
2
+ * Notes Service
3
+ *
4
+ * Provides data access methods for notes.
5
+ * Notes is a private entity - users only see notes in their team.
6
+ *
7
+ * All methods require authentication (use RLS with userId filter).
8
+ *
9
+ * @module NotesService
10
+ */
11
+
12
+ import { queryOneWithRLS, queryWithRLS, mutateWithRLS } from '@nextsparkjs/core/lib/db'
13
+
14
+ // Note interface
15
+ export interface Note {
16
+ id: string
17
+ content: string
18
+ relatedTo?: string
19
+ relatedId?: string
20
+ createdAt: string
21
+ updatedAt: string
22
+ }
23
+
24
+ // List options
25
+ export interface NoteListOptions {
26
+ limit?: number
27
+ offset?: number
28
+ relatedTo?: string
29
+ relatedId?: string
30
+ orderBy?: 'createdAt' | 'updatedAt'
31
+ orderDir?: 'asc' | 'desc'
32
+ teamId?: string
33
+ }
34
+
35
+ // List result
36
+ export interface NoteListResult {
37
+ notes: Note[]
38
+ total: number
39
+ }
40
+
41
+ // Create data
42
+ export interface NoteCreateData {
43
+ content: string
44
+ relatedTo?: string
45
+ relatedId?: string
46
+ teamId: string
47
+ }
48
+
49
+ // Update data
50
+ export interface NoteUpdateData {
51
+ content?: string
52
+ relatedTo?: string
53
+ relatedId?: string
54
+ }
55
+
56
+ // Database row type
57
+ interface DbNote {
58
+ id: string
59
+ content: string
60
+ relatedTo: string | null
61
+ relatedId: string | null
62
+ createdAt: string
63
+ updatedAt: string
64
+ }
65
+
66
+ export class NotesService {
67
+ // ============================================
68
+ // READ METHODS
69
+ // ============================================
70
+
71
+ /**
72
+ * Get a note by ID
73
+ */
74
+ static async getById(id: string, userId: string): Promise<Note | null> {
75
+ try {
76
+ if (!id?.trim()) throw new Error('Note ID is required')
77
+ if (!userId?.trim()) throw new Error('User ID is required')
78
+
79
+ const note = await queryOneWithRLS<DbNote>(
80
+ `SELECT id, content, "relatedTo", "relatedId", "createdAt", "updatedAt"
81
+ FROM notes WHERE id = $1`,
82
+ [id],
83
+ userId
84
+ )
85
+
86
+ if (!note) return null
87
+
88
+ return {
89
+ id: note.id,
90
+ content: note.content,
91
+ relatedTo: note.relatedTo ?? undefined,
92
+ relatedId: note.relatedId ?? undefined,
93
+ createdAt: note.createdAt,
94
+ updatedAt: note.updatedAt,
95
+ }
96
+ } catch (error) {
97
+ console.error('NotesService.getById error:', error)
98
+ throw new Error(error instanceof Error ? error.message : 'Failed to fetch note')
99
+ }
100
+ }
101
+
102
+ /**
103
+ * List notes with pagination and filtering
104
+ */
105
+ static async list(userId: string, options: NoteListOptions = {}): Promise<NoteListResult> {
106
+ try {
107
+ if (!userId?.trim()) throw new Error('User ID is required')
108
+
109
+ const {
110
+ limit = 10,
111
+ offset = 0,
112
+ relatedTo,
113
+ relatedId,
114
+ orderBy = 'createdAt',
115
+ orderDir = 'desc',
116
+ teamId,
117
+ } = options
118
+
119
+ // Build WHERE clause
120
+ const conditions: string[] = []
121
+ const params: unknown[] = []
122
+ let paramIndex = 1
123
+
124
+ if (relatedTo) {
125
+ conditions.push(`"relatedTo" = $${paramIndex++}`)
126
+ params.push(relatedTo)
127
+ }
128
+
129
+ if (relatedId) {
130
+ conditions.push(`"relatedId" = $${paramIndex++}`)
131
+ params.push(relatedId)
132
+ }
133
+
134
+ if (teamId) {
135
+ conditions.push(`"teamId" = $${paramIndex++}`)
136
+ params.push(teamId)
137
+ }
138
+
139
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
140
+
141
+ // Validate orderBy
142
+ const validOrderBy = ['createdAt', 'updatedAt'].includes(orderBy) ? orderBy : 'createdAt'
143
+ const validOrderDir = orderDir === 'asc' ? 'ASC' : 'DESC'
144
+ const orderColumnMap: Record<string, string> = {
145
+ createdAt: '"createdAt"',
146
+ updatedAt: '"updatedAt"',
147
+ }
148
+ const orderColumn = orderColumnMap[validOrderBy] || '"createdAt"'
149
+
150
+ // Get total count
151
+ const countResult = await queryWithRLS<{ count: string }>(
152
+ `SELECT COUNT(*)::text as count FROM notes ${whereClause}`,
153
+ params,
154
+ userId
155
+ )
156
+ const total = parseInt(countResult[0]?.count || '0', 10)
157
+
158
+ // Get notes
159
+ params.push(limit, offset)
160
+ const notes = await queryWithRLS<DbNote>(
161
+ `SELECT id, content, "relatedTo", "relatedId", "createdAt", "updatedAt"
162
+ FROM notes ${whereClause}
163
+ ORDER BY ${orderColumn} ${validOrderDir}
164
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
165
+ params,
166
+ userId
167
+ )
168
+
169
+ return {
170
+ notes: notes.map((note) => ({
171
+ id: note.id,
172
+ content: note.content,
173
+ relatedTo: note.relatedTo ?? undefined,
174
+ relatedId: note.relatedId ?? undefined,
175
+ createdAt: note.createdAt,
176
+ updatedAt: note.updatedAt,
177
+ })),
178
+ total,
179
+ }
180
+ } catch (error) {
181
+ console.error('NotesService.list error:', error)
182
+ throw new Error(error instanceof Error ? error.message : 'Failed to list notes')
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Get notes related to an entity
188
+ */
189
+ static async getByRelatedEntity(userId: string, relatedTo: string, relatedId: string, limit = 50): Promise<Note[]> {
190
+ const { notes } = await this.list(userId, {
191
+ relatedTo,
192
+ relatedId,
193
+ limit,
194
+ orderBy: 'createdAt',
195
+ orderDir: 'desc',
196
+ })
197
+ return notes
198
+ }
199
+
200
+ // ============================================
201
+ // WRITE METHODS
202
+ // ============================================
203
+
204
+ /**
205
+ * Create a new note
206
+ */
207
+ static async create(userId: string, data: NoteCreateData): Promise<Note> {
208
+ try {
209
+ if (!userId?.trim()) throw new Error('User ID is required')
210
+ if (!data.content?.trim()) throw new Error('Content is required')
211
+ if (!data.teamId?.trim()) throw new Error('Team ID is required')
212
+
213
+ const id = crypto.randomUUID()
214
+ const now = new Date().toISOString()
215
+
216
+ const result = await mutateWithRLS<DbNote>(
217
+ `INSERT INTO notes (id, "userId", "teamId", content, "relatedTo", "relatedId", "createdAt", "updatedAt")
218
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
219
+ RETURNING id, content, "relatedTo", "relatedId", "createdAt", "updatedAt"`,
220
+ [
221
+ id,
222
+ userId,
223
+ data.teamId,
224
+ data.content,
225
+ data.relatedTo || null,
226
+ data.relatedId || null,
227
+ now,
228
+ now,
229
+ ],
230
+ userId
231
+ )
232
+
233
+ if (!result.rows[0]) throw new Error('Failed to create note')
234
+
235
+ const note = result.rows[0]
236
+ return {
237
+ id: note.id,
238
+ content: note.content,
239
+ relatedTo: note.relatedTo ?? undefined,
240
+ relatedId: note.relatedId ?? undefined,
241
+ createdAt: note.createdAt,
242
+ updatedAt: note.updatedAt,
243
+ }
244
+ } catch (error) {
245
+ console.error('NotesService.create error:', error)
246
+ throw new Error(error instanceof Error ? error.message : 'Failed to create note')
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Update an existing note
252
+ */
253
+ static async update(userId: string, id: string, data: NoteUpdateData): Promise<Note> {
254
+ try {
255
+ if (!userId?.trim()) throw new Error('User ID is required')
256
+ if (!id?.trim()) throw new Error('Note ID is required')
257
+
258
+ const updates: string[] = []
259
+ const values: unknown[] = []
260
+ let paramIndex = 1
261
+
262
+ if (data.content !== undefined) {
263
+ updates.push(`content = $${paramIndex++}`)
264
+ values.push(data.content)
265
+ }
266
+ if (data.relatedTo !== undefined) {
267
+ updates.push(`"relatedTo" = $${paramIndex++}`)
268
+ values.push(data.relatedTo || null)
269
+ }
270
+ if (data.relatedId !== undefined) {
271
+ updates.push(`"relatedId" = $${paramIndex++}`)
272
+ values.push(data.relatedId || null)
273
+ }
274
+
275
+ if (updates.length === 0) throw new Error('No fields to update')
276
+
277
+ updates.push(`"updatedAt" = $${paramIndex++}`)
278
+ values.push(new Date().toISOString())
279
+ values.push(id)
280
+
281
+ const result = await mutateWithRLS<DbNote>(
282
+ `UPDATE notes SET ${updates.join(', ')} WHERE id = $${paramIndex}
283
+ RETURNING id, content, "relatedTo", "relatedId", "createdAt", "updatedAt"`,
284
+ values,
285
+ userId
286
+ )
287
+
288
+ if (!result.rows[0]) throw new Error('Note not found or update failed')
289
+
290
+ const note = result.rows[0]
291
+ return {
292
+ id: note.id,
293
+ content: note.content,
294
+ relatedTo: note.relatedTo ?? undefined,
295
+ relatedId: note.relatedId ?? undefined,
296
+ createdAt: note.createdAt,
297
+ updatedAt: note.updatedAt,
298
+ }
299
+ } catch (error) {
300
+ console.error('NotesService.update error:', error)
301
+ throw new Error(error instanceof Error ? error.message : 'Failed to update note')
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Delete a note
307
+ */
308
+ static async delete(userId: string, id: string): Promise<boolean> {
309
+ try {
310
+ if (!userId?.trim()) throw new Error('User ID is required')
311
+ if (!id?.trim()) throw new Error('Note ID is required')
312
+
313
+ const result = await mutateWithRLS(`DELETE FROM notes WHERE id = $1`, [id], userId)
314
+ return result.rowCount > 0
315
+ } catch (error) {
316
+ console.error('NotesService.delete error:', error)
317
+ throw new Error(error instanceof Error ? error.message : 'Failed to delete note')
318
+ }
319
+ }
320
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Note Service Types
3
+ *
4
+ * Type definitions for the NoteService.
5
+ * Defines types for note management with flexible entity relationships,
6
+ * privacy controls, and attachment support.
7
+ *
8
+ * @module NoteTypes
9
+ */
10
+
11
+ // Type literals for select fields
12
+ export type NoteType =
13
+ | 'general'
14
+ | 'call'
15
+ | 'meeting'
16
+ | 'email'
17
+ | 'followup'
18
+ | 'feedback'
19
+ | 'reminder'
20
+
21
+ export type NoteEntityType = 'lead' | 'contact' | 'company' | 'opportunity' | 'campaign'
22
+
23
+ // Attachment interface
24
+ export interface NoteAttachment {
25
+ name: string
26
+ url: string
27
+ size?: number
28
+ type?: string
29
+ }
30
+
31
+ // Main entity interface
32
+ export interface Note {
33
+ id: string
34
+ teamId: string
35
+ title?: string | null
36
+ content: string
37
+ type?: NoteType | null
38
+ isPinned?: boolean | null
39
+ isPrivate?: boolean | null
40
+ entityType?: NoteEntityType | null
41
+ entityId?: string | null
42
+ contactId?: string | null
43
+ companyId?: string | null
44
+ opportunityId?: string | null
45
+ attachments?: NoteAttachment[] | null
46
+ createdAt: string
47
+ updatedAt: string
48
+ }
49
+
50
+ // List options
51
+ export interface NoteListOptions {
52
+ limit?: number
53
+ offset?: number
54
+ teamId?: string
55
+ type?: NoteType
56
+ isPinned?: boolean
57
+ isPrivate?: boolean
58
+ entityType?: NoteEntityType
59
+ entityId?: string
60
+ contactId?: string
61
+ companyId?: string
62
+ opportunityId?: string
63
+ orderBy?: 'title' | 'type' | 'isPinned' | 'isPrivate' | 'createdAt' | 'updatedAt'
64
+ orderDir?: 'asc' | 'desc'
65
+ }
66
+
67
+ // List result
68
+ export interface NoteListResult {
69
+ notes: Note[]
70
+ total: number
71
+ }
72
+
73
+ // Create data (required fields + teamId + optional fields)
74
+ export interface NoteCreateData {
75
+ content: string
76
+ teamId: string
77
+ title?: string
78
+ type?: NoteType
79
+ isPinned?: boolean
80
+ isPrivate?: boolean
81
+ entityType?: NoteEntityType
82
+ entityId?: string
83
+ contactId?: string
84
+ companyId?: string
85
+ opportunityId?: string
86
+ attachments?: NoteAttachment[]
87
+ }
88
+
89
+ // Update data (all fields optional)
90
+ export interface NoteUpdateData {
91
+ title?: string | null
92
+ content?: string
93
+ type?: NoteType | null
94
+ isPinned?: boolean | null
95
+ isPrivate?: boolean | null
96
+ entityType?: NoteEntityType | null
97
+ entityId?: string | null
98
+ contactId?: string | null
99
+ companyId?: string | null
100
+ opportunityId?: string | null
101
+ attachments?: NoteAttachment[] | null
102
+ }
@@ -0,0 +1,107 @@
1
+ {
2
+ "entity": {
3
+ "name": "Opportunity",
4
+ "plural": "Opportunities",
5
+ "description": "Manage sales opportunities"
6
+ },
7
+ "fields": {
8
+ "name": {
9
+ "label": "Opportunity Name",
10
+ "description": "Opportunity name or title",
11
+ "placeholder": "Enter opportunity name..."
12
+ },
13
+ "companyId": {
14
+ "label": "Company",
15
+ "description": "Company this opportunity belongs to",
16
+ "placeholder": "Select company..."
17
+ },
18
+ "contactId": {
19
+ "label": "Primary Contact",
20
+ "description": "Primary contact for this opportunity",
21
+ "placeholder": "Select contact..."
22
+ },
23
+ "pipelineId": {
24
+ "label": "Pipeline",
25
+ "description": "Pipeline this opportunity is in",
26
+ "placeholder": "Select pipeline..."
27
+ },
28
+ "stageId": {
29
+ "label": "Stage",
30
+ "description": "Current stage in pipeline",
31
+ "placeholder": "Stage ID from pipeline"
32
+ },
33
+ "amount": {
34
+ "label": "Deal Amount",
35
+ "description": "Deal amount value",
36
+ "placeholder": "0.00"
37
+ },
38
+ "currency": {
39
+ "label": "Currency",
40
+ "description": "Currency code for amount",
41
+ "placeholder": "Select currency..."
42
+ },
43
+ "probability": {
44
+ "label": "Win Probability (%)",
45
+ "description": "Win probability percentage",
46
+ "placeholder": "0"
47
+ },
48
+ "expectedRevenue": {
49
+ "label": "Expected Revenue",
50
+ "description": "Calculated expected revenue"
51
+ },
52
+ "closeDate": {
53
+ "label": "Expected Close Date",
54
+ "description": "Expected or actual close date",
55
+ "placeholder": "Select date..."
56
+ },
57
+ "type": {
58
+ "label": "Opportunity Type",
59
+ "description": "Type of opportunity",
60
+ "placeholder": "Select type..."
61
+ },
62
+ "source": {
63
+ "label": "Source",
64
+ "description": "Source of the opportunity",
65
+ "placeholder": "Select source..."
66
+ },
67
+ "status": {
68
+ "label": "Status",
69
+ "description": "Current opportunity status",
70
+ "placeholder": "Select status..."
71
+ },
72
+ "assignedTo": {
73
+ "label": "Assigned To",
74
+ "description": "Sales rep assigned to opportunity",
75
+ "placeholder": "Select user..."
76
+ }
77
+ },
78
+ "options": {
79
+ "type": {
80
+ "new_business": "New Business",
81
+ "existing_business": "Existing Business",
82
+ "renewal": "Renewal",
83
+ "upgrade": "Upgrade",
84
+ "downgrade": "Downgrade"
85
+ },
86
+ "status": {
87
+ "open": "Open",
88
+ "won": "Won",
89
+ "lost": "Lost",
90
+ "abandoned": "Abandoned"
91
+ }
92
+ },
93
+ "actions": {
94
+ "create": "Create Opportunity",
95
+ "edit": "Edit Opportunity",
96
+ "delete": "Delete Opportunity",
97
+ "view": "View Opportunity",
98
+ "list": "List Opportunities"
99
+ },
100
+ "messages": {
101
+ "created": "Opportunity created successfully",
102
+ "updated": "Opportunity updated successfully",
103
+ "deleted": "Opportunity deleted successfully",
104
+ "notFound": "Opportunity not found",
105
+ "error": "An error occurred while processing the opportunity"
106
+ }
107
+ }
@@ -0,0 +1,107 @@
1
+ {
2
+ "entity": {
3
+ "name": "Oportunidad",
4
+ "plural": "Oportunidades",
5
+ "description": "Gestiona oportunidades de venta"
6
+ },
7
+ "fields": {
8
+ "name": {
9
+ "label": "Nombre",
10
+ "description": "Nombre de la oportunidad",
11
+ "placeholder": "Ingrese nombre..."
12
+ },
13
+ "companyId": {
14
+ "label": "Empresa",
15
+ "description": "Empresa asociada",
16
+ "placeholder": "Seleccionar empresa..."
17
+ },
18
+ "contactId": {
19
+ "label": "Contacto Principal",
20
+ "description": "Contacto principal",
21
+ "placeholder": "Seleccionar contacto..."
22
+ },
23
+ "pipelineId": {
24
+ "label": "Pipeline",
25
+ "description": "Pipeline de ventas",
26
+ "placeholder": "Seleccionar pipeline..."
27
+ },
28
+ "stageId": {
29
+ "label": "Etapa",
30
+ "description": "Etapa actual",
31
+ "placeholder": "ID de etapa..."
32
+ },
33
+ "amount": {
34
+ "label": "Monto",
35
+ "description": "Valor del trato",
36
+ "placeholder": "0.00"
37
+ },
38
+ "currency": {
39
+ "label": "Moneda",
40
+ "description": "Código de moneda",
41
+ "placeholder": "Seleccionar moneda..."
42
+ },
43
+ "probability": {
44
+ "label": "Probabilidad (%)",
45
+ "description": "Probabilidad de ganar",
46
+ "placeholder": "0"
47
+ },
48
+ "expectedRevenue": {
49
+ "label": "Ingresos Esperados",
50
+ "description": "Ingresos esperados calculados"
51
+ },
52
+ "closeDate": {
53
+ "label": "Fecha de Cierre",
54
+ "description": "Fecha esperada de cierre",
55
+ "placeholder": "Seleccionar fecha..."
56
+ },
57
+ "type": {
58
+ "label": "Tipo",
59
+ "description": "Tipo de oportunidad",
60
+ "placeholder": "Seleccionar tipo..."
61
+ },
62
+ "source": {
63
+ "label": "Fuente",
64
+ "description": "Fuente de la oportunidad",
65
+ "placeholder": "Seleccionar fuente..."
66
+ },
67
+ "status": {
68
+ "label": "Estado",
69
+ "description": "Estado actual",
70
+ "placeholder": "Seleccionar estado..."
71
+ },
72
+ "assignedTo": {
73
+ "label": "Asignado a",
74
+ "description": "Representante de ventas",
75
+ "placeholder": "Seleccionar usuario..."
76
+ }
77
+ },
78
+ "options": {
79
+ "type": {
80
+ "new_business": "Nuevo Negocio",
81
+ "existing_business": "Negocio Existente",
82
+ "renewal": "Renovación",
83
+ "upgrade": "Actualización",
84
+ "downgrade": "Reducción"
85
+ },
86
+ "status": {
87
+ "open": "Abierta",
88
+ "won": "Ganada",
89
+ "lost": "Perdida",
90
+ "abandoned": "Abandonada"
91
+ }
92
+ },
93
+ "actions": {
94
+ "create": "Crear Oportunidad",
95
+ "edit": "Editar Oportunidad",
96
+ "delete": "Eliminar Oportunidad",
97
+ "view": "Ver Oportunidad",
98
+ "list": "Listar Oportunidades"
99
+ },
100
+ "messages": {
101
+ "created": "Oportunidad creada exitosamente",
102
+ "updated": "Oportunidad actualizada exitosamente",
103
+ "deleted": "Oportunidad eliminada exitosamente",
104
+ "notFound": "Oportunidad no encontrada",
105
+ "error": "Error al procesar la oportunidad"
106
+ }
107
+ }