@contractspec/example.crm-pipeline 3.7.17 → 3.7.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/.turbo/turbo-build.log +135 -135
  2. package/CHANGELOG.md +20 -0
  3. package/dist/browser/crm-pipeline.feature.js +1 -82
  4. package/dist/browser/deal/deal.enum.js +1 -18
  5. package/dist/browser/deal/deal.operation.js +1 -396
  6. package/dist/browser/deal/deal.schema.js +1 -141
  7. package/dist/browser/deal/deal.test-spec.js +1 -58
  8. package/dist/browser/deal/index.js +1 -408
  9. package/dist/browser/docs/crm-pipeline.docblock.js +5 -49
  10. package/dist/browser/docs/index.js +5 -49
  11. package/dist/browser/entities/company.entity.js +1 -52
  12. package/dist/browser/entities/contact.entity.js +1 -66
  13. package/dist/browser/entities/deal.entity.js +1 -107
  14. package/dist/browser/entities/index.js +1 -343
  15. package/dist/browser/entities/task.entity.js +1 -99
  16. package/dist/browser/events/contact.event.js +1 -31
  17. package/dist/browser/events/deal.event.js +1 -101
  18. package/dist/browser/events/index.js +1 -158
  19. package/dist/browser/events/task.event.js +1 -28
  20. package/dist/browser/example.js +1 -39
  21. package/dist/browser/handlers/crm.handlers.js +2 -171
  22. package/dist/browser/handlers/deal.handlers.js +1 -293
  23. package/dist/browser/handlers/index.js +2 -467
  24. package/dist/browser/handlers/mock-data.js +1 -165
  25. package/dist/browser/index.js +8 -3461
  26. package/dist/browser/operations/index.js +1 -407
  27. package/dist/browser/presentations/dashboard.presentation.js +1 -55
  28. package/dist/browser/presentations/index.js +1 -290
  29. package/dist/browser/presentations/pipeline.presentation.js +1 -236
  30. package/dist/browser/seeders/index.js +1 -22
  31. package/dist/browser/ui/CrmDashboard.js +1 -1547
  32. package/dist/browser/ui/CrmDealCard.js +1 -50
  33. package/dist/browser/ui/CrmPipelineBoard.js +1 -160
  34. package/dist/browser/ui/hooks/index.js +1 -197
  35. package/dist/browser/ui/hooks/useDealList.js +1 -95
  36. package/dist/browser/ui/hooks/useDealMutations.js +1 -100
  37. package/dist/browser/ui/index.js +4 -2205
  38. package/dist/browser/ui/modals/CreateDealModal.js +1 -211
  39. package/dist/browser/ui/modals/DealActionsModal.js +1 -428
  40. package/dist/browser/ui/modals/index.js +1 -638
  41. package/dist/browser/ui/overlays/demo-overlays.js +1 -55
  42. package/dist/browser/ui/overlays/index.js +1 -55
  43. package/dist/browser/ui/renderers/index.js +4 -849
  44. package/dist/browser/ui/renderers/pipeline.markdown.js +4 -575
  45. package/dist/browser/ui/renderers/pipeline.renderer.js +1 -275
  46. package/dist/browser/ui/tables/DealListTab.js +1 -390
  47. package/dist/crm-pipeline.feature.js +1 -82
  48. package/dist/deal/deal.enum.js +1 -18
  49. package/dist/deal/deal.operation.js +1 -396
  50. package/dist/deal/deal.schema.js +1 -141
  51. package/dist/deal/deal.test-spec.js +1 -58
  52. package/dist/deal/index.js +1 -408
  53. package/dist/docs/crm-pipeline.docblock.js +5 -49
  54. package/dist/docs/index.js +5 -49
  55. package/dist/entities/company.entity.js +1 -52
  56. package/dist/entities/contact.entity.js +1 -66
  57. package/dist/entities/deal.entity.js +1 -107
  58. package/dist/entities/index.js +1 -343
  59. package/dist/entities/task.entity.js +1 -99
  60. package/dist/events/contact.event.js +1 -31
  61. package/dist/events/deal.event.js +1 -101
  62. package/dist/events/index.js +1 -158
  63. package/dist/events/task.event.js +1 -28
  64. package/dist/example.js +1 -39
  65. package/dist/handlers/crm.handlers.js +2 -171
  66. package/dist/handlers/deal.handlers.js +1 -293
  67. package/dist/handlers/index.js +2 -467
  68. package/dist/handlers/mock-data.js +1 -165
  69. package/dist/index.js +8 -3461
  70. package/dist/node/crm-pipeline.feature.js +1 -82
  71. package/dist/node/deal/deal.enum.js +1 -18
  72. package/dist/node/deal/deal.operation.js +1 -396
  73. package/dist/node/deal/deal.schema.js +1 -141
  74. package/dist/node/deal/deal.test-spec.js +1 -58
  75. package/dist/node/deal/index.js +1 -408
  76. package/dist/node/docs/crm-pipeline.docblock.js +5 -49
  77. package/dist/node/docs/index.js +5 -49
  78. package/dist/node/entities/company.entity.js +1 -52
  79. package/dist/node/entities/contact.entity.js +1 -66
  80. package/dist/node/entities/deal.entity.js +1 -107
  81. package/dist/node/entities/index.js +1 -343
  82. package/dist/node/entities/task.entity.js +1 -99
  83. package/dist/node/events/contact.event.js +1 -31
  84. package/dist/node/events/deal.event.js +1 -101
  85. package/dist/node/events/index.js +1 -158
  86. package/dist/node/events/task.event.js +1 -28
  87. package/dist/node/example.js +1 -39
  88. package/dist/node/handlers/crm.handlers.js +2 -171
  89. package/dist/node/handlers/deal.handlers.js +1 -293
  90. package/dist/node/handlers/index.js +2 -467
  91. package/dist/node/handlers/mock-data.js +1 -165
  92. package/dist/node/index.js +8 -3461
  93. package/dist/node/operations/index.js +1 -407
  94. package/dist/node/presentations/dashboard.presentation.js +1 -55
  95. package/dist/node/presentations/index.js +1 -290
  96. package/dist/node/presentations/pipeline.presentation.js +1 -236
  97. package/dist/node/seeders/index.js +1 -22
  98. package/dist/node/ui/CrmDashboard.js +1 -1547
  99. package/dist/node/ui/CrmDealCard.js +1 -50
  100. package/dist/node/ui/CrmPipelineBoard.js +1 -160
  101. package/dist/node/ui/hooks/index.js +1 -197
  102. package/dist/node/ui/hooks/useDealList.js +1 -95
  103. package/dist/node/ui/hooks/useDealMutations.js +1 -100
  104. package/dist/node/ui/index.js +4 -2205
  105. package/dist/node/ui/modals/CreateDealModal.js +1 -211
  106. package/dist/node/ui/modals/DealActionsModal.js +1 -428
  107. package/dist/node/ui/modals/index.js +1 -638
  108. package/dist/node/ui/overlays/demo-overlays.js +1 -55
  109. package/dist/node/ui/overlays/index.js +1 -55
  110. package/dist/node/ui/renderers/index.js +4 -849
  111. package/dist/node/ui/renderers/pipeline.markdown.js +4 -575
  112. package/dist/node/ui/renderers/pipeline.renderer.js +1 -275
  113. package/dist/node/ui/tables/DealListTab.js +1 -390
  114. package/dist/operations/index.js +1 -407
  115. package/dist/presentations/dashboard.presentation.js +1 -55
  116. package/dist/presentations/index.js +1 -290
  117. package/dist/presentations/pipeline.presentation.js +1 -236
  118. package/dist/seeders/index.js +1 -22
  119. package/dist/ui/CrmDashboard.js +1 -1547
  120. package/dist/ui/CrmDealCard.js +1 -50
  121. package/dist/ui/CrmPipelineBoard.js +1 -160
  122. package/dist/ui/hooks/index.js +1 -197
  123. package/dist/ui/hooks/useDealList.js +1 -95
  124. package/dist/ui/hooks/useDealMutations.js +1 -100
  125. package/dist/ui/index.js +4 -2205
  126. package/dist/ui/modals/CreateDealModal.js +1 -211
  127. package/dist/ui/modals/DealActionsModal.js +1 -428
  128. package/dist/ui/modals/index.js +1 -638
  129. package/dist/ui/overlays/demo-overlays.js +1 -55
  130. package/dist/ui/overlays/index.js +1 -55
  131. package/dist/ui/renderers/index.js +4 -849
  132. package/dist/ui/renderers/pipeline.markdown.js +4 -575
  133. package/dist/ui/renderers/pipeline.renderer.js +1 -275
  134. package/dist/ui/tables/DealListTab.js +1 -390
  135. package/package.json +13 -13
@@ -1,275 +1 @@
1
- // src/ui/CrmDealCard.tsx
2
- import { jsxDEV } from "react/jsx-dev-runtime";
3
- "use client";
4
- function formatCurrency(value, currency) {
5
- return new Intl.NumberFormat("en-US", {
6
- style: "currency",
7
- currency,
8
- minimumFractionDigits: 0,
9
- maximumFractionDigits: 0
10
- }).format(value);
11
- }
12
- function CrmDealCard({ deal, onClick }) {
13
- const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
14
- return /* @__PURE__ */ jsxDEV("div", {
15
- onClick,
16
- className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
17
- role: "button",
18
- tabIndex: 0,
19
- onKeyDown: (e) => {
20
- if (e.key === "Enter" || e.key === " ")
21
- onClick?.();
22
- },
23
- children: [
24
- /* @__PURE__ */ jsxDEV("h4", {
25
- className: "font-medium leading-snug",
26
- children: deal.name
27
- }, undefined, false, undefined, this),
28
- /* @__PURE__ */ jsxDEV("div", {
29
- className: "mt-2 font-semibold text-lg text-primary",
30
- children: formatCurrency(deal.value, deal.currency)
31
- }, undefined, false, undefined, this),
32
- /* @__PURE__ */ jsxDEV("div", {
33
- className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
34
- children: [
35
- daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
36
- className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
37
- children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
38
- }, undefined, false, undefined, this),
39
- /* @__PURE__ */ jsxDEV("span", {
40
- className: `rounded px-1.5 py-0.5 font-medium text-xs ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
41
- children: deal.status
42
- }, undefined, false, undefined, this)
43
- ]
44
- }, undefined, true, undefined, this)
45
- ]
46
- }, undefined, true, undefined, this);
47
- }
48
-
49
- // src/ui/CrmPipelineBoard.tsx
50
- import { useState } from "react";
51
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
52
- "use client";
53
- function formatCurrency2(value) {
54
- if (value >= 1e6)
55
- return `$${(value / 1e6).toFixed(1)}M`;
56
- if (value >= 1000)
57
- return `$${(value / 1000).toFixed(0)}K`;
58
- return `$${value}`;
59
- }
60
- function CrmPipelineBoard({
61
- dealsByStage,
62
- stages,
63
- onDealClick,
64
- onDealMove
65
- }) {
66
- const [quickMoveOpen, setQuickMoveOpen] = useState(null);
67
- const sortedStages = [...stages].sort((a, b) => a.position - b.position);
68
- const handleQuickMove = (dealId, toStageId) => {
69
- onDealMove?.(dealId, toStageId);
70
- setQuickMoveOpen(null);
71
- };
72
- return /* @__PURE__ */ jsxDEV2("div", {
73
- className: "flex gap-4 overflow-x-auto pb-4",
74
- children: sortedStages.map((stage) => {
75
- const deals = dealsByStage[stage.id] ?? [];
76
- const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
77
- return /* @__PURE__ */ jsxDEV2("div", {
78
- className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
79
- children: [
80
- /* @__PURE__ */ jsxDEV2("div", {
81
- className: "flex items-center justify-between border-border border-b px-3 py-2",
82
- children: [
83
- /* @__PURE__ */ jsxDEV2("div", {
84
- children: [
85
- /* @__PURE__ */ jsxDEV2("h3", {
86
- className: "font-medium",
87
- children: stage.name
88
- }, undefined, false, undefined, this),
89
- /* @__PURE__ */ jsxDEV2("p", {
90
- className: "text-muted-foreground text-xs",
91
- children: [
92
- deals.length,
93
- " deals · ",
94
- formatCurrency2(stageValue)
95
- ]
96
- }, undefined, true, undefined, this)
97
- ]
98
- }, undefined, true, undefined, this),
99
- /* @__PURE__ */ jsxDEV2("span", {
100
- className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
101
- children: deals.length
102
- }, undefined, false, undefined, this)
103
- ]
104
- }, undefined, true, undefined, this),
105
- /* @__PURE__ */ jsxDEV2("div", {
106
- className: "flex flex-1 flex-col gap-2 p-2",
107
- children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
108
- className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
109
- children: "No deals"
110
- }, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
111
- className: "group relative",
112
- children: [
113
- /* @__PURE__ */ jsxDEV2(CrmDealCard, {
114
- deal,
115
- onClick: () => onDealClick?.(deal.id)
116
- }, undefined, false, undefined, this),
117
- deal.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
118
- className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
119
- children: [
120
- /* @__PURE__ */ jsxDEV2("button", {
121
- type: "button",
122
- onClick: (e) => {
123
- e.stopPropagation();
124
- setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
125
- },
126
- className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
127
- title: "Quick move",
128
- children: "➡️"
129
- }, undefined, false, undefined, this),
130
- quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
131
- className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
132
- children: [
133
- /* @__PURE__ */ jsxDEV2("p", {
134
- className: "px-3 py-1 font-medium text-muted-foreground text-xs",
135
- children: "Move to:"
136
- }, undefined, false, undefined, this),
137
- sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
138
- type: "button",
139
- onClick: (e) => {
140
- e.stopPropagation();
141
- handleQuickMove(deal.id, s.id);
142
- },
143
- className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
144
- children: s.name
145
- }, s.id, false, undefined, this))
146
- ]
147
- }, undefined, true, undefined, this)
148
- ]
149
- }, undefined, true, undefined, this)
150
- ]
151
- }, deal.id, true, undefined, this))
152
- }, undefined, false, undefined, this)
153
- ]
154
- }, stage.id, true, undefined, this);
155
- })
156
- }, undefined, false, undefined, this);
157
- }
158
-
159
- // src/ui/hooks/useDealList.ts
160
- import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
161
- import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
162
- "use client";
163
- function useDealList(options = {}) {
164
- const { handlers, projectId } = useTemplateRuntime();
165
- const { crm } = handlers;
166
- const [data, setData] = useState2(null);
167
- const [dealsByStage, setDealsByStage] = useState2({});
168
- const [stages, setStages] = useState2([]);
169
- const [loading, setLoading] = useState2(true);
170
- const [error, setError] = useState2(null);
171
- const [internalPage, setInternalPage] = useState2(0);
172
- const pipelineId = options.pipelineId ?? "pipeline-1";
173
- const pageIndex = options.pageIndex ?? internalPage;
174
- const pageSize = options.pageSize ?? options.limit ?? 50;
175
- const [sort] = options.sorting ?? [];
176
- const sortBy = sort?.id;
177
- const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
178
- const fetchData = useCallback(async () => {
179
- setLoading(true);
180
- setError(null);
181
- try {
182
- const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
183
- crm.listDeals({
184
- projectId,
185
- pipelineId,
186
- stageId: options.stageId,
187
- status: options.status === "all" ? undefined : options.status,
188
- search: options.search,
189
- limit: pageSize,
190
- offset: pageIndex * pageSize,
191
- sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
192
- sortDirection
193
- }),
194
- crm.getDealsByStage({ projectId, pipelineId }),
195
- crm.getPipelineStages({ pipelineId })
196
- ]);
197
- setData(dealsResult);
198
- setDealsByStage(stageDealsResult);
199
- setStages(stagesResult);
200
- } catch (err) {
201
- setError(err instanceof Error ? err : new Error("Unknown error"));
202
- } finally {
203
- setLoading(false);
204
- }
205
- }, [
206
- crm,
207
- projectId,
208
- pipelineId,
209
- options.stageId,
210
- options.status,
211
- options.search,
212
- pageIndex,
213
- pageSize,
214
- sortBy,
215
- sortDirection
216
- ]);
217
- useEffect(() => {
218
- fetchData();
219
- }, [fetchData]);
220
- const stats = useMemo(() => {
221
- if (!data)
222
- return null;
223
- const open = data.deals.filter((d) => d.status === "OPEN");
224
- const won = data.deals.filter((d) => d.status === "WON");
225
- const lost = data.deals.filter((d) => d.status === "LOST");
226
- return {
227
- total: data.total,
228
- totalValue: data.totalValue,
229
- openCount: open.length,
230
- openValue: open.reduce((sum, d) => sum + d.value, 0),
231
- wonCount: won.length,
232
- wonValue: won.reduce((sum, d) => sum + d.value, 0),
233
- lostCount: lost.length
234
- };
235
- }, [data]);
236
- return {
237
- data,
238
- dealsByStage,
239
- stages,
240
- loading,
241
- error,
242
- stats,
243
- page: pageIndex + 1,
244
- pageIndex,
245
- pageSize,
246
- refetch: fetchData,
247
- nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
248
- prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
249
- };
250
- }
251
-
252
- // src/ui/renderers/pipeline.renderer.tsx
253
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
254
- function CrmPipelineBoardWrapper() {
255
- const { dealsByStage, stages } = useDealList();
256
- return /* @__PURE__ */ jsxDEV3(CrmPipelineBoard, {
257
- dealsByStage,
258
- stages
259
- }, undefined, false, undefined, this);
260
- }
261
- var crmPipelineReactRenderer = {
262
- target: "react",
263
- render: async (desc, _ctx) => {
264
- if (desc.source.type !== "component") {
265
- throw new Error("Invalid source type");
266
- }
267
- if (desc.source.componentKey !== "CrmPipelineView") {
268
- throw new Error(`Unknown component: ${desc.source.componentKey}`);
269
- }
270
- return /* @__PURE__ */ jsxDEV3(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
271
- }
272
- };
273
- export {
274
- crmPipelineReactRenderer
275
- };
1
+ import{jsx as P,jsxs as I}from"react/jsx-runtime";function j(A,J){return new Intl.NumberFormat("en-US",{style:"currency",currency:J,minimumFractionDigits:0,maximumFractionDigits:0}).format(A)}function D({deal:A,onClick:J}){let G=A.expectedCloseDate?Math.ceil((A.expectedCloseDate.getTime()-Date.now())/86400000):null;return I("div",{onClick:J,className:"cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",role:"button",tabIndex:0,onKeyDown:(_)=>{if(_.key==="Enter"||_.key===" ")J?.()},children:[P("h4",{className:"font-medium leading-snug",children:A.name}),P("div",{className:"mt-2 font-semibold text-lg text-primary",children:j(A.value,A.currency)}),I("div",{className:"mt-3 flex items-center justify-between text-muted-foreground text-xs",children:[G!==null&&P("span",{className:G<0?"text-red-500":G<=7?"text-yellow-600 dark:text-yellow-500":"",children:G<0?`${Math.abs(G)}d overdue`:G===0?"Due today":`${G}d left`}),P("span",{className:`rounded px-1.5 py-0.5 font-medium text-xs ${A.status==="WON"?"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400":A.status==="LOST"?"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400":"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,children:A.status})]})]})}import{useState as x}from"react";import{jsx as W,jsxs as V}from"react/jsx-runtime";function g(A){if(A>=1e6)return`$${(A/1e6).toFixed(1)}M`;if(A>=1000)return`$${(A/1000).toFixed(0)}K`;return`$${A}`}function S({dealsByStage:A,stages:J,onDealClick:G,onDealMove:_}){let[K,z]=x(null),E=[...J].sort(($,X)=>$.position-X.position),b=($,X)=>{_?.($,X),z(null)};return W("div",{className:"flex gap-4 overflow-x-auto pb-4",children:E.map(($)=>{let X=A[$.id]??[],w=X.reduce((F,Y)=>F+Y.value,0);return V("div",{className:"flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",children:[V("div",{className:"flex items-center justify-between border-border border-b px-3 py-2",children:[V("div",{children:[W("h3",{className:"font-medium",children:$.name}),V("p",{className:"text-muted-foreground text-xs",children:[X.length," deals · ",g(w)]})]}),W("span",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",children:X.length})]}),W("div",{className:"flex flex-1 flex-col gap-2 p-2",children:X.length===0?W("div",{className:"flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",children:"No deals"}):X.map((F)=>V("div",{className:"group relative",children:[W(D,{deal:F,onClick:()=>G?.(F.id)}),F.status==="OPEN"&&_&&V("div",{className:"absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",children:[W("button",{type:"button",onClick:(Y)=>{Y.stopPropagation(),z(K===F.id?null:F.id)},className:"flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",title:"Quick move",children:"➡️"}),K===F.id&&V("div",{className:"absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",children:[W("p",{className:"px-3 py-1 font-medium text-muted-foreground text-xs",children:"Move to:"}),E.filter((Y)=>Y.id!==F.stageId).map((Y)=>W("button",{type:"button",onClick:(L)=>{L.stopPropagation(),b(F.id,Y.id)},className:"w-full px-3 py-1.5 text-left text-sm hover:bg-muted",children:Y.name},Y.id))]})]})]},F.id))})]},$.id)})})}import{useTemplateRuntime as c}from"@contractspec/lib.example-shared-ui";import{useCallback as u,useEffect as p,useMemo as r,useState as q}from"react";function v(A={}){let{handlers:J,projectId:G}=c(),{crm:_}=J,[K,z]=q(null),[E,b]=q({}),[$,X]=q([]),[w,F]=q(!0),[Y,L]=q(null),[C,B]=q(0),O=A.pipelineId??"pipeline-1",T=A.pageIndex??C,Q=A.pageSize??A.limit??50,[U]=A.sorting??[],N=U?.id,k=U?U.desc?"desc":"asc":void 0,f=u(async()=>{F(!0),L(null);try{let[Z,R,h]=await Promise.all([_.listDeals({projectId:G,pipelineId:O,stageId:A.stageId,status:A.status==="all"?void 0:A.status,search:A.search,limit:Q,offset:T*Q,sortBy:N==="name"||N==="value"||N==="status"||N==="expectedCloseDate"||N==="updatedAt"?N:void 0,sortDirection:k}),_.getDealsByStage({projectId:G,pipelineId:O}),_.getPipelineStages({pipelineId:O})]);z(Z),b(R),X(h)}catch(Z){L(Z instanceof Error?Z:Error("Unknown error"))}finally{F(!1)}},[_,G,O,A.stageId,A.status,A.search,T,Q,N,k]);p(()=>{f()},[f]);let m=r(()=>{if(!K)return null;let Z=K.deals.filter((H)=>H.status==="OPEN"),R=K.deals.filter((H)=>H.status==="WON"),h=K.deals.filter((H)=>H.status==="LOST");return{total:K.total,totalValue:K.totalValue,openCount:Z.length,openValue:Z.reduce((H,M)=>H+M.value,0),wonCount:R.length,wonValue:R.reduce((H,M)=>H+M.value,0),lostCount:h.length}},[K]);return{data:K,dealsByStage:E,stages:$,loading:w,error:Y,stats:m,page:T+1,pageIndex:T,pageSize:Q,refetch:f,nextPage:A.pageIndex===void 0?()=>B((Z)=>Z+1):void 0,prevPage:A.pageIndex===void 0?()=>T>0&&B((Z)=>Z-1):void 0}}import{jsx as y}from"react/jsx-runtime";function i(){let{dealsByStage:A,stages:J}=v();return y(S,{dealsByStage:A,stages:J})}var JA={target:"react",render:async(A,J)=>{if(A.source.type!=="component")throw Error("Invalid source type");if(A.source.componentKey!=="CrmPipelineView")throw Error(`Unknown component: ${A.source.componentKey}`);return y(i,{})}};export{JA as crmPipelineReactRenderer};
@@ -1,390 +1 @@
1
- // src/ui/hooks/useDealList.ts
2
- import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
3
- import { useCallback, useEffect, useMemo, useState } from "react";
4
- "use client";
5
- function useDealList(options = {}) {
6
- const { handlers, projectId } = useTemplateRuntime();
7
- const { crm } = handlers;
8
- const [data, setData] = useState(null);
9
- const [dealsByStage, setDealsByStage] = useState({});
10
- const [stages, setStages] = useState([]);
11
- const [loading, setLoading] = useState(true);
12
- const [error, setError] = useState(null);
13
- const [internalPage, setInternalPage] = useState(0);
14
- const pipelineId = options.pipelineId ?? "pipeline-1";
15
- const pageIndex = options.pageIndex ?? internalPage;
16
- const pageSize = options.pageSize ?? options.limit ?? 50;
17
- const [sort] = options.sorting ?? [];
18
- const sortBy = sort?.id;
19
- const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
20
- const fetchData = useCallback(async () => {
21
- setLoading(true);
22
- setError(null);
23
- try {
24
- const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
25
- crm.listDeals({
26
- projectId,
27
- pipelineId,
28
- stageId: options.stageId,
29
- status: options.status === "all" ? undefined : options.status,
30
- search: options.search,
31
- limit: pageSize,
32
- offset: pageIndex * pageSize,
33
- sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
34
- sortDirection
35
- }),
36
- crm.getDealsByStage({ projectId, pipelineId }),
37
- crm.getPipelineStages({ pipelineId })
38
- ]);
39
- setData(dealsResult);
40
- setDealsByStage(stageDealsResult);
41
- setStages(stagesResult);
42
- } catch (err) {
43
- setError(err instanceof Error ? err : new Error("Unknown error"));
44
- } finally {
45
- setLoading(false);
46
- }
47
- }, [
48
- crm,
49
- projectId,
50
- pipelineId,
51
- options.stageId,
52
- options.status,
53
- options.search,
54
- pageIndex,
55
- pageSize,
56
- sortBy,
57
- sortDirection
58
- ]);
59
- useEffect(() => {
60
- fetchData();
61
- }, [fetchData]);
62
- const stats = useMemo(() => {
63
- if (!data)
64
- return null;
65
- const open = data.deals.filter((d) => d.status === "OPEN");
66
- const won = data.deals.filter((d) => d.status === "WON");
67
- const lost = data.deals.filter((d) => d.status === "LOST");
68
- return {
69
- total: data.total,
70
- totalValue: data.totalValue,
71
- openCount: open.length,
72
- openValue: open.reduce((sum, d) => sum + d.value, 0),
73
- wonCount: won.length,
74
- wonValue: won.reduce((sum, d) => sum + d.value, 0),
75
- lostCount: lost.length
76
- };
77
- }, [data]);
78
- return {
79
- data,
80
- dealsByStage,
81
- stages,
82
- loading,
83
- error,
84
- stats,
85
- page: pageIndex + 1,
86
- pageIndex,
87
- pageSize,
88
- refetch: fetchData,
89
- nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
90
- prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
91
- };
92
- }
93
-
94
- // src/ui/tables/DealListTab.tsx
95
- import {
96
- Button,
97
- DataTable,
98
- LoaderBlock
99
- } from "@contractspec/lib.design-system";
100
- import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
101
- import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
102
- import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
103
- import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
104
- import * as React from "react";
105
- import { jsxDEV } from "react/jsx-dev-runtime";
106
- "use client";
107
- function formatCurrency(value, currency = "USD") {
108
- return new Intl.NumberFormat("en-US", {
109
- style: "currency",
110
- currency,
111
- minimumFractionDigits: 0,
112
- maximumFractionDigits: 0
113
- }).format(value);
114
- }
115
- function statusVariant(status) {
116
- switch (status) {
117
- case "WON":
118
- return "default";
119
- case "LOST":
120
- return "destructive";
121
- case "STALE":
122
- return "outline";
123
- default:
124
- return "secondary";
125
- }
126
- }
127
- function DealListDataTable({
128
- deals,
129
- totalItems,
130
- pageIndex,
131
- pageSize,
132
- sorting,
133
- loading,
134
- onSortingChange,
135
- onPaginationChange,
136
- onDealClick
137
- }) {
138
- const controller = useContractTable({
139
- data: deals,
140
- columns: [
141
- {
142
- id: "deal",
143
- header: "Deal",
144
- label: "Deal",
145
- accessor: (deal) => deal.name,
146
- cell: ({ item }) => /* @__PURE__ */ jsxDEV(VStack, {
147
- gap: "xs",
148
- children: [
149
- /* @__PURE__ */ jsxDEV(Text, {
150
- className: "font-medium text-sm",
151
- children: item.name
152
- }, undefined, false, undefined, this),
153
- /* @__PURE__ */ jsxDEV(Text, {
154
- className: "text-muted-foreground text-xs",
155
- children: item.companyId ?? "Unassigned company"
156
- }, undefined, false, undefined, this)
157
- ]
158
- }, undefined, true, undefined, this),
159
- size: 240,
160
- minSize: 180,
161
- canSort: true,
162
- canPin: true,
163
- canResize: true
164
- },
165
- {
166
- id: "value",
167
- header: "Value",
168
- label: "Value",
169
- accessorKey: "value",
170
- cell: ({ item }) => formatCurrency(item.value, item.currency),
171
- align: "right",
172
- size: 140,
173
- canSort: true,
174
- canResize: true
175
- },
176
- {
177
- id: "status",
178
- header: "Status",
179
- label: "Status",
180
- accessorKey: "status",
181
- cell: ({ value }) => /* @__PURE__ */ jsxDEV(Badge, {
182
- variant: statusVariant(value),
183
- children: String(value)
184
- }, undefined, false, undefined, this),
185
- size: 130,
186
- canSort: true,
187
- canHide: true,
188
- canPin: true,
189
- canResize: true
190
- },
191
- {
192
- id: "expectedCloseDate",
193
- header: "Expected Close",
194
- label: "Expected Close",
195
- accessor: (deal) => deal.expectedCloseDate?.toISOString() ?? "",
196
- cell: ({ item }) => item.expectedCloseDate?.toLocaleDateString() ?? "Not scheduled",
197
- size: 170,
198
- canSort: true,
199
- canHide: true,
200
- canResize: true
201
- },
202
- {
203
- id: "updatedAt",
204
- header: "Updated",
205
- label: "Updated",
206
- accessor: (deal) => deal.updatedAt.toISOString(),
207
- cell: ({ item }) => item.updatedAt.toLocaleDateString(),
208
- size: 140,
209
- canSort: true,
210
- canHide: true,
211
- canResize: true
212
- },
213
- {
214
- id: "actions",
215
- header: "Actions",
216
- label: "Actions",
217
- accessor: (deal) => deal.id,
218
- cell: ({ item }) => /* @__PURE__ */ jsxDEV(Button, {
219
- variant: "ghost",
220
- size: "sm",
221
- onPress: () => onDealClick?.(item.id),
222
- children: "Actions"
223
- }, undefined, false, undefined, this),
224
- size: 120,
225
- canSort: false,
226
- canHide: false,
227
- canPin: false,
228
- canResize: false
229
- }
230
- ],
231
- executionMode: "server",
232
- selectionMode: "multiple",
233
- totalItems,
234
- state: {
235
- sorting,
236
- pagination: {
237
- pageIndex,
238
- pageSize
239
- }
240
- },
241
- onSortingChange,
242
- onPaginationChange,
243
- initialState: {
244
- columnVisibility: { updatedAt: false },
245
- columnPinning: { left: ["deal", "status"], right: [] }
246
- },
247
- renderExpandedContent: (deal) => /* @__PURE__ */ jsxDEV(VStack, {
248
- gap: "sm",
249
- className: "py-2",
250
- children: [
251
- /* @__PURE__ */ jsxDEV(HStack, {
252
- justify: "between",
253
- children: [
254
- /* @__PURE__ */ jsxDEV(Text, {
255
- className: "font-medium text-sm",
256
- children: "Owner"
257
- }, undefined, false, undefined, this),
258
- /* @__PURE__ */ jsxDEV(Text, {
259
- className: "text-muted-foreground text-sm",
260
- children: deal.ownerId
261
- }, undefined, false, undefined, this)
262
- ]
263
- }, undefined, true, undefined, this),
264
- /* @__PURE__ */ jsxDEV(HStack, {
265
- justify: "between",
266
- children: [
267
- /* @__PURE__ */ jsxDEV(Text, {
268
- className: "font-medium text-sm",
269
- children: "Contact"
270
- }, undefined, false, undefined, this),
271
- /* @__PURE__ */ jsxDEV(Text, {
272
- className: "text-muted-foreground text-sm",
273
- children: deal.contactId ?? "No linked contact"
274
- }, undefined, false, undefined, this)
275
- ]
276
- }, undefined, true, undefined, this),
277
- deal.wonSource ? /* @__PURE__ */ jsxDEV(HStack, {
278
- justify: "between",
279
- children: [
280
- /* @__PURE__ */ jsxDEV(Text, {
281
- className: "font-medium text-sm",
282
- children: "Won Source"
283
- }, undefined, false, undefined, this),
284
- /* @__PURE__ */ jsxDEV(Text, {
285
- className: "text-muted-foreground text-sm",
286
- children: deal.wonSource
287
- }, undefined, false, undefined, this)
288
- ]
289
- }, undefined, true, undefined, this) : null,
290
- deal.lostReason ? /* @__PURE__ */ jsxDEV(HStack, {
291
- justify: "between",
292
- children: [
293
- /* @__PURE__ */ jsxDEV(Text, {
294
- className: "font-medium text-sm",
295
- children: "Lost Reason"
296
- }, undefined, false, undefined, this),
297
- /* @__PURE__ */ jsxDEV(Text, {
298
- className: "text-muted-foreground text-sm",
299
- children: deal.lostReason
300
- }, undefined, false, undefined, this)
301
- ]
302
- }, undefined, true, undefined, this) : null,
303
- deal.notes ? /* @__PURE__ */ jsxDEV(VStack, {
304
- gap: "xs",
305
- children: [
306
- /* @__PURE__ */ jsxDEV(Text, {
307
- className: "font-medium text-sm",
308
- children: "Notes"
309
- }, undefined, false, undefined, this),
310
- /* @__PURE__ */ jsxDEV(Text, {
311
- className: "text-muted-foreground text-sm",
312
- children: deal.notes
313
- }, undefined, false, undefined, this)
314
- ]
315
- }, undefined, true, undefined, this) : null
316
- ]
317
- }, undefined, true, undefined, this),
318
- getCanExpand: () => true
319
- });
320
- return /* @__PURE__ */ jsxDEV(DataTable, {
321
- controller,
322
- title: "All Deals",
323
- description: "Server-mode table using the shared ContractSpec controller.",
324
- loading,
325
- toolbar: /* @__PURE__ */ jsxDEV(HStack, {
326
- gap: "sm",
327
- className: "flex-wrap",
328
- children: [
329
- /* @__PURE__ */ jsxDEV(Text, {
330
- className: "text-muted-foreground text-sm",
331
- children: [
332
- "Selected ",
333
- controller.selectedRowIds.length
334
- ]
335
- }, undefined, true, undefined, this),
336
- /* @__PURE__ */ jsxDEV(Text, {
337
- className: "text-muted-foreground text-sm",
338
- children: [
339
- totalItems,
340
- " total deals"
341
- ]
342
- }, undefined, true, undefined, this)
343
- ]
344
- }, undefined, true, undefined, this),
345
- footer: `Page ${controller.pageIndex + 1} of ${controller.pageCount}`,
346
- emptyState: /* @__PURE__ */ jsxDEV("div", {
347
- className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
348
- children: "No deals found"
349
- }, undefined, false, undefined, this)
350
- }, undefined, false, undefined, this);
351
- }
352
- function DealListTab({
353
- onDealClick
354
- }) {
355
- const [sorting, setSorting] = React.useState([
356
- { id: "value", desc: true }
357
- ]);
358
- const [pagination, setPagination] = React.useState({
359
- pageIndex: 0,
360
- pageSize: 3
361
- });
362
- const { data, loading } = useDealList({
363
- pageIndex: pagination.pageIndex,
364
- pageSize: pagination.pageSize,
365
- sorting
366
- });
367
- if (loading && !data) {
368
- return /* @__PURE__ */ jsxDEV(LoaderBlock, {
369
- label: "Loading deals..."
370
- }, undefined, false, undefined, this);
371
- }
372
- return /* @__PURE__ */ jsxDEV(DealListDataTable, {
373
- deals: data?.deals ?? [],
374
- totalItems: data?.total ?? 0,
375
- pageIndex: pagination.pageIndex,
376
- pageSize: pagination.pageSize,
377
- sorting,
378
- loading,
379
- onSortingChange: (nextSorting) => {
380
- setSorting(nextSorting);
381
- setPagination((current) => ({ ...current, pageIndex: 0 }));
382
- },
383
- onPaginationChange: setPagination,
384
- onDealClick
385
- }, undefined, false, undefined, this);
386
- }
387
- export {
388
- DealListTab,
389
- DealListDataTable
390
- };
1
+ import{useTemplateRuntime as j}from"@contractspec/lib.example-shared-ui";import{useCallback as m,useEffect as u,useMemo as c,useState as $}from"react";function D(A={}){let{handlers:Q,projectId:W}=j(),{crm:K}=Q,[J,X]=$(null),[Z,M]=$({}),[w,_]=$([]),[q,C]=$(!0),[T,y]=$(null),[I,B]=$(0),V=A.pipelineId??"pipeline-1",E=A.pageIndex??I,H=A.pageSize??A.limit??50,[v]=A.sorting??[],Y=v?.id,k=v?v.desc?"desc":"asc":void 0,z=m(async()=>{C(!0),y(null);try{let[N,f,L]=await Promise.all([K.listDeals({projectId:W,pipelineId:V,stageId:A.stageId,status:A.status==="all"?void 0:A.status,search:A.search,limit:H,offset:E*H,sortBy:Y==="name"||Y==="value"||Y==="status"||Y==="expectedCloseDate"||Y==="updatedAt"?Y:void 0,sortDirection:k}),K.getDealsByStage({projectId:W,pipelineId:V}),K.getPipelineStages({pipelineId:V})]);X(N),M(f),_(L)}catch(N){y(N instanceof Error?N:Error("Unknown error"))}finally{C(!1)}},[K,W,V,A.stageId,A.status,A.search,E,H,Y,k]);u(()=>{z()},[z]);let S=c(()=>{if(!J)return null;let N=J.deals.filter((U)=>U.status==="OPEN"),f=J.deals.filter((U)=>U.status==="WON"),L=J.deals.filter((U)=>U.status==="LOST");return{total:J.total,totalValue:J.totalValue,openCount:N.length,openValue:N.reduce((U,b)=>U+b.value,0),wonCount:f.length,wonValue:f.reduce((U,b)=>U+b.value,0),lostCount:L.length}},[J]);return{data:J,dealsByStage:Z,stages:w,loading:q,error:T,stats:S,page:E+1,pageIndex:E,pageSize:H,refetch:z,nextPage:A.pageIndex===void 0?()=>B((N)=>N+1):void 0,prevPage:A.pageIndex===void 0?()=>E>0&&B((N)=>N-1):void 0}}import{Button as x,DataTable as g,LoaderBlock as p}from"@contractspec/lib.design-system";import{useContractTable as d}from"@contractspec/lib.presentation-runtime-react";import{Badge as r}from"@contractspec/lib.ui-kit-web/ui/badge";import{HStack as R,VStack as P}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as G}from"@contractspec/lib.ui-kit-web/ui/text";import*as h from"react";import{jsx as F,jsxs as O}from"react/jsx-runtime";function l(A,Q="USD"){return new Intl.NumberFormat("en-US",{style:"currency",currency:Q,minimumFractionDigits:0,maximumFractionDigits:0}).format(A)}function i(A){switch(A){case"WON":return"default";case"LOST":return"destructive";case"STALE":return"outline";default:return"secondary"}}function n({deals:A,totalItems:Q,pageIndex:W,pageSize:K,sorting:J,loading:X,onSortingChange:Z,onPaginationChange:M,onDealClick:w}){let _=d({data:A,columns:[{id:"deal",header:"Deal",label:"Deal",accessor:(q)=>q.name,cell:({item:q})=>O(P,{gap:"xs",children:[F(G,{className:"font-medium text-sm",children:q.name}),F(G,{className:"text-muted-foreground text-xs",children:q.companyId??"Unassigned company"})]}),size:240,minSize:180,canSort:!0,canPin:!0,canResize:!0},{id:"value",header:"Value",label:"Value",accessorKey:"value",cell:({item:q})=>l(q.value,q.currency),align:"right",size:140,canSort:!0,canResize:!0},{id:"status",header:"Status",label:"Status",accessorKey:"status",cell:({value:q})=>F(r,{variant:i(q),children:String(q)}),size:130,canSort:!0,canHide:!0,canPin:!0,canResize:!0},{id:"expectedCloseDate",header:"Expected Close",label:"Expected Close",accessor:(q)=>q.expectedCloseDate?.toISOString()??"",cell:({item:q})=>q.expectedCloseDate?.toLocaleDateString()??"Not scheduled",size:170,canSort:!0,canHide:!0,canResize:!0},{id:"updatedAt",header:"Updated",label:"Updated",accessor:(q)=>q.updatedAt.toISOString(),cell:({item:q})=>q.updatedAt.toLocaleDateString(),size:140,canSort:!0,canHide:!0,canResize:!0},{id:"actions",header:"Actions",label:"Actions",accessor:(q)=>q.id,cell:({item:q})=>F(x,{variant:"ghost",size:"sm",onPress:()=>w?.(q.id),children:"Actions"}),size:120,canSort:!1,canHide:!1,canPin:!1,canResize:!1}],executionMode:"server",selectionMode:"multiple",totalItems:Q,state:{sorting:J,pagination:{pageIndex:W,pageSize:K}},onSortingChange:Z,onPaginationChange:M,initialState:{columnVisibility:{updatedAt:!1},columnPinning:{left:["deal","status"],right:[]}},renderExpandedContent:(q)=>O(P,{gap:"sm",className:"py-2",children:[O(R,{justify:"between",children:[F(G,{className:"font-medium text-sm",children:"Owner"}),F(G,{className:"text-muted-foreground text-sm",children:q.ownerId})]}),O(R,{justify:"between",children:[F(G,{className:"font-medium text-sm",children:"Contact"}),F(G,{className:"text-muted-foreground text-sm",children:q.contactId??"No linked contact"})]}),q.wonSource?O(R,{justify:"between",children:[F(G,{className:"font-medium text-sm",children:"Won Source"}),F(G,{className:"text-muted-foreground text-sm",children:q.wonSource})]}):null,q.lostReason?O(R,{justify:"between",children:[F(G,{className:"font-medium text-sm",children:"Lost Reason"}),F(G,{className:"text-muted-foreground text-sm",children:q.lostReason})]}):null,q.notes?O(P,{gap:"xs",children:[F(G,{className:"font-medium text-sm",children:"Notes"}),F(G,{className:"text-muted-foreground text-sm",children:q.notes})]}):null]}),getCanExpand:()=>!0});return F(g,{controller:_,title:"All Deals",description:"Server-mode table using the shared ContractSpec controller.",loading:X,toolbar:O(R,{gap:"sm",className:"flex-wrap",children:[O(G,{className:"text-muted-foreground text-sm",children:["Selected ",_.selectedRowIds.length]}),O(G,{className:"text-muted-foreground text-sm",children:[Q," total deals"]})]}),footer:`Page ${_.pageIndex+1} of ${_.pageCount}`,emptyState:F("div",{className:"rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",children:"No deals found"})})}function Jq({onDealClick:A}){let[Q,W]=h.useState([{id:"value",desc:!0}]),[K,J]=h.useState({pageIndex:0,pageSize:3}),{data:X,loading:Z}=D({pageIndex:K.pageIndex,pageSize:K.pageSize,sorting:Q});if(Z&&!X)return F(p,{label:"Loading deals..."});return F(n,{deals:X?.deals??[],totalItems:X?.total??0,pageIndex:K.pageIndex,pageSize:K.pageSize,sorting:Q,loading:Z,onSortingChange:(M)=>{W(M),J((w)=>({...w,pageIndex:0}))},onPaginationChange:J,onDealClick:A})}export{Jq as DealListTab,n as DealListDataTable};