@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.
- package/CRM_PLAN.md +343 -0
- package/about.md +122 -0
- package/config/app.config.ts +185 -0
- package/config/billing.config.ts +187 -0
- package/config/dashboard.config.ts +372 -0
- package/config/dev.config.ts +55 -0
- package/config/features.config.ts +336 -0
- package/config/flows.config.ts +511 -0
- package/config/permissions.config.ts +297 -0
- package/config/theme.config.ts +111 -0
- package/entities/activities/activities.config.ts +61 -0
- package/entities/activities/activities.fields.ts +362 -0
- package/entities/activities/activities.service.ts +503 -0
- package/entities/activities/activities.types.ts +117 -0
- package/entities/activities/messages/en.json +123 -0
- package/entities/activities/messages/es.json +123 -0
- package/entities/activities/migrations/020_activities_table.sql +123 -0
- package/entities/activities/migrations/021_activities_metas.sql +114 -0
- package/entities/activities/migrations/022_activities_sample_data.sql +420 -0
- package/entities/campaigns/campaigns.config.ts +61 -0
- package/entities/campaigns/campaigns.fields.ts +413 -0
- package/entities/campaigns/campaigns.service.ts +426 -0
- package/entities/campaigns/campaigns.types.ts +124 -0
- package/entities/campaigns/messages/en.json +145 -0
- package/entities/campaigns/messages/es.json +145 -0
- package/entities/campaigns/migrations/001_campaigns_table.sql +127 -0
- package/entities/campaigns/migrations/002_campaigns_metas.sql +114 -0
- package/entities/campaigns/migrations/003_campaigns_sample_data.sql +364 -0
- package/entities/companies/companies.config.ts +61 -0
- package/entities/companies/companies.fields.ts +429 -0
- package/entities/companies/companies.service.ts +566 -0
- package/entities/companies/companies.types.ts +125 -0
- package/entities/companies/messages/en.json +146 -0
- package/entities/companies/messages/es.json +146 -0
- package/entities/companies/migrations/001_companies_table.sql +150 -0
- package/entities/companies/migrations/002_companies_metas.sql +114 -0
- package/entities/companies/migrations/003_companies_sample_data.sql +246 -0
- package/entities/contacts/contacts.config.ts +61 -0
- package/entities/contacts/contacts.fields.ts +359 -0
- package/entities/contacts/contacts.service.ts +509 -0
- package/entities/contacts/contacts.types.ts +108 -0
- package/entities/contacts/messages/en.json +117 -0
- package/entities/contacts/messages/es.json +117 -0
- package/entities/contacts/migrations/001_contacts_table.sql +134 -0
- package/entities/contacts/migrations/002_contacts_metas.sql +114 -0
- package/entities/contacts/migrations/003_contacts_sample_data.sql +421 -0
- package/entities/leads/leads.config.ts +61 -0
- package/entities/leads/leads.fields.ts +336 -0
- package/entities/leads/leads.service.ts +496 -0
- package/entities/leads/leads.types.ts +114 -0
- package/entities/leads/messages/en.json +132 -0
- package/entities/leads/messages/es.json +132 -0
- package/entities/leads/migrations/001_leads_table.sql +150 -0
- package/entities/leads/migrations/002_leads_metas.sql +120 -0
- package/entities/leads/migrations/003_leads_sample_data.sql +242 -0
- package/entities/notes/messages/en.json +114 -0
- package/entities/notes/messages/es.json +114 -0
- package/entities/notes/migrations/020_notes_table.sql +118 -0
- package/entities/notes/migrations/021_notes_metas.sql +114 -0
- package/entities/notes/migrations/022_notes_sample_data.sql +275 -0
- package/entities/notes/notes.config.ts +61 -0
- package/entities/notes/notes.fields.ts +283 -0
- package/entities/notes/notes.service.ts +320 -0
- package/entities/notes/notes.types.ts +102 -0
- package/entities/opportunities/messages/en.json +107 -0
- package/entities/opportunities/messages/es.json +107 -0
- package/entities/opportunities/migrations/010_opportunities_table.sql +145 -0
- package/entities/opportunities/migrations/011_opportunities_metas.sql +114 -0
- package/entities/opportunities/migrations/012_opportunities_sample_data.sql +438 -0
- package/entities/opportunities/opportunities.config.ts +61 -0
- package/entities/opportunities/opportunities.fields.ts +416 -0
- package/entities/opportunities/opportunities.service.ts +525 -0
- package/entities/opportunities/opportunities.types.ts +135 -0
- package/entities/pipelines/messages/en.json +115 -0
- package/entities/pipelines/messages/es.json +115 -0
- package/entities/pipelines/migrations/001_pipelines_table.sql +106 -0
- package/entities/pipelines/migrations/002_pipelines_metas.sql +114 -0
- package/entities/pipelines/migrations/003_pipelines_sample_data.sql +91 -0
- package/entities/pipelines/pipelines.config.ts +62 -0
- package/entities/pipelines/pipelines.fields.ts +193 -0
- package/entities/pipelines/pipelines.service.ts +383 -0
- package/entities/pipelines/pipelines.types.ts +78 -0
- package/entities/products/messages/en.json +135 -0
- package/entities/products/messages/es.json +135 -0
- package/entities/products/migrations/001_products_table.sql +117 -0
- package/entities/products/migrations/002_products_metas.sql +114 -0
- package/entities/products/migrations/003_products_sample_data.sql +247 -0
- package/entities/products/products.config.ts +62 -0
- package/entities/products/products.fields.ts +361 -0
- package/entities/products/products.service.ts +437 -0
- package/entities/products/products.types.ts +125 -0
- package/lib/crm-constants.ts +77 -0
- package/lib/crm-utils.ts +185 -0
- package/lib/selectors.ts +333 -0
- package/messages/en.json +131 -0
- package/messages/es.json +131 -0
- package/migrations/999_theme_sample_data.sql +473 -0
- package/package.json +18 -0
- package/pendings.md +205 -0
- package/permissions-matrix.md +216 -0
- package/styles/components.css +414 -0
- package/styles/crm-theme.css +358 -0
- package/styles/globals.css +576 -0
- package/styles/variables.css +111 -0
- package/templates/dashboard/(main)/activities/components/ActivityCard.tsx +169 -0
- package/templates/dashboard/(main)/activities/components/ActivityTimeline.tsx +165 -0
- package/templates/dashboard/(main)/activities/page.tsx +297 -0
- package/templates/dashboard/(main)/campaigns/page.tsx +373 -0
- package/templates/dashboard/(main)/companies/page.tsx +296 -0
- package/templates/dashboard/(main)/contacts/page.tsx +347 -0
- package/templates/dashboard/(main)/layout.tsx +98 -0
- package/templates/dashboard/(main)/leads/page.tsx +335 -0
- package/templates/dashboard/(main)/opportunities/[id]/edit/page.tsx +95 -0
- package/templates/dashboard/(main)/opportunities/create/page.tsx +94 -0
- package/templates/dashboard/(main)/opportunities/page.tsx +350 -0
- package/templates/dashboard/(main)/pipelines/[id]/edit/page.tsx +95 -0
- package/templates/dashboard/(main)/pipelines/[id]/page.tsx +143 -0
- package/templates/dashboard/(main)/pipelines/create/page.tsx +94 -0
- package/templates/dashboard/(main)/pipelines/page.tsx +234 -0
- package/templates/dashboard/(main)/products/[id]/edit/page.tsx +97 -0
- package/templates/dashboard/(main)/products/[id]/page.tsx +509 -0
- package/templates/dashboard/(main)/products/create/page.tsx +96 -0
- package/templates/dashboard/(main)/products/page.tsx +308 -0
- package/templates/shared/ActionButtons.tsx +41 -0
- package/templates/shared/CRMDashboard.tsx +519 -0
- package/templates/shared/CRMDataTable.tsx +441 -0
- package/templates/shared/CRMMetricCard.tsx +76 -0
- package/templates/shared/CRMMobileNav.tsx +172 -0
- package/templates/shared/CRMSidebar.tsx +346 -0
- package/templates/shared/CRMTopBar.tsx +265 -0
- package/templates/shared/DealCard.tsx +123 -0
- package/templates/shared/EntityCard.tsx +58 -0
- package/templates/shared/OpportunityForm.tsx +649 -0
- package/templates/shared/PipelineForm.tsx +367 -0
- package/templates/shared/PipelineKanban.tsx +194 -0
- package/templates/shared/QuickFilters.tsx +47 -0
- package/templates/shared/StageColumn.tsx +175 -0
- package/templates/shared/StageSelect.tsx +177 -0
- package/templates/shared/StagesRepeater.tsx +317 -0
- 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
|
+
}
|