@contractspec/example.crm-pipeline 1.57.0 → 1.59.0

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