@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,426 @@
1
+ /**
2
+ * Campaigns Service
3
+ *
4
+ * Provides data access methods for campaigns.
5
+ * Campaigns is a private entity - users only see campaigns in their team.
6
+ *
7
+ * All methods require authentication (use RLS with userId filter).
8
+ *
9
+ * @module CampaignsService
10
+ */
11
+
12
+ import { queryOneWithRLS, queryWithRLS, mutateWithRLS } from '@nextsparkjs/core/lib/db'
13
+
14
+ // Campaign type
15
+ export type CampaignType = 'email' | 'social' | 'webinar' | 'event' | 'other'
16
+
17
+ // Campaign status type
18
+ export type CampaignStatus = 'draft' | 'active' | 'paused' | 'completed'
19
+
20
+ // Campaign interface
21
+ export interface Campaign {
22
+ id: string
23
+ name: string
24
+ type: CampaignType
25
+ status: CampaignStatus
26
+ startDate?: string
27
+ endDate?: string
28
+ budget?: number
29
+ actualCost?: number
30
+ expectedRevenue?: number
31
+ description?: string
32
+ createdAt: string
33
+ updatedAt: string
34
+ }
35
+
36
+ // List options
37
+ export interface CampaignListOptions {
38
+ limit?: number
39
+ offset?: number
40
+ type?: CampaignType
41
+ status?: CampaignStatus
42
+ orderBy?: 'name' | 'startDate' | 'budget' | 'createdAt' | 'updatedAt'
43
+ orderDir?: 'asc' | 'desc'
44
+ teamId?: string
45
+ }
46
+
47
+ // List result
48
+ export interface CampaignListResult {
49
+ campaigns: Campaign[]
50
+ total: number
51
+ }
52
+
53
+ // Create data
54
+ export interface CampaignCreateData {
55
+ name: string
56
+ type: CampaignType
57
+ status?: CampaignStatus
58
+ startDate?: string
59
+ endDate?: string
60
+ budget?: number
61
+ actualCost?: number
62
+ expectedRevenue?: number
63
+ description?: string
64
+ teamId: string
65
+ }
66
+
67
+ // Update data
68
+ export interface CampaignUpdateData {
69
+ name?: string
70
+ type?: CampaignType
71
+ status?: CampaignStatus
72
+ startDate?: string
73
+ endDate?: string
74
+ budget?: number
75
+ actualCost?: number
76
+ expectedRevenue?: number
77
+ description?: string
78
+ }
79
+
80
+ // Database row type
81
+ interface DbCampaign {
82
+ id: string
83
+ name: string
84
+ type: CampaignType
85
+ status: CampaignStatus
86
+ startDate: string | null
87
+ endDate: string | null
88
+ budget: number | null
89
+ actualCost: number | null
90
+ expectedRevenue: number | null
91
+ description: string | null
92
+ createdAt: string
93
+ updatedAt: string
94
+ }
95
+
96
+ export class CampaignsService {
97
+ // ============================================
98
+ // READ METHODS
99
+ // ============================================
100
+
101
+ /**
102
+ * Get a campaign by ID
103
+ */
104
+ static async getById(id: string, userId: string): Promise<Campaign | null> {
105
+ try {
106
+ if (!id?.trim()) throw new Error('Campaign ID is required')
107
+ if (!userId?.trim()) throw new Error('User ID is required')
108
+
109
+ const campaign = await queryOneWithRLS<DbCampaign>(
110
+ `SELECT id, name, type, status, "startDate", "endDate", budget, "actualCost", "expectedRevenue", description, "createdAt", "updatedAt"
111
+ FROM campaigns WHERE id = $1`,
112
+ [id],
113
+ userId
114
+ )
115
+
116
+ if (!campaign) return null
117
+
118
+ return {
119
+ id: campaign.id,
120
+ name: campaign.name,
121
+ type: campaign.type,
122
+ status: campaign.status,
123
+ startDate: campaign.startDate ?? undefined,
124
+ endDate: campaign.endDate ?? undefined,
125
+ budget: campaign.budget ?? undefined,
126
+ actualCost: campaign.actualCost ?? undefined,
127
+ expectedRevenue: campaign.expectedRevenue ?? undefined,
128
+ description: campaign.description ?? undefined,
129
+ createdAt: campaign.createdAt,
130
+ updatedAt: campaign.updatedAt,
131
+ }
132
+ } catch (error) {
133
+ console.error('CampaignsService.getById error:', error)
134
+ throw new Error(error instanceof Error ? error.message : 'Failed to fetch campaign')
135
+ }
136
+ }
137
+
138
+ /**
139
+ * List campaigns with pagination and filtering
140
+ */
141
+ static async list(userId: string, options: CampaignListOptions = {}): Promise<CampaignListResult> {
142
+ try {
143
+ if (!userId?.trim()) throw new Error('User ID is required')
144
+
145
+ const {
146
+ limit = 10,
147
+ offset = 0,
148
+ type,
149
+ status,
150
+ orderBy = 'createdAt',
151
+ orderDir = 'desc',
152
+ teamId,
153
+ } = options
154
+
155
+ // Build WHERE clause
156
+ const conditions: string[] = []
157
+ const params: unknown[] = []
158
+ let paramIndex = 1
159
+
160
+ if (type) {
161
+ conditions.push(`type = $${paramIndex++}`)
162
+ params.push(type)
163
+ }
164
+
165
+ if (status) {
166
+ conditions.push(`status = $${paramIndex++}`)
167
+ params.push(status)
168
+ }
169
+
170
+ if (teamId) {
171
+ conditions.push(`"teamId" = $${paramIndex++}`)
172
+ params.push(teamId)
173
+ }
174
+
175
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
176
+
177
+ // Validate orderBy
178
+ const validOrderBy = ['name', 'startDate', 'budget', 'createdAt', 'updatedAt'].includes(orderBy) ? orderBy : 'createdAt'
179
+ const validOrderDir = orderDir === 'asc' ? 'ASC' : 'DESC'
180
+ const orderColumnMap: Record<string, string> = {
181
+ name: 'name',
182
+ startDate: '"startDate"',
183
+ budget: 'budget',
184
+ createdAt: '"createdAt"',
185
+ updatedAt: '"updatedAt"',
186
+ }
187
+ const orderColumn = orderColumnMap[validOrderBy] || '"createdAt"'
188
+
189
+ // Get total count
190
+ const countResult = await queryWithRLS<{ count: string }>(
191
+ `SELECT COUNT(*)::text as count FROM campaigns ${whereClause}`,
192
+ params,
193
+ userId
194
+ )
195
+ const total = parseInt(countResult[0]?.count || '0', 10)
196
+
197
+ // Get campaigns
198
+ params.push(limit, offset)
199
+ const campaigns = await queryWithRLS<DbCampaign>(
200
+ `SELECT id, name, type, status, "startDate", "endDate", budget, "actualCost", "expectedRevenue", description, "createdAt", "updatedAt"
201
+ FROM campaigns ${whereClause}
202
+ ORDER BY ${orderColumn} ${validOrderDir}
203
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
204
+ params,
205
+ userId
206
+ )
207
+
208
+ return {
209
+ campaigns: campaigns.map((campaign) => ({
210
+ id: campaign.id,
211
+ name: campaign.name,
212
+ type: campaign.type,
213
+ status: campaign.status,
214
+ startDate: campaign.startDate ?? undefined,
215
+ endDate: campaign.endDate ?? undefined,
216
+ budget: campaign.budget ?? undefined,
217
+ actualCost: campaign.actualCost ?? undefined,
218
+ expectedRevenue: campaign.expectedRevenue ?? undefined,
219
+ description: campaign.description ?? undefined,
220
+ createdAt: campaign.createdAt,
221
+ updatedAt: campaign.updatedAt,
222
+ })),
223
+ total,
224
+ }
225
+ } catch (error) {
226
+ console.error('CampaignsService.list error:', error)
227
+ throw new Error(error instanceof Error ? error.message : 'Failed to list campaigns')
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Get campaigns by status
233
+ */
234
+ static async getByStatus(userId: string, status: CampaignStatus, limit = 50): Promise<Campaign[]> {
235
+ const { campaigns } = await this.list(userId, {
236
+ status,
237
+ limit,
238
+ orderBy: 'startDate',
239
+ orderDir: 'desc',
240
+ })
241
+ return campaigns
242
+ }
243
+
244
+ /**
245
+ * Get active campaigns
246
+ */
247
+ static async getActive(userId: string, limit = 20): Promise<Campaign[]> {
248
+ return this.getByStatus(userId, 'active', limit)
249
+ }
250
+
251
+ /**
252
+ * Get campaigns by type
253
+ */
254
+ static async getByType(userId: string, type: CampaignType, limit = 50): Promise<Campaign[]> {
255
+ const { campaigns } = await this.list(userId, {
256
+ type,
257
+ limit,
258
+ orderBy: 'startDate',
259
+ orderDir: 'desc',
260
+ })
261
+ return campaigns
262
+ }
263
+
264
+ // ============================================
265
+ // WRITE METHODS
266
+ // ============================================
267
+
268
+ /**
269
+ * Create a new campaign
270
+ */
271
+ static async create(userId: string, data: CampaignCreateData): Promise<Campaign> {
272
+ try {
273
+ if (!userId?.trim()) throw new Error('User ID is required')
274
+ if (!data.name?.trim()) throw new Error('Campaign name is required')
275
+ if (!data.teamId?.trim()) throw new Error('Team ID is required')
276
+
277
+ const id = crypto.randomUUID()
278
+ const now = new Date().toISOString()
279
+
280
+ const result = await mutateWithRLS<DbCampaign>(
281
+ `INSERT INTO campaigns (id, "userId", "teamId", name, type, status, "startDate", "endDate", budget, "actualCost", "expectedRevenue", description, "createdAt", "updatedAt")
282
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
283
+ RETURNING id, name, type, status, "startDate", "endDate", budget, "actualCost", "expectedRevenue", description, "createdAt", "updatedAt"`,
284
+ [
285
+ id,
286
+ userId,
287
+ data.teamId,
288
+ data.name,
289
+ data.type,
290
+ data.status || 'draft',
291
+ data.startDate || null,
292
+ data.endDate || null,
293
+ data.budget || null,
294
+ data.actualCost || null,
295
+ data.expectedRevenue || null,
296
+ data.description || null,
297
+ now,
298
+ now,
299
+ ],
300
+ userId
301
+ )
302
+
303
+ if (!result.rows[0]) throw new Error('Failed to create campaign')
304
+
305
+ const campaign = result.rows[0]
306
+ return {
307
+ id: campaign.id,
308
+ name: campaign.name,
309
+ type: campaign.type,
310
+ status: campaign.status,
311
+ startDate: campaign.startDate ?? undefined,
312
+ endDate: campaign.endDate ?? undefined,
313
+ budget: campaign.budget ?? undefined,
314
+ actualCost: campaign.actualCost ?? undefined,
315
+ expectedRevenue: campaign.expectedRevenue ?? undefined,
316
+ description: campaign.description ?? undefined,
317
+ createdAt: campaign.createdAt,
318
+ updatedAt: campaign.updatedAt,
319
+ }
320
+ } catch (error) {
321
+ console.error('CampaignsService.create error:', error)
322
+ throw new Error(error instanceof Error ? error.message : 'Failed to create campaign')
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Update an existing campaign
328
+ */
329
+ static async update(userId: string, id: string, data: CampaignUpdateData): Promise<Campaign> {
330
+ try {
331
+ if (!userId?.trim()) throw new Error('User ID is required')
332
+ if (!id?.trim()) throw new Error('Campaign ID is required')
333
+
334
+ const updates: string[] = []
335
+ const values: unknown[] = []
336
+ let paramIndex = 1
337
+
338
+ if (data.name !== undefined) {
339
+ updates.push(`name = $${paramIndex++}`)
340
+ values.push(data.name)
341
+ }
342
+ if (data.type !== undefined) {
343
+ updates.push(`type = $${paramIndex++}`)
344
+ values.push(data.type)
345
+ }
346
+ if (data.status !== undefined) {
347
+ updates.push(`status = $${paramIndex++}`)
348
+ values.push(data.status)
349
+ }
350
+ if (data.startDate !== undefined) {
351
+ updates.push(`"startDate" = $${paramIndex++}`)
352
+ values.push(data.startDate || null)
353
+ }
354
+ if (data.endDate !== undefined) {
355
+ updates.push(`"endDate" = $${paramIndex++}`)
356
+ values.push(data.endDate || null)
357
+ }
358
+ if (data.budget !== undefined) {
359
+ updates.push(`budget = $${paramIndex++}`)
360
+ values.push(data.budget)
361
+ }
362
+ if (data.actualCost !== undefined) {
363
+ updates.push(`"actualCost" = $${paramIndex++}`)
364
+ values.push(data.actualCost)
365
+ }
366
+ if (data.expectedRevenue !== undefined) {
367
+ updates.push(`"expectedRevenue" = $${paramIndex++}`)
368
+ values.push(data.expectedRevenue)
369
+ }
370
+ if (data.description !== undefined) {
371
+ updates.push(`description = $${paramIndex++}`)
372
+ values.push(data.description || null)
373
+ }
374
+
375
+ if (updates.length === 0) throw new Error('No fields to update')
376
+
377
+ updates.push(`"updatedAt" = $${paramIndex++}`)
378
+ values.push(new Date().toISOString())
379
+ values.push(id)
380
+
381
+ const result = await mutateWithRLS<DbCampaign>(
382
+ `UPDATE campaigns SET ${updates.join(', ')} WHERE id = $${paramIndex}
383
+ RETURNING id, name, type, status, "startDate", "endDate", budget, "actualCost", "expectedRevenue", description, "createdAt", "updatedAt"`,
384
+ values,
385
+ userId
386
+ )
387
+
388
+ if (!result.rows[0]) throw new Error('Campaign not found or update failed')
389
+
390
+ const campaign = result.rows[0]
391
+ return {
392
+ id: campaign.id,
393
+ name: campaign.name,
394
+ type: campaign.type,
395
+ status: campaign.status,
396
+ startDate: campaign.startDate ?? undefined,
397
+ endDate: campaign.endDate ?? undefined,
398
+ budget: campaign.budget ?? undefined,
399
+ actualCost: campaign.actualCost ?? undefined,
400
+ expectedRevenue: campaign.expectedRevenue ?? undefined,
401
+ description: campaign.description ?? undefined,
402
+ createdAt: campaign.createdAt,
403
+ updatedAt: campaign.updatedAt,
404
+ }
405
+ } catch (error) {
406
+ console.error('CampaignsService.update error:', error)
407
+ throw new Error(error instanceof Error ? error.message : 'Failed to update campaign')
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Delete a campaign
413
+ */
414
+ static async delete(userId: string, id: string): Promise<boolean> {
415
+ try {
416
+ if (!userId?.trim()) throw new Error('User ID is required')
417
+ if (!id?.trim()) throw new Error('Campaign ID is required')
418
+
419
+ const result = await mutateWithRLS(`DELETE FROM campaigns WHERE id = $1`, [id], userId)
420
+ return result.rowCount > 0
421
+ } catch (error) {
422
+ console.error('CampaignsService.delete error:', error)
423
+ throw new Error(error instanceof Error ? error.message : 'Failed to delete campaign')
424
+ }
425
+ }
426
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Campaign Service Types
3
+ *
4
+ * Type definitions for the CampaignService.
5
+ * Defines types for marketing campaign management including budget tracking,
6
+ * lead generation metrics, and ROI calculations.
7
+ *
8
+ * @module CampaignTypes
9
+ */
10
+
11
+ // Type literals for select fields
12
+ export type CampaignType =
13
+ | 'email'
14
+ | 'social'
15
+ | 'event'
16
+ | 'webinar'
17
+ | 'advertising'
18
+ | 'content'
19
+ | 'other'
20
+
21
+ export type CampaignStatus = 'planned' | 'active' | 'paused' | 'completed' | 'cancelled'
22
+
23
+ export type CampaignChannel =
24
+ | 'email'
25
+ | 'social_media'
26
+ | 'web'
27
+ | 'print'
28
+ | 'tv'
29
+ | 'radio'
30
+ | 'other'
31
+
32
+ // Main entity interface
33
+ export interface Campaign {
34
+ id: string
35
+ teamId: string
36
+ name: string
37
+ type?: CampaignType | null
38
+ status?: CampaignStatus | null
39
+ objective?: string | null
40
+ description?: string | null
41
+ startDate: string
42
+ endDate: string
43
+ budget?: number | null
44
+ actualCost?: number | null
45
+ targetAudience?: string | null
46
+ targetLeads?: number | null
47
+ actualLeads?: number | null
48
+ targetRevenue?: number | null
49
+ actualRevenue?: number | null
50
+ roi?: number | null
51
+ channel?: CampaignChannel | null
52
+ assignedTo?: string | null
53
+ createdAt: string
54
+ updatedAt: string
55
+ }
56
+
57
+ // List options
58
+ export interface CampaignListOptions {
59
+ limit?: number
60
+ offset?: number
61
+ teamId?: string
62
+ type?: CampaignType
63
+ status?: CampaignStatus
64
+ channel?: CampaignChannel
65
+ assignedTo?: string
66
+ orderBy?:
67
+ | 'name'
68
+ | 'startDate'
69
+ | 'endDate'
70
+ | 'budget'
71
+ | 'actualCost'
72
+ | 'actualLeads'
73
+ | 'roi'
74
+ | 'createdAt'
75
+ | 'updatedAt'
76
+ orderDir?: 'asc' | 'desc'
77
+ }
78
+
79
+ // List result
80
+ export interface CampaignListResult {
81
+ campaigns: Campaign[]
82
+ total: number
83
+ }
84
+
85
+ // Create data (required fields + teamId + optional fields)
86
+ export interface CampaignCreateData {
87
+ name: string
88
+ startDate: string
89
+ endDate: string
90
+ teamId: string
91
+ type?: CampaignType
92
+ status?: CampaignStatus
93
+ objective?: string
94
+ description?: string
95
+ budget?: number
96
+ actualCost?: number
97
+ targetAudience?: string
98
+ targetLeads?: number
99
+ actualLeads?: number
100
+ targetRevenue?: number
101
+ actualRevenue?: number
102
+ channel?: CampaignChannel
103
+ assignedTo?: string
104
+ }
105
+
106
+ // Update data (all fields optional)
107
+ export interface CampaignUpdateData {
108
+ name?: string
109
+ type?: CampaignType | null
110
+ status?: CampaignStatus | null
111
+ objective?: string | null
112
+ description?: string | null
113
+ startDate?: string
114
+ endDate?: string
115
+ budget?: number | null
116
+ actualCost?: number | null
117
+ targetAudience?: string | null
118
+ targetLeads?: number | null
119
+ actualLeads?: number | null
120
+ targetRevenue?: number | null
121
+ actualRevenue?: number | null
122
+ channel?: CampaignChannel | null
123
+ assignedTo?: string | null
124
+ }
@@ -0,0 +1,145 @@
1
+ {
2
+ "entity": {
3
+ "name": "Campaign",
4
+ "plural": "Campaigns",
5
+ "description": "Manage marketing campaigns and outreach"
6
+ },
7
+ "fields": {
8
+ "name": {
9
+ "label": "Campaign Name",
10
+ "description": "Campaign name or title",
11
+ "placeholder": "Enter campaign name..."
12
+ },
13
+ "type": {
14
+ "label": "Campaign Type",
15
+ "description": "Type of marketing campaign",
16
+ "placeholder": "Select type..."
17
+ },
18
+ "status": {
19
+ "label": "Status",
20
+ "description": "Current campaign status",
21
+ "placeholder": "Select status..."
22
+ },
23
+ "channel": {
24
+ "label": "Channel",
25
+ "description": "Marketing channel used",
26
+ "placeholder": "Select channel..."
27
+ },
28
+ "budget": {
29
+ "label": "Budget",
30
+ "description": "Campaign budget amount",
31
+ "placeholder": "0.00"
32
+ },
33
+ "spend": {
34
+ "label": "Amount Spent",
35
+ "description": "Amount already spent",
36
+ "placeholder": "0.00"
37
+ },
38
+ "targetAudience": {
39
+ "label": "Target Audience",
40
+ "description": "Target audience description",
41
+ "placeholder": "Enter target audience..."
42
+ },
43
+ "startDate": {
44
+ "label": "Start Date",
45
+ "description": "Campaign start date",
46
+ "placeholder": "Select date..."
47
+ },
48
+ "endDate": {
49
+ "label": "End Date",
50
+ "description": "Campaign end date",
51
+ "placeholder": "Select date..."
52
+ },
53
+ "impressions": {
54
+ "label": "Impressions",
55
+ "description": "Number of impressions",
56
+ "placeholder": "0"
57
+ },
58
+ "clicks": {
59
+ "label": "Clicks",
60
+ "description": "Number of clicks",
61
+ "placeholder": "0"
62
+ },
63
+ "conversions": {
64
+ "label": "Conversions",
65
+ "description": "Number of conversions",
66
+ "placeholder": "0"
67
+ },
68
+ "ctr": {
69
+ "label": "CTR (%)",
70
+ "description": "Click-through rate percentage",
71
+ "placeholder": "0.00"
72
+ },
73
+ "conversionRate": {
74
+ "label": "Conversion Rate (%)",
75
+ "description": "Conversion rate percentage",
76
+ "placeholder": "0.00"
77
+ },
78
+ "roi": {
79
+ "label": "ROI (%)",
80
+ "description": "Return on investment percentage",
81
+ "placeholder": "0.00"
82
+ },
83
+ "assignedTo": {
84
+ "label": "Campaign Manager",
85
+ "description": "User managing this campaign",
86
+ "placeholder": "Select user..."
87
+ }
88
+ },
89
+ "options": {
90
+ "type": {
91
+ "email": "Email Marketing",
92
+ "social": "Social Media",
93
+ "ppc": "Pay-Per-Click",
94
+ "content": "Content Marketing",
95
+ "webinar": "Webinar",
96
+ "event": "Event",
97
+ "referral": "Referral",
98
+ "affiliate": "Affiliate",
99
+ "retargeting": "Retargeting"
100
+ },
101
+ "status": {
102
+ "draft": "Draft",
103
+ "scheduled": "Scheduled",
104
+ "active": "Active",
105
+ "paused": "Paused",
106
+ "completed": "Completed",
107
+ "cancelled": "Cancelled"
108
+ },
109
+ "channel": {
110
+ "email": "Email",
111
+ "facebook": "Facebook",
112
+ "instagram": "Instagram",
113
+ "linkedin": "LinkedIn",
114
+ "twitter": "Twitter",
115
+ "google_ads": "Google Ads",
116
+ "youtube": "YouTube",
117
+ "tiktok": "TikTok",
118
+ "website": "Website",
119
+ "blog": "Blog",
120
+ "seo": "SEO",
121
+ "other": "Other"
122
+ }
123
+ },
124
+ "actions": {
125
+ "create": "Create Campaign",
126
+ "edit": "Edit Campaign",
127
+ "delete": "Delete Campaign",
128
+ "view": "View Campaign",
129
+ "list": "List Campaigns",
130
+ "launch": "Launch Campaign",
131
+ "pause": "Pause Campaign",
132
+ "resume": "Resume Campaign",
133
+ "duplicate": "Duplicate Campaign"
134
+ },
135
+ "messages": {
136
+ "created": "Campaign created successfully",
137
+ "updated": "Campaign updated successfully",
138
+ "deleted": "Campaign deleted successfully",
139
+ "launched": "Campaign launched successfully",
140
+ "paused": "Campaign paused successfully",
141
+ "resumed": "Campaign resumed successfully",
142
+ "notFound": "Campaign not found",
143
+ "error": "An error occurred while processing the campaign"
144
+ }
145
+ }