@contractspec/example.crm-pipeline 3.7.17 → 3.7.18

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 (135) hide show
  1. package/.turbo/turbo-build.log +135 -135
  2. package/CHANGELOG.md +20 -0
  3. package/dist/browser/crm-pipeline.feature.js +1 -82
  4. package/dist/browser/deal/deal.enum.js +1 -18
  5. package/dist/browser/deal/deal.operation.js +1 -396
  6. package/dist/browser/deal/deal.schema.js +1 -141
  7. package/dist/browser/deal/deal.test-spec.js +1 -58
  8. package/dist/browser/deal/index.js +1 -408
  9. package/dist/browser/docs/crm-pipeline.docblock.js +5 -49
  10. package/dist/browser/docs/index.js +5 -49
  11. package/dist/browser/entities/company.entity.js +1 -52
  12. package/dist/browser/entities/contact.entity.js +1 -66
  13. package/dist/browser/entities/deal.entity.js +1 -107
  14. package/dist/browser/entities/index.js +1 -343
  15. package/dist/browser/entities/task.entity.js +1 -99
  16. package/dist/browser/events/contact.event.js +1 -31
  17. package/dist/browser/events/deal.event.js +1 -101
  18. package/dist/browser/events/index.js +1 -158
  19. package/dist/browser/events/task.event.js +1 -28
  20. package/dist/browser/example.js +1 -39
  21. package/dist/browser/handlers/crm.handlers.js +2 -171
  22. package/dist/browser/handlers/deal.handlers.js +1 -293
  23. package/dist/browser/handlers/index.js +2 -467
  24. package/dist/browser/handlers/mock-data.js +1 -165
  25. package/dist/browser/index.js +8 -3461
  26. package/dist/browser/operations/index.js +1 -407
  27. package/dist/browser/presentations/dashboard.presentation.js +1 -55
  28. package/dist/browser/presentations/index.js +1 -290
  29. package/dist/browser/presentations/pipeline.presentation.js +1 -236
  30. package/dist/browser/seeders/index.js +1 -22
  31. package/dist/browser/ui/CrmDashboard.js +1 -1547
  32. package/dist/browser/ui/CrmDealCard.js +1 -50
  33. package/dist/browser/ui/CrmPipelineBoard.js +1 -160
  34. package/dist/browser/ui/hooks/index.js +1 -197
  35. package/dist/browser/ui/hooks/useDealList.js +1 -95
  36. package/dist/browser/ui/hooks/useDealMutations.js +1 -100
  37. package/dist/browser/ui/index.js +4 -2205
  38. package/dist/browser/ui/modals/CreateDealModal.js +1 -211
  39. package/dist/browser/ui/modals/DealActionsModal.js +1 -428
  40. package/dist/browser/ui/modals/index.js +1 -638
  41. package/dist/browser/ui/overlays/demo-overlays.js +1 -55
  42. package/dist/browser/ui/overlays/index.js +1 -55
  43. package/dist/browser/ui/renderers/index.js +4 -849
  44. package/dist/browser/ui/renderers/pipeline.markdown.js +4 -575
  45. package/dist/browser/ui/renderers/pipeline.renderer.js +1 -275
  46. package/dist/browser/ui/tables/DealListTab.js +1 -390
  47. package/dist/crm-pipeline.feature.js +1 -82
  48. package/dist/deal/deal.enum.js +1 -18
  49. package/dist/deal/deal.operation.js +1 -396
  50. package/dist/deal/deal.schema.js +1 -141
  51. package/dist/deal/deal.test-spec.js +1 -58
  52. package/dist/deal/index.js +1 -408
  53. package/dist/docs/crm-pipeline.docblock.js +5 -49
  54. package/dist/docs/index.js +5 -49
  55. package/dist/entities/company.entity.js +1 -52
  56. package/dist/entities/contact.entity.js +1 -66
  57. package/dist/entities/deal.entity.js +1 -107
  58. package/dist/entities/index.js +1 -343
  59. package/dist/entities/task.entity.js +1 -99
  60. package/dist/events/contact.event.js +1 -31
  61. package/dist/events/deal.event.js +1 -101
  62. package/dist/events/index.js +1 -158
  63. package/dist/events/task.event.js +1 -28
  64. package/dist/example.js +1 -39
  65. package/dist/handlers/crm.handlers.js +2 -171
  66. package/dist/handlers/deal.handlers.js +1 -293
  67. package/dist/handlers/index.js +2 -467
  68. package/dist/handlers/mock-data.js +1 -165
  69. package/dist/index.js +8 -3461
  70. package/dist/node/crm-pipeline.feature.js +1 -82
  71. package/dist/node/deal/deal.enum.js +1 -18
  72. package/dist/node/deal/deal.operation.js +1 -396
  73. package/dist/node/deal/deal.schema.js +1 -141
  74. package/dist/node/deal/deal.test-spec.js +1 -58
  75. package/dist/node/deal/index.js +1 -408
  76. package/dist/node/docs/crm-pipeline.docblock.js +5 -49
  77. package/dist/node/docs/index.js +5 -49
  78. package/dist/node/entities/company.entity.js +1 -52
  79. package/dist/node/entities/contact.entity.js +1 -66
  80. package/dist/node/entities/deal.entity.js +1 -107
  81. package/dist/node/entities/index.js +1 -343
  82. package/dist/node/entities/task.entity.js +1 -99
  83. package/dist/node/events/contact.event.js +1 -31
  84. package/dist/node/events/deal.event.js +1 -101
  85. package/dist/node/events/index.js +1 -158
  86. package/dist/node/events/task.event.js +1 -28
  87. package/dist/node/example.js +1 -39
  88. package/dist/node/handlers/crm.handlers.js +2 -171
  89. package/dist/node/handlers/deal.handlers.js +1 -293
  90. package/dist/node/handlers/index.js +2 -467
  91. package/dist/node/handlers/mock-data.js +1 -165
  92. package/dist/node/index.js +8 -3461
  93. package/dist/node/operations/index.js +1 -407
  94. package/dist/node/presentations/dashboard.presentation.js +1 -55
  95. package/dist/node/presentations/index.js +1 -290
  96. package/dist/node/presentations/pipeline.presentation.js +1 -236
  97. package/dist/node/seeders/index.js +1 -22
  98. package/dist/node/ui/CrmDashboard.js +1 -1547
  99. package/dist/node/ui/CrmDealCard.js +1 -50
  100. package/dist/node/ui/CrmPipelineBoard.js +1 -160
  101. package/dist/node/ui/hooks/index.js +1 -197
  102. package/dist/node/ui/hooks/useDealList.js +1 -95
  103. package/dist/node/ui/hooks/useDealMutations.js +1 -100
  104. package/dist/node/ui/index.js +4 -2205
  105. package/dist/node/ui/modals/CreateDealModal.js +1 -211
  106. package/dist/node/ui/modals/DealActionsModal.js +1 -428
  107. package/dist/node/ui/modals/index.js +1 -638
  108. package/dist/node/ui/overlays/demo-overlays.js +1 -55
  109. package/dist/node/ui/overlays/index.js +1 -55
  110. package/dist/node/ui/renderers/index.js +4 -849
  111. package/dist/node/ui/renderers/pipeline.markdown.js +4 -575
  112. package/dist/node/ui/renderers/pipeline.renderer.js +1 -275
  113. package/dist/node/ui/tables/DealListTab.js +1 -390
  114. package/dist/operations/index.js +1 -407
  115. package/dist/presentations/dashboard.presentation.js +1 -55
  116. package/dist/presentations/index.js +1 -290
  117. package/dist/presentations/pipeline.presentation.js +1 -236
  118. package/dist/seeders/index.js +1 -22
  119. package/dist/ui/CrmDashboard.js +1 -1547
  120. package/dist/ui/CrmDealCard.js +1 -50
  121. package/dist/ui/CrmPipelineBoard.js +1 -160
  122. package/dist/ui/hooks/index.js +1 -197
  123. package/dist/ui/hooks/useDealList.js +1 -95
  124. package/dist/ui/hooks/useDealMutations.js +1 -100
  125. package/dist/ui/index.js +4 -2205
  126. package/dist/ui/modals/CreateDealModal.js +1 -211
  127. package/dist/ui/modals/DealActionsModal.js +1 -428
  128. package/dist/ui/modals/index.js +1 -638
  129. package/dist/ui/overlays/demo-overlays.js +1 -55
  130. package/dist/ui/overlays/index.js +1 -55
  131. package/dist/ui/renderers/index.js +4 -849
  132. package/dist/ui/renderers/pipeline.markdown.js +4 -575
  133. package/dist/ui/renderers/pipeline.renderer.js +1 -275
  134. package/dist/ui/tables/DealListTab.js +1 -390
  135. package/package.json +13 -13
package/dist/ui/index.js CHANGED
@@ -1,2206 +1,5 @@
1
1
  // @bun
2
- // src/handlers/crm.handlers.ts
3
- import { web } from "@contractspec/lib.runtime-sandbox";
4
- var { generateId } = web;
5
- function rowToDeal(row) {
6
- return {
7
- id: row.id,
8
- projectId: row.projectId,
9
- name: row.name,
10
- value: row.value,
11
- currency: row.currency,
12
- pipelineId: row.pipelineId,
13
- stageId: row.stageId,
14
- status: row.status,
15
- contactId: row.contactId ?? undefined,
16
- companyId: row.companyId ?? undefined,
17
- ownerId: row.ownerId,
18
- expectedCloseDate: row.expectedCloseDate ? new Date(row.expectedCloseDate) : undefined,
19
- wonSource: row.wonSource ?? undefined,
20
- lostReason: row.lostReason ?? undefined,
21
- notes: row.notes ?? undefined,
22
- createdAt: new Date(row.createdAt),
23
- updatedAt: new Date(row.updatedAt)
24
- };
25
- }
26
- var DEAL_SORT_COLUMNS = {
27
- name: "name",
28
- value: "value",
29
- status: "status",
30
- expectedCloseDate: "expectedCloseDate",
31
- updatedAt: "updatedAt"
32
- };
33
- function createCrmHandlers(db) {
34
- async function listDeals(input) {
35
- const {
36
- projectId,
37
- pipelineId,
38
- stageId,
39
- status,
40
- ownerId,
41
- search,
42
- limit = 20,
43
- offset = 0,
44
- sortBy = "value",
45
- sortDirection = "desc"
46
- } = input;
47
- let whereClause = "WHERE projectId = ?";
48
- const params = [projectId];
49
- if (pipelineId) {
50
- whereClause += " AND pipelineId = ?";
51
- params.push(pipelineId);
52
- }
53
- if (stageId) {
54
- whereClause += " AND stageId = ?";
55
- params.push(stageId);
56
- }
57
- if (status && status !== "all") {
58
- whereClause += " AND status = ?";
59
- params.push(status);
60
- }
61
- if (ownerId) {
62
- whereClause += " AND ownerId = ?";
63
- params.push(ownerId);
64
- }
65
- if (search) {
66
- whereClause += " AND name LIKE ?";
67
- params.push(`%${search}%`);
68
- }
69
- const countResult = (await db.query(`SELECT COUNT(*) as count FROM crm_deal ${whereClause}`, params)).rows;
70
- const total = countResult[0]?.count ?? 0;
71
- const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
72
- const totalValue = valueResult[0]?.total ?? 0;
73
- const orderByColumn = DEAL_SORT_COLUMNS[sortBy] ?? DEAL_SORT_COLUMNS.value;
74
- const orderByDirection = sortDirection === "asc" ? "ASC" : "DESC";
75
- const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY ${orderByColumn} ${orderByDirection} LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
76
- return {
77
- deals: dealRows.map(rowToDeal),
78
- total,
79
- totalValue
80
- };
81
- }
82
- async function createDeal(input, context) {
83
- const id = generateId("deal");
84
- const now = new Date().toISOString();
85
- await db.execute(`INSERT INTO crm_deal (id, projectId, pipelineId, stageId, name, value, currency, status, contactId, companyId, ownerId, expectedCloseDate, createdAt, updatedAt)
86
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
87
- id,
88
- context.projectId,
89
- input.pipelineId,
90
- input.stageId,
91
- input.name,
92
- input.value,
93
- input.currency ?? "USD",
94
- "OPEN",
95
- input.contactId ?? null,
96
- input.companyId ?? null,
97
- context.ownerId,
98
- input.expectedCloseDate?.toISOString() ?? null,
99
- now,
100
- now
101
- ]);
102
- const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [id])).rows;
103
- if (!rows[0]) {
104
- throw new Error("Failed to create deal");
105
- }
106
- return rowToDeal(rows[0]);
107
- }
108
- async function moveDeal(input) {
109
- const now = new Date().toISOString();
110
- const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
111
- if (!existing[0]) {
112
- throw new Error("NOT_FOUND");
113
- }
114
- const stage = (await db.query(`SELECT * FROM crm_stage WHERE id = ?`, [input.stageId])).rows;
115
- if (!stage[0]) {
116
- throw new Error("INVALID_STAGE");
117
- }
118
- await db.execute(`UPDATE crm_deal SET stageId = ?, updatedAt = ? WHERE id = ?`, [input.stageId, now, input.dealId]);
119
- const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
120
- return rowToDeal(rows[0]);
121
- }
122
- async function winDeal(input) {
123
- const now = new Date().toISOString();
124
- const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
125
- if (!existing[0]) {
126
- throw new Error("NOT_FOUND");
127
- }
128
- await db.execute(`UPDATE crm_deal SET status = 'WON', wonSource = ?, notes = ?, updatedAt = ? WHERE id = ?`, [input.wonSource ?? null, input.notes ?? null, now, input.dealId]);
129
- const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
130
- return rowToDeal(rows[0]);
131
- }
132
- async function loseDeal(input) {
133
- const now = new Date().toISOString();
134
- const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
135
- if (!existing[0]) {
136
- throw new Error("NOT_FOUND");
137
- }
138
- await db.execute(`UPDATE crm_deal SET status = 'LOST', lostReason = ?, notes = ?, updatedAt = ? WHERE id = ?`, [input.lostReason, input.notes ?? null, now, input.dealId]);
139
- const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
140
- return rowToDeal(rows[0]);
141
- }
142
- async function getDealsByStage(input) {
143
- const deals = (await db.query(`SELECT * FROM crm_deal WHERE projectId = ? AND pipelineId = ? AND status = 'OPEN' ORDER BY value DESC`, [input.projectId, input.pipelineId])).rows;
144
- const stages = (await db.query(`SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`, [input.pipelineId])).rows;
145
- const grouped = {};
146
- for (const stage of stages) {
147
- grouped[stage.id] = deals.filter((d) => d.stageId === stage.id).map(rowToDeal);
148
- }
149
- return grouped;
150
- }
151
- async function getPipelineStages(input) {
152
- const rows = (await db.query(`SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`, [input.pipelineId])).rows;
153
- return rows.map((row) => ({
154
- id: row.id,
155
- pipelineId: row.pipelineId,
156
- name: row.name,
157
- position: row.position
158
- }));
159
- }
160
- return {
161
- listDeals,
162
- createDeal,
163
- moveDeal,
164
- winDeal,
165
- loseDeal,
166
- getDealsByStage,
167
- getPipelineStages
168
- };
169
- }
170
-
171
- // src/handlers/mock-data.ts
172
- var MOCK_STAGES = [
173
- { id: "stage-1", name: "Lead", position: 1, pipelineId: "pipeline-1" },
174
- { id: "stage-2", name: "Qualified", position: 2, pipelineId: "pipeline-1" },
175
- { id: "stage-3", name: "Proposal", position: 3, pipelineId: "pipeline-1" },
176
- { id: "stage-4", name: "Negotiation", position: 4, pipelineId: "pipeline-1" },
177
- { id: "stage-5", name: "Closed", position: 5, pipelineId: "pipeline-1" }
178
- ];
179
- var MOCK_DEALS = [
180
- {
181
- id: "deal-1",
182
- name: "Enterprise License - Acme Corp",
183
- value: 75000,
184
- currency: "USD",
185
- pipelineId: "pipeline-1",
186
- stageId: "stage-3",
187
- status: "OPEN",
188
- contactId: "contact-1",
189
- companyId: "company-1",
190
- ownerId: "user-1",
191
- expectedCloseDate: new Date("2024-05-15T00:00:00Z"),
192
- createdAt: new Date("2024-02-01T10:00:00Z"),
193
- updatedAt: new Date("2024-04-10T14:30:00Z")
194
- },
195
- {
196
- id: "deal-2",
197
- name: "Startup Plan - TechStart Inc",
198
- value: 12000,
199
- currency: "USD",
200
- pipelineId: "pipeline-1",
201
- stageId: "stage-2",
202
- status: "OPEN",
203
- contactId: "contact-2",
204
- companyId: "company-2",
205
- ownerId: "user-2",
206
- expectedCloseDate: new Date("2024-04-30T00:00:00Z"),
207
- createdAt: new Date("2024-03-15T09:00:00Z"),
208
- updatedAt: new Date("2024-04-08T11:15:00Z")
209
- },
210
- {
211
- id: "deal-3",
212
- name: "Professional Services - Global Ltd",
213
- value: 45000,
214
- currency: "USD",
215
- pipelineId: "pipeline-1",
216
- stageId: "stage-4",
217
- status: "OPEN",
218
- contactId: "contact-3",
219
- companyId: "company-3",
220
- ownerId: "user-1",
221
- expectedCloseDate: new Date("2024-04-20T00:00:00Z"),
222
- createdAt: new Date("2024-01-20T08:00:00Z"),
223
- updatedAt: new Date("2024-04-12T16:45:00Z")
224
- },
225
- {
226
- id: "deal-4",
227
- name: "Annual Contract - SmallBiz Co",
228
- value: 8500,
229
- currency: "USD",
230
- pipelineId: "pipeline-1",
231
- stageId: "stage-1",
232
- status: "OPEN",
233
- contactId: "contact-4",
234
- companyId: "company-4",
235
- ownerId: "user-3",
236
- createdAt: new Date("2024-04-05T12:00:00Z"),
237
- updatedAt: new Date("2024-04-05T12:00:00Z")
238
- },
239
- {
240
- id: "deal-5",
241
- name: "Custom Integration - MegaCorp",
242
- value: 125000,
243
- currency: "USD",
244
- pipelineId: "pipeline-1",
245
- stageId: "stage-5",
246
- status: "WON",
247
- contactId: "contact-5",
248
- companyId: "company-5",
249
- ownerId: "user-1",
250
- expectedCloseDate: new Date("2024-03-31T00:00:00Z"),
251
- createdAt: new Date("2023-11-10T10:00:00Z"),
252
- updatedAt: new Date("2024-03-28T09:00:00Z")
253
- },
254
- {
255
- id: "deal-6",
256
- name: "Pilot Project - NewCo",
257
- value: 5000,
258
- currency: "USD",
259
- pipelineId: "pipeline-1",
260
- stageId: "stage-2",
261
- status: "LOST",
262
- contactId: "contact-6",
263
- companyId: "company-6",
264
- ownerId: "user-2",
265
- createdAt: new Date("2024-01-15T14:00:00Z"),
266
- updatedAt: new Date("2024-02-28T10:30:00Z")
267
- }
268
- ];
269
- var MOCK_COMPANIES = [
270
- {
271
- id: "company-1",
272
- name: "Acme Corporation",
273
- domain: "acme.com",
274
- industry: "Technology",
275
- size: "1000-5000",
276
- website: "https://acme.com",
277
- createdAt: new Date("2024-01-01T00:00:00Z")
278
- },
279
- {
280
- id: "company-2",
281
- name: "TechStart Inc",
282
- domain: "techstart.io",
283
- industry: "Software",
284
- size: "10-50",
285
- website: "https://techstart.io",
286
- createdAt: new Date("2024-02-15T00:00:00Z")
287
- },
288
- {
289
- id: "company-3",
290
- name: "Global Ltd",
291
- domain: "global.com",
292
- industry: "Consulting",
293
- size: "500-1000",
294
- website: "https://global.com",
295
- createdAt: new Date("2023-12-01T00:00:00Z")
296
- }
297
- ];
298
- var MOCK_CONTACTS = [
299
- {
300
- id: "contact-1",
301
- firstName: "John",
302
- lastName: "Smith",
303
- email: "john.smith@acme.com",
304
- phone: "+1-555-0101",
305
- title: "VP of Engineering",
306
- companyId: "company-1",
307
- createdAt: new Date("2024-01-05T00:00:00Z")
308
- },
309
- {
310
- id: "contact-2",
311
- firstName: "Sarah",
312
- lastName: "Johnson",
313
- email: "sarah@techstart.io",
314
- phone: "+1-555-0102",
315
- title: "CEO",
316
- companyId: "company-2",
317
- createdAt: new Date("2024-02-20T00:00:00Z")
318
- },
319
- {
320
- id: "contact-3",
321
- firstName: "Michael",
322
- lastName: "Brown",
323
- email: "michael.brown@global.com",
324
- phone: "+1-555-0103",
325
- title: "CTO",
326
- companyId: "company-3",
327
- createdAt: new Date("2023-12-10T00:00:00Z")
328
- }
329
- ];
330
-
331
- // src/handlers/deal.handlers.ts
332
- async function mockListDealsHandler(input) {
333
- const {
334
- pipelineId,
335
- stageId,
336
- status,
337
- ownerId,
338
- search,
339
- limit = 20,
340
- offset = 0
341
- } = input;
342
- let filtered = [...MOCK_DEALS];
343
- if (pipelineId) {
344
- filtered = filtered.filter((d) => d.pipelineId === pipelineId);
345
- }
346
- if (stageId) {
347
- filtered = filtered.filter((d) => d.stageId === stageId);
348
- }
349
- if (status && status !== "all") {
350
- filtered = filtered.filter((d) => d.status === status);
351
- }
352
- if (ownerId) {
353
- filtered = filtered.filter((d) => d.ownerId === ownerId);
354
- }
355
- if (search) {
356
- const q = search.toLowerCase();
357
- filtered = filtered.filter((d) => d.name.toLowerCase().includes(q));
358
- }
359
- filtered.sort((a, b) => b.value - a.value);
360
- const total = filtered.length;
361
- const totalValue = filtered.reduce((sum, d) => sum + d.value, 0);
362
- const deals = filtered.slice(offset, offset + limit);
363
- return {
364
- deals,
365
- total,
366
- totalValue
367
- };
368
- }
369
- async function mockCreateDealHandler(input, context) {
370
- const now = new Date;
371
- const deal = {
372
- id: `deal-${Date.now()}`,
373
- name: input.name,
374
- value: input.value,
375
- currency: input.currency ?? "USD",
376
- pipelineId: input.pipelineId,
377
- stageId: input.stageId,
378
- status: "OPEN",
379
- contactId: input.contactId,
380
- companyId: input.companyId,
381
- ownerId: context.ownerId,
382
- expectedCloseDate: input.expectedCloseDate,
383
- createdAt: now,
384
- updatedAt: now
385
- };
386
- MOCK_DEALS.push(deal);
387
- return deal;
388
- }
389
- async function mockMoveDealHandler(input) {
390
- const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
391
- if (dealIndex === -1) {
392
- throw new Error("NOT_FOUND");
393
- }
394
- const deal = MOCK_DEALS[dealIndex];
395
- if (!deal) {
396
- throw new Error("NOT_FOUND");
397
- }
398
- const stage = MOCK_STAGES.find((s) => s.id === input.stageId);
399
- if (!stage) {
400
- throw new Error("INVALID_STAGE");
401
- }
402
- const updatedDeal = {
403
- ...deal,
404
- stageId: input.stageId,
405
- updatedAt: new Date
406
- };
407
- MOCK_DEALS[dealIndex] = updatedDeal;
408
- return updatedDeal;
409
- }
410
- async function mockWinDealHandler(input) {
411
- const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
412
- if (dealIndex === -1) {
413
- throw new Error("NOT_FOUND");
414
- }
415
- const deal = MOCK_DEALS[dealIndex];
416
- if (!deal) {
417
- throw new Error("NOT_FOUND");
418
- }
419
- const updatedDeal = {
420
- ...deal,
421
- status: "WON",
422
- updatedAt: new Date
423
- };
424
- MOCK_DEALS[dealIndex] = updatedDeal;
425
- return updatedDeal;
426
- }
427
- async function mockLoseDealHandler(input) {
428
- const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
429
- if (dealIndex === -1) {
430
- throw new Error("NOT_FOUND");
431
- }
432
- const deal = MOCK_DEALS[dealIndex];
433
- if (!deal) {
434
- throw new Error("NOT_FOUND");
435
- }
436
- const updatedDeal = {
437
- ...deal,
438
- status: "LOST",
439
- updatedAt: new Date
440
- };
441
- MOCK_DEALS[dealIndex] = updatedDeal;
442
- return updatedDeal;
443
- }
444
- async function mockGetDealsByStageHandler(input) {
445
- const deals = MOCK_DEALS.filter((d) => d.pipelineId === input.pipelineId && d.status === "OPEN");
446
- const grouped = {};
447
- for (const stage of MOCK_STAGES) {
448
- grouped[stage.id] = deals.filter((d) => d.stageId === stage.id);
449
- }
450
- return grouped;
451
- }
452
- async function mockGetPipelineStagesHandler(input) {
453
- return MOCK_STAGES.filter((s) => s.pipelineId === input.pipelineId);
454
- }
455
- // src/ui/CrmDealCard.tsx
456
- import { jsxDEV } from "react/jsx-dev-runtime";
457
- "use client";
458
- function formatCurrency(value, currency) {
459
- return new Intl.NumberFormat("en-US", {
460
- style: "currency",
461
- currency,
462
- minimumFractionDigits: 0,
463
- maximumFractionDigits: 0
464
- }).format(value);
465
- }
466
- function CrmDealCard({ deal, onClick }) {
467
- const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
468
- return /* @__PURE__ */ jsxDEV("div", {
469
- onClick,
470
- className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
471
- role: "button",
472
- tabIndex: 0,
473
- onKeyDown: (e) => {
474
- if (e.key === "Enter" || e.key === " ")
475
- onClick?.();
476
- },
477
- children: [
478
- /* @__PURE__ */ jsxDEV("h4", {
479
- className: "font-medium leading-snug",
480
- children: deal.name
481
- }, undefined, false, undefined, this),
482
- /* @__PURE__ */ jsxDEV("div", {
483
- className: "mt-2 font-semibold text-lg text-primary",
484
- children: formatCurrency(deal.value, deal.currency)
485
- }, undefined, false, undefined, this),
486
- /* @__PURE__ */ jsxDEV("div", {
487
- className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
488
- children: [
489
- daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
490
- className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
491
- children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
492
- }, undefined, false, undefined, this),
493
- /* @__PURE__ */ jsxDEV("span", {
494
- className: `rounded px-1.5 py-0.5 font-medium text-xs ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
495
- children: deal.status
496
- }, undefined, false, undefined, this)
497
- ]
498
- }, undefined, true, undefined, this)
499
- ]
500
- }, undefined, true, undefined, this);
501
- }
502
-
503
- // src/ui/CrmPipelineBoard.tsx
504
- import { useState } from "react";
505
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
506
- "use client";
507
- function formatCurrency2(value) {
508
- if (value >= 1e6)
509
- return `$${(value / 1e6).toFixed(1)}M`;
510
- if (value >= 1000)
511
- return `$${(value / 1000).toFixed(0)}K`;
512
- return `$${value}`;
513
- }
514
- function CrmPipelineBoard({
515
- dealsByStage,
516
- stages,
517
- onDealClick,
518
- onDealMove
519
- }) {
520
- const [quickMoveOpen, setQuickMoveOpen] = useState(null);
521
- const sortedStages = [...stages].sort((a, b) => a.position - b.position);
522
- const handleQuickMove = (dealId, toStageId) => {
523
- onDealMove?.(dealId, toStageId);
524
- setQuickMoveOpen(null);
525
- };
526
- return /* @__PURE__ */ jsxDEV2("div", {
527
- className: "flex gap-4 overflow-x-auto pb-4",
528
- children: sortedStages.map((stage) => {
529
- const deals = dealsByStage[stage.id] ?? [];
530
- const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
531
- return /* @__PURE__ */ jsxDEV2("div", {
532
- className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
533
- children: [
534
- /* @__PURE__ */ jsxDEV2("div", {
535
- className: "flex items-center justify-between border-border border-b px-3 py-2",
536
- children: [
537
- /* @__PURE__ */ jsxDEV2("div", {
538
- children: [
539
- /* @__PURE__ */ jsxDEV2("h3", {
540
- className: "font-medium",
541
- children: stage.name
542
- }, undefined, false, undefined, this),
543
- /* @__PURE__ */ jsxDEV2("p", {
544
- className: "text-muted-foreground text-xs",
545
- children: [
546
- deals.length,
547
- " deals \xB7 ",
548
- formatCurrency2(stageValue)
549
- ]
550
- }, undefined, true, undefined, this)
551
- ]
552
- }, undefined, true, undefined, this),
553
- /* @__PURE__ */ jsxDEV2("span", {
554
- className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
555
- children: deals.length
556
- }, undefined, false, undefined, this)
557
- ]
558
- }, undefined, true, undefined, this),
559
- /* @__PURE__ */ jsxDEV2("div", {
560
- className: "flex flex-1 flex-col gap-2 p-2",
561
- children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
562
- className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
563
- children: "No deals"
564
- }, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
565
- className: "group relative",
566
- children: [
567
- /* @__PURE__ */ jsxDEV2(CrmDealCard, {
568
- deal,
569
- onClick: () => onDealClick?.(deal.id)
570
- }, undefined, false, undefined, this),
571
- deal.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
572
- className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
573
- children: [
574
- /* @__PURE__ */ jsxDEV2("button", {
575
- type: "button",
576
- onClick: (e) => {
577
- e.stopPropagation();
578
- setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
579
- },
580
- className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
581
- title: "Quick move",
582
- children: "\u27A1\uFE0F"
583
- }, undefined, false, undefined, this),
584
- quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
585
- className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
586
- children: [
587
- /* @__PURE__ */ jsxDEV2("p", {
588
- className: "px-3 py-1 font-medium text-muted-foreground text-xs",
589
- children: "Move to:"
590
- }, undefined, false, undefined, this),
591
- sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
592
- type: "button",
593
- onClick: (e) => {
594
- e.stopPropagation();
595
- handleQuickMove(deal.id, s.id);
596
- },
597
- className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
598
- children: s.name
599
- }, s.id, false, undefined, this))
600
- ]
601
- }, undefined, true, undefined, this)
602
- ]
603
- }, undefined, true, undefined, this)
604
- ]
605
- }, deal.id, true, undefined, this))
606
- }, undefined, false, undefined, this)
607
- ]
608
- }, stage.id, true, undefined, this);
609
- })
610
- }, undefined, false, undefined, this);
611
- }
612
-
613
- // src/ui/hooks/useDealList.ts
614
- import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
615
- import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
616
- "use client";
617
- function useDealList(options = {}) {
618
- const { handlers, projectId } = useTemplateRuntime();
619
- const { crm: crm2 } = handlers;
620
- const [data, setData] = useState2(null);
621
- const [dealsByStage, setDealsByStage] = useState2({});
622
- const [stages, setStages] = useState2([]);
623
- const [loading, setLoading] = useState2(true);
624
- const [error, setError] = useState2(null);
625
- const [internalPage, setInternalPage] = useState2(0);
626
- const pipelineId = options.pipelineId ?? "pipeline-1";
627
- const pageIndex = options.pageIndex ?? internalPage;
628
- const pageSize = options.pageSize ?? options.limit ?? 50;
629
- const [sort] = options.sorting ?? [];
630
- const sortBy = sort?.id;
631
- const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
632
- const fetchData = useCallback(async () => {
633
- setLoading(true);
634
- setError(null);
635
- try {
636
- const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
637
- crm2.listDeals({
638
- projectId,
639
- pipelineId,
640
- stageId: options.stageId,
641
- status: options.status === "all" ? undefined : options.status,
642
- search: options.search,
643
- limit: pageSize,
644
- offset: pageIndex * pageSize,
645
- sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
646
- sortDirection
647
- }),
648
- crm2.getDealsByStage({ projectId, pipelineId }),
649
- crm2.getPipelineStages({ pipelineId })
650
- ]);
651
- setData(dealsResult);
652
- setDealsByStage(stageDealsResult);
653
- setStages(stagesResult);
654
- } catch (err) {
655
- setError(err instanceof Error ? err : new Error("Unknown error"));
656
- } finally {
657
- setLoading(false);
658
- }
659
- }, [
660
- crm2,
661
- projectId,
662
- pipelineId,
663
- options.stageId,
664
- options.status,
665
- options.search,
666
- pageIndex,
667
- pageSize,
668
- sortBy,
669
- sortDirection
670
- ]);
671
- useEffect(() => {
672
- fetchData();
673
- }, [fetchData]);
674
- const stats = useMemo(() => {
675
- if (!data)
676
- return null;
677
- const open = data.deals.filter((d) => d.status === "OPEN");
678
- const won = data.deals.filter((d) => d.status === "WON");
679
- const lost = data.deals.filter((d) => d.status === "LOST");
680
- return {
681
- total: data.total,
682
- totalValue: data.totalValue,
683
- openCount: open.length,
684
- openValue: open.reduce((sum, d) => sum + d.value, 0),
685
- wonCount: won.length,
686
- wonValue: won.reduce((sum, d) => sum + d.value, 0),
687
- lostCount: lost.length
688
- };
689
- }, [data]);
690
- return {
691
- data,
692
- dealsByStage,
693
- stages,
694
- loading,
695
- error,
696
- stats,
697
- page: pageIndex + 1,
698
- pageIndex,
699
- pageSize,
700
- refetch: fetchData,
701
- nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
702
- prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
703
- };
704
- }
705
-
706
- // src/ui/hooks/useDealMutations.ts
707
- import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
708
- import { useCallback as useCallback2, useState as useState3 } from "react";
709
- function useDealMutations(options = {}) {
710
- const { handlers, projectId } = useTemplateRuntime2();
711
- const { crm: crm2 } = handlers;
712
- const [createState, setCreateState] = useState3({
713
- loading: false,
714
- error: null,
715
- data: null
716
- });
717
- const [moveState, setMoveState] = useState3({
718
- loading: false,
719
- error: null,
720
- data: null
721
- });
722
- const [winState, setWinState] = useState3({
723
- loading: false,
724
- error: null,
725
- data: null
726
- });
727
- const [loseState, setLoseState] = useState3({
728
- loading: false,
729
- error: null,
730
- data: null
731
- });
732
- const createDeal = useCallback2(async (input) => {
733
- setCreateState({ loading: true, error: null, data: null });
734
- try {
735
- const result = await crm2.createDeal(input, {
736
- projectId,
737
- ownerId: "user-1"
738
- });
739
- setCreateState({ loading: false, error: null, data: result });
740
- options.onSuccess?.();
741
- return result;
742
- } catch (err) {
743
- const error = err instanceof Error ? err : new Error("Failed to create deal");
744
- setCreateState({ loading: false, error, data: null });
745
- options.onError?.(error);
746
- return null;
747
- }
748
- }, [crm2, projectId, options]);
749
- const moveDeal = useCallback2(async (input) => {
750
- setMoveState({ loading: true, error: null, data: null });
751
- try {
752
- const result = await crm2.moveDeal(input);
753
- setMoveState({ loading: false, error: null, data: result });
754
- options.onSuccess?.();
755
- return result;
756
- } catch (err) {
757
- const error = err instanceof Error ? err : new Error("Failed to move deal");
758
- setMoveState({ loading: false, error, data: null });
759
- options.onError?.(error);
760
- return null;
761
- }
762
- }, [crm2, options]);
763
- const winDeal = useCallback2(async (input) => {
764
- setWinState({ loading: true, error: null, data: null });
765
- try {
766
- const result = await crm2.winDeal(input);
767
- setWinState({ loading: false, error: null, data: result });
768
- options.onSuccess?.();
769
- return result;
770
- } catch (err) {
771
- const error = err instanceof Error ? err : new Error("Failed to mark deal as won");
772
- setWinState({ loading: false, error, data: null });
773
- options.onError?.(error);
774
- return null;
775
- }
776
- }, [crm2, options]);
777
- const loseDeal = useCallback2(async (input) => {
778
- setLoseState({ loading: true, error: null, data: null });
779
- try {
780
- const result = await crm2.loseDeal(input);
781
- setLoseState({ loading: false, error: null, data: result });
782
- options.onSuccess?.();
783
- return result;
784
- } catch (err) {
785
- const error = err instanceof Error ? err : new Error("Failed to mark deal as lost");
786
- setLoseState({ loading: false, error, data: null });
787
- options.onError?.(error);
788
- return null;
789
- }
790
- }, [crm2, options]);
791
- return {
792
- createDeal,
793
- moveDeal,
794
- winDeal,
795
- loseDeal,
796
- createState,
797
- moveState,
798
- winState,
799
- loseState,
800
- isLoading: createState.loading || moveState.loading || winState.loading || loseState.loading
801
- };
802
- }
803
-
804
- // src/ui/modals/CreateDealModal.tsx
805
- import { Button, Input } from "@contractspec/lib.design-system";
806
- import { useState as useState4 } from "react";
807
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
808
- "use client";
809
- var CURRENCIES = ["USD", "EUR", "GBP", "CAD"];
810
- var DEFAULT_PIPELINE_ID = "pipeline-1";
811
- function CreateDealModal({
812
- isOpen,
813
- onClose,
814
- onSubmit,
815
- stages,
816
- isLoading = false
817
- }) {
818
- const [name, setName] = useState4("");
819
- const [value, setValue] = useState4("");
820
- const [currency, setCurrency] = useState4("USD");
821
- const [stageId, setStageId] = useState4(stages[0]?.id ?? "");
822
- const [expectedCloseDate, setExpectedCloseDate] = useState4("");
823
- const [error, setError] = useState4(null);
824
- const handleSubmit = async (e) => {
825
- e.preventDefault();
826
- setError(null);
827
- if (!name.trim()) {
828
- setError("Deal name is required");
829
- return;
830
- }
831
- const numericValue = parseFloat(value);
832
- if (isNaN(numericValue) || numericValue <= 0) {
833
- setError("Value must be a positive number");
834
- return;
835
- }
836
- if (!stageId) {
837
- setError("Please select a pipeline stage");
838
- return;
839
- }
840
- try {
841
- await onSubmit({
842
- name: name.trim(),
843
- value: numericValue,
844
- currency,
845
- pipelineId: DEFAULT_PIPELINE_ID,
846
- stageId,
847
- expectedCloseDate: expectedCloseDate ? new Date(expectedCloseDate) : undefined
848
- });
849
- setName("");
850
- setValue("");
851
- setCurrency("USD");
852
- setStageId(stages[0]?.id ?? "");
853
- setExpectedCloseDate("");
854
- onClose();
855
- } catch (err) {
856
- setError(err instanceof Error ? err.message : "Failed to create deal");
857
- }
858
- };
859
- if (!isOpen)
860
- return null;
861
- return /* @__PURE__ */ jsxDEV3("div", {
862
- className: "fixed inset-0 z-50 flex items-center justify-center",
863
- children: [
864
- /* @__PURE__ */ jsxDEV3("div", {
865
- className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
866
- onClick: onClose,
867
- role: "button",
868
- tabIndex: 0,
869
- onKeyDown: (e) => {
870
- if (e.key === "Enter" || e.key === " ")
871
- onClose();
872
- },
873
- "aria-label": "Close modal"
874
- }, undefined, false, undefined, this),
875
- /* @__PURE__ */ jsxDEV3("div", {
876
- className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
877
- children: [
878
- /* @__PURE__ */ jsxDEV3("h2", {
879
- className: "mb-4 font-semibold text-xl",
880
- children: "Create New Deal"
881
- }, undefined, false, undefined, this),
882
- /* @__PURE__ */ jsxDEV3("form", {
883
- onSubmit: handleSubmit,
884
- className: "space-y-4",
885
- children: [
886
- /* @__PURE__ */ jsxDEV3("div", {
887
- children: [
888
- /* @__PURE__ */ jsxDEV3("label", {
889
- htmlFor: "deal-name",
890
- className: "mb-1 block font-medium text-muted-foreground text-sm",
891
- children: "Deal Name *"
892
- }, undefined, false, undefined, this),
893
- /* @__PURE__ */ jsxDEV3(Input, {
894
- id: "deal-name",
895
- value: name,
896
- onChange: (e) => setName(e.target.value),
897
- placeholder: "e.g., Enterprise License - Acme Corp",
898
- disabled: isLoading
899
- }, undefined, false, undefined, this)
900
- ]
901
- }, undefined, true, undefined, this),
902
- /* @__PURE__ */ jsxDEV3("div", {
903
- className: "flex gap-3",
904
- children: [
905
- /* @__PURE__ */ jsxDEV3("div", {
906
- className: "flex-1",
907
- children: [
908
- /* @__PURE__ */ jsxDEV3("label", {
909
- htmlFor: "deal-value",
910
- className: "mb-1 block font-medium text-muted-foreground text-sm",
911
- children: "Value *"
912
- }, undefined, false, undefined, this),
913
- /* @__PURE__ */ jsxDEV3(Input, {
914
- id: "deal-value",
915
- type: "number",
916
- min: "0",
917
- step: "0.01",
918
- value,
919
- onChange: (e) => setValue(e.target.value),
920
- placeholder: "50000",
921
- disabled: isLoading
922
- }, undefined, false, undefined, this)
923
- ]
924
- }, undefined, true, undefined, this),
925
- /* @__PURE__ */ jsxDEV3("div", {
926
- className: "w-24",
927
- children: [
928
- /* @__PURE__ */ jsxDEV3("label", {
929
- htmlFor: "deal-currency",
930
- className: "mb-1 block font-medium text-muted-foreground text-sm",
931
- children: "Currency"
932
- }, undefined, false, undefined, this),
933
- /* @__PURE__ */ jsxDEV3("select", {
934
- id: "deal-currency",
935
- value: currency,
936
- onChange: (e) => setCurrency(e.target.value),
937
- disabled: isLoading,
938
- className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
939
- children: CURRENCIES.map((c) => /* @__PURE__ */ jsxDEV3("option", {
940
- value: c,
941
- children: c
942
- }, c, false, undefined, this))
943
- }, undefined, false, undefined, this)
944
- ]
945
- }, undefined, true, undefined, this)
946
- ]
947
- }, undefined, true, undefined, this),
948
- /* @__PURE__ */ jsxDEV3("div", {
949
- children: [
950
- /* @__PURE__ */ jsxDEV3("label", {
951
- htmlFor: "deal-stage",
952
- className: "mb-1 block font-medium text-muted-foreground text-sm",
953
- children: "Pipeline Stage *"
954
- }, undefined, false, undefined, this),
955
- /* @__PURE__ */ jsxDEV3("select", {
956
- id: "deal-stage",
957
- value: stageId,
958
- onChange: (e) => setStageId(e.target.value),
959
- disabled: isLoading,
960
- className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
961
- children: stages.map((stage) => /* @__PURE__ */ jsxDEV3("option", {
962
- value: stage.id,
963
- children: stage.name
964
- }, stage.id, false, undefined, this))
965
- }, undefined, false, undefined, this)
966
- ]
967
- }, undefined, true, undefined, this),
968
- /* @__PURE__ */ jsxDEV3("div", {
969
- children: [
970
- /* @__PURE__ */ jsxDEV3("label", {
971
- htmlFor: "deal-close-date",
972
- className: "mb-1 block font-medium text-muted-foreground text-sm",
973
- children: "Expected Close Date"
974
- }, undefined, false, undefined, this),
975
- /* @__PURE__ */ jsxDEV3(Input, {
976
- id: "deal-close-date",
977
- type: "date",
978
- value: expectedCloseDate,
979
- onChange: (e) => setExpectedCloseDate(e.target.value),
980
- disabled: isLoading
981
- }, undefined, false, undefined, this)
982
- ]
983
- }, undefined, true, undefined, this),
984
- error && /* @__PURE__ */ jsxDEV3("div", {
985
- className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
986
- children: error
987
- }, undefined, false, undefined, this),
988
- /* @__PURE__ */ jsxDEV3("div", {
989
- className: "flex justify-end gap-3 pt-2",
990
- children: [
991
- /* @__PURE__ */ jsxDEV3(Button, {
992
- type: "button",
993
- variant: "ghost",
994
- onPress: onClose,
995
- disabled: isLoading,
996
- children: "Cancel"
997
- }, undefined, false, undefined, this),
998
- /* @__PURE__ */ jsxDEV3(Button, {
999
- type: "submit",
1000
- disabled: isLoading,
1001
- children: isLoading ? "Creating..." : "Create Deal"
1002
- }, undefined, false, undefined, this)
1003
- ]
1004
- }, undefined, true, undefined, this)
1005
- ]
1006
- }, undefined, true, undefined, this)
1007
- ]
1008
- }, undefined, true, undefined, this)
1009
- ]
1010
- }, undefined, true, undefined, this);
1011
- }
1012
-
1013
- // src/ui/modals/DealActionsModal.tsx
1014
- import { Button as Button2 } from "@contractspec/lib.design-system";
1015
- import { useState as useState5 } from "react";
1016
- import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
1017
- "use client";
1018
- function formatCurrency3(value, currency) {
1019
- return new Intl.NumberFormat("en-US", {
1020
- style: "currency",
1021
- currency,
1022
- minimumFractionDigits: 0,
1023
- maximumFractionDigits: 0
1024
- }).format(value);
1025
- }
1026
- function DealActionsModal({
1027
- isOpen,
1028
- deal,
1029
- stages,
1030
- onClose,
1031
- onWin,
1032
- onLose,
1033
- onMove,
1034
- isLoading = false
1035
- }) {
1036
- const [mode, setMode] = useState5("menu");
1037
- const [wonSource, setWonSource] = useState5("");
1038
- const [lostReason, setLostReason] = useState5("");
1039
- const [notes, setNotes] = useState5("");
1040
- const [selectedStageId, setSelectedStageId] = useState5("");
1041
- const [error, setError] = useState5(null);
1042
- const resetForm = () => {
1043
- setMode("menu");
1044
- setWonSource("");
1045
- setLostReason("");
1046
- setNotes("");
1047
- setSelectedStageId("");
1048
- setError(null);
1049
- };
1050
- const handleClose = () => {
1051
- resetForm();
1052
- onClose();
1053
- };
1054
- const handleWin = async () => {
1055
- if (!deal)
1056
- return;
1057
- setError(null);
1058
- try {
1059
- await onWin({
1060
- dealId: deal.id,
1061
- wonSource: wonSource.trim() || undefined,
1062
- notes: notes.trim() || undefined
1063
- });
1064
- handleClose();
1065
- } catch (err) {
1066
- setError(err instanceof Error ? err.message : "Failed to mark deal as won");
1067
- }
1068
- };
1069
- const handleLose = async () => {
1070
- if (!deal)
1071
- return;
1072
- setError(null);
1073
- if (!lostReason.trim()) {
1074
- setError("Please provide a reason for losing the deal");
1075
- return;
1076
- }
1077
- try {
1078
- await onLose({
1079
- dealId: deal.id,
1080
- lostReason: lostReason.trim(),
1081
- notes: notes.trim() || undefined
1082
- });
1083
- handleClose();
1084
- } catch (err) {
1085
- setError(err instanceof Error ? err.message : "Failed to mark deal as lost");
1086
- }
1087
- };
1088
- const handleMove = async () => {
1089
- if (!deal)
1090
- return;
1091
- setError(null);
1092
- if (!selectedStageId) {
1093
- setError("Please select a stage");
1094
- return;
1095
- }
1096
- if (selectedStageId === deal.stageId) {
1097
- setError("Deal is already in this stage");
1098
- return;
1099
- }
1100
- try {
1101
- await onMove({
1102
- dealId: deal.id,
1103
- stageId: selectedStageId
1104
- });
1105
- handleClose();
1106
- } catch (err) {
1107
- setError(err instanceof Error ? err.message : "Failed to move deal");
1108
- }
1109
- };
1110
- if (!isOpen || !deal)
1111
- return null;
1112
- return /* @__PURE__ */ jsxDEV4("div", {
1113
- className: "fixed inset-0 z-50 flex items-center justify-center",
1114
- children: [
1115
- /* @__PURE__ */ jsxDEV4("div", {
1116
- className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
1117
- onClick: handleClose,
1118
- role: "button",
1119
- tabIndex: 0,
1120
- onKeyDown: (e) => {
1121
- if (e.key === "Enter" || e.key === " ")
1122
- handleClose();
1123
- },
1124
- "aria-label": "Close modal"
1125
- }, undefined, false, undefined, this),
1126
- /* @__PURE__ */ jsxDEV4("div", {
1127
- className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
1128
- children: [
1129
- /* @__PURE__ */ jsxDEV4("div", {
1130
- className: "mb-4 border-border border-b pb-4",
1131
- children: [
1132
- /* @__PURE__ */ jsxDEV4("h2", {
1133
- className: "font-semibold text-xl",
1134
- children: deal.name
1135
- }, undefined, false, undefined, this),
1136
- /* @__PURE__ */ jsxDEV4("p", {
1137
- className: "font-medium text-lg text-primary",
1138
- children: formatCurrency3(deal.value, deal.currency)
1139
- }, undefined, false, undefined, this),
1140
- /* @__PURE__ */ jsxDEV4("span", {
1141
- className: `mt-2 inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
1142
- children: deal.status
1143
- }, undefined, false, undefined, this)
1144
- ]
1145
- }, undefined, true, undefined, this),
1146
- mode === "menu" && /* @__PURE__ */ jsxDEV4("div", {
1147
- className: "space-y-3",
1148
- children: [
1149
- deal.status === "OPEN" && /* @__PURE__ */ jsxDEV4(Fragment, {
1150
- children: [
1151
- /* @__PURE__ */ jsxDEV4(Button2, {
1152
- className: "w-full justify-start",
1153
- variant: "ghost",
1154
- onPress: () => setMode("win"),
1155
- children: [
1156
- /* @__PURE__ */ jsxDEV4("span", {
1157
- className: "mr-2",
1158
- children: "\uD83C\uDFC6"
1159
- }, undefined, false, undefined, this),
1160
- " Mark as Won"
1161
- ]
1162
- }, undefined, true, undefined, this),
1163
- /* @__PURE__ */ jsxDEV4(Button2, {
1164
- className: "w-full justify-start",
1165
- variant: "ghost",
1166
- onPress: () => setMode("lose"),
1167
- children: [
1168
- /* @__PURE__ */ jsxDEV4("span", {
1169
- className: "mr-2",
1170
- children: "\u274C"
1171
- }, undefined, false, undefined, this),
1172
- " Mark as Lost"
1173
- ]
1174
- }, undefined, true, undefined, this),
1175
- /* @__PURE__ */ jsxDEV4(Button2, {
1176
- className: "w-full justify-start",
1177
- variant: "ghost",
1178
- onPress: () => {
1179
- setSelectedStageId(deal.stageId);
1180
- setMode("move");
1181
- },
1182
- children: [
1183
- /* @__PURE__ */ jsxDEV4("span", {
1184
- className: "mr-2",
1185
- children: "\u27A1\uFE0F"
1186
- }, undefined, false, undefined, this),
1187
- " Move to Stage"
1188
- ]
1189
- }, undefined, true, undefined, this)
1190
- ]
1191
- }, undefined, true, undefined, this),
1192
- deal.status !== "OPEN" && /* @__PURE__ */ jsxDEV4("p", {
1193
- className: "py-4 text-center text-muted-foreground",
1194
- children: [
1195
- "This deal is already ",
1196
- deal.status.toLowerCase(),
1197
- ". No actions available."
1198
- ]
1199
- }, undefined, true, undefined, this),
1200
- /* @__PURE__ */ jsxDEV4("div", {
1201
- className: "border-border border-t pt-3",
1202
- children: /* @__PURE__ */ jsxDEV4(Button2, {
1203
- className: "w-full",
1204
- variant: "outline",
1205
- onPress: handleClose,
1206
- children: "Close"
1207
- }, undefined, false, undefined, this)
1208
- }, undefined, false, undefined, this)
1209
- ]
1210
- }, undefined, true, undefined, this),
1211
- mode === "win" && /* @__PURE__ */ jsxDEV4("div", {
1212
- className: "space-y-4",
1213
- children: [
1214
- /* @__PURE__ */ jsxDEV4("div", {
1215
- children: [
1216
- /* @__PURE__ */ jsxDEV4("label", {
1217
- htmlFor: "won-source",
1218
- className: "mb-1 block font-medium text-muted-foreground text-sm",
1219
- children: "How did you win this deal?"
1220
- }, undefined, false, undefined, this),
1221
- /* @__PURE__ */ jsxDEV4("select", {
1222
- id: "won-source",
1223
- value: wonSource,
1224
- onChange: (e) => setWonSource(e.target.value),
1225
- className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
1226
- children: [
1227
- /* @__PURE__ */ jsxDEV4("option", {
1228
- value: "",
1229
- children: "Select a source..."
1230
- }, undefined, false, undefined, this),
1231
- /* @__PURE__ */ jsxDEV4("option", {
1232
- value: "referral",
1233
- children: "Referral"
1234
- }, undefined, false, undefined, this),
1235
- /* @__PURE__ */ jsxDEV4("option", {
1236
- value: "cold_outreach",
1237
- children: "Cold Outreach"
1238
- }, undefined, false, undefined, this),
1239
- /* @__PURE__ */ jsxDEV4("option", {
1240
- value: "inbound",
1241
- children: "Inbound Lead"
1242
- }, undefined, false, undefined, this),
1243
- /* @__PURE__ */ jsxDEV4("option", {
1244
- value: "upsell",
1245
- children: "Upsell"
1246
- }, undefined, false, undefined, this),
1247
- /* @__PURE__ */ jsxDEV4("option", {
1248
- value: "other",
1249
- children: "Other"
1250
- }, undefined, false, undefined, this)
1251
- ]
1252
- }, undefined, true, undefined, this)
1253
- ]
1254
- }, undefined, true, undefined, this),
1255
- /* @__PURE__ */ jsxDEV4("div", {
1256
- children: [
1257
- /* @__PURE__ */ jsxDEV4("label", {
1258
- htmlFor: "win-notes",
1259
- className: "mb-1 block font-medium text-muted-foreground text-sm",
1260
- children: "Notes (optional)"
1261
- }, undefined, false, undefined, this),
1262
- /* @__PURE__ */ jsxDEV4("textarea", {
1263
- id: "win-notes",
1264
- value: notes,
1265
- onChange: (e) => setNotes(e.target.value),
1266
- placeholder: "Any additional notes about the win...",
1267
- rows: 3,
1268
- className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
1269
- }, undefined, false, undefined, this)
1270
- ]
1271
- }, undefined, true, undefined, this),
1272
- error && /* @__PURE__ */ jsxDEV4("div", {
1273
- className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
1274
- children: error
1275
- }, undefined, false, undefined, this),
1276
- /* @__PURE__ */ jsxDEV4("div", {
1277
- className: "flex justify-end gap-3 pt-2",
1278
- children: [
1279
- /* @__PURE__ */ jsxDEV4(Button2, {
1280
- variant: "ghost",
1281
- onPress: () => setMode("menu"),
1282
- disabled: isLoading,
1283
- children: "Back"
1284
- }, undefined, false, undefined, this),
1285
- /* @__PURE__ */ jsxDEV4(Button2, {
1286
- onPress: handleWin,
1287
- disabled: isLoading,
1288
- children: isLoading ? "Processing..." : "\uD83C\uDFC6 Confirm Win"
1289
- }, undefined, false, undefined, this)
1290
- ]
1291
- }, undefined, true, undefined, this)
1292
- ]
1293
- }, undefined, true, undefined, this),
1294
- mode === "lose" && /* @__PURE__ */ jsxDEV4("div", {
1295
- className: "space-y-4",
1296
- children: [
1297
- /* @__PURE__ */ jsxDEV4("div", {
1298
- children: [
1299
- /* @__PURE__ */ jsxDEV4("label", {
1300
- htmlFor: "lost-reason",
1301
- className: "mb-1 block font-medium text-muted-foreground text-sm",
1302
- children: "Why was this deal lost? *"
1303
- }, undefined, false, undefined, this),
1304
- /* @__PURE__ */ jsxDEV4("select", {
1305
- id: "lost-reason",
1306
- value: lostReason,
1307
- onChange: (e) => setLostReason(e.target.value),
1308
- className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
1309
- children: [
1310
- /* @__PURE__ */ jsxDEV4("option", {
1311
- value: "",
1312
- children: "Select a reason..."
1313
- }, undefined, false, undefined, this),
1314
- /* @__PURE__ */ jsxDEV4("option", {
1315
- value: "price",
1316
- children: "Price too high"
1317
- }, undefined, false, undefined, this),
1318
- /* @__PURE__ */ jsxDEV4("option", {
1319
- value: "competitor",
1320
- children: "Lost to competitor"
1321
- }, undefined, false, undefined, this),
1322
- /* @__PURE__ */ jsxDEV4("option", {
1323
- value: "no_budget",
1324
- children: "No budget"
1325
- }, undefined, false, undefined, this),
1326
- /* @__PURE__ */ jsxDEV4("option", {
1327
- value: "no_decision",
1328
- children: "No decision made"
1329
- }, undefined, false, undefined, this),
1330
- /* @__PURE__ */ jsxDEV4("option", {
1331
- value: "timing",
1332
- children: "Bad timing"
1333
- }, undefined, false, undefined, this),
1334
- /* @__PURE__ */ jsxDEV4("option", {
1335
- value: "product_fit",
1336
- children: "Product not a fit"
1337
- }, undefined, false, undefined, this),
1338
- /* @__PURE__ */ jsxDEV4("option", {
1339
- value: "other",
1340
- children: "Other"
1341
- }, undefined, false, undefined, this)
1342
- ]
1343
- }, undefined, true, undefined, this)
1344
- ]
1345
- }, undefined, true, undefined, this),
1346
- /* @__PURE__ */ jsxDEV4("div", {
1347
- children: [
1348
- /* @__PURE__ */ jsxDEV4("label", {
1349
- htmlFor: "lose-notes",
1350
- className: "mb-1 block font-medium text-muted-foreground text-sm",
1351
- children: "Notes (optional)"
1352
- }, undefined, false, undefined, this),
1353
- /* @__PURE__ */ jsxDEV4("textarea", {
1354
- id: "lose-notes",
1355
- value: notes,
1356
- onChange: (e) => setNotes(e.target.value),
1357
- placeholder: "Any additional details...",
1358
- rows: 3,
1359
- className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
1360
- }, undefined, false, undefined, this)
1361
- ]
1362
- }, undefined, true, undefined, this),
1363
- error && /* @__PURE__ */ jsxDEV4("div", {
1364
- className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
1365
- children: error
1366
- }, undefined, false, undefined, this),
1367
- /* @__PURE__ */ jsxDEV4("div", {
1368
- className: "flex justify-end gap-3 pt-2",
1369
- children: [
1370
- /* @__PURE__ */ jsxDEV4(Button2, {
1371
- variant: "ghost",
1372
- onPress: () => setMode("menu"),
1373
- disabled: isLoading,
1374
- children: "Back"
1375
- }, undefined, false, undefined, this),
1376
- /* @__PURE__ */ jsxDEV4(Button2, {
1377
- variant: "destructive",
1378
- onPress: handleLose,
1379
- disabled: isLoading,
1380
- children: isLoading ? "Processing..." : "\u274C Confirm Loss"
1381
- }, undefined, false, undefined, this)
1382
- ]
1383
- }, undefined, true, undefined, this)
1384
- ]
1385
- }, undefined, true, undefined, this),
1386
- mode === "move" && /* @__PURE__ */ jsxDEV4("div", {
1387
- className: "space-y-4",
1388
- children: [
1389
- /* @__PURE__ */ jsxDEV4("div", {
1390
- children: [
1391
- /* @__PURE__ */ jsxDEV4("label", {
1392
- htmlFor: "move-stage",
1393
- className: "mb-1 block font-medium text-muted-foreground text-sm",
1394
- children: "Move to Stage"
1395
- }, undefined, false, undefined, this),
1396
- /* @__PURE__ */ jsxDEV4("select", {
1397
- id: "move-stage",
1398
- value: selectedStageId,
1399
- onChange: (e) => setSelectedStageId(e.target.value),
1400
- className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
1401
- children: stages.map((stage) => /* @__PURE__ */ jsxDEV4("option", {
1402
- value: stage.id,
1403
- children: [
1404
- stage.name,
1405
- stage.id === deal.stageId ? " (current)" : ""
1406
- ]
1407
- }, stage.id, true, undefined, this))
1408
- }, undefined, false, undefined, this)
1409
- ]
1410
- }, undefined, true, undefined, this),
1411
- error && /* @__PURE__ */ jsxDEV4("div", {
1412
- className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
1413
- children: error
1414
- }, undefined, false, undefined, this),
1415
- /* @__PURE__ */ jsxDEV4("div", {
1416
- className: "flex justify-end gap-3 pt-2",
1417
- children: [
1418
- /* @__PURE__ */ jsxDEV4(Button2, {
1419
- variant: "ghost",
1420
- onPress: () => setMode("menu"),
1421
- disabled: isLoading,
1422
- children: "Back"
1423
- }, undefined, false, undefined, this),
1424
- /* @__PURE__ */ jsxDEV4(Button2, {
1425
- onPress: handleMove,
1426
- disabled: isLoading,
1427
- children: isLoading ? "Moving..." : "\u27A1\uFE0F Move Deal"
1428
- }, undefined, false, undefined, this)
1429
- ]
1430
- }, undefined, true, undefined, this)
1431
- ]
1432
- }, undefined, true, undefined, this)
1433
- ]
1434
- }, undefined, true, undefined, this)
1435
- ]
1436
- }, undefined, true, undefined, this);
1437
- }
1438
-
1439
- // src/ui/tables/DealListTab.tsx
1440
- import {
1441
- Button as Button3,
1442
- DataTable,
1443
- LoaderBlock
1444
- } from "@contractspec/lib.design-system";
1445
- import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
1446
- import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
1447
- import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
1448
- import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
1449
- import * as React from "react";
1450
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
1451
- "use client";
1452
- function formatCurrency4(value, currency = "USD") {
1453
- return new Intl.NumberFormat("en-US", {
1454
- style: "currency",
1455
- currency,
1456
- minimumFractionDigits: 0,
1457
- maximumFractionDigits: 0
1458
- }).format(value);
1459
- }
1460
- function statusVariant(status) {
1461
- switch (status) {
1462
- case "WON":
1463
- return "default";
1464
- case "LOST":
1465
- return "destructive";
1466
- case "STALE":
1467
- return "outline";
1468
- default:
1469
- return "secondary";
1470
- }
1471
- }
1472
- function DealListDataTable({
1473
- deals,
1474
- totalItems,
1475
- pageIndex,
1476
- pageSize,
1477
- sorting,
1478
- loading,
1479
- onSortingChange,
1480
- onPaginationChange,
1481
- onDealClick
1482
- }) {
1483
- const controller = useContractTable({
1484
- data: deals,
1485
- columns: [
1486
- {
1487
- id: "deal",
1488
- header: "Deal",
1489
- label: "Deal",
1490
- accessor: (deal) => deal.name,
1491
- cell: ({ item }) => /* @__PURE__ */ jsxDEV5(VStack, {
1492
- gap: "xs",
1493
- children: [
1494
- /* @__PURE__ */ jsxDEV5(Text, {
1495
- className: "font-medium text-sm",
1496
- children: item.name
1497
- }, undefined, false, undefined, this),
1498
- /* @__PURE__ */ jsxDEV5(Text, {
1499
- className: "text-muted-foreground text-xs",
1500
- children: item.companyId ?? "Unassigned company"
1501
- }, undefined, false, undefined, this)
1502
- ]
1503
- }, undefined, true, undefined, this),
1504
- size: 240,
1505
- minSize: 180,
1506
- canSort: true,
1507
- canPin: true,
1508
- canResize: true
1509
- },
1510
- {
1511
- id: "value",
1512
- header: "Value",
1513
- label: "Value",
1514
- accessorKey: "value",
1515
- cell: ({ item }) => formatCurrency4(item.value, item.currency),
1516
- align: "right",
1517
- size: 140,
1518
- canSort: true,
1519
- canResize: true
1520
- },
1521
- {
1522
- id: "status",
1523
- header: "Status",
1524
- label: "Status",
1525
- accessorKey: "status",
1526
- cell: ({ value }) => /* @__PURE__ */ jsxDEV5(Badge, {
1527
- variant: statusVariant(value),
1528
- children: String(value)
1529
- }, undefined, false, undefined, this),
1530
- size: 130,
1531
- canSort: true,
1532
- canHide: true,
1533
- canPin: true,
1534
- canResize: true
1535
- },
1536
- {
1537
- id: "expectedCloseDate",
1538
- header: "Expected Close",
1539
- label: "Expected Close",
1540
- accessor: (deal) => deal.expectedCloseDate?.toISOString() ?? "",
1541
- cell: ({ item }) => item.expectedCloseDate?.toLocaleDateString() ?? "Not scheduled",
1542
- size: 170,
1543
- canSort: true,
1544
- canHide: true,
1545
- canResize: true
1546
- },
1547
- {
1548
- id: "updatedAt",
1549
- header: "Updated",
1550
- label: "Updated",
1551
- accessor: (deal) => deal.updatedAt.toISOString(),
1552
- cell: ({ item }) => item.updatedAt.toLocaleDateString(),
1553
- size: 140,
1554
- canSort: true,
1555
- canHide: true,
1556
- canResize: true
1557
- },
1558
- {
1559
- id: "actions",
1560
- header: "Actions",
1561
- label: "Actions",
1562
- accessor: (deal) => deal.id,
1563
- cell: ({ item }) => /* @__PURE__ */ jsxDEV5(Button3, {
1564
- variant: "ghost",
1565
- size: "sm",
1566
- onPress: () => onDealClick?.(item.id),
1567
- children: "Actions"
1568
- }, undefined, false, undefined, this),
1569
- size: 120,
1570
- canSort: false,
1571
- canHide: false,
1572
- canPin: false,
1573
- canResize: false
1574
- }
1575
- ],
1576
- executionMode: "server",
1577
- selectionMode: "multiple",
1578
- totalItems,
1579
- state: {
1580
- sorting,
1581
- pagination: {
1582
- pageIndex,
1583
- pageSize
1584
- }
1585
- },
1586
- onSortingChange,
1587
- onPaginationChange,
1588
- initialState: {
1589
- columnVisibility: { updatedAt: false },
1590
- columnPinning: { left: ["deal", "status"], right: [] }
1591
- },
1592
- renderExpandedContent: (deal) => /* @__PURE__ */ jsxDEV5(VStack, {
1593
- gap: "sm",
1594
- className: "py-2",
1595
- children: [
1596
- /* @__PURE__ */ jsxDEV5(HStack, {
1597
- justify: "between",
1598
- children: [
1599
- /* @__PURE__ */ jsxDEV5(Text, {
1600
- className: "font-medium text-sm",
1601
- children: "Owner"
1602
- }, undefined, false, undefined, this),
1603
- /* @__PURE__ */ jsxDEV5(Text, {
1604
- className: "text-muted-foreground text-sm",
1605
- children: deal.ownerId
1606
- }, undefined, false, undefined, this)
1607
- ]
1608
- }, undefined, true, undefined, this),
1609
- /* @__PURE__ */ jsxDEV5(HStack, {
1610
- justify: "between",
1611
- children: [
1612
- /* @__PURE__ */ jsxDEV5(Text, {
1613
- className: "font-medium text-sm",
1614
- children: "Contact"
1615
- }, undefined, false, undefined, this),
1616
- /* @__PURE__ */ jsxDEV5(Text, {
1617
- className: "text-muted-foreground text-sm",
1618
- children: deal.contactId ?? "No linked contact"
1619
- }, undefined, false, undefined, this)
1620
- ]
1621
- }, undefined, true, undefined, this),
1622
- deal.wonSource ? /* @__PURE__ */ jsxDEV5(HStack, {
1623
- justify: "between",
1624
- children: [
1625
- /* @__PURE__ */ jsxDEV5(Text, {
1626
- className: "font-medium text-sm",
1627
- children: "Won Source"
1628
- }, undefined, false, undefined, this),
1629
- /* @__PURE__ */ jsxDEV5(Text, {
1630
- className: "text-muted-foreground text-sm",
1631
- children: deal.wonSource
1632
- }, undefined, false, undefined, this)
1633
- ]
1634
- }, undefined, true, undefined, this) : null,
1635
- deal.lostReason ? /* @__PURE__ */ jsxDEV5(HStack, {
1636
- justify: "between",
1637
- children: [
1638
- /* @__PURE__ */ jsxDEV5(Text, {
1639
- className: "font-medium text-sm",
1640
- children: "Lost Reason"
1641
- }, undefined, false, undefined, this),
1642
- /* @__PURE__ */ jsxDEV5(Text, {
1643
- className: "text-muted-foreground text-sm",
1644
- children: deal.lostReason
1645
- }, undefined, false, undefined, this)
1646
- ]
1647
- }, undefined, true, undefined, this) : null,
1648
- deal.notes ? /* @__PURE__ */ jsxDEV5(VStack, {
1649
- gap: "xs",
1650
- children: [
1651
- /* @__PURE__ */ jsxDEV5(Text, {
1652
- className: "font-medium text-sm",
1653
- children: "Notes"
1654
- }, undefined, false, undefined, this),
1655
- /* @__PURE__ */ jsxDEV5(Text, {
1656
- className: "text-muted-foreground text-sm",
1657
- children: deal.notes
1658
- }, undefined, false, undefined, this)
1659
- ]
1660
- }, undefined, true, undefined, this) : null
1661
- ]
1662
- }, undefined, true, undefined, this),
1663
- getCanExpand: () => true
1664
- });
1665
- return /* @__PURE__ */ jsxDEV5(DataTable, {
1666
- controller,
1667
- title: "All Deals",
1668
- description: "Server-mode table using the shared ContractSpec controller.",
1669
- loading,
1670
- toolbar: /* @__PURE__ */ jsxDEV5(HStack, {
1671
- gap: "sm",
1672
- className: "flex-wrap",
1673
- children: [
1674
- /* @__PURE__ */ jsxDEV5(Text, {
1675
- className: "text-muted-foreground text-sm",
1676
- children: [
1677
- "Selected ",
1678
- controller.selectedRowIds.length
1679
- ]
1680
- }, undefined, true, undefined, this),
1681
- /* @__PURE__ */ jsxDEV5(Text, {
1682
- className: "text-muted-foreground text-sm",
1683
- children: [
1684
- totalItems,
1685
- " total deals"
1686
- ]
1687
- }, undefined, true, undefined, this)
1688
- ]
1689
- }, undefined, true, undefined, this),
1690
- footer: `Page ${controller.pageIndex + 1} of ${controller.pageCount}`,
1691
- emptyState: /* @__PURE__ */ jsxDEV5("div", {
1692
- className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
1693
- children: "No deals found"
1694
- }, undefined, false, undefined, this)
1695
- }, undefined, false, undefined, this);
1696
- }
1697
- function DealListTab({
1698
- onDealClick
1699
- }) {
1700
- const [sorting, setSorting] = React.useState([
1701
- { id: "value", desc: true }
1702
- ]);
1703
- const [pagination, setPagination] = React.useState({
1704
- pageIndex: 0,
1705
- pageSize: 3
1706
- });
1707
- const { data, loading } = useDealList({
1708
- pageIndex: pagination.pageIndex,
1709
- pageSize: pagination.pageSize,
1710
- sorting
1711
- });
1712
- if (loading && !data) {
1713
- return /* @__PURE__ */ jsxDEV5(LoaderBlock, {
1714
- label: "Loading deals..."
1715
- }, undefined, false, undefined, this);
1716
- }
1717
- return /* @__PURE__ */ jsxDEV5(DealListDataTable, {
1718
- deals: data?.deals ?? [],
1719
- totalItems: data?.total ?? 0,
1720
- pageIndex: pagination.pageIndex,
1721
- pageSize: pagination.pageSize,
1722
- sorting,
1723
- loading,
1724
- onSortingChange: (nextSorting) => {
1725
- setSorting(nextSorting);
1726
- setPagination((current) => ({ ...current, pageIndex: 0 }));
1727
- },
1728
- onPaginationChange: setPagination,
1729
- onDealClick
1730
- }, undefined, false, undefined, this);
1731
- }
1732
-
1733
- // src/ui/CrmDashboard.tsx
1734
- import {
1735
- Button as Button4,
1736
- ErrorState,
1737
- LoaderBlock as LoaderBlock2,
1738
- StatCard,
1739
- StatCardGroup
1740
- } from "@contractspec/lib.design-system";
1741
- import {
1742
- Tabs,
1743
- TabsContent,
1744
- TabsList,
1745
- TabsTrigger
1746
- } from "@contractspec/lib.ui-kit-web/ui/tabs";
1747
- import { useCallback as useCallback3, useState as useState7 } from "react";
1748
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
1749
- "use client";
1750
- function formatCurrency5(value, currency = "USD") {
1751
- return new Intl.NumberFormat("en-US", {
1752
- style: "currency",
1753
- currency,
1754
- minimumFractionDigits: 0,
1755
- maximumFractionDigits: 0
1756
- }).format(value);
1757
- }
1758
- function CrmDashboard() {
1759
- const [isCreateModalOpen, setIsCreateModalOpen] = useState7(false);
1760
- const [selectedDeal, setSelectedDeal] = useState7(null);
1761
- const [isDealActionsOpen, setIsDealActionsOpen] = useState7(false);
1762
- const { data, dealsByStage, stages, loading, error, stats, refetch } = useDealList();
1763
- const mutations = useDealMutations({
1764
- onSuccess: () => {
1765
- refetch();
1766
- }
1767
- });
1768
- const handleDealClick = useCallback3((dealId) => {
1769
- const deal = dealsByStage ? Object.values(dealsByStage).flat().find((d) => d.id === dealId) : null;
1770
- if (deal) {
1771
- setSelectedDeal(deal);
1772
- setIsDealActionsOpen(true);
1773
- }
1774
- }, [dealsByStage]);
1775
- const handleDealMove = useCallback3(async (dealId, toStageId) => {
1776
- await mutations.moveDeal({ dealId, stageId: toStageId });
1777
- }, [mutations]);
1778
- if (loading && !data) {
1779
- return /* @__PURE__ */ jsxDEV6(LoaderBlock2, {
1780
- label: "Loading CRM..."
1781
- }, undefined, false, undefined, this);
1782
- }
1783
- if (error) {
1784
- return /* @__PURE__ */ jsxDEV6(ErrorState, {
1785
- title: "Failed to load CRM",
1786
- description: error.message,
1787
- onRetry: refetch,
1788
- retryLabel: "Retry"
1789
- }, undefined, false, undefined, this);
1790
- }
1791
- return /* @__PURE__ */ jsxDEV6("div", {
1792
- className: "space-y-6",
1793
- children: [
1794
- /* @__PURE__ */ jsxDEV6("div", {
1795
- className: "flex items-center justify-between",
1796
- children: [
1797
- /* @__PURE__ */ jsxDEV6("h2", {
1798
- className: "font-bold text-2xl",
1799
- children: "CRM Pipeline"
1800
- }, undefined, false, undefined, this),
1801
- /* @__PURE__ */ jsxDEV6(Button4, {
1802
- onClick: () => setIsCreateModalOpen(true),
1803
- children: [
1804
- /* @__PURE__ */ jsxDEV6("span", {
1805
- className: "mr-2",
1806
- children: "+"
1807
- }, undefined, false, undefined, this),
1808
- " Create Deal"
1809
- ]
1810
- }, undefined, true, undefined, this)
1811
- ]
1812
- }, undefined, true, undefined, this),
1813
- stats && /* @__PURE__ */ jsxDEV6(StatCardGroup, {
1814
- children: [
1815
- /* @__PURE__ */ jsxDEV6(StatCard, {
1816
- label: "Total Pipeline",
1817
- value: formatCurrency5(stats.totalValue),
1818
- hint: `${stats.total} deals`
1819
- }, undefined, false, undefined, this),
1820
- /* @__PURE__ */ jsxDEV6(StatCard, {
1821
- label: "Open Deals",
1822
- value: formatCurrency5(stats.openValue),
1823
- hint: `${stats.openCount} active`
1824
- }, undefined, false, undefined, this),
1825
- /* @__PURE__ */ jsxDEV6(StatCard, {
1826
- label: "Won",
1827
- value: formatCurrency5(stats.wonValue),
1828
- hint: `${stats.wonCount} closed`
1829
- }, undefined, false, undefined, this),
1830
- /* @__PURE__ */ jsxDEV6(StatCard, {
1831
- label: "Lost",
1832
- value: stats.lostCount,
1833
- hint: "deals lost"
1834
- }, undefined, false, undefined, this)
1835
- ]
1836
- }, undefined, true, undefined, this),
1837
- /* @__PURE__ */ jsxDEV6(Tabs, {
1838
- defaultValue: "pipeline",
1839
- className: "w-full",
1840
- children: [
1841
- /* @__PURE__ */ jsxDEV6(TabsList, {
1842
- children: [
1843
- /* @__PURE__ */ jsxDEV6(TabsTrigger, {
1844
- value: "pipeline",
1845
- children: [
1846
- /* @__PURE__ */ jsxDEV6("span", {
1847
- className: "mr-2",
1848
- children: "\uD83D\uDCCA"
1849
- }, undefined, false, undefined, this),
1850
- "Pipeline"
1851
- ]
1852
- }, undefined, true, undefined, this),
1853
- /* @__PURE__ */ jsxDEV6(TabsTrigger, {
1854
- value: "list",
1855
- children: [
1856
- /* @__PURE__ */ jsxDEV6("span", {
1857
- className: "mr-2",
1858
- children: "\uD83D\uDCCB"
1859
- }, undefined, false, undefined, this),
1860
- "All Deals"
1861
- ]
1862
- }, undefined, true, undefined, this),
1863
- /* @__PURE__ */ jsxDEV6(TabsTrigger, {
1864
- value: "metrics",
1865
- children: [
1866
- /* @__PURE__ */ jsxDEV6("span", {
1867
- className: "mr-2",
1868
- children: "\uD83D\uDCC8"
1869
- }, undefined, false, undefined, this),
1870
- "Metrics"
1871
- ]
1872
- }, undefined, true, undefined, this)
1873
- ]
1874
- }, undefined, true, undefined, this),
1875
- /* @__PURE__ */ jsxDEV6(TabsContent, {
1876
- value: "pipeline",
1877
- className: "min-h-[400px]",
1878
- children: /* @__PURE__ */ jsxDEV6(CrmPipelineBoard, {
1879
- dealsByStage,
1880
- stages,
1881
- onDealClick: handleDealClick,
1882
- onDealMove: handleDealMove
1883
- }, undefined, false, undefined, this)
1884
- }, undefined, false, undefined, this),
1885
- /* @__PURE__ */ jsxDEV6(TabsContent, {
1886
- value: "list",
1887
- className: "min-h-[400px]",
1888
- children: /* @__PURE__ */ jsxDEV6(DealListTab, {
1889
- onDealClick: handleDealClick
1890
- }, undefined, false, undefined, this)
1891
- }, undefined, false, undefined, this),
1892
- /* @__PURE__ */ jsxDEV6(TabsContent, {
1893
- value: "metrics",
1894
- className: "min-h-[400px]",
1895
- children: /* @__PURE__ */ jsxDEV6(MetricsTab, {
1896
- stats
1897
- }, undefined, false, undefined, this)
1898
- }, undefined, false, undefined, this)
1899
- ]
1900
- }, undefined, true, undefined, this),
1901
- /* @__PURE__ */ jsxDEV6(CreateDealModal, {
1902
- isOpen: isCreateModalOpen,
1903
- onClose: () => setIsCreateModalOpen(false),
1904
- onSubmit: async (input) => {
1905
- await mutations.createDeal(input);
1906
- },
1907
- stages,
1908
- isLoading: mutations.createState.loading
1909
- }, undefined, false, undefined, this),
1910
- /* @__PURE__ */ jsxDEV6(DealActionsModal, {
1911
- isOpen: isDealActionsOpen,
1912
- deal: selectedDeal,
1913
- stages,
1914
- onClose: () => {
1915
- setIsDealActionsOpen(false);
1916
- setSelectedDeal(null);
1917
- },
1918
- onWin: async (input) => {
1919
- await mutations.winDeal(input);
1920
- },
1921
- onLose: async (input) => {
1922
- await mutations.loseDeal(input);
1923
- },
1924
- onMove: async (input) => {
1925
- await mutations.moveDeal(input);
1926
- refetch();
1927
- },
1928
- isLoading: mutations.isLoading
1929
- }, undefined, false, undefined, this)
1930
- ]
1931
- }, undefined, true, undefined, this);
1932
- }
1933
- function MetricsTab({
1934
- stats
1935
- }) {
1936
- if (!stats)
1937
- return null;
1938
- return /* @__PURE__ */ jsxDEV6("div", {
1939
- className: "space-y-6",
1940
- children: /* @__PURE__ */ jsxDEV6("div", {
1941
- className: "rounded-xl border border-border bg-card p-6",
1942
- children: [
1943
- /* @__PURE__ */ jsxDEV6("h3", {
1944
- className: "mb-4 font-semibold text-lg",
1945
- children: "Pipeline Overview"
1946
- }, undefined, false, undefined, this),
1947
- /* @__PURE__ */ jsxDEV6("dl", {
1948
- className: "grid gap-4 sm:grid-cols-3",
1949
- children: [
1950
- /* @__PURE__ */ jsxDEV6("div", {
1951
- children: [
1952
- /* @__PURE__ */ jsxDEV6("dt", {
1953
- className: "text-muted-foreground text-sm",
1954
- children: "Win Rate"
1955
- }, undefined, false, undefined, this),
1956
- /* @__PURE__ */ jsxDEV6("dd", {
1957
- className: "font-semibold text-2xl",
1958
- children: [
1959
- stats.total > 0 ? (stats.wonCount / stats.total * 100).toFixed(0) : 0,
1960
- "%"
1961
- ]
1962
- }, undefined, true, undefined, this)
1963
- ]
1964
- }, undefined, true, undefined, this),
1965
- /* @__PURE__ */ jsxDEV6("div", {
1966
- children: [
1967
- /* @__PURE__ */ jsxDEV6("dt", {
1968
- className: "text-muted-foreground text-sm",
1969
- children: "Avg Deal Size"
1970
- }, undefined, false, undefined, this),
1971
- /* @__PURE__ */ jsxDEV6("dd", {
1972
- className: "font-semibold text-2xl",
1973
- children: formatCurrency5(stats.total > 0 ? stats.totalValue / stats.total : 0)
1974
- }, undefined, false, undefined, this)
1975
- ]
1976
- }, undefined, true, undefined, this),
1977
- /* @__PURE__ */ jsxDEV6("div", {
1978
- children: [
1979
- /* @__PURE__ */ jsxDEV6("dt", {
1980
- className: "text-muted-foreground text-sm",
1981
- children: "Conversion"
1982
- }, undefined, false, undefined, this),
1983
- /* @__PURE__ */ jsxDEV6("dd", {
1984
- className: "font-semibold text-2xl",
1985
- children: [
1986
- stats.wonCount,
1987
- " / ",
1988
- stats.total
1989
- ]
1990
- }, undefined, true, undefined, this)
1991
- ]
1992
- }, undefined, true, undefined, this)
1993
- ]
1994
- }, undefined, true, undefined, this)
1995
- ]
1996
- }, undefined, true, undefined, this)
1997
- }, undefined, false, undefined, this);
1998
- }
1999
-
2000
- // src/ui/hooks/index.ts
2001
- "use client";
2002
- // src/ui/overlays/demo-overlays.ts
2003
- var crmDemoOverlay = {
2004
- overlayId: "crm-pipeline.demo-user",
2005
- version: "1.0.0",
2006
- description: "Demo mode with sample data",
2007
- appliesTo: {
2008
- feature: "crm-pipeline",
2009
- role: "demo"
2010
- },
2011
- modifications: [
2012
- {
2013
- type: "hideField",
2014
- field: "importButton",
2015
- reason: "Not available in demo"
2016
- },
2017
- {
2018
- type: "hideField",
2019
- field: "exportButton",
2020
- reason: "Not available in demo"
2021
- },
2022
- {
2023
- type: "addBadge",
2024
- position: "header",
2025
- label: "Demo Mode",
2026
- variant: "warning"
2027
- }
2028
- ]
2029
- };
2030
- var crmSalesRepOverlay = {
2031
- overlayId: "crm-pipeline.sales-rep",
2032
- version: "1.0.0",
2033
- description: "Sales rep focused view",
2034
- appliesTo: {
2035
- feature: "crm-pipeline",
2036
- role: "sales-rep"
2037
- },
2038
- modifications: [
2039
- {
2040
- type: "hideField",
2041
- field: "teamMetrics",
2042
- reason: "Team metrics for managers only"
2043
- },
2044
- { type: "hideField", field: "pipelineSettings", reason: "Admin only" },
2045
- { type: "renameLabel", field: "deals", newLabel: "My Deals" }
2046
- ]
2047
- };
2048
- var crmOverlays = [
2049
- crmDemoOverlay,
2050
- crmSalesRepOverlay
2051
- ];
2052
- // src/ui/renderers/pipeline.markdown.ts
2053
- function formatCurrency6(value, currency = "USD") {
2054
- return new Intl.NumberFormat("en-US", {
2055
- style: "currency",
2056
- currency,
2057
- minimumFractionDigits: 0
2058
- }).format(value);
2059
- }
2060
- var crmPipelineMarkdownRenderer = {
2061
- target: "markdown",
2062
- render: async (desc, _ctx) => {
2063
- if (desc.source.type !== "component" || desc.source.componentKey !== "PipelineKanbanView") {
2064
- throw new Error("crmPipelineMarkdownRenderer: not PipelineKanbanView");
2065
- }
2066
- const pipelineId = "pipeline-1";
2067
- const [dealsResult, stages] = await Promise.all([
2068
- mockListDealsHandler({ pipelineId, limit: 50 }),
2069
- mockGetPipelineStagesHandler({ pipelineId })
2070
- ]);
2071
- const deals = dealsResult.deals;
2072
- const stageList = stages;
2073
- const dealsByStage = {};
2074
- for (const stage of stageList) {
2075
- dealsByStage[stage.id] = deals.filter((d) => d.stageId === stage.id && d.status === "OPEN");
2076
- }
2077
- const lines = [
2078
- "# CRM Pipeline",
2079
- "",
2080
- `**Total Value**: ${formatCurrency6(dealsResult.totalValue)}`,
2081
- `**Total Deals**: ${dealsResult.total}`,
2082
- ""
2083
- ];
2084
- for (const stage of stageList.sort((a, b) => a.position - b.position)) {
2085
- const stageDeals = dealsByStage[stage.id] ?? [];
2086
- const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
2087
- lines.push(`## ${stage.name}`);
2088
- lines.push(`_${stageDeals.length} deals \xB7 ${formatCurrency6(stageValue)}_`);
2089
- lines.push("");
2090
- if (stageDeals.length === 0) {
2091
- lines.push("_No deals_");
2092
- } else {
2093
- for (const deal of stageDeals) {
2094
- lines.push(`- **${deal.name}** - ${formatCurrency6(deal.value, deal.currency)}`);
2095
- }
2096
- }
2097
- lines.push("");
2098
- }
2099
- return {
2100
- mimeType: "text/markdown",
2101
- body: lines.join(`
2102
- `)
2103
- };
2104
- }
2105
- };
2106
- var crmDashboardMarkdownRenderer = {
2107
- target: "markdown",
2108
- render: async (desc, _ctx) => {
2109
- if (desc.source.type !== "component" || desc.source.componentKey !== "CrmDashboard") {
2110
- throw new Error("crmDashboardMarkdownRenderer: not CrmDashboard");
2111
- }
2112
- const pipelineId = "pipeline-1";
2113
- const [dealsResult, stages] = await Promise.all([
2114
- mockListDealsHandler({ pipelineId, limit: 100 }),
2115
- mockGetPipelineStagesHandler({ pipelineId })
2116
- ]);
2117
- const deals = dealsResult.deals;
2118
- const stageList = stages;
2119
- const openDeals = deals.filter((d) => d.status === "OPEN");
2120
- const wonDeals = deals.filter((d) => d.status === "WON");
2121
- const lostDeals = deals.filter((d) => d.status === "LOST");
2122
- const openValue = openDeals.reduce((sum, d) => sum + d.value, 0);
2123
- const wonValue = wonDeals.reduce((sum, d) => sum + d.value, 0);
2124
- const lines = [
2125
- "# CRM Dashboard",
2126
- "",
2127
- "> Sales pipeline overview and key metrics",
2128
- "",
2129
- "## Summary",
2130
- "",
2131
- "| Metric | Value |",
2132
- "|--------|-------|",
2133
- `| Total Deals | ${dealsResult.total} |`,
2134
- `| Pipeline Value | ${formatCurrency6(dealsResult.totalValue)} |`,
2135
- `| Open Deals | ${openDeals.length} (${formatCurrency6(openValue)}) |`,
2136
- `| Won Deals | ${wonDeals.length} (${formatCurrency6(wonValue)}) |`,
2137
- `| Lost Deals | ${lostDeals.length} |`,
2138
- "",
2139
- "## Pipeline Stages",
2140
- ""
2141
- ];
2142
- lines.push("| Stage | Deals | Value |");
2143
- lines.push("|-------|-------|-------|");
2144
- for (const stage of stageList.sort((a, b) => a.position - b.position)) {
2145
- const stageDeals = openDeals.filter((d) => d.stageId === stage.id);
2146
- const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
2147
- lines.push(`| ${stage.name} | ${stageDeals.length} | ${formatCurrency6(stageValue)} |`);
2148
- }
2149
- lines.push("");
2150
- lines.push("## Recent Deals");
2151
- lines.push("");
2152
- const recentDeals = deals.slice(0, 10);
2153
- if (recentDeals.length === 0) {
2154
- lines.push("_No deals yet._");
2155
- } else {
2156
- lines.push("| Deal | Value | Stage | Status |");
2157
- lines.push("|------|-------|-------|--------|");
2158
- for (const deal of recentDeals) {
2159
- const stage = stageList.find((s) => s.id === deal.stageId);
2160
- lines.push(`| ${deal.name} | ${formatCurrency6(deal.value, deal.currency)} | ${stage?.name ?? "-"} | ${deal.status} |`);
2161
- }
2162
- }
2163
- return {
2164
- mimeType: "text/markdown",
2165
- body: lines.join(`
2166
- `)
2167
- };
2168
- }
2169
- };
2170
-
2171
- // src/ui/renderers/pipeline.renderer.tsx
2172
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
2173
- function CrmPipelineBoardWrapper() {
2174
- const { dealsByStage, stages } = useDealList();
2175
- return /* @__PURE__ */ jsxDEV7(CrmPipelineBoard, {
2176
- dealsByStage,
2177
- stages
2178
- }, undefined, false, undefined, this);
2179
- }
2180
- var crmPipelineReactRenderer = {
2181
- target: "react",
2182
- render: async (desc, _ctx) => {
2183
- if (desc.source.type !== "component") {
2184
- throw new Error("Invalid source type");
2185
- }
2186
- if (desc.source.componentKey !== "CrmPipelineView") {
2187
- throw new Error(`Unknown component: ${desc.source.componentKey}`);
2188
- }
2189
- return /* @__PURE__ */ jsxDEV7(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
2190
- }
2191
- };
2192
- export {
2193
- useDealMutations,
2194
- useDealList,
2195
- crmSalesRepOverlay,
2196
- crmPipelineReactRenderer,
2197
- crmPipelineMarkdownRenderer,
2198
- crmOverlays,
2199
- crmDemoOverlay,
2200
- crmDashboardMarkdownRenderer,
2201
- DealActionsModal,
2202
- CrmPipelineBoard,
2203
- CrmDealCard,
2204
- CrmDashboard,
2205
- CreateDealModal
2206
- };
2
+ import{web as OJ}from"@contractspec/lib.runtime-sandbox";var{generateId:fJ}=OJ;function c(J){return{id:J.id,projectId:J.projectId,name:J.name,value:J.value,currency:J.currency,pipelineId:J.pipelineId,stageId:J.stageId,status:J.status,contactId:J.contactId??void 0,companyId:J.companyId??void 0,ownerId:J.ownerId,expectedCloseDate:J.expectedCloseDate?new Date(J.expectedCloseDate):void 0,wonSource:J.wonSource??void 0,lostReason:J.lostReason??void 0,notes:J.notes??void 0,createdAt:new Date(J.createdAt),updatedAt:new Date(J.updatedAt)}}var NJ={name:"name",value:"value",status:"status",expectedCloseDate:"expectedCloseDate",updatedAt:"updatedAt"};function GX(J){async function Y(X){let{projectId:H,pipelineId:Z,stageId:G,status:$,ownerId:R,search:V,limit:f=20,offset:U=0,sortBy:W="value",sortDirection:z="desc"}=X,O="WHERE projectId = ?",T=[H];if(Z)O+=" AND pipelineId = ?",T.push(Z);if(G)O+=" AND stageId = ?",T.push(G);if($&&$!=="all")O+=" AND status = ?",T.push($);if(R)O+=" AND ownerId = ?",T.push(R);if(V)O+=" AND name LIKE ?",T.push(`%${V}%`);let j=(await J.query(`SELECT COUNT(*) as count FROM crm_deal ${O}`,T)).rows[0]?.count??0,M=(await J.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${O}`,T)).rows[0]?.total??0,_=NJ[W]??NJ.value,r=z==="asc"?"ASC":"DESC";return{deals:(await J.query(`SELECT * FROM crm_deal ${O} ORDER BY ${_} ${r} LIMIT ? OFFSET ?`,[...T,f,U])).rows.map(c),total:j,totalValue:M}}async function Q(X,H){let Z=fJ("deal"),G=new Date().toISOString();await J.execute(`INSERT INTO crm_deal (id, projectId, pipelineId, stageId, name, value, currency, status, contactId, companyId, ownerId, expectedCloseDate, createdAt, updatedAt)
3
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[Z,H.projectId,X.pipelineId,X.stageId,X.name,X.value,X.currency??"USD","OPEN",X.contactId??null,X.companyId??null,H.ownerId,X.expectedCloseDate?.toISOString()??null,G,G]);let $=(await J.query("SELECT * FROM crm_deal WHERE id = ?",[Z])).rows;if(!$[0])throw Error("Failed to create deal");return c($[0])}async function q(X){let H=new Date().toISOString();if(!(await J.query("SELECT * FROM crm_deal WHERE id = ?",[X.dealId])).rows[0])throw Error("NOT_FOUND");if(!(await J.query("SELECT * FROM crm_stage WHERE id = ?",[X.stageId])).rows[0])throw Error("INVALID_STAGE");await J.execute("UPDATE crm_deal SET stageId = ?, updatedAt = ? WHERE id = ?",[X.stageId,H,X.dealId]);let $=(await J.query("SELECT * FROM crm_deal WHERE id = ?",[X.dealId])).rows;return c($[0])}async function F(X){let H=new Date().toISOString();if(!(await J.query("SELECT * FROM crm_deal WHERE id = ?",[X.dealId])).rows[0])throw Error("NOT_FOUND");await J.execute("UPDATE crm_deal SET status = 'WON', wonSource = ?, notes = ?, updatedAt = ? WHERE id = ?",[X.wonSource??null,X.notes??null,H,X.dealId]);let G=(await J.query("SELECT * FROM crm_deal WHERE id = ?",[X.dealId])).rows;return c(G[0])}async function K(X){let H=new Date().toISOString();if(!(await J.query("SELECT * FROM crm_deal WHERE id = ?",[X.dealId])).rows[0])throw Error("NOT_FOUND");await J.execute("UPDATE crm_deal SET status = 'LOST', lostReason = ?, notes = ?, updatedAt = ? WHERE id = ?",[X.lostReason,X.notes??null,H,X.dealId]);let G=(await J.query("SELECT * FROM crm_deal WHERE id = ?",[X.dealId])).rows;return c(G[0])}async function A(X){let H=(await J.query("SELECT * FROM crm_deal WHERE projectId = ? AND pipelineId = ? AND status = 'OPEN' ORDER BY value DESC",[X.projectId,X.pipelineId])).rows,Z=(await J.query("SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position",[X.pipelineId])).rows,G={};for(let $ of Z)G[$.id]=H.filter((R)=>R.stageId===$.id).map(c);return G}async function N(X){return(await J.query("SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position",[X.pipelineId])).rows.map((Z)=>({id:Z.id,pipelineId:Z.pipelineId,name:Z.name,position:Z.position}))}return{listDeals:Y,createDeal:Q,moveDeal:q,winDeal:F,loseDeal:K,getDealsByStage:A,getPipelineStages:N}}var l=[{id:"stage-1",name:"Lead",position:1,pipelineId:"pipeline-1"},{id:"stage-2",name:"Qualified",position:2,pipelineId:"pipeline-1"},{id:"stage-3",name:"Proposal",position:3,pipelineId:"pipeline-1"},{id:"stage-4",name:"Negotiation",position:4,pipelineId:"pipeline-1"},{id:"stage-5",name:"Closed",position:5,pipelineId:"pipeline-1"}],L=[{id:"deal-1",name:"Enterprise License - Acme Corp",value:75000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-3",status:"OPEN",contactId:"contact-1",companyId:"company-1",ownerId:"user-1",expectedCloseDate:new Date("2024-05-15T00:00:00Z"),createdAt:new Date("2024-02-01T10:00:00Z"),updatedAt:new Date("2024-04-10T14:30:00Z")},{id:"deal-2",name:"Startup Plan - TechStart Inc",value:12000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-2",status:"OPEN",contactId:"contact-2",companyId:"company-2",ownerId:"user-2",expectedCloseDate:new Date("2024-04-30T00:00:00Z"),createdAt:new Date("2024-03-15T09:00:00Z"),updatedAt:new Date("2024-04-08T11:15:00Z")},{id:"deal-3",name:"Professional Services - Global Ltd",value:45000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-4",status:"OPEN",contactId:"contact-3",companyId:"company-3",ownerId:"user-1",expectedCloseDate:new Date("2024-04-20T00:00:00Z"),createdAt:new Date("2024-01-20T08:00:00Z"),updatedAt:new Date("2024-04-12T16:45:00Z")},{id:"deal-4",name:"Annual Contract - SmallBiz Co",value:8500,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-1",status:"OPEN",contactId:"contact-4",companyId:"company-4",ownerId:"user-3",createdAt:new Date("2024-04-05T12:00:00Z"),updatedAt:new Date("2024-04-05T12:00:00Z")},{id:"deal-5",name:"Custom Integration - MegaCorp",value:125000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-5",status:"WON",contactId:"contact-5",companyId:"company-5",ownerId:"user-1",expectedCloseDate:new Date("2024-03-31T00:00:00Z"),createdAt:new Date("2023-11-10T10:00:00Z"),updatedAt:new Date("2024-03-28T09:00:00Z")},{id:"deal-6",name:"Pilot Project - NewCo",value:5000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-2",status:"LOST",contactId:"contact-6",companyId:"company-6",ownerId:"user-2",createdAt:new Date("2024-01-15T14:00:00Z"),updatedAt:new Date("2024-02-28T10:30:00Z")}],zX=[{id:"company-1",name:"Acme Corporation",domain:"acme.com",industry:"Technology",size:"1000-5000",website:"https://acme.com",createdAt:new Date("2024-01-01T00:00:00Z")},{id:"company-2",name:"TechStart Inc",domain:"techstart.io",industry:"Software",size:"10-50",website:"https://techstart.io",createdAt:new Date("2024-02-15T00:00:00Z")},{id:"company-3",name:"Global Ltd",domain:"global.com",industry:"Consulting",size:"500-1000",website:"https://global.com",createdAt:new Date("2023-12-01T00:00:00Z")}],FX=[{id:"contact-1",firstName:"John",lastName:"Smith",email:"john.smith@acme.com",phone:"+1-555-0101",title:"VP of Engineering",companyId:"company-1",createdAt:new Date("2024-01-05T00:00:00Z")},{id:"contact-2",firstName:"Sarah",lastName:"Johnson",email:"sarah@techstart.io",phone:"+1-555-0102",title:"CEO",companyId:"company-2",createdAt:new Date("2024-02-20T00:00:00Z")},{id:"contact-3",firstName:"Michael",lastName:"Brown",email:"michael.brown@global.com",phone:"+1-555-0103",title:"CTO",companyId:"company-3",createdAt:new Date("2023-12-10T00:00:00Z")}];async function o(J){let{pipelineId:Y,stageId:Q,status:q,ownerId:F,search:K,limit:A=20,offset:N=0}=J,X=[...L];if(Y)X=X.filter(($)=>$.pipelineId===Y);if(Q)X=X.filter(($)=>$.stageId===Q);if(q&&q!=="all")X=X.filter(($)=>$.status===q);if(F)X=X.filter(($)=>$.ownerId===F);if(K){let $=K.toLowerCase();X=X.filter((R)=>R.name.toLowerCase().includes($))}X.sort(($,R)=>R.value-$.value);let H=X.length,Z=X.reduce(($,R)=>$+R.value,0);return{deals:X.slice(N,N+A),total:H,totalValue:Z}}async function kJ(J,Y){let Q=new Date,q={id:`deal-${Date.now()}`,name:J.name,value:J.value,currency:J.currency??"USD",pipelineId:J.pipelineId,stageId:J.stageId,status:"OPEN",contactId:J.contactId,companyId:J.companyId,ownerId:Y.ownerId,expectedCloseDate:J.expectedCloseDate,createdAt:Q,updatedAt:Q};return L.push(q),q}async function EJ(J){let Y=L.findIndex((K)=>K.id===J.dealId);if(Y===-1)throw Error("NOT_FOUND");let Q=L[Y];if(!Q)throw Error("NOT_FOUND");if(!l.find((K)=>K.id===J.stageId))throw Error("INVALID_STAGE");let F={...Q,stageId:J.stageId,updatedAt:new Date};return L[Y]=F,F}async function TJ(J){let Y=L.findIndex((F)=>F.id===J.dealId);if(Y===-1)throw Error("NOT_FOUND");let Q=L[Y];if(!Q)throw Error("NOT_FOUND");let q={...Q,status:"WON",updatedAt:new Date};return L[Y]=q,q}async function BJ(J){let Y=L.findIndex((F)=>F.id===J.dealId);if(Y===-1)throw Error("NOT_FOUND");let Q=L[Y];if(!Q)throw Error("NOT_FOUND");let q={...Q,status:"LOST",updatedAt:new Date};return L[Y]=q,q}async function vJ(J){let Y=L.filter((q)=>q.pipelineId===J.pipelineId&&q.status==="OPEN"),Q={};for(let q of l)Q[q.id]=Y.filter((F)=>F.stageId===q.id);return Q}async function a(J){return l.filter((Y)=>Y.pipelineId===J.pipelineId)}import{jsx as t,jsxs as PJ}from"react/jsx-runtime";function hJ(J,Y){return new Intl.NumberFormat("en-US",{style:"currency",currency:Y,minimumFractionDigits:0,maximumFractionDigits:0}).format(J)}function KJ({deal:J,onClick:Y}){let Q=J.expectedCloseDate?Math.ceil((J.expectedCloseDate.getTime()-Date.now())/86400000):null;return PJ("div",{onClick:Y,className:"cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",role:"button",tabIndex:0,onKeyDown:(q)=>{if(q.key==="Enter"||q.key===" ")Y?.()},children:[t("h4",{className:"font-medium leading-snug",children:J.name}),t("div",{className:"mt-2 font-semibold text-lg text-primary",children:hJ(J.value,J.currency)}),PJ("div",{className:"mt-3 flex items-center justify-between text-muted-foreground text-xs",children:[Q!==null&&t("span",{className:Q<0?"text-red-500":Q<=7?"text-yellow-600 dark:text-yellow-500":"",children:Q<0?`${Math.abs(Q)}d overdue`:Q===0?"Due today":`${Q}d left`}),t("span",{className:`rounded px-1.5 py-0.5 font-medium text-xs ${J.status==="WON"?"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400":J.status==="LOST"?"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400":"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,children:J.status})]})]})}import{useState as MJ}from"react";import{jsx as C,jsxs as g}from"react/jsx-runtime";function yJ(J){if(J>=1e6)return`$${(J/1e6).toFixed(1)}M`;if(J>=1000)return`$${(J/1000).toFixed(0)}K`;return`$${J}`}function s({dealsByStage:J,stages:Y,onDealClick:Q,onDealMove:q}){let[F,K]=MJ(null),A=[...Y].sort((X,H)=>X.position-H.position),N=(X,H)=>{q?.(X,H),K(null)};return C("div",{className:"flex gap-4 overflow-x-auto pb-4",children:A.map((X)=>{let H=J[X.id]??[],Z=H.reduce((G,$)=>G+$.value,0);return g("div",{className:"flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",children:[g("div",{className:"flex items-center justify-between border-border border-b px-3 py-2",children:[g("div",{children:[C("h3",{className:"font-medium",children:X.name}),g("p",{className:"text-muted-foreground text-xs",children:[H.length," deals \xB7 ",yJ(Z)]})]}),C("span",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",children:H.length})]}),C("div",{className:"flex flex-1 flex-col gap-2 p-2",children:H.length===0?C("div",{className:"flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",children:"No deals"}):H.map((G)=>g("div",{className:"group relative",children:[C(KJ,{deal:G,onClick:()=>Q?.(G.id)}),G.status==="OPEN"&&q&&g("div",{className:"absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",children:[C("button",{type:"button",onClick:($)=>{$.stopPropagation(),K(F===G.id?null:G.id)},className:"flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",title:"Quick move",children:"\u27A1\uFE0F"}),F===G.id&&g("div",{className:"absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",children:[C("p",{className:"px-3 py-1 font-medium text-muted-foreground text-xs",children:"Move to:"}),A.filter(($)=>$.id!==G.stageId).map(($)=>C("button",{type:"button",onClick:(R)=>{R.stopPropagation(),N(G.id,$.id)},className:"w-full px-3 py-1.5 text-left text-sm hover:bg-muted",children:$.name},$.id))]})]})]},G.id))})]},X.id)})})}import{useTemplateRuntime as LJ}from"@contractspec/lib.example-shared-ui";import{useCallback as IJ,useEffect as bJ,useMemo as wJ,useState as p}from"react";function x(J={}){let{handlers:Y,projectId:Q}=LJ(),{crm:q}=Y,[F,K]=p(null),[A,N]=p({}),[X,H]=p([]),[Z,G]=p(!0),[$,R]=p(null),[V,f]=p(0),U=J.pipelineId??"pipeline-1",W=J.pageIndex??V,z=J.pageSize??J.limit??50,[O]=J.sorting??[],T=O?.id,I=O?O.desc?"desc":"asc":void 0,j=IJ(async()=>{G(!0),R(null);try{let[M,_,r]=await Promise.all([q.listDeals({projectId:Q,pipelineId:U,stageId:J.stageId,status:J.status==="all"?void 0:J.status,search:J.search,limit:z,offset:W*z,sortBy:T==="name"||T==="value"||T==="status"||T==="expectedCloseDate"||T==="updatedAt"?T:void 0,sortDirection:I}),q.getDealsByStage({projectId:Q,pipelineId:U}),q.getPipelineStages({pipelineId:U})]);K(M),N(_),H(r)}catch(M){R(M instanceof Error?M:Error("Unknown error"))}finally{G(!1)}},[q,Q,U,J.stageId,J.status,J.search,W,z,T,I]);bJ(()=>{j()},[j]);let n=wJ(()=>{if(!F)return null;let M=F.deals.filter((b)=>b.status==="OPEN"),_=F.deals.filter((b)=>b.status==="WON"),r=F.deals.filter((b)=>b.status==="LOST");return{total:F.total,totalValue:F.totalValue,openCount:M.length,openValue:M.reduce((b,ZJ)=>b+ZJ.value,0),wonCount:_.length,wonValue:_.reduce((b,ZJ)=>b+ZJ.value,0),lostCount:r.length}},[F]);return{data:F,dealsByStage:A,stages:X,loading:Z,error:$,stats:n,page:W+1,pageIndex:W,pageSize:z,refetch:j,nextPage:J.pageIndex===void 0?()=>f((M)=>M+1):void 0,prevPage:J.pageIndex===void 0?()=>W>0&&f((M)=>M-1):void 0}}import{useTemplateRuntime as DJ}from"@contractspec/lib.example-shared-ui";import{useCallback as e,useState as JJ}from"react";function $J(J={}){let{handlers:Y,projectId:Q}=DJ(),{crm:q}=Y,[F,K]=JJ({loading:!1,error:null,data:null}),[A,N]=JJ({loading:!1,error:null,data:null}),[X,H]=JJ({loading:!1,error:null,data:null}),[Z,G]=JJ({loading:!1,error:null,data:null}),$=e(async(U)=>{K({loading:!0,error:null,data:null});try{let W=await q.createDeal(U,{projectId:Q,ownerId:"user-1"});return K({loading:!1,error:null,data:W}),J.onSuccess?.(),W}catch(W){let z=W instanceof Error?W:Error("Failed to create deal");return K({loading:!1,error:z,data:null}),J.onError?.(z),null}},[q,Q,J]),R=e(async(U)=>{N({loading:!0,error:null,data:null});try{let W=await q.moveDeal(U);return N({loading:!1,error:null,data:W}),J.onSuccess?.(),W}catch(W){let z=W instanceof Error?W:Error("Failed to move deal");return N({loading:!1,error:z,data:null}),J.onError?.(z),null}},[q,J]),V=e(async(U)=>{H({loading:!0,error:null,data:null});try{let W=await q.winDeal(U);return H({loading:!1,error:null,data:W}),J.onSuccess?.(),W}catch(W){let z=W instanceof Error?W:Error("Failed to mark deal as won");return H({loading:!1,error:z,data:null}),J.onError?.(z),null}},[q,J]),f=e(async(U)=>{G({loading:!0,error:null,data:null});try{let W=await q.loseDeal(U);return G({loading:!1,error:null,data:W}),J.onSuccess?.(),W}catch(W){let z=W instanceof Error?W:Error("Failed to mark deal as lost");return G({loading:!1,error:z,data:null}),J.onError?.(z),null}},[q,J]);return{createDeal:$,moveDeal:R,winDeal:V,loseDeal:f,createState:F,moveState:A,winState:X,loseState:Z,isLoading:F.loading||A.loading||X.loading||Z.loading}}import{Button as RJ,Input as qJ}from"@contractspec/lib.design-system";import{useState as u}from"react";import{jsx as v,jsxs as w}from"react/jsx-runtime";var mJ=["USD","EUR","GBP","CAD"],CJ="pipeline-1";function HJ({isOpen:J,onClose:Y,onSubmit:Q,stages:q,isLoading:F=!1}){let[K,A]=u(""),[N,X]=u(""),[H,Z]=u("USD"),[G,$]=u(q[0]?.id??""),[R,V]=u(""),[f,U]=u(null),W=async(z)=>{if(z.preventDefault(),U(null),!K.trim()){U("Deal name is required");return}let O=parseFloat(N);if(isNaN(O)||O<=0){U("Value must be a positive number");return}if(!G){U("Please select a pipeline stage");return}try{await Q({name:K.trim(),value:O,currency:H,pipelineId:CJ,stageId:G,expectedCloseDate:R?new Date(R):void 0}),A(""),X(""),Z("USD"),$(q[0]?.id??""),V(""),Y()}catch(T){U(T instanceof Error?T.message:"Failed to create deal")}};if(!J)return null;return w("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[v("div",{className:"absolute inset-0 bg-background/80 backdrop-blur-sm",onClick:Y,role:"button",tabIndex:0,onKeyDown:(z)=>{if(z.key==="Enter"||z.key===" ")Y()},"aria-label":"Close modal"}),w("div",{className:"relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",children:[v("h2",{className:"mb-4 font-semibold text-xl",children:"Create New Deal"}),w("form",{onSubmit:W,className:"space-y-4",children:[w("div",{children:[v("label",{htmlFor:"deal-name",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Deal Name *"}),v(qJ,{id:"deal-name",value:K,onChange:(z)=>A(z.target.value),placeholder:"e.g., Enterprise License - Acme Corp",disabled:F})]}),w("div",{className:"flex gap-3",children:[w("div",{className:"flex-1",children:[v("label",{htmlFor:"deal-value",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Value *"}),v(qJ,{id:"deal-value",type:"number",min:"0",step:"0.01",value:N,onChange:(z)=>X(z.target.value),placeholder:"50000",disabled:F})]}),w("div",{className:"w-24",children:[v("label",{htmlFor:"deal-currency",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Currency"}),v("select",{id:"deal-currency",value:H,onChange:(z)=>Z(z.target.value),disabled:F,className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",children:mJ.map((z)=>v("option",{value:z,children:z},z))})]})]}),w("div",{children:[v("label",{htmlFor:"deal-stage",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Pipeline Stage *"}),v("select",{id:"deal-stage",value:G,onChange:(z)=>$(z.target.value),disabled:F,className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",children:q.map((z)=>v("option",{value:z.id,children:z.name},z.id))})]}),w("div",{children:[v("label",{htmlFor:"deal-close-date",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Expected Close Date"}),v(qJ,{id:"deal-close-date",type:"date",value:R,onChange:(z)=>V(z.target.value),disabled:F})]}),f&&v("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:f}),w("div",{className:"flex justify-end gap-3 pt-2",children:[v(RJ,{type:"button",variant:"ghost",onPress:Y,disabled:F,children:"Cancel"}),v(RJ,{type:"submit",disabled:F,children:F?"Creating...":"Create Deal"})]})]})]})]})}import{Button as D}from"@contractspec/lib.design-system";import{useState as i}from"react";import{jsx as P,jsxs as E,Fragment as jJ}from"react/jsx-runtime";function SJ(J,Y){return new Intl.NumberFormat("en-US",{style:"currency",currency:Y,minimumFractionDigits:0,maximumFractionDigits:0}).format(J)}function GJ({isOpen:J,deal:Y,stages:Q,onClose:q,onWin:F,onLose:K,onMove:A,isLoading:N=!1}){let[X,H]=i("menu"),[Z,G]=i(""),[$,R]=i(""),[V,f]=i(""),[U,W]=i(""),[z,O]=i(null),T=()=>{H("menu"),G(""),R(""),f(""),W(""),O(null)},I=()=>{T(),q()},j=async()=>{if(!Y)return;O(null);try{await F({dealId:Y.id,wonSource:Z.trim()||void 0,notes:V.trim()||void 0}),I()}catch(_){O(_ instanceof Error?_.message:"Failed to mark deal as won")}},n=async()=>{if(!Y)return;if(O(null),!$.trim()){O("Please provide a reason for losing the deal");return}try{await K({dealId:Y.id,lostReason:$.trim(),notes:V.trim()||void 0}),I()}catch(_){O(_ instanceof Error?_.message:"Failed to mark deal as lost")}},M=async()=>{if(!Y)return;if(O(null),!U){O("Please select a stage");return}if(U===Y.stageId){O("Deal is already in this stage");return}try{await A({dealId:Y.id,stageId:U}),I()}catch(_){O(_ instanceof Error?_.message:"Failed to move deal")}};if(!J||!Y)return null;return E("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[P("div",{className:"absolute inset-0 bg-background/80 backdrop-blur-sm",onClick:I,role:"button",tabIndex:0,onKeyDown:(_)=>{if(_.key==="Enter"||_.key===" ")I()},"aria-label":"Close modal"}),E("div",{className:"relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",children:[E("div",{className:"mb-4 border-border border-b pb-4",children:[P("h2",{className:"font-semibold text-xl",children:Y.name}),P("p",{className:"font-medium text-lg text-primary",children:SJ(Y.value,Y.currency)}),P("span",{className:`mt-2 inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${Y.status==="WON"?"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400":Y.status==="LOST"?"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400":"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,children:Y.status})]}),X==="menu"&&E("div",{className:"space-y-3",children:[Y.status==="OPEN"&&E(jJ,{children:[E(D,{className:"w-full justify-start",variant:"ghost",onPress:()=>H("win"),children:[P("span",{className:"mr-2",children:"\uD83C\uDFC6"})," Mark as Won"]}),E(D,{className:"w-full justify-start",variant:"ghost",onPress:()=>H("lose"),children:[P("span",{className:"mr-2",children:"\u274C"})," Mark as Lost"]}),E(D,{className:"w-full justify-start",variant:"ghost",onPress:()=>{W(Y.stageId),H("move")},children:[P("span",{className:"mr-2",children:"\u27A1\uFE0F"})," Move to Stage"]})]}),Y.status!=="OPEN"&&E("p",{className:"py-4 text-center text-muted-foreground",children:["This deal is already ",Y.status.toLowerCase(),". No actions available."]}),P("div",{className:"border-border border-t pt-3",children:P(D,{className:"w-full",variant:"outline",onPress:I,children:"Close"})})]}),X==="win"&&E("div",{className:"space-y-4",children:[E("div",{children:[P("label",{htmlFor:"won-source",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"How did you win this deal?"}),E("select",{id:"won-source",value:Z,onChange:(_)=>G(_.target.value),className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",children:[P("option",{value:"",children:"Select a source..."}),P("option",{value:"referral",children:"Referral"}),P("option",{value:"cold_outreach",children:"Cold Outreach"}),P("option",{value:"inbound",children:"Inbound Lead"}),P("option",{value:"upsell",children:"Upsell"}),P("option",{value:"other",children:"Other"})]})]}),E("div",{children:[P("label",{htmlFor:"win-notes",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Notes (optional)"}),P("textarea",{id:"win-notes",value:V,onChange:(_)=>f(_.target.value),placeholder:"Any additional notes about the win...",rows:3,className:"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"})]}),z&&P("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:z}),E("div",{className:"flex justify-end gap-3 pt-2",children:[P(D,{variant:"ghost",onPress:()=>H("menu"),disabled:N,children:"Back"}),P(D,{onPress:j,disabled:N,children:N?"Processing...":"\uD83C\uDFC6 Confirm Win"})]})]}),X==="lose"&&E("div",{className:"space-y-4",children:[E("div",{children:[P("label",{htmlFor:"lost-reason",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Why was this deal lost? *"}),E("select",{id:"lost-reason",value:$,onChange:(_)=>R(_.target.value),className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",children:[P("option",{value:"",children:"Select a reason..."}),P("option",{value:"price",children:"Price too high"}),P("option",{value:"competitor",children:"Lost to competitor"}),P("option",{value:"no_budget",children:"No budget"}),P("option",{value:"no_decision",children:"No decision made"}),P("option",{value:"timing",children:"Bad timing"}),P("option",{value:"product_fit",children:"Product not a fit"}),P("option",{value:"other",children:"Other"})]})]}),E("div",{children:[P("label",{htmlFor:"lose-notes",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Notes (optional)"}),P("textarea",{id:"lose-notes",value:V,onChange:(_)=>f(_.target.value),placeholder:"Any additional details...",rows:3,className:"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"})]}),z&&P("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:z}),E("div",{className:"flex justify-end gap-3 pt-2",children:[P(D,{variant:"ghost",onPress:()=>H("menu"),disabled:N,children:"Back"}),P(D,{variant:"destructive",onPress:n,disabled:N,children:N?"Processing...":"\u274C Confirm Loss"})]})]}),X==="move"&&E("div",{className:"space-y-4",children:[E("div",{children:[P("label",{htmlFor:"move-stage",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Move to Stage"}),P("select",{id:"move-stage",value:U,onChange:(_)=>W(_.target.value),className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",children:Q.map((_)=>E("option",{value:_.id,children:[_.name,_.id===Y.stageId?" (current)":""]},_.id))})]}),z&&P("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:z}),E("div",{className:"flex justify-end gap-3 pt-2",children:[P(D,{variant:"ghost",onPress:()=>H("menu"),disabled:N,children:"Back"}),P(D,{onPress:M,disabled:N,children:N?"Moving...":"\u27A1\uFE0F Move Deal"})]})]})]})]})}import{Button as gJ,DataTable as xJ,LoaderBlock as cJ}from"@contractspec/lib.design-system";import{useContractTable as pJ}from"@contractspec/lib.presentation-runtime-react";import{Badge as uJ}from"@contractspec/lib.ui-kit-web/ui/badge";import{HStack as d,VStack as QJ}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as y}from"@contractspec/lib.ui-kit-web/ui/text";import*as zJ from"react";import{jsx as B,jsxs as m}from"react/jsx-runtime";function iJ(J,Y="USD"){return new Intl.NumberFormat("en-US",{style:"currency",currency:Y,minimumFractionDigits:0,maximumFractionDigits:0}).format(J)}function rJ(J){switch(J){case"WON":return"default";case"LOST":return"destructive";case"STALE":return"outline";default:return"secondary"}}function lJ({deals:J,totalItems:Y,pageIndex:Q,pageSize:q,sorting:F,loading:K,onSortingChange:A,onPaginationChange:N,onDealClick:X}){let H=pJ({data:J,columns:[{id:"deal",header:"Deal",label:"Deal",accessor:(Z)=>Z.name,cell:({item:Z})=>m(QJ,{gap:"xs",children:[B(y,{className:"font-medium text-sm",children:Z.name}),B(y,{className:"text-muted-foreground text-xs",children:Z.companyId??"Unassigned company"})]}),size:240,minSize:180,canSort:!0,canPin:!0,canResize:!0},{id:"value",header:"Value",label:"Value",accessorKey:"value",cell:({item:Z})=>iJ(Z.value,Z.currency),align:"right",size:140,canSort:!0,canResize:!0},{id:"status",header:"Status",label:"Status",accessorKey:"status",cell:({value:Z})=>B(uJ,{variant:rJ(Z),children:String(Z)}),size:130,canSort:!0,canHide:!0,canPin:!0,canResize:!0},{id:"expectedCloseDate",header:"Expected Close",label:"Expected Close",accessor:(Z)=>Z.expectedCloseDate?.toISOString()??"",cell:({item:Z})=>Z.expectedCloseDate?.toLocaleDateString()??"Not scheduled",size:170,canSort:!0,canHide:!0,canResize:!0},{id:"updatedAt",header:"Updated",label:"Updated",accessor:(Z)=>Z.updatedAt.toISOString(),cell:({item:Z})=>Z.updatedAt.toLocaleDateString(),size:140,canSort:!0,canHide:!0,canResize:!0},{id:"actions",header:"Actions",label:"Actions",accessor:(Z)=>Z.id,cell:({item:Z})=>B(gJ,{variant:"ghost",size:"sm",onPress:()=>X?.(Z.id),children:"Actions"}),size:120,canSort:!1,canHide:!1,canPin:!1,canResize:!1}],executionMode:"server",selectionMode:"multiple",totalItems:Y,state:{sorting:F,pagination:{pageIndex:Q,pageSize:q}},onSortingChange:A,onPaginationChange:N,initialState:{columnVisibility:{updatedAt:!1},columnPinning:{left:["deal","status"],right:[]}},renderExpandedContent:(Z)=>m(QJ,{gap:"sm",className:"py-2",children:[m(d,{justify:"between",children:[B(y,{className:"font-medium text-sm",children:"Owner"}),B(y,{className:"text-muted-foreground text-sm",children:Z.ownerId})]}),m(d,{justify:"between",children:[B(y,{className:"font-medium text-sm",children:"Contact"}),B(y,{className:"text-muted-foreground text-sm",children:Z.contactId??"No linked contact"})]}),Z.wonSource?m(d,{justify:"between",children:[B(y,{className:"font-medium text-sm",children:"Won Source"}),B(y,{className:"text-muted-foreground text-sm",children:Z.wonSource})]}):null,Z.lostReason?m(d,{justify:"between",children:[B(y,{className:"font-medium text-sm",children:"Lost Reason"}),B(y,{className:"text-muted-foreground text-sm",children:Z.lostReason})]}):null,Z.notes?m(QJ,{gap:"xs",children:[B(y,{className:"font-medium text-sm",children:"Notes"}),B(y,{className:"text-muted-foreground text-sm",children:Z.notes})]}):null]}),getCanExpand:()=>!0});return B(xJ,{controller:H,title:"All Deals",description:"Server-mode table using the shared ContractSpec controller.",loading:K,toolbar:m(d,{gap:"sm",className:"flex-wrap",children:[m(y,{className:"text-muted-foreground text-sm",children:["Selected ",H.selectedRowIds.length]}),m(y,{className:"text-muted-foreground text-sm",children:[Y," total deals"]})]}),footer:`Page ${H.pageIndex+1} of ${H.pageCount}`,emptyState:B("div",{className:"rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",children:"No deals found"})})}function VJ({onDealClick:J}){let[Y,Q]=zJ.useState([{id:"value",desc:!0}]),[q,F]=zJ.useState({pageIndex:0,pageSize:3}),{data:K,loading:A}=x({pageIndex:q.pageIndex,pageSize:q.pageSize,sorting:Y});if(A&&!K)return B(cJ,{label:"Loading deals..."});return B(lJ,{deals:K?.deals??[],totalItems:K?.total??0,pageIndex:q.pageIndex,pageSize:q.pageSize,sorting:Y,loading:A,onSortingChange:(N)=>{Q(N),F((X)=>({...X,pageIndex:0}))},onPaginationChange:F,onDealClick:J})}import{Button as dJ,ErrorState as nJ,LoaderBlock as oJ,StatCard as XJ,StatCardGroup as aJ}from"@contractspec/lib.design-system";import{Tabs as tJ,TabsContent as FJ,TabsList as sJ,TabsTrigger as WJ}from"@contractspec/lib.ui-kit-web/ui/tabs";import{useCallback as _J,useState as UJ}from"react";import{jsx as k,jsxs as h}from"react/jsx-runtime";function YJ(J,Y="USD"){return new Intl.NumberFormat("en-US",{style:"currency",currency:Y,minimumFractionDigits:0,maximumFractionDigits:0}).format(J)}function XY(){let[J,Y]=UJ(!1),[Q,q]=UJ(null),[F,K]=UJ(!1),{data:A,dealsByStage:N,stages:X,loading:H,error:Z,stats:G,refetch:$}=x(),R=$J({onSuccess:()=>{$()}}),V=_J((U)=>{let W=N?Object.values(N).flat().find((z)=>z.id===U):null;if(W)q(W),K(!0)},[N]),f=_J(async(U,W)=>{await R.moveDeal({dealId:U,stageId:W})},[R]);if(H&&!A)return k(oJ,{label:"Loading CRM..."});if(Z)return k(nJ,{title:"Failed to load CRM",description:Z.message,onRetry:$,retryLabel:"Retry"});return h("div",{className:"space-y-6",children:[h("div",{className:"flex items-center justify-between",children:[k("h2",{className:"font-bold text-2xl",children:"CRM Pipeline"}),h(dJ,{onClick:()=>Y(!0),children:[k("span",{className:"mr-2",children:"+"})," Create Deal"]})]}),G&&h(aJ,{children:[k(XJ,{label:"Total Pipeline",value:YJ(G.totalValue),hint:`${G.total} deals`}),k(XJ,{label:"Open Deals",value:YJ(G.openValue),hint:`${G.openCount} active`}),k(XJ,{label:"Won",value:YJ(G.wonValue),hint:`${G.wonCount} closed`}),k(XJ,{label:"Lost",value:G.lostCount,hint:"deals lost"})]}),h(tJ,{defaultValue:"pipeline",className:"w-full",children:[h(sJ,{children:[h(WJ,{value:"pipeline",children:[k("span",{className:"mr-2",children:"\uD83D\uDCCA"}),"Pipeline"]}),h(WJ,{value:"list",children:[k("span",{className:"mr-2",children:"\uD83D\uDCCB"}),"All Deals"]}),h(WJ,{value:"metrics",children:[k("span",{className:"mr-2",children:"\uD83D\uDCC8"}),"Metrics"]})]}),k(FJ,{value:"pipeline",className:"min-h-[400px]",children:k(s,{dealsByStage:N,stages:X,onDealClick:V,onDealMove:f})}),k(FJ,{value:"list",className:"min-h-[400px]",children:k(VJ,{onDealClick:V})}),k(FJ,{value:"metrics",className:"min-h-[400px]",children:k(eJ,{stats:G})})]}),k(HJ,{isOpen:J,onClose:()=>Y(!1),onSubmit:async(U)=>{await R.createDeal(U)},stages:X,isLoading:R.createState.loading}),k(GJ,{isOpen:F,deal:Q,stages:X,onClose:()=>{K(!1),q(null)},onWin:async(U)=>{await R.winDeal(U)},onLose:async(U)=>{await R.loseDeal(U)},onMove:async(U)=>{await R.moveDeal(U),$()},isLoading:R.isLoading})]})}function eJ({stats:J}){if(!J)return null;return k("div",{className:"space-y-6",children:h("div",{className:"rounded-xl border border-border bg-card p-6",children:[k("h3",{className:"mb-4 font-semibold text-lg",children:"Pipeline Overview"}),h("dl",{className:"grid gap-4 sm:grid-cols-3",children:[h("div",{children:[k("dt",{className:"text-muted-foreground text-sm",children:"Win Rate"}),h("dd",{className:"font-semibold text-2xl",children:[J.total>0?(J.wonCount/J.total*100).toFixed(0):0,"%"]})]}),h("div",{children:[k("dt",{className:"text-muted-foreground text-sm",children:"Avg Deal Size"}),k("dd",{className:"font-semibold text-2xl",children:YJ(J.total>0?J.totalValue/J.total:0)})]}),h("div",{children:[k("dt",{className:"text-muted-foreground text-sm",children:"Conversion"}),h("dd",{className:"font-semibold text-2xl",children:[J.wonCount," / ",J.total]})]})]})]})})}var JX={overlayId:"crm-pipeline.demo-user",version:"1.0.0",description:"Demo mode with sample data",appliesTo:{feature:"crm-pipeline",role:"demo"},modifications:[{type:"hideField",field:"importButton",reason:"Not available in demo"},{type:"hideField",field:"exportButton",reason:"Not available in demo"},{type:"addBadge",position:"header",label:"Demo Mode",variant:"warning"}]},XX={overlayId:"crm-pipeline.sales-rep",version:"1.0.0",description:"Sales rep focused view",appliesTo:{feature:"crm-pipeline",role:"sales-rep"},modifications:[{type:"hideField",field:"teamMetrics",reason:"Team metrics for managers only"},{type:"hideField",field:"pipelineSettings",reason:"Admin only"},{type:"renameLabel",field:"deals",newLabel:"My Deals"}]},FY=[JX,XX];function S(J,Y="USD"){return new Intl.NumberFormat("en-US",{style:"currency",currency:Y,minimumFractionDigits:0}).format(J)}var YX={target:"markdown",render:async(J,Y)=>{if(J.source.type!=="component"||J.source.componentKey!=="PipelineKanbanView")throw Error("crmPipelineMarkdownRenderer: not PipelineKanbanView");let Q="pipeline-1",[q,F]=await Promise.all([o({pipelineId:Q,limit:50}),a({pipelineId:Q})]),K=q.deals,A=F,N={};for(let H of A)N[H.id]=K.filter((Z)=>Z.stageId===H.id&&Z.status==="OPEN");let X=["# CRM Pipeline","",`**Total Value**: ${S(q.totalValue)}`,`**Total Deals**: ${q.total}`,""];for(let H of A.sort((Z,G)=>Z.position-G.position)){let Z=N[H.id]??[],G=Z.reduce(($,R)=>$+R.value,0);if(X.push(`## ${H.name}`),X.push(`_${Z.length} deals \xB7 ${S(G)}_`),X.push(""),Z.length===0)X.push("_No deals_");else for(let $ of Z)X.push(`- **${$.name}** - ${S($.value,$.currency)}`);X.push("")}return{mimeType:"text/markdown",body:X.join(`
4
+ `)}}},ZX={target:"markdown",render:async(J,Y)=>{if(J.source.type!=="component"||J.source.componentKey!=="CrmDashboard")throw Error("crmDashboardMarkdownRenderer: not CrmDashboard");let Q="pipeline-1",[q,F]=await Promise.all([o({pipelineId:Q,limit:100}),a({pipelineId:Q})]),K=q.deals,A=F,N=K.filter((V)=>V.status==="OPEN"),X=K.filter((V)=>V.status==="WON"),H=K.filter((V)=>V.status==="LOST"),Z=N.reduce((V,f)=>V+f.value,0),G=X.reduce((V,f)=>V+f.value,0),$=["# CRM Dashboard","","> Sales pipeline overview and key metrics","","## Summary","","| Metric | Value |","|--------|-------|",`| Total Deals | ${q.total} |`,`| Pipeline Value | ${S(q.totalValue)} |`,`| Open Deals | ${N.length} (${S(Z)}) |`,`| Won Deals | ${X.length} (${S(G)}) |`,`| Lost Deals | ${H.length} |`,"","## Pipeline Stages",""];$.push("| Stage | Deals | Value |"),$.push("|-------|-------|-------|");for(let V of A.sort((f,U)=>f.position-U.position)){let f=N.filter((W)=>W.stageId===V.id),U=f.reduce((W,z)=>W+z.value,0);$.push(`| ${V.name} | ${f.length} | ${S(U)} |`)}$.push(""),$.push("## Recent Deals"),$.push("");let R=K.slice(0,10);if(R.length===0)$.push("_No deals yet._");else{$.push("| Deal | Value | Stage | Status |"),$.push("|------|-------|-------|--------|");for(let V of R){let f=A.find((U)=>U.id===V.stageId);$.push(`| ${V.name} | ${S(V.value,V.currency)} | ${f?.name??"-"} | ${V.status} |`)}}return{mimeType:"text/markdown",body:$.join(`
5
+ `)}}};import{jsx as AJ}from"react/jsx-runtime";function $X(){let{dealsByStage:J,stages:Y}=x();return AJ(s,{dealsByStage:J,stages:Y})}var qX={target:"react",render:async(J,Y)=>{if(J.source.type!=="component")throw Error("Invalid source type");if(J.source.componentKey!=="CrmPipelineView")throw Error(`Unknown component: ${J.source.componentKey}`);return AJ($X,{})}};export{$J as useDealMutations,x as useDealList,XX as crmSalesRepOverlay,qX as crmPipelineReactRenderer,YX as crmPipelineMarkdownRenderer,FY as crmOverlays,JX as crmDemoOverlay,ZX as crmDashboardMarkdownRenderer,GJ as DealActionsModal,s as CrmPipelineBoard,KJ as CrmDealCard,XY as CrmDashboard,HJ as CreateDealModal};