@contractspec/example.crm-pipeline 1.57.0 → 1.58.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 +20 -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
package/dist/index.js CHANGED
@@ -1,56 +1,3280 @@
1
- import { CrmPipelineFeature } from "./crm-pipeline.feature.js";
2
- import example from "./example.js";
3
- import { CompanyEntity, CompanySizeEnum } from "./entities/company.entity.js";
4
- import { ContactEntity, ContactStatusEnum } from "./entities/contact.entity.js";
5
- import { DealEntity, DealStatusEnum, PipelineEntity, StageEntity } from "./entities/deal.entity.js";
6
- import { ActivityEntity, TaskEntity, TaskPriorityEnum, TaskStatusEnum, TaskTypeEnum } from "./entities/task.entity.js";
7
- import { crmPipelineSchemaContribution } from "./entities/index.js";
8
- import { DealStatusFilterEnum } from "./deal/deal.enum.js";
9
- import { CreateDealInputModel, DealLostPayloadModel, DealModel, DealMovedPayloadModel, DealWonPayloadModel, ListDealsInputModel, ListDealsOutputModel, LoseDealInputModel, MoveDealInputModel, WinDealInputModel } from "./deal/deal.schema.js";
10
- import { CreateDealContract, ListDealsContract, LoseDealContract, MoveDealContract, WinDealContract } from "./deal/deal.operation.js";
11
- import "./operations/index.js";
12
- import { ContactCreatedEvent } from "./events/contact.event.js";
13
- import { DealCreatedEvent, DealLostEvent, DealMovedEvent, DealWonEvent } from "./events/deal.event.js";
14
- import { TaskCompletedEvent } from "./events/task.event.js";
15
- import "./events/index.js";
16
- import { MOCK_COMPANIES, MOCK_CONTACTS, MOCK_DEALS, MOCK_STAGES } from "./handlers/mock-data.js";
17
- import { mockCreateDealHandler, mockGetDealsByStageHandler, mockGetPipelineStagesHandler, mockListDealsHandler, mockLoseDealHandler, mockMoveDealHandler, mockWinDealHandler } from "./handlers/deal.handlers.js";
18
- import { createCrmHandlers } from "./handlers/crm.handlers.js";
19
- import "./handlers/index.js";
20
- import { CrmDashboardPresentation, PipelineMetricsPresentation } from "./presentations/dashboard.presentation.js";
21
- import { DealCardPresentation, DealDetailPresentation, DealListPresentation, PipelineKanbanPresentation } from "./presentations/pipeline.presentation.js";
22
- import "./presentations/index.js";
23
- import { useDealList } from "./ui/hooks/useDealList.js";
24
- import { useDealMutations } from "./ui/hooks/useDealMutations.js";
25
- import { CrmDealCard } from "./ui/CrmDealCard.js";
26
- import { CrmPipelineBoard } from "./ui/CrmPipelineBoard.js";
27
- import { CreateDealModal } from "./ui/modals/CreateDealModal.js";
28
- import { DealActionsModal } from "./ui/modals/DealActionsModal.js";
29
- import { CrmDashboard } from "./ui/CrmDashboard.js";
30
- import { crmPipelineReactRenderer } from "./ui/renderers/pipeline.renderer.js";
31
- import { crmDashboardMarkdownRenderer, crmPipelineMarkdownRenderer } from "./ui/renderers/pipeline.markdown.js";
32
- import { crmDemoOverlay, crmOverlays, crmSalesRepOverlay } from "./ui/overlays/demo-overlays.js";
33
- import "./ui/index.js";
34
- import "./docs/index.js";
1
+ // @bun
2
+ // src/crm-pipeline.feature.ts
3
+ import { defineFeature } from "@contractspec/lib.contracts";
4
+ var CrmPipelineFeature = defineFeature({
5
+ meta: {
6
+ key: "crm-pipeline",
7
+ title: "CRM Pipeline",
8
+ description: "CRM and sales pipeline management with deals, contacts, and companies",
9
+ domain: "crm",
10
+ owners: ["@crm-team"],
11
+ tags: ["crm", "sales", "pipeline", "deals"],
12
+ stability: "experimental",
13
+ version: "1.0.0"
14
+ },
15
+ operations: [
16
+ { key: "crm.deal.create", version: "1.0.0" },
17
+ { key: "crm.deal.move", version: "1.0.0" },
18
+ { key: "crm.deal.win", version: "1.0.0" },
19
+ { key: "crm.deal.lose", version: "1.0.0" },
20
+ { key: "crm.deal.list", version: "1.0.0" }
21
+ ],
22
+ events: [
23
+ { key: "deal.created", version: "1.0.0" },
24
+ { key: "deal.moved", version: "1.0.0" },
25
+ { key: "deal.won", version: "1.0.0" },
26
+ { key: "deal.lost", version: "1.0.0" },
27
+ { key: "contact.created", version: "1.0.0" },
28
+ { key: "task.completed", version: "1.0.0" }
29
+ ],
30
+ presentations: [
31
+ { key: "crm.dashboard", version: "1.0.0" },
32
+ { key: "crm.pipeline.kanban", version: "1.0.0" },
33
+ { key: "crm.deal.viewList", version: "1.0.0" },
34
+ { key: "crm.deal.detail", version: "1.0.0" },
35
+ { key: "crm.deal.card", version: "1.0.0" },
36
+ { key: "crm.pipeline.metrics", version: "1.0.0" }
37
+ ],
38
+ opToPresentation: [
39
+ {
40
+ op: { key: "crm.deal.list", version: "1.0.0" },
41
+ pres: { key: "crm.pipeline.kanban", version: "1.0.0" }
42
+ },
43
+ {
44
+ op: { key: "crm.deal.move", version: "1.0.0" },
45
+ pres: { key: "crm.pipeline.kanban", version: "1.0.0" }
46
+ }
47
+ ],
48
+ presentationsTargets: [
49
+ { key: "crm.dashboard", version: "1.0.0", targets: ["react", "markdown"] },
50
+ {
51
+ key: "crm.pipeline.kanban",
52
+ version: "1.0.0",
53
+ targets: ["react", "markdown"]
54
+ },
55
+ {
56
+ key: "crm.deal.viewList",
57
+ version: "1.0.0",
58
+ targets: ["react", "markdown", "application/json"]
59
+ },
60
+ {
61
+ key: "crm.pipeline.metrics",
62
+ version: "1.0.0",
63
+ targets: ["react", "markdown"]
64
+ }
65
+ ],
66
+ capabilities: {
67
+ requires: [
68
+ { key: "identity", version: "1.0.0" },
69
+ { key: "audit-trail", version: "1.0.0" },
70
+ { key: "notifications", version: "1.0.0" }
71
+ ]
72
+ }
73
+ });
74
+
75
+ // src/deal/deal.enum.ts
76
+ import { defineEnum } from "@contractspec/lib.schema";
77
+ var DealStatusEnum = defineEnum("DealStatus", [
78
+ "OPEN",
79
+ "WON",
80
+ "LOST",
81
+ "STALE"
82
+ ]);
83
+ var DealStatusFilterEnum = defineEnum("DealStatusFilter", [
84
+ "OPEN",
85
+ "WON",
86
+ "LOST",
87
+ "all"
88
+ ]);
89
+
90
+ // src/deal/deal.schema.ts
91
+ import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
92
+ var DealModel = defineSchemaModel({
93
+ name: "Deal",
94
+ description: "A deal in the CRM pipeline",
95
+ fields: {
96
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
97
+ name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
98
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
99
+ currency: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
100
+ pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
101
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
102
+ status: { type: DealStatusEnum, isOptional: false },
103
+ contactId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
104
+ companyId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
105
+ ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
106
+ expectedCloseDate: { type: ScalarTypeEnum.DateTime(), isOptional: true },
107
+ createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
108
+ updatedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
109
+ }
110
+ });
111
+ var CreateDealInputModel = defineSchemaModel({
112
+ name: "CreateDealInput",
113
+ description: "Input for creating a deal",
114
+ fields: {
115
+ name: { type: ScalarTypeEnum.NonEmptyString(), isOptional: false },
116
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
117
+ currency: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
118
+ pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
119
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
120
+ contactId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
121
+ companyId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
122
+ expectedCloseDate: { type: ScalarTypeEnum.DateTime(), isOptional: true }
123
+ }
124
+ });
125
+ var MoveDealInputModel = defineSchemaModel({
126
+ name: "MoveDealInput",
127
+ description: "Input for moving a deal to another stage",
128
+ fields: {
129
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
130
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
131
+ position: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
132
+ }
133
+ });
134
+ var DealMovedPayloadModel = defineSchemaModel({
135
+ name: "DealMovedPayload",
136
+ fields: {
137
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
138
+ fromStage: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
139
+ toStage: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
140
+ }
141
+ });
142
+ var WinDealInputModel = defineSchemaModel({
143
+ name: "WinDealInput",
144
+ description: "Input for marking a deal as won",
145
+ fields: {
146
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
147
+ wonSource: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
148
+ notes: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
149
+ }
150
+ });
151
+ var DealWonPayloadModel = defineSchemaModel({
152
+ name: "DealWonPayload",
153
+ fields: {
154
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
155
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false }
156
+ }
157
+ });
158
+ var LoseDealInputModel = defineSchemaModel({
159
+ name: "LoseDealInput",
160
+ description: "Input for marking a deal as lost",
161
+ fields: {
162
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
163
+ lostReason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
164
+ notes: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
165
+ }
166
+ });
167
+ var DealLostPayloadModel = defineSchemaModel({
168
+ name: "DealLostPayload",
169
+ fields: {
170
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
171
+ reason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
172
+ }
173
+ });
174
+ var ListDealsInputModel = defineSchemaModel({
175
+ name: "ListDealsInput",
176
+ description: "Input for listing deals",
177
+ fields: {
178
+ pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
179
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
180
+ status: { type: DealStatusFilterEnum, isOptional: true },
181
+ ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
182
+ search: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
183
+ limit: {
184
+ type: ScalarTypeEnum.Int_unsecure(),
185
+ isOptional: true,
186
+ defaultValue: 20
187
+ },
188
+ offset: {
189
+ type: ScalarTypeEnum.Int_unsecure(),
190
+ isOptional: true,
191
+ defaultValue: 0
192
+ }
193
+ }
194
+ });
195
+ var ListDealsOutputModel = defineSchemaModel({
196
+ name: "ListDealsOutput",
197
+ description: "Output for listing deals",
198
+ fields: {
199
+ deals: { type: DealModel, isArray: true, isOptional: false },
200
+ total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
201
+ totalValue: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false }
202
+ }
203
+ });
204
+
205
+ // src/deal/deal.operation.ts
206
+ import {
207
+ defineCommand,
208
+ defineQuery
209
+ } from "@contractspec/lib.contracts/operations";
210
+ var OWNERS = ["@example.crm-pipeline"];
211
+ var CreateDealContract = defineCommand({
212
+ meta: {
213
+ key: "crm.deal.create",
214
+ version: "1.0.0",
215
+ stability: "stable",
216
+ owners: [...OWNERS],
217
+ tags: ["crm", "deal", "create"],
218
+ description: "Create a new deal in the pipeline.",
219
+ goal: "Allow sales reps to create new opportunities.",
220
+ context: "Deal creation UI, quick add."
221
+ },
222
+ io: {
223
+ input: CreateDealInputModel,
224
+ output: DealModel
225
+ },
226
+ policy: {
227
+ auth: "user"
228
+ },
229
+ sideEffects: {
230
+ emits: [
231
+ {
232
+ key: "deal.created",
233
+ version: "1.0.0",
234
+ when: "Deal is created",
235
+ payload: DealModel
236
+ }
237
+ ],
238
+ audit: ["deal.created"]
239
+ },
240
+ acceptance: {
241
+ scenarios: [
242
+ {
243
+ key: "create-deal-happy-path",
244
+ given: ["User is authenticated"],
245
+ when: ["User creates a deal with valid data"],
246
+ then: ["Deal is created", "DealCreated event is emitted"]
247
+ }
248
+ ],
249
+ examples: [
250
+ {
251
+ key: "create-basic-deal",
252
+ input: {
253
+ title: "Big Corp Q3 License",
254
+ stageId: "stage-lead",
255
+ value: 50000,
256
+ companyId: "comp-123"
257
+ },
258
+ output: {
259
+ id: "deal-789",
260
+ title: "Big Corp Q3 License",
261
+ status: "open"
262
+ }
263
+ }
264
+ ]
265
+ }
266
+ });
267
+ var MoveDealContract = defineCommand({
268
+ meta: {
269
+ key: "crm.deal.move",
270
+ version: "1.0.0",
271
+ stability: "stable",
272
+ owners: [...OWNERS],
273
+ tags: ["crm", "deal", "move", "kanban"],
274
+ description: "Move a deal to a different stage.",
275
+ goal: "Allow drag-and-drop stage movement in Kanban.",
276
+ context: "Pipeline Kanban view."
277
+ },
278
+ io: {
279
+ input: MoveDealInputModel,
280
+ output: DealModel
281
+ },
282
+ policy: {
283
+ auth: "user"
284
+ },
285
+ sideEffects: {
286
+ emits: [
287
+ {
288
+ key: "deal.moved",
289
+ version: "1.0.0",
290
+ when: "Deal stage changed",
291
+ payload: DealMovedPayloadModel
292
+ }
293
+ ],
294
+ audit: ["deal.moved"]
295
+ },
296
+ acceptance: {
297
+ scenarios: [
298
+ {
299
+ key: "move-deal-happy-path",
300
+ given: ["Deal exists in stage A"],
301
+ when: ["User moves deal to stage B"],
302
+ then: ["Deal stage is updated", "DealMoved event is emitted"]
303
+ }
304
+ ],
305
+ examples: [
306
+ {
307
+ key: "move-to-negotiation",
308
+ input: { dealId: "deal-789", targetStageId: "stage-negotiation" },
309
+ output: {
310
+ id: "deal-789",
311
+ stageId: "stage-negotiation",
312
+ movedAt: "2025-01-15T10:00:00Z"
313
+ }
314
+ }
315
+ ]
316
+ }
317
+ });
318
+ var WinDealContract = defineCommand({
319
+ meta: {
320
+ key: "crm.deal.win",
321
+ version: "1.0.0",
322
+ stability: "stable",
323
+ owners: [...OWNERS],
324
+ tags: ["crm", "deal", "won"],
325
+ description: "Mark a deal as won.",
326
+ goal: "Close a deal as successful.",
327
+ context: "Deal closing flow."
328
+ },
329
+ io: {
330
+ input: WinDealInputModel,
331
+ output: DealModel
332
+ },
333
+ policy: {
334
+ auth: "user"
335
+ },
336
+ sideEffects: {
337
+ emits: [
338
+ {
339
+ key: "deal.won",
340
+ version: "1.0.0",
341
+ when: "Deal is won",
342
+ payload: DealWonPayloadModel
343
+ }
344
+ ],
345
+ audit: ["deal.won"]
346
+ },
347
+ acceptance: {
348
+ scenarios: [
349
+ {
350
+ key: "win-deal-happy-path",
351
+ given: ["Deal is open"],
352
+ when: ["User marks deal as won"],
353
+ then: ["Deal status becomes WON", "DealWon event is emitted"]
354
+ }
355
+ ],
356
+ examples: [
357
+ {
358
+ key: "mark-won",
359
+ input: {
360
+ dealId: "deal-789",
361
+ actualValue: 52000,
362
+ note: "Signed contract attached"
363
+ },
364
+ output: {
365
+ id: "deal-789",
366
+ status: "won",
367
+ closedAt: "2025-01-20T14:30:00Z"
368
+ }
369
+ }
370
+ ]
371
+ }
372
+ });
373
+ var LoseDealContract = defineCommand({
374
+ meta: {
375
+ key: "crm.deal.lose",
376
+ version: "1.0.0",
377
+ stability: "stable",
378
+ owners: [...OWNERS],
379
+ tags: ["crm", "deal", "lost"],
380
+ description: "Mark a deal as lost.",
381
+ goal: "Close a deal as unsuccessful.",
382
+ context: "Deal closing flow."
383
+ },
384
+ io: {
385
+ input: LoseDealInputModel,
386
+ output: DealModel
387
+ },
388
+ policy: {
389
+ auth: "user"
390
+ },
391
+ sideEffects: {
392
+ emits: [
393
+ {
394
+ key: "deal.lost",
395
+ version: "1.0.0",
396
+ when: "Deal is lost",
397
+ payload: DealLostPayloadModel
398
+ }
399
+ ],
400
+ audit: ["deal.lost"]
401
+ },
402
+ acceptance: {
403
+ scenarios: [
404
+ {
405
+ key: "lose-deal-happy-path",
406
+ given: ["Deal is open"],
407
+ when: ["User marks deal as lost"],
408
+ then: ["Deal status becomes LOST", "DealLost event is emitted"]
409
+ }
410
+ ],
411
+ examples: [
412
+ {
413
+ key: "mark-lost",
414
+ input: {
415
+ dealId: "deal-789",
416
+ reason: "competitor",
417
+ note: "Went with cheaper option"
418
+ },
419
+ output: {
420
+ id: "deal-789",
421
+ status: "lost",
422
+ closedAt: "2025-01-21T09:00:00Z"
423
+ }
424
+ }
425
+ ]
426
+ }
427
+ });
428
+ var ListDealsContract = defineQuery({
429
+ meta: {
430
+ key: "crm.deal.list",
431
+ version: "1.0.0",
432
+ stability: "stable",
433
+ owners: [...OWNERS],
434
+ tags: ["crm", "deal", "list"],
435
+ description: "List deals with filters.",
436
+ goal: "Show pipeline, deal lists, dashboards.",
437
+ context: "Pipeline view, deal list."
438
+ },
439
+ io: {
440
+ input: ListDealsInputModel,
441
+ output: ListDealsOutputModel
442
+ },
443
+ policy: {
444
+ auth: "user"
445
+ },
446
+ acceptance: {
447
+ scenarios: [
448
+ {
449
+ key: "list-deals-happy-path",
450
+ given: ["User has access to deals"],
451
+ when: ["User lists deals"],
452
+ then: ["List of deals is returned"]
453
+ }
454
+ ],
455
+ examples: [
456
+ {
457
+ key: "list-filter-stage",
458
+ input: { stageId: "stage-lead", limit: 20 },
459
+ output: { items: [], total: 5, hasMore: false }
460
+ }
461
+ ]
462
+ }
463
+ });
464
+ // src/docs/crm-pipeline.docblock.ts
465
+ import { registerDocBlocks } from "@contractspec/lib.contracts/docs";
466
+ var crmPipelineDocBlocks = [
467
+ {
468
+ id: "docs.examples.crm-pipeline.goal",
469
+ title: "CRM Pipeline \u2014 Goal",
470
+ summary: "Deals, stages, contacts, companies, and tasks with auditable stage movement.",
471
+ kind: "goal",
472
+ visibility: "public",
473
+ route: "/docs/examples/crm-pipeline/goal",
474
+ tags: ["crm", "goal"],
475
+ body: `## Why it matters
476
+ - Regenerable CRM flow for deals/stages without code drift.
477
+ - Ensures stage movement, tasks, and contacts stay aligned across surfaces.
478
+
479
+ ## Business/Product goal
480
+ - Give sales teams a governed pipeline with auditable moves and notifications.
481
+ - Allow experimentation (feature flags) on stage definitions and task flows.
482
+
483
+ ## Success criteria
484
+ - Stage/state changes emit events and remain declarative in spec.
485
+ - PII (contacts) is scoped/redacted in presentations.`
486
+ },
487
+ {
488
+ id: "docs.examples.crm-pipeline.usage",
489
+ title: "CRM Pipeline \u2014 Usage",
490
+ summary: "How to seed, extend, and regenerate the CRM pipeline.",
491
+ kind: "usage",
492
+ visibility: "public",
493
+ route: "/docs/examples/crm-pipeline/usage",
494
+ tags: ["crm", "usage"],
495
+ body: `## Setup
496
+ 1) Seed (if available) or create pipeline stages, deals, contacts, companies, tasks.
497
+ 2) Configure Notifications for stage changes/tasks; set policy.pii for contact data.
498
+
499
+ ## Extend & regenerate
500
+ 1) Adjust stage schema/order, deal fields, task fields in the spec.
501
+ 2) Regenerate to sync UI/API/events; ensure kanban/action buttons update.
502
+ 3) Use Feature Flags to trial new stages or SLA rules.
503
+
504
+ ## Guardrails
505
+ - Emit events for stage moves and task completions; log to Audit Trail.
506
+ - Keep required fields enforced in contracts; avoid freeform state.
507
+ - Redact contact PII in markdown/JSON outputs.
508
+
509
+ ## Adoption narrative
510
+
511
+ ### Before
512
+ - A CRM app with hand-written data models and handler logic.
513
+ - Pipeline stage rules live in code and drift across UI/API/events.
514
+ - Regeneration is risky because specs and implementations are not aligned.
515
+
516
+ ### After
517
+ - Contracts define deals, stages, and tasks as the source of truth.
518
+ - Regeneration keeps UI/API/events in sync when stages change.
519
+ - Compliance surfaces (audits, notifications) stay consistent with specs.
520
+
521
+ ### Minimal adoption steps
522
+ 1) Add ContractSpec CLI and core libraries.
523
+ 2) Define one operation (for example, deal/create).
524
+ 3) Run contractspec build to generate handlers and types.
525
+ 4) Wire the generated handler into your existing router.
526
+ 5) Expand to events and presentations as you add surface areas.
527
+ `
528
+ },
529
+ {
530
+ id: "docs.examples.crm-pipeline.reference",
531
+ title: "CRM Pipeline \u2014 Reference",
532
+ summary: "Entities, contracts, events, and presentations for the CRM template.",
533
+ kind: "reference",
534
+ visibility: "public",
535
+ route: "/docs/examples/crm-pipeline",
536
+ tags: ["crm", "reference"],
537
+ body: `## Entities
538
+ - Contact, Company, Deal, Pipeline, Stage, Task.
539
+
540
+ ## Contracts
541
+ - deal/create, stage/move, contact/company CRUD, task create/complete.
542
+
543
+ ## Events
544
+ - deal.created, stage.moved, task.completed, contact.updated.
545
+
546
+ ## Presentations
547
+ - Pipelines/kanban, deal detail, contact/company profiles, task lists.
548
+
549
+ ## Notes
550
+ - Stage definitions should be declarative; enforce via spec and regeneration.
551
+ - Use Notifications for deal/task updates; Audit Trail for state changes.`
552
+ },
553
+ {
554
+ id: "docs.examples.crm-pipeline.constraints",
555
+ title: "CRM Pipeline \u2014 Constraints & Safety",
556
+ summary: "Internal guardrails for stages, PII, and regeneration semantics in the CRM template.",
557
+ kind: "reference",
558
+ visibility: "internal",
559
+ route: "/docs/examples/crm-pipeline/constraints",
560
+ tags: ["crm", "constraints", "internal"],
561
+ body: `## Constraints
562
+ - Stage definitions/order must remain declarative; no imperative overrides in code.
563
+ - Events to emit: deal.created, stage.moved, task.completed, contact.updated (minimum).
564
+ - Regeneration should not alter stage semantics without explicit spec change.
565
+
566
+ ## PII
567
+ - Mark contact/company PII (emails, phones) for redaction in presentations.
568
+ - Ensure MCP/web outputs avoid raw PII when not needed.
569
+
570
+ ## Verification
571
+ - Add fixtures for stage move rules and SLA/task changes.
572
+ - Ensure Audit/Notifications remain wired for stage and task events.
573
+ - Use Feature Flags for experimental stages/SLAs; default safe/off.`
574
+ }
575
+ ];
576
+ registerDocBlocks(crmPipelineDocBlocks);
577
+ // src/entities/company.entity.ts
578
+ import {
579
+ defineEntity,
580
+ defineEntityEnum,
581
+ field,
582
+ index
583
+ } from "@contractspec/lib.schema";
584
+ var CompanySizeEnum = defineEntityEnum({
585
+ name: "CompanySize",
586
+ values: ["STARTUP", "SMALL", "MEDIUM", "LARGE", "ENTERPRISE"],
587
+ schema: "crm",
588
+ description: "Size category of a company."
589
+ });
590
+ var CompanyEntity = defineEntity({
591
+ name: "Company",
592
+ description: "A company/organization in the CRM.",
593
+ schema: "crm",
594
+ map: "company",
595
+ fields: {
596
+ id: field.id({ description: "Unique company ID" }),
597
+ name: field.string({ description: "Company name" }),
598
+ domain: field.string({ isOptional: true, description: "Website domain" }),
599
+ website: field.url({ isOptional: true }),
600
+ industry: field.string({ isOptional: true }),
601
+ size: field.enum("CompanySize", { isOptional: true }),
602
+ employeeCount: field.int({ isOptional: true }),
603
+ annualRevenue: field.decimal({ isOptional: true }),
604
+ organizationId: field.foreignKey(),
605
+ ownerId: field.foreignKey({ description: "Account owner" }),
606
+ phone: field.string({ isOptional: true }),
607
+ email: field.email({ isOptional: true }),
608
+ address: field.string({ isOptional: true }),
609
+ city: field.string({ isOptional: true }),
610
+ state: field.string({ isOptional: true }),
611
+ country: field.string({ isOptional: true }),
612
+ postalCode: field.string({ isOptional: true }),
613
+ linkedInUrl: field.url({ isOptional: true }),
614
+ description: field.string({ isOptional: true }),
615
+ tags: field.string({ isArray: true }),
616
+ customFields: field.json({ isOptional: true }),
617
+ createdAt: field.createdAt(),
618
+ updatedAt: field.updatedAt(),
619
+ contacts: field.hasMany("Contact"),
620
+ deals: field.hasMany("Deal")
621
+ },
622
+ indexes: [index.on(["organizationId", "ownerId"]), index.on(["domain"])],
623
+ enums: [CompanySizeEnum]
624
+ });
625
+
626
+ // src/entities/contact.entity.ts
627
+ import {
628
+ defineEntity as defineEntity2,
629
+ defineEntityEnum as defineEntityEnum2,
630
+ field as field2,
631
+ index as index2
632
+ } from "@contractspec/lib.schema";
633
+ var ContactStatusEnum = defineEntityEnum2({
634
+ name: "ContactStatus",
635
+ values: ["LEAD", "PROSPECT", "CUSTOMER", "CHURNED", "ARCHIVED"],
636
+ schema: "crm",
637
+ description: "Status of a contact in the sales funnel."
638
+ });
639
+ var ContactEntity = defineEntity2({
640
+ name: "Contact",
641
+ description: "An individual person in the CRM.",
642
+ schema: "crm",
643
+ map: "contact",
644
+ fields: {
645
+ id: field2.id({ description: "Unique contact ID" }),
646
+ firstName: field2.string({ description: "First name" }),
647
+ lastName: field2.string({ description: "Last name" }),
648
+ email: field2.email({ isOptional: true, isUnique: true }),
649
+ phone: field2.string({ isOptional: true }),
650
+ companyId: field2.string({
651
+ isOptional: true,
652
+ description: "Associated company"
653
+ }),
654
+ jobTitle: field2.string({ isOptional: true }),
655
+ status: field2.enum("ContactStatus", { default: "LEAD" }),
656
+ organizationId: field2.foreignKey(),
657
+ ownerId: field2.foreignKey({
658
+ description: "Sales rep who owns this contact"
659
+ }),
660
+ source: field2.string({ isOptional: true, description: "Lead source" }),
661
+ linkedInUrl: field2.url({ isOptional: true }),
662
+ twitterHandle: field2.string({ isOptional: true }),
663
+ address: field2.string({ isOptional: true }),
664
+ city: field2.string({ isOptional: true }),
665
+ state: field2.string({ isOptional: true }),
666
+ country: field2.string({ isOptional: true }),
667
+ postalCode: field2.string({ isOptional: true }),
668
+ notes: field2.string({ isOptional: true }),
669
+ tags: field2.string({ isArray: true }),
670
+ customFields: field2.json({ isOptional: true }),
671
+ lastContactedAt: field2.dateTime({ isOptional: true }),
672
+ nextFollowUpAt: field2.dateTime({ isOptional: true }),
673
+ createdAt: field2.createdAt(),
674
+ updatedAt: field2.updatedAt(),
675
+ company: field2.belongsTo("Company", ["companyId"], ["id"]),
676
+ deals: field2.hasMany("Deal"),
677
+ tasks: field2.hasMany("Task"),
678
+ activities: field2.hasMany("Activity")
679
+ },
680
+ indexes: [
681
+ index2.on(["organizationId", "status"]),
682
+ index2.on(["organizationId", "ownerId"]),
683
+ index2.on(["organizationId", "companyId"]),
684
+ index2.on(["email"])
685
+ ],
686
+ enums: [ContactStatusEnum]
687
+ });
688
+
689
+ // src/entities/deal.entity.ts
690
+ import {
691
+ defineEntity as defineEntity3,
692
+ defineEntityEnum as defineEntityEnum3,
693
+ field as field3,
694
+ index as index3
695
+ } from "@contractspec/lib.schema";
696
+ var DealStatusEnum2 = defineEntityEnum3({
697
+ name: "DealStatus",
698
+ values: ["OPEN", "WON", "LOST", "STALE"],
699
+ schema: "crm",
700
+ description: "Status of a deal."
701
+ });
702
+ var PipelineEntity = defineEntity3({
703
+ name: "Pipeline",
704
+ description: "A sales pipeline with stages.",
705
+ schema: "crm",
706
+ map: "pipeline",
707
+ fields: {
708
+ id: field3.id(),
709
+ name: field3.string({ description: "Pipeline name" }),
710
+ description: field3.string({ isOptional: true }),
711
+ organizationId: field3.foreignKey(),
712
+ isDefault: field3.boolean({ default: false }),
713
+ createdAt: field3.createdAt(),
714
+ updatedAt: field3.updatedAt(),
715
+ stages: field3.hasMany("Stage"),
716
+ deals: field3.hasMany("Deal")
717
+ }
718
+ });
719
+ var StageEntity = defineEntity3({
720
+ name: "Stage",
721
+ description: "A stage within a sales pipeline.",
722
+ schema: "crm",
723
+ map: "stage",
724
+ fields: {
725
+ id: field3.id(),
726
+ name: field3.string({ description: "Stage name" }),
727
+ pipelineId: field3.foreignKey(),
728
+ position: field3.int({ description: "Order in pipeline" }),
729
+ probability: field3.int({
730
+ default: 0,
731
+ description: "Win probability (0-100)"
732
+ }),
733
+ isWonStage: field3.boolean({ default: false }),
734
+ isLostStage: field3.boolean({ default: false }),
735
+ color: field3.string({
736
+ isOptional: true,
737
+ description: "Stage color for UI"
738
+ }),
739
+ createdAt: field3.createdAt(),
740
+ updatedAt: field3.updatedAt(),
741
+ pipeline: field3.belongsTo("Pipeline", ["pipelineId"], ["id"], {
742
+ onDelete: "Cascade"
743
+ }),
744
+ deals: field3.hasMany("Deal")
745
+ },
746
+ indexes: [index3.on(["pipelineId", "position"])]
747
+ });
748
+ var DealEntity = defineEntity3({
749
+ name: "Deal",
750
+ description: "A sales opportunity/deal.",
751
+ schema: "crm",
752
+ map: "deal",
753
+ fields: {
754
+ id: field3.id({ description: "Unique deal ID" }),
755
+ name: field3.string({ description: "Deal name" }),
756
+ value: field3.decimal({ description: "Deal value" }),
757
+ currency: field3.string({ default: '"USD"' }),
758
+ pipelineId: field3.foreignKey(),
759
+ stageId: field3.foreignKey(),
760
+ status: field3.enum("DealStatus", { default: "OPEN" }),
761
+ contactId: field3.string({ isOptional: true }),
762
+ companyId: field3.string({ isOptional: true }),
763
+ organizationId: field3.foreignKey(),
764
+ ownerId: field3.foreignKey({ description: "Deal owner" }),
765
+ expectedCloseDate: field3.dateTime({ isOptional: true }),
766
+ closedAt: field3.dateTime({ isOptional: true }),
767
+ lostReason: field3.string({ isOptional: true }),
768
+ wonSource: field3.string({ isOptional: true }),
769
+ notes: field3.string({ isOptional: true }),
770
+ tags: field3.string({ isArray: true }),
771
+ customFields: field3.json({ isOptional: true }),
772
+ stagePosition: field3.int({ default: 0 }),
773
+ createdAt: field3.createdAt(),
774
+ updatedAt: field3.updatedAt(),
775
+ pipeline: field3.belongsTo("Pipeline", ["pipelineId"], ["id"]),
776
+ stage: field3.belongsTo("Stage", ["stageId"], ["id"]),
777
+ contact: field3.belongsTo("Contact", ["contactId"], ["id"]),
778
+ company: field3.belongsTo("Company", ["companyId"], ["id"]),
779
+ tasks: field3.hasMany("Task"),
780
+ activities: field3.hasMany("Activity")
781
+ },
782
+ indexes: [
783
+ index3.on(["organizationId", "status"]),
784
+ index3.on(["pipelineId", "stageId", "stagePosition"]),
785
+ index3.on(["ownerId", "status"]),
786
+ index3.on(["expectedCloseDate"])
787
+ ],
788
+ enums: [DealStatusEnum2]
789
+ });
790
+
791
+ // src/entities/task.entity.ts
792
+ import {
793
+ defineEntity as defineEntity4,
794
+ defineEntityEnum as defineEntityEnum4,
795
+ field as field4,
796
+ index as index4
797
+ } from "@contractspec/lib.schema";
798
+ var TaskTypeEnum = defineEntityEnum4({
799
+ name: "TaskType",
800
+ values: ["CALL", "EMAIL", "MEETING", "TODO", "FOLLOW_UP", "OTHER"],
801
+ schema: "crm",
802
+ description: "Type of CRM task."
803
+ });
804
+ var TaskPriorityEnum = defineEntityEnum4({
805
+ name: "TaskPriority",
806
+ values: ["LOW", "NORMAL", "HIGH", "URGENT"],
807
+ schema: "crm",
808
+ description: "Priority of a task."
809
+ });
810
+ var TaskStatusEnum = defineEntityEnum4({
811
+ name: "TaskStatus",
812
+ values: ["PENDING", "IN_PROGRESS", "COMPLETED", "CANCELLED"],
813
+ schema: "crm",
814
+ description: "Status of a task."
815
+ });
816
+ var TaskEntity = defineEntity4({
817
+ name: "Task",
818
+ description: "A task or follow-up activity.",
819
+ schema: "crm",
820
+ map: "task",
821
+ fields: {
822
+ id: field4.id(),
823
+ title: field4.string({ description: "Task title" }),
824
+ description: field4.string({ isOptional: true }),
825
+ type: field4.enum("TaskType", { default: "TODO" }),
826
+ priority: field4.enum("TaskPriority", { default: "NORMAL" }),
827
+ status: field4.enum("TaskStatus", { default: "PENDING" }),
828
+ dueDate: field4.dateTime({ isOptional: true }),
829
+ reminderAt: field4.dateTime({ isOptional: true }),
830
+ contactId: field4.string({ isOptional: true }),
831
+ dealId: field4.string({ isOptional: true }),
832
+ companyId: field4.string({ isOptional: true }),
833
+ organizationId: field4.foreignKey(),
834
+ assignedTo: field4.foreignKey({ description: "User assigned to this task" }),
835
+ createdBy: field4.foreignKey(),
836
+ completedAt: field4.dateTime({ isOptional: true }),
837
+ completedBy: field4.string({ isOptional: true }),
838
+ createdAt: field4.createdAt(),
839
+ updatedAt: field4.updatedAt(),
840
+ contact: field4.belongsTo("Contact", ["contactId"], ["id"]),
841
+ deal: field4.belongsTo("Deal", ["dealId"], ["id"]),
842
+ company: field4.belongsTo("Company", ["companyId"], ["id"])
843
+ },
844
+ indexes: [
845
+ index4.on(["organizationId", "assignedTo", "status"]),
846
+ index4.on(["dueDate", "status"]),
847
+ index4.on(["contactId"]),
848
+ index4.on(["dealId"])
849
+ ],
850
+ enums: [TaskTypeEnum, TaskPriorityEnum, TaskStatusEnum]
851
+ });
852
+ var ActivityEntity = defineEntity4({
853
+ name: "Activity",
854
+ description: "An activity/interaction logged in the CRM.",
855
+ schema: "crm",
856
+ map: "activity",
857
+ fields: {
858
+ id: field4.id(),
859
+ type: field4.enum("TaskType"),
860
+ subject: field4.string(),
861
+ description: field4.string({ isOptional: true }),
862
+ contactId: field4.string({ isOptional: true }),
863
+ dealId: field4.string({ isOptional: true }),
864
+ companyId: field4.string({ isOptional: true }),
865
+ organizationId: field4.foreignKey(),
866
+ performedBy: field4.foreignKey(),
867
+ outcome: field4.string({ isOptional: true }),
868
+ occurredAt: field4.dateTime(),
869
+ duration: field4.int({
870
+ isOptional: true,
871
+ description: "Duration in minutes"
872
+ }),
873
+ createdAt: field4.createdAt(),
874
+ contact: field4.belongsTo("Contact", ["contactId"], ["id"]),
875
+ deal: field4.belongsTo("Deal", ["dealId"], ["id"]),
876
+ company: field4.belongsTo("Company", ["companyId"], ["id"])
877
+ },
878
+ indexes: [
879
+ index4.on(["contactId", "occurredAt"]),
880
+ index4.on(["dealId", "occurredAt"])
881
+ ]
882
+ });
883
+ // src/entities/index.ts
884
+ var crmPipelineSchemaContribution = {
885
+ moduleId: "@contractspec/example.crm-pipeline",
886
+ entities: [
887
+ CompanyEntity,
888
+ ContactEntity,
889
+ DealEntity,
890
+ PipelineEntity,
891
+ StageEntity,
892
+ TaskEntity,
893
+ ActivityEntity
894
+ ],
895
+ enums: [
896
+ CompanySizeEnum,
897
+ ContactStatusEnum,
898
+ DealStatusEnum2,
899
+ TaskTypeEnum,
900
+ TaskPriorityEnum,
901
+ TaskStatusEnum
902
+ ]
903
+ };
904
+
905
+ // src/events/contact.event.ts
906
+ import { ScalarTypeEnum as ScalarTypeEnum2, defineSchemaModel as defineSchemaModel2 } from "@contractspec/lib.schema";
907
+ import { defineEvent } from "@contractspec/lib.contracts";
908
+ var ContactCreatedPayload = defineSchemaModel2({
909
+ name: "ContactCreatedPayload",
910
+ description: "Payload when a contact is created",
911
+ fields: {
912
+ contactId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
913
+ email: { type: ScalarTypeEnum2.EmailAddress(), isOptional: true },
914
+ organizationId: {
915
+ type: ScalarTypeEnum2.String_unsecure(),
916
+ isOptional: false
917
+ },
918
+ ownerId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
919
+ createdAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
920
+ }
921
+ });
922
+ var ContactCreatedEvent = defineEvent({
923
+ meta: {
924
+ key: "contact.created",
925
+ version: "1.0.0",
926
+ description: "A new contact has been created.",
927
+ stability: "stable",
928
+ owners: ["@crm-team"],
929
+ tags: ["contact", "created"]
930
+ },
931
+ payload: ContactCreatedPayload
932
+ });
933
+
934
+ // src/events/deal.event.ts
935
+ import { ScalarTypeEnum as ScalarTypeEnum3, defineSchemaModel as defineSchemaModel3 } from "@contractspec/lib.schema";
936
+ import { defineEvent as defineEvent2 } from "@contractspec/lib.contracts";
937
+ var DealCreatedPayload = defineSchemaModel3({
938
+ name: "DealCreatedPayload",
939
+ description: "Payload when a deal is created",
940
+ fields: {
941
+ dealId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
942
+ name: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
943
+ value: { type: ScalarTypeEnum3.Float_unsecure(), isOptional: false },
944
+ pipelineId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
945
+ stageId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
946
+ ownerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
947
+ createdAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
948
+ }
949
+ });
950
+ var DealMovedPayload = defineSchemaModel3({
951
+ name: "DealMovedEventPayload",
952
+ description: "Payload when a deal is moved to another stage",
953
+ fields: {
954
+ dealId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
955
+ fromStageId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
956
+ toStageId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
957
+ movedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
958
+ movedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
959
+ }
960
+ });
961
+ var DealWonPayload = defineSchemaModel3({
962
+ name: "DealWonEventPayload",
963
+ description: "Payload when a deal is won",
964
+ fields: {
965
+ dealId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
966
+ value: { type: ScalarTypeEnum3.Float_unsecure(), isOptional: false },
967
+ currency: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
968
+ contactId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
969
+ companyId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
970
+ ownerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
971
+ wonAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
972
+ }
973
+ });
974
+ var DealLostPayload = defineSchemaModel3({
975
+ name: "DealLostEventPayload",
976
+ description: "Payload when a deal is lost",
977
+ fields: {
978
+ dealId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
979
+ value: { type: ScalarTypeEnum3.Float_unsecure(), isOptional: false },
980
+ reason: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
981
+ ownerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
982
+ lostAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
983
+ }
984
+ });
985
+ var DealCreatedEvent = defineEvent2({
986
+ meta: {
987
+ key: "deal.created",
988
+ version: "1.0.0",
989
+ description: "A new deal has been created.",
990
+ stability: "stable",
991
+ owners: ["@crm-team"],
992
+ tags: ["deal", "created"]
993
+ },
994
+ payload: DealCreatedPayload
995
+ });
996
+ var DealMovedEvent = defineEvent2({
997
+ meta: {
998
+ key: "deal.moved",
999
+ version: "1.0.0",
1000
+ description: "A deal has been moved to a different stage.",
1001
+ stability: "stable",
1002
+ owners: ["@crm-team"],
1003
+ tags: ["deal", "moved"]
1004
+ },
1005
+ payload: DealMovedPayload
1006
+ });
1007
+ var DealWonEvent = defineEvent2({
1008
+ meta: {
1009
+ key: "deal.won",
1010
+ version: "1.0.0",
1011
+ description: "A deal has been won.",
1012
+ stability: "stable",
1013
+ owners: ["@crm-team"],
1014
+ tags: ["deal", "won"]
1015
+ },
1016
+ payload: DealWonPayload
1017
+ });
1018
+ var DealLostEvent = defineEvent2({
1019
+ meta: {
1020
+ key: "deal.lost",
1021
+ version: "1.0.0",
1022
+ description: "A deal has been lost.",
1023
+ stability: "stable",
1024
+ owners: ["@crm-team"],
1025
+ tags: ["deal", "lost"]
1026
+ },
1027
+ payload: DealLostPayload
1028
+ });
1029
+
1030
+ // src/events/task.event.ts
1031
+ import { ScalarTypeEnum as ScalarTypeEnum4, defineSchemaModel as defineSchemaModel4 } from "@contractspec/lib.schema";
1032
+ import { defineEvent as defineEvent3 } from "@contractspec/lib.contracts";
1033
+ var TaskCompletedPayload = defineSchemaModel4({
1034
+ name: "TaskCompletedPayload",
1035
+ description: "Payload when a task is completed",
1036
+ fields: {
1037
+ taskId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1038
+ type: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1039
+ assignedTo: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1040
+ completedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
1041
+ completedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
1042
+ }
1043
+ });
1044
+ var TaskCompletedEvent = defineEvent3({
1045
+ meta: {
1046
+ key: "task.completed",
1047
+ version: "1.0.0",
1048
+ description: "A task has been completed.",
1049
+ stability: "stable",
1050
+ owners: ["@crm-team"],
1051
+ tags: ["task", "lifecycle"]
1052
+ },
1053
+ payload: TaskCompletedPayload
1054
+ });
1055
+ // src/example.ts
1056
+ import { defineExample } from "@contractspec/lib.contracts";
1057
+ var example = defineExample({
1058
+ meta: {
1059
+ key: "crm-pipeline",
1060
+ version: "1.0.0",
1061
+ title: "CRM Pipeline",
1062
+ description: "Sales CRM with contacts, companies, deals, pipelines, and tasks.",
1063
+ kind: "template",
1064
+ visibility: "public",
1065
+ stability: "experimental",
1066
+ owners: ["@platform.core"],
1067
+ tags: ["crm", "sales", "pipeline", "deals"]
1068
+ },
1069
+ docs: {
1070
+ rootDocId: "docs.examples.crm-pipeline"
1071
+ },
1072
+ entrypoints: {
1073
+ packageName: "@contractspec/example.crm-pipeline",
1074
+ feature: "./feature",
1075
+ contracts: "./contracts",
1076
+ presentations: "./presentations",
1077
+ handlers: "./handlers",
1078
+ docs: "./docs"
1079
+ },
1080
+ surfaces: {
1081
+ templates: true,
1082
+ sandbox: {
1083
+ enabled: true,
1084
+ modes: ["playground", "specs", "builder", "markdown", "evolution"]
1085
+ },
1086
+ studio: { enabled: true, installable: true },
1087
+ mcp: { enabled: true }
1088
+ }
1089
+ });
1090
+ var example_default = example;
1091
+
1092
+ // src/handlers/crm.handlers.ts
1093
+ import { web } from "@contractspec/lib.runtime-sandbox";
1094
+ var { generateId } = web;
1095
+ function rowToDeal(row) {
1096
+ return {
1097
+ id: row.id,
1098
+ projectId: row.projectId,
1099
+ name: row.name,
1100
+ value: row.value,
1101
+ currency: row.currency,
1102
+ pipelineId: row.pipelineId,
1103
+ stageId: row.stageId,
1104
+ status: row.status,
1105
+ contactId: row.contactId ?? undefined,
1106
+ companyId: row.companyId ?? undefined,
1107
+ ownerId: row.ownerId,
1108
+ expectedCloseDate: row.expectedCloseDate ? new Date(row.expectedCloseDate) : undefined,
1109
+ wonSource: row.wonSource ?? undefined,
1110
+ lostReason: row.lostReason ?? undefined,
1111
+ notes: row.notes ?? undefined,
1112
+ createdAt: new Date(row.createdAt),
1113
+ updatedAt: new Date(row.updatedAt)
1114
+ };
1115
+ }
1116
+ function createCrmHandlers(db) {
1117
+ async function listDeals(input) {
1118
+ const {
1119
+ projectId,
1120
+ pipelineId,
1121
+ stageId,
1122
+ status,
1123
+ ownerId,
1124
+ search,
1125
+ limit = 20,
1126
+ offset = 0
1127
+ } = input;
1128
+ let whereClause = "WHERE projectId = ?";
1129
+ const params = [projectId];
1130
+ if (pipelineId) {
1131
+ whereClause += " AND pipelineId = ?";
1132
+ params.push(pipelineId);
1133
+ }
1134
+ if (stageId) {
1135
+ whereClause += " AND stageId = ?";
1136
+ params.push(stageId);
1137
+ }
1138
+ if (status && status !== "all") {
1139
+ whereClause += " AND status = ?";
1140
+ params.push(status);
1141
+ }
1142
+ if (ownerId) {
1143
+ whereClause += " AND ownerId = ?";
1144
+ params.push(ownerId);
1145
+ }
1146
+ if (search) {
1147
+ whereClause += " AND name LIKE ?";
1148
+ params.push(`%${search}%`);
1149
+ }
1150
+ const countResult = (await db.query(`SELECT COUNT(*) as count FROM crm_deal ${whereClause}`, params)).rows;
1151
+ const total = countResult[0]?.count ?? 0;
1152
+ const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
1153
+ const totalValue = valueResult[0]?.total ?? 0;
1154
+ const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY value DESC LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
1155
+ return {
1156
+ deals: dealRows.map(rowToDeal),
1157
+ total,
1158
+ totalValue
1159
+ };
1160
+ }
1161
+ async function createDeal(input, context) {
1162
+ const id = generateId("deal");
1163
+ const now = new Date().toISOString();
1164
+ await db.execute(`INSERT INTO crm_deal (id, projectId, pipelineId, stageId, name, value, currency, status, contactId, companyId, ownerId, expectedCloseDate, createdAt, updatedAt)
1165
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
1166
+ id,
1167
+ context.projectId,
1168
+ input.pipelineId,
1169
+ input.stageId,
1170
+ input.name,
1171
+ input.value,
1172
+ input.currency ?? "USD",
1173
+ "OPEN",
1174
+ input.contactId ?? null,
1175
+ input.companyId ?? null,
1176
+ context.ownerId,
1177
+ input.expectedCloseDate?.toISOString() ?? null,
1178
+ now,
1179
+ now
1180
+ ]);
1181
+ const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [id])).rows;
1182
+ if (!rows[0]) {
1183
+ throw new Error("Failed to create deal");
1184
+ }
1185
+ return rowToDeal(rows[0]);
1186
+ }
1187
+ async function moveDeal(input) {
1188
+ const now = new Date().toISOString();
1189
+ const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
1190
+ if (!existing[0]) {
1191
+ throw new Error("NOT_FOUND");
1192
+ }
1193
+ const stage = (await db.query(`SELECT * FROM crm_stage WHERE id = ?`, [input.stageId])).rows;
1194
+ if (!stage[0]) {
1195
+ throw new Error("INVALID_STAGE");
1196
+ }
1197
+ await db.execute(`UPDATE crm_deal SET stageId = ?, updatedAt = ? WHERE id = ?`, [input.stageId, now, input.dealId]);
1198
+ const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
1199
+ return rowToDeal(rows[0]);
1200
+ }
1201
+ async function winDeal(input) {
1202
+ const now = new Date().toISOString();
1203
+ const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
1204
+ if (!existing[0]) {
1205
+ throw new Error("NOT_FOUND");
1206
+ }
1207
+ await db.execute(`UPDATE crm_deal SET status = 'WON', wonSource = ?, notes = ?, updatedAt = ? WHERE id = ?`, [input.wonSource ?? null, input.notes ?? null, now, input.dealId]);
1208
+ const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
1209
+ return rowToDeal(rows[0]);
1210
+ }
1211
+ async function loseDeal(input) {
1212
+ const now = new Date().toISOString();
1213
+ const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
1214
+ if (!existing[0]) {
1215
+ throw new Error("NOT_FOUND");
1216
+ }
1217
+ await db.execute(`UPDATE crm_deal SET status = 'LOST', lostReason = ?, notes = ?, updatedAt = ? WHERE id = ?`, [input.lostReason, input.notes ?? null, now, input.dealId]);
1218
+ const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
1219
+ return rowToDeal(rows[0]);
1220
+ }
1221
+ async function getDealsByStage(input) {
1222
+ 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;
1223
+ const stages = (await db.query(`SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`, [input.pipelineId])).rows;
1224
+ const grouped = {};
1225
+ for (const stage of stages) {
1226
+ grouped[stage.id] = deals.filter((d) => d.stageId === stage.id).map(rowToDeal);
1227
+ }
1228
+ return grouped;
1229
+ }
1230
+ async function getPipelineStages(input) {
1231
+ const rows = (await db.query(`SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`, [input.pipelineId])).rows;
1232
+ return rows.map((row) => ({
1233
+ id: row.id,
1234
+ pipelineId: row.pipelineId,
1235
+ name: row.name,
1236
+ position: row.position
1237
+ }));
1238
+ }
1239
+ return {
1240
+ listDeals,
1241
+ createDeal,
1242
+ moveDeal,
1243
+ winDeal,
1244
+ loseDeal,
1245
+ getDealsByStage,
1246
+ getPipelineStages
1247
+ };
1248
+ }
1249
+
1250
+ // src/handlers/mock-data.ts
1251
+ var MOCK_STAGES = [
1252
+ { id: "stage-1", name: "Lead", position: 1, pipelineId: "pipeline-1" },
1253
+ { id: "stage-2", name: "Qualified", position: 2, pipelineId: "pipeline-1" },
1254
+ { id: "stage-3", name: "Proposal", position: 3, pipelineId: "pipeline-1" },
1255
+ { id: "stage-4", name: "Negotiation", position: 4, pipelineId: "pipeline-1" },
1256
+ { id: "stage-5", name: "Closed", position: 5, pipelineId: "pipeline-1" }
1257
+ ];
1258
+ var MOCK_DEALS = [
1259
+ {
1260
+ id: "deal-1",
1261
+ name: "Enterprise License - Acme Corp",
1262
+ value: 75000,
1263
+ currency: "USD",
1264
+ pipelineId: "pipeline-1",
1265
+ stageId: "stage-3",
1266
+ status: "OPEN",
1267
+ contactId: "contact-1",
1268
+ companyId: "company-1",
1269
+ ownerId: "user-1",
1270
+ expectedCloseDate: new Date("2024-05-15T00:00:00Z"),
1271
+ createdAt: new Date("2024-02-01T10:00:00Z"),
1272
+ updatedAt: new Date("2024-04-10T14:30:00Z")
1273
+ },
1274
+ {
1275
+ id: "deal-2",
1276
+ name: "Startup Plan - TechStart Inc",
1277
+ value: 12000,
1278
+ currency: "USD",
1279
+ pipelineId: "pipeline-1",
1280
+ stageId: "stage-2",
1281
+ status: "OPEN",
1282
+ contactId: "contact-2",
1283
+ companyId: "company-2",
1284
+ ownerId: "user-2",
1285
+ expectedCloseDate: new Date("2024-04-30T00:00:00Z"),
1286
+ createdAt: new Date("2024-03-15T09:00:00Z"),
1287
+ updatedAt: new Date("2024-04-08T11:15:00Z")
1288
+ },
1289
+ {
1290
+ id: "deal-3",
1291
+ name: "Professional Services - Global Ltd",
1292
+ value: 45000,
1293
+ currency: "USD",
1294
+ pipelineId: "pipeline-1",
1295
+ stageId: "stage-4",
1296
+ status: "OPEN",
1297
+ contactId: "contact-3",
1298
+ companyId: "company-3",
1299
+ ownerId: "user-1",
1300
+ expectedCloseDate: new Date("2024-04-20T00:00:00Z"),
1301
+ createdAt: new Date("2024-01-20T08:00:00Z"),
1302
+ updatedAt: new Date("2024-04-12T16:45:00Z")
1303
+ },
1304
+ {
1305
+ id: "deal-4",
1306
+ name: "Annual Contract - SmallBiz Co",
1307
+ value: 8500,
1308
+ currency: "USD",
1309
+ pipelineId: "pipeline-1",
1310
+ stageId: "stage-1",
1311
+ status: "OPEN",
1312
+ contactId: "contact-4",
1313
+ companyId: "company-4",
1314
+ ownerId: "user-3",
1315
+ createdAt: new Date("2024-04-05T12:00:00Z"),
1316
+ updatedAt: new Date("2024-04-05T12:00:00Z")
1317
+ },
1318
+ {
1319
+ id: "deal-5",
1320
+ name: "Custom Integration - MegaCorp",
1321
+ value: 125000,
1322
+ currency: "USD",
1323
+ pipelineId: "pipeline-1",
1324
+ stageId: "stage-5",
1325
+ status: "WON",
1326
+ contactId: "contact-5",
1327
+ companyId: "company-5",
1328
+ ownerId: "user-1",
1329
+ expectedCloseDate: new Date("2024-03-31T00:00:00Z"),
1330
+ createdAt: new Date("2023-11-10T10:00:00Z"),
1331
+ updatedAt: new Date("2024-03-28T09:00:00Z")
1332
+ },
1333
+ {
1334
+ id: "deal-6",
1335
+ name: "Pilot Project - NewCo",
1336
+ value: 5000,
1337
+ currency: "USD",
1338
+ pipelineId: "pipeline-1",
1339
+ stageId: "stage-2",
1340
+ status: "LOST",
1341
+ contactId: "contact-6",
1342
+ companyId: "company-6",
1343
+ ownerId: "user-2",
1344
+ createdAt: new Date("2024-01-15T14:00:00Z"),
1345
+ updatedAt: new Date("2024-02-28T10:30:00Z")
1346
+ }
1347
+ ];
1348
+ var MOCK_COMPANIES = [
1349
+ {
1350
+ id: "company-1",
1351
+ name: "Acme Corporation",
1352
+ domain: "acme.com",
1353
+ industry: "Technology",
1354
+ size: "1000-5000",
1355
+ website: "https://acme.com",
1356
+ createdAt: new Date("2024-01-01T00:00:00Z")
1357
+ },
1358
+ {
1359
+ id: "company-2",
1360
+ name: "TechStart Inc",
1361
+ domain: "techstart.io",
1362
+ industry: "Software",
1363
+ size: "10-50",
1364
+ website: "https://techstart.io",
1365
+ createdAt: new Date("2024-02-15T00:00:00Z")
1366
+ },
1367
+ {
1368
+ id: "company-3",
1369
+ name: "Global Ltd",
1370
+ domain: "global.com",
1371
+ industry: "Consulting",
1372
+ size: "500-1000",
1373
+ website: "https://global.com",
1374
+ createdAt: new Date("2023-12-01T00:00:00Z")
1375
+ }
1376
+ ];
1377
+ var MOCK_CONTACTS = [
1378
+ {
1379
+ id: "contact-1",
1380
+ firstName: "John",
1381
+ lastName: "Smith",
1382
+ email: "john.smith@acme.com",
1383
+ phone: "+1-555-0101",
1384
+ title: "VP of Engineering",
1385
+ companyId: "company-1",
1386
+ createdAt: new Date("2024-01-05T00:00:00Z")
1387
+ },
1388
+ {
1389
+ id: "contact-2",
1390
+ firstName: "Sarah",
1391
+ lastName: "Johnson",
1392
+ email: "sarah@techstart.io",
1393
+ phone: "+1-555-0102",
1394
+ title: "CEO",
1395
+ companyId: "company-2",
1396
+ createdAt: new Date("2024-02-20T00:00:00Z")
1397
+ },
1398
+ {
1399
+ id: "contact-3",
1400
+ firstName: "Michael",
1401
+ lastName: "Brown",
1402
+ email: "michael.brown@global.com",
1403
+ phone: "+1-555-0103",
1404
+ title: "CTO",
1405
+ companyId: "company-3",
1406
+ createdAt: new Date("2023-12-10T00:00:00Z")
1407
+ }
1408
+ ];
1409
+
1410
+ // src/handlers/deal.handlers.ts
1411
+ async function mockListDealsHandler(input) {
1412
+ const {
1413
+ pipelineId,
1414
+ stageId,
1415
+ status,
1416
+ ownerId,
1417
+ search,
1418
+ limit = 20,
1419
+ offset = 0
1420
+ } = input;
1421
+ let filtered = [...MOCK_DEALS];
1422
+ if (pipelineId) {
1423
+ filtered = filtered.filter((d) => d.pipelineId === pipelineId);
1424
+ }
1425
+ if (stageId) {
1426
+ filtered = filtered.filter((d) => d.stageId === stageId);
1427
+ }
1428
+ if (status && status !== "all") {
1429
+ filtered = filtered.filter((d) => d.status === status);
1430
+ }
1431
+ if (ownerId) {
1432
+ filtered = filtered.filter((d) => d.ownerId === ownerId);
1433
+ }
1434
+ if (search) {
1435
+ const q = search.toLowerCase();
1436
+ filtered = filtered.filter((d) => d.name.toLowerCase().includes(q));
1437
+ }
1438
+ filtered.sort((a, b) => b.value - a.value);
1439
+ const total = filtered.length;
1440
+ const totalValue = filtered.reduce((sum, d) => sum + d.value, 0);
1441
+ const deals = filtered.slice(offset, offset + limit);
1442
+ return {
1443
+ deals,
1444
+ total,
1445
+ totalValue
1446
+ };
1447
+ }
1448
+ async function mockCreateDealHandler(input, context) {
1449
+ const now = new Date;
1450
+ const deal3 = {
1451
+ id: `deal-${Date.now()}`,
1452
+ name: input.name,
1453
+ value: input.value,
1454
+ currency: input.currency ?? "USD",
1455
+ pipelineId: input.pipelineId,
1456
+ stageId: input.stageId,
1457
+ status: "OPEN",
1458
+ contactId: input.contactId,
1459
+ companyId: input.companyId,
1460
+ ownerId: context.ownerId,
1461
+ expectedCloseDate: input.expectedCloseDate,
1462
+ createdAt: now,
1463
+ updatedAt: now
1464
+ };
1465
+ MOCK_DEALS.push(deal3);
1466
+ return deal3;
1467
+ }
1468
+ async function mockMoveDealHandler(input) {
1469
+ const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
1470
+ if (dealIndex === -1) {
1471
+ throw new Error("NOT_FOUND");
1472
+ }
1473
+ const deal3 = MOCK_DEALS[dealIndex];
1474
+ if (!deal3) {
1475
+ throw new Error("NOT_FOUND");
1476
+ }
1477
+ const stage = MOCK_STAGES.find((s) => s.id === input.stageId);
1478
+ if (!stage) {
1479
+ throw new Error("INVALID_STAGE");
1480
+ }
1481
+ const updatedDeal = {
1482
+ ...deal3,
1483
+ stageId: input.stageId,
1484
+ updatedAt: new Date
1485
+ };
1486
+ MOCK_DEALS[dealIndex] = updatedDeal;
1487
+ return updatedDeal;
1488
+ }
1489
+ async function mockWinDealHandler(input) {
1490
+ const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
1491
+ if (dealIndex === -1) {
1492
+ throw new Error("NOT_FOUND");
1493
+ }
1494
+ const deal3 = MOCK_DEALS[dealIndex];
1495
+ if (!deal3) {
1496
+ throw new Error("NOT_FOUND");
1497
+ }
1498
+ const updatedDeal = {
1499
+ ...deal3,
1500
+ status: "WON",
1501
+ updatedAt: new Date
1502
+ };
1503
+ MOCK_DEALS[dealIndex] = updatedDeal;
1504
+ return updatedDeal;
1505
+ }
1506
+ async function mockLoseDealHandler(input) {
1507
+ const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
1508
+ if (dealIndex === -1) {
1509
+ throw new Error("NOT_FOUND");
1510
+ }
1511
+ const deal3 = MOCK_DEALS[dealIndex];
1512
+ if (!deal3) {
1513
+ throw new Error("NOT_FOUND");
1514
+ }
1515
+ const updatedDeal = {
1516
+ ...deal3,
1517
+ status: "LOST",
1518
+ updatedAt: new Date
1519
+ };
1520
+ MOCK_DEALS[dealIndex] = updatedDeal;
1521
+ return updatedDeal;
1522
+ }
1523
+ async function mockGetDealsByStageHandler(input) {
1524
+ const deals = MOCK_DEALS.filter((d) => d.pipelineId === input.pipelineId && d.status === "OPEN");
1525
+ const grouped = {};
1526
+ for (const stage of MOCK_STAGES) {
1527
+ grouped[stage.id] = deals.filter((d) => d.stageId === stage.id);
1528
+ }
1529
+ return grouped;
1530
+ }
1531
+ async function mockGetPipelineStagesHandler(input) {
1532
+ return MOCK_STAGES.filter((s) => s.pipelineId === input.pipelineId);
1533
+ }
1534
+ // src/presentations/dashboard.presentation.ts
1535
+ import { definePresentation, StabilityEnum } from "@contractspec/lib.contracts";
1536
+ var CrmDashboardPresentation = definePresentation({
1537
+ meta: {
1538
+ key: "crm.dashboard",
1539
+ version: "1.0.0",
1540
+ title: "CRM Dashboard",
1541
+ description: "Main CRM dashboard with pipeline overview, deal stats, and activities",
1542
+ domain: "crm-pipeline",
1543
+ owners: ["@crm-team"],
1544
+ tags: ["dashboard", "overview"],
1545
+ stability: StabilityEnum.Experimental,
1546
+ goal: "Provide a high-level overview of CRM performance and active deals.",
1547
+ context: "The landing page for CRM users."
1548
+ },
1549
+ source: {
1550
+ type: "component",
1551
+ framework: "react",
1552
+ componentKey: "CrmDashboard"
1553
+ },
1554
+ targets: ["react", "markdown"],
1555
+ policy: {
1556
+ flags: ["crm.enabled"]
1557
+ }
1558
+ });
1559
+ var PipelineMetricsPresentation = definePresentation({
1560
+ meta: {
1561
+ key: "crm.pipeline.metrics",
1562
+ version: "1.0.0",
1563
+ title: "Pipeline Metrics",
1564
+ description: "Pipeline metrics and forecasting view",
1565
+ domain: "crm-pipeline",
1566
+ owners: ["@crm-team"],
1567
+ tags: ["pipeline", "metrics", "forecast"],
1568
+ stability: StabilityEnum.Experimental,
1569
+ goal: "Track pipeline health and sales forecasts.",
1570
+ context: "Data-intensive widget for sales managers."
1571
+ },
1572
+ source: {
1573
+ type: "component",
1574
+ framework: "react",
1575
+ componentKey: "PipelineMetricsView"
1576
+ },
1577
+ targets: ["react", "markdown"],
1578
+ policy: {
1579
+ flags: ["crm.metrics.enabled"]
1580
+ }
1581
+ });
1582
+
1583
+ // src/presentations/pipeline.presentation.ts
1584
+ import { definePresentation as definePresentation2, StabilityEnum as StabilityEnum2 } from "@contractspec/lib.contracts";
1585
+ var PipelineKanbanPresentation = definePresentation2({
1586
+ meta: {
1587
+ key: "crm.pipeline.kanban",
1588
+ version: "1.0.0",
1589
+ title: "Pipeline Kanban",
1590
+ description: "Kanban board view of deals organized by stage",
1591
+ domain: "crm-pipeline",
1592
+ owners: ["@crm-team"],
1593
+ tags: ["pipeline", "kanban", "deals"],
1594
+ stability: StabilityEnum2.Experimental,
1595
+ goal: "Visualize the sales pipeline status and deal distribution across stages.",
1596
+ context: "Used in the sales dashboard and management reports."
1597
+ },
1598
+ source: {
1599
+ type: "component",
1600
+ framework: "react",
1601
+ componentKey: "PipelineKanbanView",
1602
+ props: DealModel
1603
+ },
1604
+ targets: ["react", "markdown"],
1605
+ policy: {
1606
+ flags: ["crm.pipeline.enabled"]
1607
+ }
1608
+ });
1609
+ var DealListPresentation = definePresentation2({
1610
+ meta: {
1611
+ key: "crm.deal.viewList",
1612
+ version: "1.0.0",
1613
+ title: "Deal List",
1614
+ description: "List view of deals with value, status, and owner info",
1615
+ domain: "crm-pipeline",
1616
+ owners: ["@crm-team"],
1617
+ tags: ["deal", "list"],
1618
+ stability: StabilityEnum2.Experimental,
1619
+ goal: "Search, filter, and review deal lists.",
1620
+ context: "Standard view for deal management and bulk actions."
1621
+ },
1622
+ source: {
1623
+ type: "component",
1624
+ framework: "react",
1625
+ componentKey: "DealListView",
1626
+ props: DealModel
1627
+ },
1628
+ targets: ["react", "markdown", "application/json"],
1629
+ policy: {
1630
+ flags: ["crm.deals.enabled"]
1631
+ }
1632
+ });
1633
+ var DealDetailPresentation = definePresentation2({
1634
+ meta: {
1635
+ key: "crm.deal.detail",
1636
+ version: "1.0.0",
1637
+ title: "Deal Details",
1638
+ description: "Detailed view of a deal with activities, contacts, and history",
1639
+ domain: "crm-pipeline",
1640
+ owners: ["@crm-team"],
1641
+ tags: ["deal", "detail"],
1642
+ stability: StabilityEnum2.Experimental,
1643
+ goal: "Deep dive into deal details and historical activities.",
1644
+ context: "The main workspace for managing a single deal execution."
1645
+ },
1646
+ source: {
1647
+ type: "component",
1648
+ framework: "react",
1649
+ componentKey: "DealDetailView"
1650
+ },
1651
+ targets: ["react", "markdown"],
1652
+ policy: {
1653
+ flags: ["crm.deals.enabled"]
1654
+ }
1655
+ });
1656
+ var DealCardPresentation = definePresentation2({
1657
+ meta: {
1658
+ key: "crm.deal.card",
1659
+ version: "1.0.0",
1660
+ title: "Deal Card",
1661
+ description: "Compact deal card for kanban board display",
1662
+ domain: "crm-pipeline",
1663
+ owners: ["@crm-team"],
1664
+ tags: ["deal", "card", "kanban"],
1665
+ stability: StabilityEnum2.Experimental,
1666
+ goal: "Provide a quick overview of deal status in the pipeline view.",
1667
+ context: "Condensed representation used within the Pipeline Kanban board."
1668
+ },
1669
+ source: {
1670
+ type: "component",
1671
+ framework: "react",
1672
+ componentKey: "DealCard",
1673
+ props: DealModel
1674
+ },
1675
+ targets: ["react"],
1676
+ policy: {
1677
+ flags: ["crm.deals.enabled"]
1678
+ }
1679
+ });
1680
+ // src/ui/hooks/useDealList.ts
1681
+ import { useCallback, useEffect, useMemo, useState } from "react";
1682
+ import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
1683
+ "use client";
1684
+ function useDealList(options = {}) {
1685
+ const { handlers, projectId } = useTemplateRuntime();
1686
+ const { crm: crm2 } = handlers;
1687
+ const [data, setData] = useState(null);
1688
+ const [dealsByStage, setDealsByStage] = useState({});
1689
+ const [stages, setStages] = useState([]);
1690
+ const [loading, setLoading] = useState(true);
1691
+ const [error, setError] = useState(null);
1692
+ const [page, setPage] = useState(1);
1693
+ const pipelineId = options.pipelineId ?? "pipeline-1";
1694
+ const fetchData = useCallback(async () => {
1695
+ setLoading(true);
1696
+ setError(null);
1697
+ try {
1698
+ const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
1699
+ crm2.listDeals({
1700
+ projectId,
1701
+ pipelineId,
1702
+ stageId: options.stageId,
1703
+ status: options.status === "all" ? undefined : options.status,
1704
+ search: options.search,
1705
+ limit: options.limit ?? 50,
1706
+ offset: (page - 1) * (options.limit ?? 50)
1707
+ }),
1708
+ crm2.getDealsByStage({ projectId, pipelineId }),
1709
+ crm2.getPipelineStages({ pipelineId })
1710
+ ]);
1711
+ setData(dealsResult);
1712
+ setDealsByStage(stageDealsResult);
1713
+ setStages(stagesResult);
1714
+ } catch (err) {
1715
+ setError(err instanceof Error ? err : new Error("Unknown error"));
1716
+ } finally {
1717
+ setLoading(false);
1718
+ }
1719
+ }, [
1720
+ crm2,
1721
+ projectId,
1722
+ pipelineId,
1723
+ options.stageId,
1724
+ options.status,
1725
+ options.search,
1726
+ options.limit,
1727
+ page
1728
+ ]);
1729
+ useEffect(() => {
1730
+ fetchData();
1731
+ }, [fetchData]);
1732
+ const stats = useMemo(() => {
1733
+ if (!data)
1734
+ return null;
1735
+ const open = data.deals.filter((d) => d.status === "OPEN");
1736
+ const won = data.deals.filter((d) => d.status === "WON");
1737
+ const lost = data.deals.filter((d) => d.status === "LOST");
1738
+ return {
1739
+ total: data.total,
1740
+ totalValue: data.totalValue,
1741
+ openCount: open.length,
1742
+ openValue: open.reduce((sum, d) => sum + d.value, 0),
1743
+ wonCount: won.length,
1744
+ wonValue: won.reduce((sum, d) => sum + d.value, 0),
1745
+ lostCount: lost.length
1746
+ };
1747
+ }, [data]);
1748
+ return {
1749
+ data,
1750
+ dealsByStage,
1751
+ stages,
1752
+ loading,
1753
+ error,
1754
+ stats,
1755
+ page,
1756
+ refetch: fetchData,
1757
+ nextPage: () => setPage((p) => p + 1),
1758
+ prevPage: () => page > 1 && setPage((p) => p - 1)
1759
+ };
1760
+ }
1761
+
1762
+ // src/ui/hooks/useDealMutations.ts
1763
+ import { useCallback as useCallback2, useState as useState2 } from "react";
1764
+ import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
1765
+ function useDealMutations(options = {}) {
1766
+ const { handlers, projectId } = useTemplateRuntime2();
1767
+ const { crm: crm2 } = handlers;
1768
+ const [createState, setCreateState] = useState2({
1769
+ loading: false,
1770
+ error: null,
1771
+ data: null
1772
+ });
1773
+ const [moveState, setMoveState] = useState2({
1774
+ loading: false,
1775
+ error: null,
1776
+ data: null
1777
+ });
1778
+ const [winState, setWinState] = useState2({
1779
+ loading: false,
1780
+ error: null,
1781
+ data: null
1782
+ });
1783
+ const [loseState, setLoseState] = useState2({
1784
+ loading: false,
1785
+ error: null,
1786
+ data: null
1787
+ });
1788
+ const createDeal = useCallback2(async (input) => {
1789
+ setCreateState({ loading: true, error: null, data: null });
1790
+ try {
1791
+ const result = await crm2.createDeal(input, {
1792
+ projectId,
1793
+ ownerId: "user-1"
1794
+ });
1795
+ setCreateState({ loading: false, error: null, data: result });
1796
+ options.onSuccess?.();
1797
+ return result;
1798
+ } catch (err) {
1799
+ const error = err instanceof Error ? err : new Error("Failed to create deal");
1800
+ setCreateState({ loading: false, error, data: null });
1801
+ options.onError?.(error);
1802
+ return null;
1803
+ }
1804
+ }, [crm2, projectId, options]);
1805
+ const moveDeal = useCallback2(async (input) => {
1806
+ setMoveState({ loading: true, error: null, data: null });
1807
+ try {
1808
+ const result = await crm2.moveDeal(input);
1809
+ setMoveState({ loading: false, error: null, data: result });
1810
+ options.onSuccess?.();
1811
+ return result;
1812
+ } catch (err) {
1813
+ const error = err instanceof Error ? err : new Error("Failed to move deal");
1814
+ setMoveState({ loading: false, error, data: null });
1815
+ options.onError?.(error);
1816
+ return null;
1817
+ }
1818
+ }, [crm2, options]);
1819
+ const winDeal = useCallback2(async (input) => {
1820
+ setWinState({ loading: true, error: null, data: null });
1821
+ try {
1822
+ const result = await crm2.winDeal(input);
1823
+ setWinState({ loading: false, error: null, data: result });
1824
+ options.onSuccess?.();
1825
+ return result;
1826
+ } catch (err) {
1827
+ const error = err instanceof Error ? err : new Error("Failed to mark deal as won");
1828
+ setWinState({ loading: false, error, data: null });
1829
+ options.onError?.(error);
1830
+ return null;
1831
+ }
1832
+ }, [crm2, options]);
1833
+ const loseDeal = useCallback2(async (input) => {
1834
+ setLoseState({ loading: true, error: null, data: null });
1835
+ try {
1836
+ const result = await crm2.loseDeal(input);
1837
+ setLoseState({ loading: false, error: null, data: result });
1838
+ options.onSuccess?.();
1839
+ return result;
1840
+ } catch (err) {
1841
+ const error = err instanceof Error ? err : new Error("Failed to mark deal as lost");
1842
+ setLoseState({ loading: false, error, data: null });
1843
+ options.onError?.(error);
1844
+ return null;
1845
+ }
1846
+ }, [crm2, options]);
1847
+ return {
1848
+ createDeal,
1849
+ moveDeal,
1850
+ winDeal,
1851
+ loseDeal,
1852
+ createState,
1853
+ moveState,
1854
+ winState,
1855
+ loseState,
1856
+ isLoading: createState.loading || moveState.loading || winState.loading || loseState.loading
1857
+ };
1858
+ }
1859
+
1860
+ // src/ui/CrmDealCard.tsx
1861
+ import { jsxDEV } from "react/jsx-dev-runtime";
1862
+ "use client";
1863
+ function formatCurrency(value, currency) {
1864
+ return new Intl.NumberFormat("en-US", {
1865
+ style: "currency",
1866
+ currency,
1867
+ minimumFractionDigits: 0,
1868
+ maximumFractionDigits: 0
1869
+ }).format(value);
1870
+ }
1871
+ function CrmDealCard({ deal: deal3, onClick }) {
1872
+ const daysUntilClose = deal3.expectedCloseDate ? Math.ceil((deal3.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
1873
+ return /* @__PURE__ */ jsxDEV("div", {
1874
+ onClick,
1875
+ className: "border-border bg-card cursor-pointer rounded-lg border p-3 shadow-sm transition-shadow hover:shadow-md",
1876
+ role: "button",
1877
+ tabIndex: 0,
1878
+ onKeyDown: (e) => {
1879
+ if (e.key === "Enter" || e.key === " ")
1880
+ onClick?.();
1881
+ },
1882
+ children: [
1883
+ /* @__PURE__ */ jsxDEV("h4", {
1884
+ className: "leading-snug font-medium",
1885
+ children: deal3.name
1886
+ }, undefined, false, undefined, this),
1887
+ /* @__PURE__ */ jsxDEV("div", {
1888
+ className: "text-primary mt-2 text-lg font-semibold",
1889
+ children: formatCurrency(deal3.value, deal3.currency)
1890
+ }, undefined, false, undefined, this),
1891
+ /* @__PURE__ */ jsxDEV("div", {
1892
+ className: "text-muted-foreground mt-3 flex items-center justify-between text-xs",
1893
+ children: [
1894
+ daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
1895
+ className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
1896
+ children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
1897
+ }, undefined, false, undefined, this),
1898
+ /* @__PURE__ */ jsxDEV("span", {
1899
+ className: `rounded px-1.5 py-0.5 text-xs font-medium ${deal3.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal3.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"}`,
1900
+ children: deal3.status
1901
+ }, undefined, false, undefined, this)
1902
+ ]
1903
+ }, undefined, true, undefined, this)
1904
+ ]
1905
+ }, undefined, true, undefined, this);
1906
+ }
1907
+
1908
+ // src/ui/CrmPipelineBoard.tsx
1909
+ import { useState as useState3 } from "react";
1910
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
1911
+ "use client";
1912
+ function formatCurrency2(value) {
1913
+ if (value >= 1e6)
1914
+ return `$${(value / 1e6).toFixed(1)}M`;
1915
+ if (value >= 1000)
1916
+ return `$${(value / 1000).toFixed(0)}K`;
1917
+ return `$${value}`;
1918
+ }
1919
+ function CrmPipelineBoard({
1920
+ dealsByStage,
1921
+ stages,
1922
+ onDealClick,
1923
+ onDealMove
1924
+ }) {
1925
+ const [quickMoveOpen, setQuickMoveOpen] = useState3(null);
1926
+ const sortedStages = [...stages].sort((a, b) => a.position - b.position);
1927
+ const handleQuickMove = (dealId, toStageId) => {
1928
+ onDealMove?.(dealId, toStageId);
1929
+ setQuickMoveOpen(null);
1930
+ };
1931
+ return /* @__PURE__ */ jsxDEV2("div", {
1932
+ className: "flex gap-4 overflow-x-auto pb-4",
1933
+ children: sortedStages.map((stage) => {
1934
+ const deals = dealsByStage[stage.id] ?? [];
1935
+ const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
1936
+ return /* @__PURE__ */ jsxDEV2("div", {
1937
+ className: "bg-muted/30 flex w-72 flex-shrink-0 flex-col rounded-lg",
1938
+ children: [
1939
+ /* @__PURE__ */ jsxDEV2("div", {
1940
+ className: "border-border flex items-center justify-between border-b px-3 py-2",
1941
+ children: [
1942
+ /* @__PURE__ */ jsxDEV2("div", {
1943
+ children: [
1944
+ /* @__PURE__ */ jsxDEV2("h3", {
1945
+ className: "font-medium",
1946
+ children: stage.name
1947
+ }, undefined, false, undefined, this),
1948
+ /* @__PURE__ */ jsxDEV2("p", {
1949
+ className: "text-muted-foreground text-xs",
1950
+ children: [
1951
+ deals.length,
1952
+ " deals \xB7 ",
1953
+ formatCurrency2(stageValue)
1954
+ ]
1955
+ }, undefined, true, undefined, this)
1956
+ ]
1957
+ }, undefined, true, undefined, this),
1958
+ /* @__PURE__ */ jsxDEV2("span", {
1959
+ className: "bg-muted flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium",
1960
+ children: deals.length
1961
+ }, undefined, false, undefined, this)
1962
+ ]
1963
+ }, undefined, true, undefined, this),
1964
+ /* @__PURE__ */ jsxDEV2("div", {
1965
+ className: "flex flex-1 flex-col gap-2 p-2",
1966
+ children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
1967
+ className: "border-muted-foreground/20 text-muted-foreground flex h-24 items-center justify-center rounded-md border-2 border-dashed text-xs",
1968
+ children: "No deals"
1969
+ }, undefined, false, undefined, this) : deals.map((deal3) => /* @__PURE__ */ jsxDEV2("div", {
1970
+ className: "group relative",
1971
+ children: [
1972
+ /* @__PURE__ */ jsxDEV2(CrmDealCard, {
1973
+ deal: deal3,
1974
+ onClick: () => onDealClick?.(deal3.id)
1975
+ }, undefined, false, undefined, this),
1976
+ deal3.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
1977
+ className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
1978
+ children: [
1979
+ /* @__PURE__ */ jsxDEV2("button", {
1980
+ type: "button",
1981
+ onClick: (e) => {
1982
+ e.stopPropagation();
1983
+ setQuickMoveOpen(quickMoveOpen === deal3.id ? null : deal3.id);
1984
+ },
1985
+ className: "bg-background border-border hover:bg-muted flex h-6 w-6 items-center justify-center rounded border text-xs shadow-sm",
1986
+ title: "Quick move",
1987
+ children: "\u27A1\uFE0F"
1988
+ }, undefined, false, undefined, this),
1989
+ quickMoveOpen === deal3.id && /* @__PURE__ */ jsxDEV2("div", {
1990
+ className: "bg-card border-border absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border py-1 shadow-lg",
1991
+ children: [
1992
+ /* @__PURE__ */ jsxDEV2("p", {
1993
+ className: "text-muted-foreground px-3 py-1 text-xs font-medium",
1994
+ children: "Move to:"
1995
+ }, undefined, false, undefined, this),
1996
+ sortedStages.filter((s) => s.id !== deal3.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
1997
+ type: "button",
1998
+ onClick: (e) => {
1999
+ e.stopPropagation();
2000
+ handleQuickMove(deal3.id, s.id);
2001
+ },
2002
+ className: "hover:bg-muted w-full px-3 py-1.5 text-left text-sm",
2003
+ children: s.name
2004
+ }, s.id, false, undefined, this))
2005
+ ]
2006
+ }, undefined, true, undefined, this)
2007
+ ]
2008
+ }, undefined, true, undefined, this)
2009
+ ]
2010
+ }, deal3.id, true, undefined, this))
2011
+ }, undefined, false, undefined, this)
2012
+ ]
2013
+ }, stage.id, true, undefined, this);
2014
+ })
2015
+ }, undefined, false, undefined, this);
2016
+ }
2017
+
2018
+ // src/ui/modals/CreateDealModal.tsx
2019
+ import { useState as useState4 } from "react";
2020
+ import { Button, Input } from "@contractspec/lib.design-system";
2021
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
2022
+ "use client";
2023
+ var CURRENCIES = ["USD", "EUR", "GBP", "CAD"];
2024
+ var DEFAULT_PIPELINE_ID = "pipeline-1";
2025
+ function CreateDealModal({
2026
+ isOpen,
2027
+ onClose,
2028
+ onSubmit,
2029
+ stages,
2030
+ isLoading = false
2031
+ }) {
2032
+ const [name, setName] = useState4("");
2033
+ const [value, setValue] = useState4("");
2034
+ const [currency, setCurrency] = useState4("USD");
2035
+ const [stageId, setStageId] = useState4(stages[0]?.id ?? "");
2036
+ const [expectedCloseDate, setExpectedCloseDate] = useState4("");
2037
+ const [error, setError] = useState4(null);
2038
+ const handleSubmit = async (e) => {
2039
+ e.preventDefault();
2040
+ setError(null);
2041
+ if (!name.trim()) {
2042
+ setError("Deal name is required");
2043
+ return;
2044
+ }
2045
+ const numericValue = parseFloat(value);
2046
+ if (isNaN(numericValue) || numericValue <= 0) {
2047
+ setError("Value must be a positive number");
2048
+ return;
2049
+ }
2050
+ if (!stageId) {
2051
+ setError("Please select a pipeline stage");
2052
+ return;
2053
+ }
2054
+ try {
2055
+ await onSubmit({
2056
+ name: name.trim(),
2057
+ value: numericValue,
2058
+ currency,
2059
+ pipelineId: DEFAULT_PIPELINE_ID,
2060
+ stageId,
2061
+ expectedCloseDate: expectedCloseDate ? new Date(expectedCloseDate) : undefined
2062
+ });
2063
+ setName("");
2064
+ setValue("");
2065
+ setCurrency("USD");
2066
+ setStageId(stages[0]?.id ?? "");
2067
+ setExpectedCloseDate("");
2068
+ onClose();
2069
+ } catch (err) {
2070
+ setError(err instanceof Error ? err.message : "Failed to create deal");
2071
+ }
2072
+ };
2073
+ if (!isOpen)
2074
+ return null;
2075
+ return /* @__PURE__ */ jsxDEV3("div", {
2076
+ className: "fixed inset-0 z-50 flex items-center justify-center",
2077
+ children: [
2078
+ /* @__PURE__ */ jsxDEV3("div", {
2079
+ className: "bg-background/80 absolute inset-0 backdrop-blur-sm",
2080
+ onClick: onClose,
2081
+ role: "button",
2082
+ tabIndex: 0,
2083
+ onKeyDown: (e) => {
2084
+ if (e.key === "Enter" || e.key === " ")
2085
+ onClose();
2086
+ },
2087
+ "aria-label": "Close modal"
2088
+ }, undefined, false, undefined, this),
2089
+ /* @__PURE__ */ jsxDEV3("div", {
2090
+ className: "bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl",
2091
+ children: [
2092
+ /* @__PURE__ */ jsxDEV3("h2", {
2093
+ className: "mb-4 text-xl font-semibold",
2094
+ children: "Create New Deal"
2095
+ }, undefined, false, undefined, this),
2096
+ /* @__PURE__ */ jsxDEV3("form", {
2097
+ onSubmit: handleSubmit,
2098
+ className: "space-y-4",
2099
+ children: [
2100
+ /* @__PURE__ */ jsxDEV3("div", {
2101
+ children: [
2102
+ /* @__PURE__ */ jsxDEV3("label", {
2103
+ htmlFor: "deal-name",
2104
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2105
+ children: "Deal Name *"
2106
+ }, undefined, false, undefined, this),
2107
+ /* @__PURE__ */ jsxDEV3(Input, {
2108
+ id: "deal-name",
2109
+ value: name,
2110
+ onChange: (e) => setName(e.target.value),
2111
+ placeholder: "e.g., Enterprise License - Acme Corp",
2112
+ disabled: isLoading
2113
+ }, undefined, false, undefined, this)
2114
+ ]
2115
+ }, undefined, true, undefined, this),
2116
+ /* @__PURE__ */ jsxDEV3("div", {
2117
+ className: "flex gap-3",
2118
+ children: [
2119
+ /* @__PURE__ */ jsxDEV3("div", {
2120
+ className: "flex-1",
2121
+ children: [
2122
+ /* @__PURE__ */ jsxDEV3("label", {
2123
+ htmlFor: "deal-value",
2124
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2125
+ children: "Value *"
2126
+ }, undefined, false, undefined, this),
2127
+ /* @__PURE__ */ jsxDEV3(Input, {
2128
+ id: "deal-value",
2129
+ type: "number",
2130
+ min: "0",
2131
+ step: "0.01",
2132
+ value,
2133
+ onChange: (e) => setValue(e.target.value),
2134
+ placeholder: "50000",
2135
+ disabled: isLoading
2136
+ }, undefined, false, undefined, this)
2137
+ ]
2138
+ }, undefined, true, undefined, this),
2139
+ /* @__PURE__ */ jsxDEV3("div", {
2140
+ className: "w-24",
2141
+ children: [
2142
+ /* @__PURE__ */ jsxDEV3("label", {
2143
+ htmlFor: "deal-currency",
2144
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2145
+ children: "Currency"
2146
+ }, undefined, false, undefined, this),
2147
+ /* @__PURE__ */ jsxDEV3("select", {
2148
+ id: "deal-currency",
2149
+ value: currency,
2150
+ onChange: (e) => setCurrency(e.target.value),
2151
+ disabled: isLoading,
2152
+ 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",
2153
+ children: CURRENCIES.map((c) => /* @__PURE__ */ jsxDEV3("option", {
2154
+ value: c,
2155
+ children: c
2156
+ }, c, false, undefined, this))
2157
+ }, undefined, false, undefined, this)
2158
+ ]
2159
+ }, undefined, true, undefined, this)
2160
+ ]
2161
+ }, undefined, true, undefined, this),
2162
+ /* @__PURE__ */ jsxDEV3("div", {
2163
+ children: [
2164
+ /* @__PURE__ */ jsxDEV3("label", {
2165
+ htmlFor: "deal-stage",
2166
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2167
+ children: "Pipeline Stage *"
2168
+ }, undefined, false, undefined, this),
2169
+ /* @__PURE__ */ jsxDEV3("select", {
2170
+ id: "deal-stage",
2171
+ value: stageId,
2172
+ onChange: (e) => setStageId(e.target.value),
2173
+ disabled: isLoading,
2174
+ 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",
2175
+ children: stages.map((stage) => /* @__PURE__ */ jsxDEV3("option", {
2176
+ value: stage.id,
2177
+ children: stage.name
2178
+ }, stage.id, false, undefined, this))
2179
+ }, undefined, false, undefined, this)
2180
+ ]
2181
+ }, undefined, true, undefined, this),
2182
+ /* @__PURE__ */ jsxDEV3("div", {
2183
+ children: [
2184
+ /* @__PURE__ */ jsxDEV3("label", {
2185
+ htmlFor: "deal-close-date",
2186
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2187
+ children: "Expected Close Date"
2188
+ }, undefined, false, undefined, this),
2189
+ /* @__PURE__ */ jsxDEV3(Input, {
2190
+ id: "deal-close-date",
2191
+ type: "date",
2192
+ value: expectedCloseDate,
2193
+ onChange: (e) => setExpectedCloseDate(e.target.value),
2194
+ disabled: isLoading
2195
+ }, undefined, false, undefined, this)
2196
+ ]
2197
+ }, undefined, true, undefined, this),
2198
+ error && /* @__PURE__ */ jsxDEV3("div", {
2199
+ className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
2200
+ children: error
2201
+ }, undefined, false, undefined, this),
2202
+ /* @__PURE__ */ jsxDEV3("div", {
2203
+ className: "flex justify-end gap-3 pt-2",
2204
+ children: [
2205
+ /* @__PURE__ */ jsxDEV3(Button, {
2206
+ type: "button",
2207
+ variant: "ghost",
2208
+ onPress: onClose,
2209
+ disabled: isLoading,
2210
+ children: "Cancel"
2211
+ }, undefined, false, undefined, this),
2212
+ /* @__PURE__ */ jsxDEV3(Button, {
2213
+ type: "submit",
2214
+ disabled: isLoading,
2215
+ children: isLoading ? "Creating..." : "Create Deal"
2216
+ }, undefined, false, undefined, this)
2217
+ ]
2218
+ }, undefined, true, undefined, this)
2219
+ ]
2220
+ }, undefined, true, undefined, this)
2221
+ ]
2222
+ }, undefined, true, undefined, this)
2223
+ ]
2224
+ }, undefined, true, undefined, this);
2225
+ }
2226
+
2227
+ // src/ui/modals/DealActionsModal.tsx
2228
+ import { useState as useState5 } from "react";
2229
+ import { Button as Button2 } from "@contractspec/lib.design-system";
2230
+ import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
2231
+ "use client";
2232
+ function formatCurrency3(value, currency) {
2233
+ return new Intl.NumberFormat("en-US", {
2234
+ style: "currency",
2235
+ currency,
2236
+ minimumFractionDigits: 0,
2237
+ maximumFractionDigits: 0
2238
+ }).format(value);
2239
+ }
2240
+ function DealActionsModal({
2241
+ isOpen,
2242
+ deal: deal3,
2243
+ stages,
2244
+ onClose,
2245
+ onWin,
2246
+ onLose,
2247
+ onMove,
2248
+ isLoading = false
2249
+ }) {
2250
+ const [mode, setMode] = useState5("menu");
2251
+ const [wonSource, setWonSource] = useState5("");
2252
+ const [lostReason, setLostReason] = useState5("");
2253
+ const [notes, setNotes] = useState5("");
2254
+ const [selectedStageId, setSelectedStageId] = useState5("");
2255
+ const [error, setError] = useState5(null);
2256
+ const resetForm = () => {
2257
+ setMode("menu");
2258
+ setWonSource("");
2259
+ setLostReason("");
2260
+ setNotes("");
2261
+ setSelectedStageId("");
2262
+ setError(null);
2263
+ };
2264
+ const handleClose = () => {
2265
+ resetForm();
2266
+ onClose();
2267
+ };
2268
+ const handleWin = async () => {
2269
+ if (!deal3)
2270
+ return;
2271
+ setError(null);
2272
+ try {
2273
+ await onWin({
2274
+ dealId: deal3.id,
2275
+ wonSource: wonSource.trim() || undefined,
2276
+ notes: notes.trim() || undefined
2277
+ });
2278
+ handleClose();
2279
+ } catch (err) {
2280
+ setError(err instanceof Error ? err.message : "Failed to mark deal as won");
2281
+ }
2282
+ };
2283
+ const handleLose = async () => {
2284
+ if (!deal3)
2285
+ return;
2286
+ setError(null);
2287
+ if (!lostReason.trim()) {
2288
+ setError("Please provide a reason for losing the deal");
2289
+ return;
2290
+ }
2291
+ try {
2292
+ await onLose({
2293
+ dealId: deal3.id,
2294
+ lostReason: lostReason.trim(),
2295
+ notes: notes.trim() || undefined
2296
+ });
2297
+ handleClose();
2298
+ } catch (err) {
2299
+ setError(err instanceof Error ? err.message : "Failed to mark deal as lost");
2300
+ }
2301
+ };
2302
+ const handleMove = async () => {
2303
+ if (!deal3)
2304
+ return;
2305
+ setError(null);
2306
+ if (!selectedStageId) {
2307
+ setError("Please select a stage");
2308
+ return;
2309
+ }
2310
+ if (selectedStageId === deal3.stageId) {
2311
+ setError("Deal is already in this stage");
2312
+ return;
2313
+ }
2314
+ try {
2315
+ await onMove({
2316
+ dealId: deal3.id,
2317
+ stageId: selectedStageId
2318
+ });
2319
+ handleClose();
2320
+ } catch (err) {
2321
+ setError(err instanceof Error ? err.message : "Failed to move deal");
2322
+ }
2323
+ };
2324
+ if (!isOpen || !deal3)
2325
+ return null;
2326
+ return /* @__PURE__ */ jsxDEV4("div", {
2327
+ className: "fixed inset-0 z-50 flex items-center justify-center",
2328
+ children: [
2329
+ /* @__PURE__ */ jsxDEV4("div", {
2330
+ className: "bg-background/80 absolute inset-0 backdrop-blur-sm",
2331
+ onClick: handleClose,
2332
+ role: "button",
2333
+ tabIndex: 0,
2334
+ onKeyDown: (e) => {
2335
+ if (e.key === "Enter" || e.key === " ")
2336
+ handleClose();
2337
+ },
2338
+ "aria-label": "Close modal"
2339
+ }, undefined, false, undefined, this),
2340
+ /* @__PURE__ */ jsxDEV4("div", {
2341
+ className: "bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl",
2342
+ children: [
2343
+ /* @__PURE__ */ jsxDEV4("div", {
2344
+ className: "border-border mb-4 border-b pb-4",
2345
+ children: [
2346
+ /* @__PURE__ */ jsxDEV4("h2", {
2347
+ className: "text-xl font-semibold",
2348
+ children: deal3.name
2349
+ }, undefined, false, undefined, this),
2350
+ /* @__PURE__ */ jsxDEV4("p", {
2351
+ className: "text-primary text-lg font-medium",
2352
+ children: formatCurrency3(deal3.value, deal3.currency)
2353
+ }, undefined, false, undefined, this),
2354
+ /* @__PURE__ */ jsxDEV4("span", {
2355
+ className: `mt-2 inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${deal3.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal3.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"}`,
2356
+ children: deal3.status
2357
+ }, undefined, false, undefined, this)
2358
+ ]
2359
+ }, undefined, true, undefined, this),
2360
+ mode === "menu" && /* @__PURE__ */ jsxDEV4("div", {
2361
+ className: "space-y-3",
2362
+ children: [
2363
+ deal3.status === "OPEN" && /* @__PURE__ */ jsxDEV4(Fragment, {
2364
+ children: [
2365
+ /* @__PURE__ */ jsxDEV4(Button2, {
2366
+ className: "w-full justify-start",
2367
+ variant: "ghost",
2368
+ onPress: () => setMode("win"),
2369
+ children: [
2370
+ /* @__PURE__ */ jsxDEV4("span", {
2371
+ className: "mr-2",
2372
+ children: "\uD83C\uDFC6"
2373
+ }, undefined, false, undefined, this),
2374
+ " Mark as Won"
2375
+ ]
2376
+ }, undefined, true, undefined, this),
2377
+ /* @__PURE__ */ jsxDEV4(Button2, {
2378
+ className: "w-full justify-start",
2379
+ variant: "ghost",
2380
+ onPress: () => setMode("lose"),
2381
+ children: [
2382
+ /* @__PURE__ */ jsxDEV4("span", {
2383
+ className: "mr-2",
2384
+ children: "\u274C"
2385
+ }, undefined, false, undefined, this),
2386
+ " Mark as Lost"
2387
+ ]
2388
+ }, undefined, true, undefined, this),
2389
+ /* @__PURE__ */ jsxDEV4(Button2, {
2390
+ className: "w-full justify-start",
2391
+ variant: "ghost",
2392
+ onPress: () => {
2393
+ setSelectedStageId(deal3.stageId);
2394
+ setMode("move");
2395
+ },
2396
+ children: [
2397
+ /* @__PURE__ */ jsxDEV4("span", {
2398
+ className: "mr-2",
2399
+ children: "\u27A1\uFE0F"
2400
+ }, undefined, false, undefined, this),
2401
+ " Move to Stage"
2402
+ ]
2403
+ }, undefined, true, undefined, this)
2404
+ ]
2405
+ }, undefined, true, undefined, this),
2406
+ deal3.status !== "OPEN" && /* @__PURE__ */ jsxDEV4("p", {
2407
+ className: "text-muted-foreground py-4 text-center",
2408
+ children: [
2409
+ "This deal is already ",
2410
+ deal3.status.toLowerCase(),
2411
+ ". No actions available."
2412
+ ]
2413
+ }, undefined, true, undefined, this),
2414
+ /* @__PURE__ */ jsxDEV4("div", {
2415
+ className: "border-border border-t pt-3",
2416
+ children: /* @__PURE__ */ jsxDEV4(Button2, {
2417
+ className: "w-full",
2418
+ variant: "outline",
2419
+ onPress: handleClose,
2420
+ children: "Close"
2421
+ }, undefined, false, undefined, this)
2422
+ }, undefined, false, undefined, this)
2423
+ ]
2424
+ }, undefined, true, undefined, this),
2425
+ mode === "win" && /* @__PURE__ */ jsxDEV4("div", {
2426
+ className: "space-y-4",
2427
+ children: [
2428
+ /* @__PURE__ */ jsxDEV4("div", {
2429
+ children: [
2430
+ /* @__PURE__ */ jsxDEV4("label", {
2431
+ htmlFor: "won-source",
2432
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2433
+ children: "How did you win this deal?"
2434
+ }, undefined, false, undefined, this),
2435
+ /* @__PURE__ */ jsxDEV4("select", {
2436
+ id: "won-source",
2437
+ value: wonSource,
2438
+ onChange: (e) => setWonSource(e.target.value),
2439
+ 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",
2440
+ children: [
2441
+ /* @__PURE__ */ jsxDEV4("option", {
2442
+ value: "",
2443
+ children: "Select a source..."
2444
+ }, undefined, false, undefined, this),
2445
+ /* @__PURE__ */ jsxDEV4("option", {
2446
+ value: "referral",
2447
+ children: "Referral"
2448
+ }, undefined, false, undefined, this),
2449
+ /* @__PURE__ */ jsxDEV4("option", {
2450
+ value: "cold_outreach",
2451
+ children: "Cold Outreach"
2452
+ }, undefined, false, undefined, this),
2453
+ /* @__PURE__ */ jsxDEV4("option", {
2454
+ value: "inbound",
2455
+ children: "Inbound Lead"
2456
+ }, undefined, false, undefined, this),
2457
+ /* @__PURE__ */ jsxDEV4("option", {
2458
+ value: "upsell",
2459
+ children: "Upsell"
2460
+ }, undefined, false, undefined, this),
2461
+ /* @__PURE__ */ jsxDEV4("option", {
2462
+ value: "other",
2463
+ children: "Other"
2464
+ }, undefined, false, undefined, this)
2465
+ ]
2466
+ }, undefined, true, undefined, this)
2467
+ ]
2468
+ }, undefined, true, undefined, this),
2469
+ /* @__PURE__ */ jsxDEV4("div", {
2470
+ children: [
2471
+ /* @__PURE__ */ jsxDEV4("label", {
2472
+ htmlFor: "win-notes",
2473
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2474
+ children: "Notes (optional)"
2475
+ }, undefined, false, undefined, this),
2476
+ /* @__PURE__ */ jsxDEV4("textarea", {
2477
+ id: "win-notes",
2478
+ value: notes,
2479
+ onChange: (e) => setNotes(e.target.value),
2480
+ placeholder: "Any additional notes about the win...",
2481
+ rows: 3,
2482
+ 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"
2483
+ }, undefined, false, undefined, this)
2484
+ ]
2485
+ }, undefined, true, undefined, this),
2486
+ error && /* @__PURE__ */ jsxDEV4("div", {
2487
+ className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
2488
+ children: error
2489
+ }, undefined, false, undefined, this),
2490
+ /* @__PURE__ */ jsxDEV4("div", {
2491
+ className: "flex justify-end gap-3 pt-2",
2492
+ children: [
2493
+ /* @__PURE__ */ jsxDEV4(Button2, {
2494
+ variant: "ghost",
2495
+ onPress: () => setMode("menu"),
2496
+ disabled: isLoading,
2497
+ children: "Back"
2498
+ }, undefined, false, undefined, this),
2499
+ /* @__PURE__ */ jsxDEV4(Button2, {
2500
+ onPress: handleWin,
2501
+ disabled: isLoading,
2502
+ children: isLoading ? "Processing..." : "\uD83C\uDFC6 Confirm Win"
2503
+ }, undefined, false, undefined, this)
2504
+ ]
2505
+ }, undefined, true, undefined, this)
2506
+ ]
2507
+ }, undefined, true, undefined, this),
2508
+ mode === "lose" && /* @__PURE__ */ jsxDEV4("div", {
2509
+ className: "space-y-4",
2510
+ children: [
2511
+ /* @__PURE__ */ jsxDEV4("div", {
2512
+ children: [
2513
+ /* @__PURE__ */ jsxDEV4("label", {
2514
+ htmlFor: "lost-reason",
2515
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2516
+ children: "Why was this deal lost? *"
2517
+ }, undefined, false, undefined, this),
2518
+ /* @__PURE__ */ jsxDEV4("select", {
2519
+ id: "lost-reason",
2520
+ value: lostReason,
2521
+ onChange: (e) => setLostReason(e.target.value),
2522
+ 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",
2523
+ children: [
2524
+ /* @__PURE__ */ jsxDEV4("option", {
2525
+ value: "",
2526
+ children: "Select a reason..."
2527
+ }, undefined, false, undefined, this),
2528
+ /* @__PURE__ */ jsxDEV4("option", {
2529
+ value: "price",
2530
+ children: "Price too high"
2531
+ }, undefined, false, undefined, this),
2532
+ /* @__PURE__ */ jsxDEV4("option", {
2533
+ value: "competitor",
2534
+ children: "Lost to competitor"
2535
+ }, undefined, false, undefined, this),
2536
+ /* @__PURE__ */ jsxDEV4("option", {
2537
+ value: "no_budget",
2538
+ children: "No budget"
2539
+ }, undefined, false, undefined, this),
2540
+ /* @__PURE__ */ jsxDEV4("option", {
2541
+ value: "no_decision",
2542
+ children: "No decision made"
2543
+ }, undefined, false, undefined, this),
2544
+ /* @__PURE__ */ jsxDEV4("option", {
2545
+ value: "timing",
2546
+ children: "Bad timing"
2547
+ }, undefined, false, undefined, this),
2548
+ /* @__PURE__ */ jsxDEV4("option", {
2549
+ value: "product_fit",
2550
+ children: "Product not a fit"
2551
+ }, undefined, false, undefined, this),
2552
+ /* @__PURE__ */ jsxDEV4("option", {
2553
+ value: "other",
2554
+ children: "Other"
2555
+ }, undefined, false, undefined, this)
2556
+ ]
2557
+ }, undefined, true, undefined, this)
2558
+ ]
2559
+ }, undefined, true, undefined, this),
2560
+ /* @__PURE__ */ jsxDEV4("div", {
2561
+ children: [
2562
+ /* @__PURE__ */ jsxDEV4("label", {
2563
+ htmlFor: "lose-notes",
2564
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2565
+ children: "Notes (optional)"
2566
+ }, undefined, false, undefined, this),
2567
+ /* @__PURE__ */ jsxDEV4("textarea", {
2568
+ id: "lose-notes",
2569
+ value: notes,
2570
+ onChange: (e) => setNotes(e.target.value),
2571
+ placeholder: "Any additional details...",
2572
+ rows: 3,
2573
+ 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"
2574
+ }, undefined, false, undefined, this)
2575
+ ]
2576
+ }, undefined, true, undefined, this),
2577
+ error && /* @__PURE__ */ jsxDEV4("div", {
2578
+ className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
2579
+ children: error
2580
+ }, undefined, false, undefined, this),
2581
+ /* @__PURE__ */ jsxDEV4("div", {
2582
+ className: "flex justify-end gap-3 pt-2",
2583
+ children: [
2584
+ /* @__PURE__ */ jsxDEV4(Button2, {
2585
+ variant: "ghost",
2586
+ onPress: () => setMode("menu"),
2587
+ disabled: isLoading,
2588
+ children: "Back"
2589
+ }, undefined, false, undefined, this),
2590
+ /* @__PURE__ */ jsxDEV4(Button2, {
2591
+ variant: "destructive",
2592
+ onPress: handleLose,
2593
+ disabled: isLoading,
2594
+ children: isLoading ? "Processing..." : "\u274C Confirm Loss"
2595
+ }, undefined, false, undefined, this)
2596
+ ]
2597
+ }, undefined, true, undefined, this)
2598
+ ]
2599
+ }, undefined, true, undefined, this),
2600
+ mode === "move" && /* @__PURE__ */ jsxDEV4("div", {
2601
+ className: "space-y-4",
2602
+ children: [
2603
+ /* @__PURE__ */ jsxDEV4("div", {
2604
+ children: [
2605
+ /* @__PURE__ */ jsxDEV4("label", {
2606
+ htmlFor: "move-stage",
2607
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
2608
+ children: "Move to Stage"
2609
+ }, undefined, false, undefined, this),
2610
+ /* @__PURE__ */ jsxDEV4("select", {
2611
+ id: "move-stage",
2612
+ value: selectedStageId,
2613
+ onChange: (e) => setSelectedStageId(e.target.value),
2614
+ 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",
2615
+ children: stages.map((stage) => /* @__PURE__ */ jsxDEV4("option", {
2616
+ value: stage.id,
2617
+ children: [
2618
+ stage.name,
2619
+ stage.id === deal3.stageId ? " (current)" : ""
2620
+ ]
2621
+ }, stage.id, true, undefined, this))
2622
+ }, undefined, false, undefined, this)
2623
+ ]
2624
+ }, undefined, true, undefined, this),
2625
+ error && /* @__PURE__ */ jsxDEV4("div", {
2626
+ className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
2627
+ children: error
2628
+ }, undefined, false, undefined, this),
2629
+ /* @__PURE__ */ jsxDEV4("div", {
2630
+ className: "flex justify-end gap-3 pt-2",
2631
+ children: [
2632
+ /* @__PURE__ */ jsxDEV4(Button2, {
2633
+ variant: "ghost",
2634
+ onPress: () => setMode("menu"),
2635
+ disabled: isLoading,
2636
+ children: "Back"
2637
+ }, undefined, false, undefined, this),
2638
+ /* @__PURE__ */ jsxDEV4(Button2, {
2639
+ onPress: handleMove,
2640
+ disabled: isLoading,
2641
+ children: isLoading ? "Moving..." : "\u27A1\uFE0F Move Deal"
2642
+ }, undefined, false, undefined, this)
2643
+ ]
2644
+ }, undefined, true, undefined, this)
2645
+ ]
2646
+ }, undefined, true, undefined, this)
2647
+ ]
2648
+ }, undefined, true, undefined, this)
2649
+ ]
2650
+ }, undefined, true, undefined, this);
2651
+ }
2652
+
2653
+ // src/ui/CrmDashboard.tsx
2654
+ import { useCallback as useCallback3, useState as useState6 } from "react";
2655
+ import {
2656
+ Button as Button3,
2657
+ ErrorState,
2658
+ LoaderBlock,
2659
+ StatCard,
2660
+ StatCardGroup
2661
+ } from "@contractspec/lib.design-system";
2662
+ import {
2663
+ Tabs,
2664
+ TabsContent,
2665
+ TabsList,
2666
+ TabsTrigger
2667
+ } from "@contractspec/lib.ui-kit-web/ui/tabs";
2668
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
2669
+ "use client";
2670
+ function formatCurrency4(value, currency = "USD") {
2671
+ return new Intl.NumberFormat("en-US", {
2672
+ style: "currency",
2673
+ currency,
2674
+ minimumFractionDigits: 0,
2675
+ maximumFractionDigits: 0
2676
+ }).format(value);
2677
+ }
2678
+ function CrmDashboard() {
2679
+ const [isCreateModalOpen, setIsCreateModalOpen] = useState6(false);
2680
+ const [selectedDeal, setSelectedDeal] = useState6(null);
2681
+ const [isDealActionsOpen, setIsDealActionsOpen] = useState6(false);
2682
+ const { data, dealsByStage, stages, loading, error, stats, refetch } = useDealList();
2683
+ const mutations = useDealMutations({
2684
+ onSuccess: () => {
2685
+ refetch();
2686
+ }
2687
+ });
2688
+ const handleDealClick = useCallback3((dealId) => {
2689
+ const deal3 = dealsByStage ? Object.values(dealsByStage).flat().find((d) => d.id === dealId) : null;
2690
+ if (deal3) {
2691
+ setSelectedDeal(deal3);
2692
+ setIsDealActionsOpen(true);
2693
+ }
2694
+ }, [dealsByStage]);
2695
+ const handleDealMove = useCallback3(async (dealId, toStageId) => {
2696
+ await mutations.moveDeal({ dealId, stageId: toStageId });
2697
+ }, [mutations]);
2698
+ if (loading && !data) {
2699
+ return /* @__PURE__ */ jsxDEV5(LoaderBlock, {
2700
+ label: "Loading CRM..."
2701
+ }, undefined, false, undefined, this);
2702
+ }
2703
+ if (error) {
2704
+ return /* @__PURE__ */ jsxDEV5(ErrorState, {
2705
+ title: "Failed to load CRM",
2706
+ description: error.message,
2707
+ onRetry: refetch,
2708
+ retryLabel: "Retry"
2709
+ }, undefined, false, undefined, this);
2710
+ }
2711
+ return /* @__PURE__ */ jsxDEV5("div", {
2712
+ className: "space-y-6",
2713
+ children: [
2714
+ /* @__PURE__ */ jsxDEV5("div", {
2715
+ className: "flex items-center justify-between",
2716
+ children: [
2717
+ /* @__PURE__ */ jsxDEV5("h2", {
2718
+ className: "text-2xl font-bold",
2719
+ children: "CRM Pipeline"
2720
+ }, undefined, false, undefined, this),
2721
+ /* @__PURE__ */ jsxDEV5(Button3, {
2722
+ onClick: () => setIsCreateModalOpen(true),
2723
+ children: [
2724
+ /* @__PURE__ */ jsxDEV5("span", {
2725
+ className: "mr-2",
2726
+ children: "+"
2727
+ }, undefined, false, undefined, this),
2728
+ " Create Deal"
2729
+ ]
2730
+ }, undefined, true, undefined, this)
2731
+ ]
2732
+ }, undefined, true, undefined, this),
2733
+ stats && /* @__PURE__ */ jsxDEV5(StatCardGroup, {
2734
+ children: [
2735
+ /* @__PURE__ */ jsxDEV5(StatCard, {
2736
+ label: "Total Pipeline",
2737
+ value: formatCurrency4(stats.totalValue),
2738
+ hint: `${stats.total} deals`
2739
+ }, undefined, false, undefined, this),
2740
+ /* @__PURE__ */ jsxDEV5(StatCard, {
2741
+ label: "Open Deals",
2742
+ value: formatCurrency4(stats.openValue),
2743
+ hint: `${stats.openCount} active`
2744
+ }, undefined, false, undefined, this),
2745
+ /* @__PURE__ */ jsxDEV5(StatCard, {
2746
+ label: "Won",
2747
+ value: formatCurrency4(stats.wonValue),
2748
+ hint: `${stats.wonCount} closed`
2749
+ }, undefined, false, undefined, this),
2750
+ /* @__PURE__ */ jsxDEV5(StatCard, {
2751
+ label: "Lost",
2752
+ value: stats.lostCount,
2753
+ hint: "deals lost"
2754
+ }, undefined, false, undefined, this)
2755
+ ]
2756
+ }, undefined, true, undefined, this),
2757
+ /* @__PURE__ */ jsxDEV5(Tabs, {
2758
+ defaultValue: "pipeline",
2759
+ className: "w-full",
2760
+ children: [
2761
+ /* @__PURE__ */ jsxDEV5(TabsList, {
2762
+ children: [
2763
+ /* @__PURE__ */ jsxDEV5(TabsTrigger, {
2764
+ value: "pipeline",
2765
+ children: [
2766
+ /* @__PURE__ */ jsxDEV5("span", {
2767
+ className: "mr-2",
2768
+ children: "\uD83D\uDCCA"
2769
+ }, undefined, false, undefined, this),
2770
+ "Pipeline"
2771
+ ]
2772
+ }, undefined, true, undefined, this),
2773
+ /* @__PURE__ */ jsxDEV5(TabsTrigger, {
2774
+ value: "list",
2775
+ children: [
2776
+ /* @__PURE__ */ jsxDEV5("span", {
2777
+ className: "mr-2",
2778
+ children: "\uD83D\uDCCB"
2779
+ }, undefined, false, undefined, this),
2780
+ "All Deals"
2781
+ ]
2782
+ }, undefined, true, undefined, this),
2783
+ /* @__PURE__ */ jsxDEV5(TabsTrigger, {
2784
+ value: "metrics",
2785
+ children: [
2786
+ /* @__PURE__ */ jsxDEV5("span", {
2787
+ className: "mr-2",
2788
+ children: "\uD83D\uDCC8"
2789
+ }, undefined, false, undefined, this),
2790
+ "Metrics"
2791
+ ]
2792
+ }, undefined, true, undefined, this)
2793
+ ]
2794
+ }, undefined, true, undefined, this),
2795
+ /* @__PURE__ */ jsxDEV5(TabsContent, {
2796
+ value: "pipeline",
2797
+ className: "min-h-[400px]",
2798
+ children: /* @__PURE__ */ jsxDEV5(CrmPipelineBoard, {
2799
+ dealsByStage,
2800
+ stages,
2801
+ onDealClick: handleDealClick,
2802
+ onDealMove: handleDealMove
2803
+ }, undefined, false, undefined, this)
2804
+ }, undefined, false, undefined, this),
2805
+ /* @__PURE__ */ jsxDEV5(TabsContent, {
2806
+ value: "list",
2807
+ className: "min-h-[400px]",
2808
+ children: /* @__PURE__ */ jsxDEV5(DealListTab, {
2809
+ data,
2810
+ onDealClick: handleDealClick
2811
+ }, undefined, false, undefined, this)
2812
+ }, undefined, false, undefined, this),
2813
+ /* @__PURE__ */ jsxDEV5(TabsContent, {
2814
+ value: "metrics",
2815
+ className: "min-h-[400px]",
2816
+ children: /* @__PURE__ */ jsxDEV5(MetricsTab, {
2817
+ stats
2818
+ }, undefined, false, undefined, this)
2819
+ }, undefined, false, undefined, this)
2820
+ ]
2821
+ }, undefined, true, undefined, this),
2822
+ /* @__PURE__ */ jsxDEV5(CreateDealModal, {
2823
+ isOpen: isCreateModalOpen,
2824
+ onClose: () => setIsCreateModalOpen(false),
2825
+ onSubmit: async (input) => {
2826
+ await mutations.createDeal(input);
2827
+ },
2828
+ stages,
2829
+ isLoading: mutations.createState.loading
2830
+ }, undefined, false, undefined, this),
2831
+ /* @__PURE__ */ jsxDEV5(DealActionsModal, {
2832
+ isOpen: isDealActionsOpen,
2833
+ deal: selectedDeal,
2834
+ stages,
2835
+ onClose: () => {
2836
+ setIsDealActionsOpen(false);
2837
+ setSelectedDeal(null);
2838
+ },
2839
+ onWin: async (input) => {
2840
+ await mutations.winDeal(input);
2841
+ },
2842
+ onLose: async (input) => {
2843
+ await mutations.loseDeal(input);
2844
+ },
2845
+ onMove: async (input) => {
2846
+ await mutations.moveDeal(input);
2847
+ refetch();
2848
+ },
2849
+ isLoading: mutations.isLoading
2850
+ }, undefined, false, undefined, this)
2851
+ ]
2852
+ }, undefined, true, undefined, this);
2853
+ }
2854
+ function DealListTab({ data, onDealClick }) {
2855
+ if (!data?.deals.length) {
2856
+ return /* @__PURE__ */ jsxDEV5("div", {
2857
+ className: "text-muted-foreground flex h-64 items-center justify-center",
2858
+ children: "No deals found"
2859
+ }, undefined, false, undefined, this);
2860
+ }
2861
+ return /* @__PURE__ */ jsxDEV5("div", {
2862
+ className: "border-border rounded-lg border",
2863
+ children: /* @__PURE__ */ jsxDEV5("table", {
2864
+ className: "w-full",
2865
+ children: [
2866
+ /* @__PURE__ */ jsxDEV5("thead", {
2867
+ className: "border-border bg-muted/30 border-b",
2868
+ children: /* @__PURE__ */ jsxDEV5("tr", {
2869
+ children: [
2870
+ /* @__PURE__ */ jsxDEV5("th", {
2871
+ className: "px-4 py-3 text-left text-sm font-medium",
2872
+ children: "Deal"
2873
+ }, undefined, false, undefined, this),
2874
+ /* @__PURE__ */ jsxDEV5("th", {
2875
+ className: "px-4 py-3 text-left text-sm font-medium",
2876
+ children: "Value"
2877
+ }, undefined, false, undefined, this),
2878
+ /* @__PURE__ */ jsxDEV5("th", {
2879
+ className: "px-4 py-3 text-left text-sm font-medium",
2880
+ children: "Status"
2881
+ }, undefined, false, undefined, this),
2882
+ /* @__PURE__ */ jsxDEV5("th", {
2883
+ className: "px-4 py-3 text-left text-sm font-medium",
2884
+ children: "Expected Close"
2885
+ }, undefined, false, undefined, this),
2886
+ /* @__PURE__ */ jsxDEV5("th", {
2887
+ className: "px-4 py-3 text-left text-sm font-medium",
2888
+ children: "Actions"
2889
+ }, undefined, false, undefined, this)
2890
+ ]
2891
+ }, undefined, true, undefined, this)
2892
+ }, undefined, false, undefined, this),
2893
+ /* @__PURE__ */ jsxDEV5("tbody", {
2894
+ className: "divide-border divide-y",
2895
+ children: data.deals.map((deal3) => /* @__PURE__ */ jsxDEV5("tr", {
2896
+ className: "hover:bg-muted/50",
2897
+ children: [
2898
+ /* @__PURE__ */ jsxDEV5("td", {
2899
+ className: "px-4 py-3",
2900
+ children: /* @__PURE__ */ jsxDEV5("div", {
2901
+ className: "font-medium",
2902
+ children: deal3.name
2903
+ }, undefined, false, undefined, this)
2904
+ }, undefined, false, undefined, this),
2905
+ /* @__PURE__ */ jsxDEV5("td", {
2906
+ className: "px-4 py-3 font-mono",
2907
+ children: formatCurrency4(deal3.value, deal3.currency)
2908
+ }, undefined, false, undefined, this),
2909
+ /* @__PURE__ */ jsxDEV5("td", {
2910
+ className: "px-4 py-3",
2911
+ children: /* @__PURE__ */ jsxDEV5("span", {
2912
+ className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${deal3.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal3.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"}`,
2913
+ children: deal3.status
2914
+ }, undefined, false, undefined, this)
2915
+ }, undefined, false, undefined, this),
2916
+ /* @__PURE__ */ jsxDEV5("td", {
2917
+ className: "text-muted-foreground px-4 py-3",
2918
+ children: deal3.expectedCloseDate?.toLocaleDateString() ?? "-"
2919
+ }, undefined, false, undefined, this),
2920
+ /* @__PURE__ */ jsxDEV5("td", {
2921
+ className: "px-4 py-3",
2922
+ children: /* @__PURE__ */ jsxDEV5(Button3, {
2923
+ variant: "ghost",
2924
+ size: "sm",
2925
+ onPress: () => onDealClick?.(deal3.id),
2926
+ children: "Actions"
2927
+ }, undefined, false, undefined, this)
2928
+ }, undefined, false, undefined, this)
2929
+ ]
2930
+ }, deal3.id, true, undefined, this))
2931
+ }, undefined, false, undefined, this)
2932
+ ]
2933
+ }, undefined, true, undefined, this)
2934
+ }, undefined, false, undefined, this);
2935
+ }
2936
+ function MetricsTab({
2937
+ stats
2938
+ }) {
2939
+ if (!stats)
2940
+ return null;
2941
+ return /* @__PURE__ */ jsxDEV5("div", {
2942
+ className: "space-y-6",
2943
+ children: /* @__PURE__ */ jsxDEV5("div", {
2944
+ className: "border-border bg-card rounded-xl border p-6",
2945
+ children: [
2946
+ /* @__PURE__ */ jsxDEV5("h3", {
2947
+ className: "mb-4 text-lg font-semibold",
2948
+ children: "Pipeline Overview"
2949
+ }, undefined, false, undefined, this),
2950
+ /* @__PURE__ */ jsxDEV5("dl", {
2951
+ className: "grid gap-4 sm:grid-cols-3",
2952
+ children: [
2953
+ /* @__PURE__ */ jsxDEV5("div", {
2954
+ children: [
2955
+ /* @__PURE__ */ jsxDEV5("dt", {
2956
+ className: "text-muted-foreground text-sm",
2957
+ children: "Win Rate"
2958
+ }, undefined, false, undefined, this),
2959
+ /* @__PURE__ */ jsxDEV5("dd", {
2960
+ className: "text-2xl font-semibold",
2961
+ children: [
2962
+ stats.total > 0 ? (stats.wonCount / stats.total * 100).toFixed(0) : 0,
2963
+ "%"
2964
+ ]
2965
+ }, undefined, true, undefined, this)
2966
+ ]
2967
+ }, undefined, true, undefined, this),
2968
+ /* @__PURE__ */ jsxDEV5("div", {
2969
+ children: [
2970
+ /* @__PURE__ */ jsxDEV5("dt", {
2971
+ className: "text-muted-foreground text-sm",
2972
+ children: "Avg Deal Size"
2973
+ }, undefined, false, undefined, this),
2974
+ /* @__PURE__ */ jsxDEV5("dd", {
2975
+ className: "text-2xl font-semibold",
2976
+ children: formatCurrency4(stats.total > 0 ? stats.totalValue / stats.total : 0)
2977
+ }, undefined, false, undefined, this)
2978
+ ]
2979
+ }, undefined, true, undefined, this),
2980
+ /* @__PURE__ */ jsxDEV5("div", {
2981
+ children: [
2982
+ /* @__PURE__ */ jsxDEV5("dt", {
2983
+ className: "text-muted-foreground text-sm",
2984
+ children: "Conversion"
2985
+ }, undefined, false, undefined, this),
2986
+ /* @__PURE__ */ jsxDEV5("dd", {
2987
+ className: "text-2xl font-semibold",
2988
+ children: [
2989
+ stats.wonCount,
2990
+ " / ",
2991
+ stats.total
2992
+ ]
2993
+ }, undefined, true, undefined, this)
2994
+ ]
2995
+ }, undefined, true, undefined, this)
2996
+ ]
2997
+ }, undefined, true, undefined, this)
2998
+ ]
2999
+ }, undefined, true, undefined, this)
3000
+ }, undefined, false, undefined, this);
3001
+ }
3002
+ // src/ui/hooks/index.ts
3003
+ "use client";
3004
+
3005
+ // src/ui/renderers/pipeline.renderer.tsx
3006
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
3007
+ function CrmPipelineBoardWrapper() {
3008
+ const { dealsByStage, stages } = useDealList();
3009
+ return /* @__PURE__ */ jsxDEV6(CrmPipelineBoard, {
3010
+ dealsByStage,
3011
+ stages
3012
+ }, undefined, false, undefined, this);
3013
+ }
3014
+ var crmPipelineReactRenderer = {
3015
+ target: "react",
3016
+ render: async (desc, _ctx) => {
3017
+ if (desc.source.type !== "component") {
3018
+ throw new Error("Invalid source type");
3019
+ }
3020
+ if (desc.source.componentKey !== "CrmPipelineView") {
3021
+ throw new Error(`Unknown component: ${desc.source.componentKey}`);
3022
+ }
3023
+ return /* @__PURE__ */ jsxDEV6(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
3024
+ }
3025
+ };
3026
+
3027
+ // src/ui/renderers/pipeline.markdown.ts
3028
+ function formatCurrency5(value, currency = "USD") {
3029
+ return new Intl.NumberFormat("en-US", {
3030
+ style: "currency",
3031
+ currency,
3032
+ minimumFractionDigits: 0
3033
+ }).format(value);
3034
+ }
3035
+ var crmPipelineMarkdownRenderer = {
3036
+ target: "markdown",
3037
+ render: async (desc, _ctx) => {
3038
+ if (desc.source.type !== "component" || desc.source.componentKey !== "PipelineKanbanView") {
3039
+ throw new Error("crmPipelineMarkdownRenderer: not PipelineKanbanView");
3040
+ }
3041
+ const pipelineId = "pipeline-1";
3042
+ const [dealsResult, stages] = await Promise.all([
3043
+ mockListDealsHandler({ pipelineId, limit: 50 }),
3044
+ mockGetPipelineStagesHandler({ pipelineId })
3045
+ ]);
3046
+ const deals = dealsResult.deals;
3047
+ const stageList = stages;
3048
+ const dealsByStage = {};
3049
+ for (const stage of stageList) {
3050
+ dealsByStage[stage.id] = deals.filter((d) => d.stageId === stage.id && d.status === "OPEN");
3051
+ }
3052
+ const lines = [
3053
+ "# CRM Pipeline",
3054
+ "",
3055
+ `**Total Value**: ${formatCurrency5(dealsResult.totalValue)}`,
3056
+ `**Total Deals**: ${dealsResult.total}`,
3057
+ ""
3058
+ ];
3059
+ for (const stage of stageList.sort((a, b) => a.position - b.position)) {
3060
+ const stageDeals = dealsByStage[stage.id] ?? [];
3061
+ const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
3062
+ lines.push(`## ${stage.name}`);
3063
+ lines.push(`_${stageDeals.length} deals \xB7 ${formatCurrency5(stageValue)}_`);
3064
+ lines.push("");
3065
+ if (stageDeals.length === 0) {
3066
+ lines.push("_No deals_");
3067
+ } else {
3068
+ for (const deal3 of stageDeals) {
3069
+ lines.push(`- **${deal3.name}** - ${formatCurrency5(deal3.value, deal3.currency)}`);
3070
+ }
3071
+ }
3072
+ lines.push("");
3073
+ }
3074
+ return {
3075
+ mimeType: "text/markdown",
3076
+ body: lines.join(`
3077
+ `)
3078
+ };
3079
+ }
3080
+ };
3081
+ var crmDashboardMarkdownRenderer = {
3082
+ target: "markdown",
3083
+ render: async (desc, _ctx) => {
3084
+ if (desc.source.type !== "component" || desc.source.componentKey !== "CrmDashboard") {
3085
+ throw new Error("crmDashboardMarkdownRenderer: not CrmDashboard");
3086
+ }
3087
+ const pipelineId = "pipeline-1";
3088
+ const [dealsResult, stages] = await Promise.all([
3089
+ mockListDealsHandler({ pipelineId, limit: 100 }),
3090
+ mockGetPipelineStagesHandler({ pipelineId })
3091
+ ]);
3092
+ const deals = dealsResult.deals;
3093
+ const stageList = stages;
3094
+ const openDeals = deals.filter((d) => d.status === "OPEN");
3095
+ const wonDeals = deals.filter((d) => d.status === "WON");
3096
+ const lostDeals = deals.filter((d) => d.status === "LOST");
3097
+ const openValue = openDeals.reduce((sum, d) => sum + d.value, 0);
3098
+ const wonValue = wonDeals.reduce((sum, d) => sum + d.value, 0);
3099
+ const lines = [
3100
+ "# CRM Dashboard",
3101
+ "",
3102
+ "> Sales pipeline overview and key metrics",
3103
+ "",
3104
+ "## Summary",
3105
+ "",
3106
+ "| Metric | Value |",
3107
+ "|--------|-------|",
3108
+ `| Total Deals | ${dealsResult.total} |`,
3109
+ `| Pipeline Value | ${formatCurrency5(dealsResult.totalValue)} |`,
3110
+ `| Open Deals | ${openDeals.length} (${formatCurrency5(openValue)}) |`,
3111
+ `| Won Deals | ${wonDeals.length} (${formatCurrency5(wonValue)}) |`,
3112
+ `| Lost Deals | ${lostDeals.length} |`,
3113
+ "",
3114
+ "## Pipeline Stages",
3115
+ ""
3116
+ ];
3117
+ lines.push("| Stage | Deals | Value |");
3118
+ lines.push("|-------|-------|-------|");
3119
+ for (const stage of stageList.sort((a, b) => a.position - b.position)) {
3120
+ const stageDeals = openDeals.filter((d) => d.stageId === stage.id);
3121
+ const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
3122
+ lines.push(`| ${stage.name} | ${stageDeals.length} | ${formatCurrency5(stageValue)} |`);
3123
+ }
3124
+ lines.push("");
3125
+ lines.push("## Recent Deals");
3126
+ lines.push("");
3127
+ const recentDeals = deals.slice(0, 10);
3128
+ if (recentDeals.length === 0) {
3129
+ lines.push("_No deals yet._");
3130
+ } else {
3131
+ lines.push("| Deal | Value | Stage | Status |");
3132
+ lines.push("|------|-------|-------|--------|");
3133
+ for (const deal3 of recentDeals) {
3134
+ const stage = stageList.find((s) => s.id === deal3.stageId);
3135
+ lines.push(`| ${deal3.name} | ${formatCurrency5(deal3.value, deal3.currency)} | ${stage?.name ?? "-"} | ${deal3.status} |`);
3136
+ }
3137
+ }
3138
+ return {
3139
+ mimeType: "text/markdown",
3140
+ body: lines.join(`
3141
+ `)
3142
+ };
3143
+ }
3144
+ };
3145
+ // src/ui/overlays/demo-overlays.ts
3146
+ var crmDemoOverlay = {
3147
+ overlayId: "crm-pipeline.demo-user",
3148
+ version: "1.0.0",
3149
+ description: "Demo mode with sample data",
3150
+ appliesTo: {
3151
+ feature: "crm-pipeline",
3152
+ role: "demo"
3153
+ },
3154
+ modifications: [
3155
+ {
3156
+ type: "hideField",
3157
+ field: "importButton",
3158
+ reason: "Not available in demo"
3159
+ },
3160
+ {
3161
+ type: "hideField",
3162
+ field: "exportButton",
3163
+ reason: "Not available in demo"
3164
+ },
3165
+ {
3166
+ type: "addBadge",
3167
+ position: "header",
3168
+ label: "Demo Mode",
3169
+ variant: "warning"
3170
+ }
3171
+ ]
3172
+ };
3173
+ var crmSalesRepOverlay = {
3174
+ overlayId: "crm-pipeline.sales-rep",
3175
+ version: "1.0.0",
3176
+ description: "Sales rep focused view",
3177
+ appliesTo: {
3178
+ feature: "crm-pipeline",
3179
+ role: "sales-rep"
3180
+ },
3181
+ modifications: [
3182
+ {
3183
+ type: "hideField",
3184
+ field: "teamMetrics",
3185
+ reason: "Team metrics for managers only"
3186
+ },
3187
+ { type: "hideField", field: "pipelineSettings", reason: "Admin only" },
3188
+ { type: "renameLabel", field: "deals", newLabel: "My Deals" }
3189
+ ]
3190
+ };
3191
+ var crmOverlays = [
3192
+ crmDemoOverlay,
3193
+ crmSalesRepOverlay
3194
+ ];
3195
+ // src/index.ts
35
3196
  import { identityRbacSchemaContribution } from "@contractspec/lib.identity-rbac";
36
3197
  import { auditTrailSchemaContribution } from "@contractspec/module.audit-trail";
37
3198
  import { notificationsSchemaContribution } from "@contractspec/module.notifications";
38
-
39
- //#region src/index.ts
40
- /**
41
- * Complete schema composition for CRM Pipeline.
42
- */
43
- const schemaComposition = {
44
- modules: [
45
- identityRbacSchemaContribution,
46
- auditTrailSchemaContribution,
47
- notificationsSchemaContribution,
48
- crmPipelineSchemaContribution
49
- ],
50
- provider: "postgresql",
51
- outputPath: "./prisma/schema/generated.prisma"
52
- };
53
-
54
- //#endregion
55
- export { ActivityEntity, CompanyEntity, CompanySizeEnum, ContactCreatedEvent, ContactEntity, ContactStatusEnum, CreateDealContract, CreateDealInputModel, CreateDealModal, CrmDashboard, CrmDashboardPresentation, CrmDealCard, CrmPipelineBoard, CrmPipelineFeature, DealActionsModal, DealCardPresentation, DealCreatedEvent, DealDetailPresentation, DealEntity, DealListPresentation, DealLostEvent, DealLostPayloadModel, DealModel, DealMovedEvent, DealMovedPayloadModel, DealStatusEnum, DealStatusFilterEnum, DealWonEvent, DealWonPayloadModel, ListDealsContract, ListDealsInputModel, ListDealsOutputModel, LoseDealContract, LoseDealInputModel, MOCK_COMPANIES, MOCK_CONTACTS, MOCK_DEALS, MOCK_STAGES, MoveDealContract, MoveDealInputModel, PipelineEntity, PipelineKanbanPresentation, PipelineMetricsPresentation, StageEntity, TaskCompletedEvent, TaskEntity, TaskPriorityEnum, TaskStatusEnum, TaskTypeEnum, WinDealContract, WinDealInputModel, createCrmHandlers, crmDashboardMarkdownRenderer, crmDemoOverlay, crmOverlays, crmPipelineMarkdownRenderer, crmPipelineReactRenderer, crmPipelineSchemaContribution, crmSalesRepOverlay, example, mockCreateDealHandler, mockGetDealsByStageHandler, mockGetPipelineStagesHandler, mockListDealsHandler, mockLoseDealHandler, mockMoveDealHandler, mockWinDealHandler, schemaComposition, useDealList, useDealMutations };
56
- //# sourceMappingURL=index.js.map
3199
+ var schemaComposition = {
3200
+ modules: [
3201
+ identityRbacSchemaContribution,
3202
+ auditTrailSchemaContribution,
3203
+ notificationsSchemaContribution,
3204
+ crmPipelineSchemaContribution
3205
+ ],
3206
+ provider: "postgresql",
3207
+ outputPath: "./prisma/schema/generated.prisma"
3208
+ };
3209
+ export {
3210
+ useDealMutations,
3211
+ useDealList,
3212
+ schemaComposition,
3213
+ mockWinDealHandler,
3214
+ mockMoveDealHandler,
3215
+ mockLoseDealHandler,
3216
+ mockListDealsHandler,
3217
+ mockGetPipelineStagesHandler,
3218
+ mockGetDealsByStageHandler,
3219
+ mockCreateDealHandler,
3220
+ example_default as example,
3221
+ crmSalesRepOverlay,
3222
+ crmPipelineSchemaContribution,
3223
+ crmPipelineReactRenderer,
3224
+ crmPipelineMarkdownRenderer,
3225
+ crmOverlays,
3226
+ crmDemoOverlay,
3227
+ crmDashboardMarkdownRenderer,
3228
+ createCrmHandlers,
3229
+ WinDealInputModel,
3230
+ WinDealContract,
3231
+ TaskTypeEnum,
3232
+ TaskStatusEnum,
3233
+ TaskPriorityEnum,
3234
+ TaskEntity,
3235
+ TaskCompletedEvent,
3236
+ StageEntity,
3237
+ PipelineMetricsPresentation,
3238
+ PipelineKanbanPresentation,
3239
+ PipelineEntity,
3240
+ MoveDealInputModel,
3241
+ MoveDealContract,
3242
+ MOCK_STAGES,
3243
+ MOCK_DEALS,
3244
+ MOCK_CONTACTS,
3245
+ MOCK_COMPANIES,
3246
+ LoseDealInputModel,
3247
+ LoseDealContract,
3248
+ ListDealsOutputModel,
3249
+ ListDealsInputModel,
3250
+ ListDealsContract,
3251
+ DealWonPayloadModel,
3252
+ DealWonEvent,
3253
+ DealStatusFilterEnum,
3254
+ DealStatusEnum2 as DealStatusEnum,
3255
+ DealMovedPayloadModel,
3256
+ DealMovedEvent,
3257
+ DealModel,
3258
+ DealLostPayloadModel,
3259
+ DealLostEvent,
3260
+ DealListPresentation,
3261
+ DealEntity,
3262
+ DealDetailPresentation,
3263
+ DealCreatedEvent,
3264
+ DealCardPresentation,
3265
+ DealActionsModal,
3266
+ CrmPipelineFeature,
3267
+ CrmPipelineBoard,
3268
+ CrmDealCard,
3269
+ CrmDashboardPresentation,
3270
+ CrmDashboard,
3271
+ CreateDealModal,
3272
+ CreateDealInputModel,
3273
+ CreateDealContract,
3274
+ ContactStatusEnum,
3275
+ ContactEntity,
3276
+ ContactCreatedEvent,
3277
+ CompanySizeEnum,
3278
+ CompanyEntity,
3279
+ ActivityEntity
3280
+ };