@contractspec/example.crm-pipeline 3.7.6 → 3.7.10

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 (130) hide show
  1. package/.turbo/turbo-build.log +45 -42
  2. package/AGENTS.md +51 -33
  3. package/CHANGELOG.md +36 -0
  4. package/README.md +67 -148
  5. package/dist/browser/docs/crm-pipeline.docblock.js +1 -1
  6. package/dist/browser/docs/index.js +1 -1
  7. package/dist/browser/events/contact.event.js +1 -1
  8. package/dist/browser/events/deal.event.js +1 -1
  9. package/dist/browser/events/index.js +3 -3
  10. package/dist/browser/events/task.event.js +1 -1
  11. package/dist/browser/handlers/crm.handlers.js +13 -2
  12. package/dist/browser/handlers/index.js +13 -2
  13. package/dist/browser/index.js +680 -447
  14. package/dist/browser/ui/CrmDashboard.js +574 -352
  15. package/dist/browser/ui/CrmDealCard.js +5 -5
  16. package/dist/browser/ui/CrmPipelineBoard.js +13 -13
  17. package/dist/browser/ui/hooks/index.js +21 -10
  18. package/dist/browser/ui/hooks/useDealList.js +20 -9
  19. package/dist/browser/ui/hooks/useDealMutations.js +1 -1
  20. package/dist/browser/ui/index.js +683 -450
  21. package/dist/browser/ui/modals/CreateDealModal.js +12 -12
  22. package/dist/browser/ui/modals/DealActionsModal.js +21 -21
  23. package/dist/browser/ui/modals/index.js +33 -33
  24. package/dist/browser/ui/renderers/index.js +140 -118
  25. package/dist/browser/ui/renderers/pipeline.markdown.js +13 -2
  26. package/dist/browser/ui/renderers/pipeline.renderer.js +108 -97
  27. package/dist/browser/ui/tables/DealListTab.js +390 -0
  28. package/dist/deal/index.d.ts +2 -2
  29. package/dist/docs/crm-pipeline.docblock.js +1 -1
  30. package/dist/docs/index.js +1 -1
  31. package/dist/events/contact.event.js +1 -1
  32. package/dist/events/deal.event.js +1 -1
  33. package/dist/events/index.js +3 -3
  34. package/dist/events/task.event.js +1 -1
  35. package/dist/handlers/crm.handlers.d.ts +2 -0
  36. package/dist/handlers/crm.handlers.js +13 -2
  37. package/dist/handlers/index.d.ts +2 -2
  38. package/dist/handlers/index.js +13 -2
  39. package/dist/index.d.ts +3 -3
  40. package/dist/index.js +680 -447
  41. package/dist/node/docs/crm-pipeline.docblock.js +1 -1
  42. package/dist/node/docs/index.js +1 -1
  43. package/dist/node/events/contact.event.js +1 -1
  44. package/dist/node/events/deal.event.js +1 -1
  45. package/dist/node/events/index.js +3 -3
  46. package/dist/node/events/task.event.js +1 -1
  47. package/dist/node/handlers/crm.handlers.js +13 -2
  48. package/dist/node/handlers/index.js +13 -2
  49. package/dist/node/index.js +680 -447
  50. package/dist/node/ui/CrmDashboard.js +574 -352
  51. package/dist/node/ui/CrmDealCard.js +5 -5
  52. package/dist/node/ui/CrmPipelineBoard.js +13 -13
  53. package/dist/node/ui/hooks/index.js +21 -10
  54. package/dist/node/ui/hooks/useDealList.js +20 -9
  55. package/dist/node/ui/hooks/useDealMutations.js +1 -1
  56. package/dist/node/ui/index.js +683 -450
  57. package/dist/node/ui/modals/CreateDealModal.js +12 -12
  58. package/dist/node/ui/modals/DealActionsModal.js +21 -21
  59. package/dist/node/ui/modals/index.js +33 -33
  60. package/dist/node/ui/renderers/index.js +140 -118
  61. package/dist/node/ui/renderers/pipeline.markdown.js +13 -2
  62. package/dist/node/ui/renderers/pipeline.renderer.js +108 -97
  63. package/dist/node/ui/tables/DealListTab.js +390 -0
  64. package/dist/operations/index.d.ts +1 -1
  65. package/dist/ui/CrmDashboard.js +574 -352
  66. package/dist/ui/CrmDealCard.js +5 -5
  67. package/dist/ui/CrmPipelineBoard.js +13 -13
  68. package/dist/ui/hooks/index.d.ts +2 -2
  69. package/dist/ui/hooks/index.js +21 -10
  70. package/dist/ui/hooks/useDealList.d.ts +8 -2
  71. package/dist/ui/hooks/useDealList.js +20 -9
  72. package/dist/ui/hooks/useDealMutations.d.ts +9 -0
  73. package/dist/ui/hooks/useDealMutations.js +1 -1
  74. package/dist/ui/index.d.ts +3 -3
  75. package/dist/ui/index.js +683 -450
  76. package/dist/ui/modals/CreateDealModal.js +12 -12
  77. package/dist/ui/modals/DealActionsModal.js +21 -21
  78. package/dist/ui/modals/index.js +33 -33
  79. package/dist/ui/renderers/index.d.ts +1 -1
  80. package/dist/ui/renderers/index.js +140 -118
  81. package/dist/ui/renderers/pipeline.markdown.js +13 -2
  82. package/dist/ui/renderers/pipeline.renderer.d.ts +1 -1
  83. package/dist/ui/renderers/pipeline.renderer.js +108 -97
  84. package/dist/ui/tables/DealListTab.d.ts +20 -0
  85. package/dist/ui/tables/DealListTab.js +391 -0
  86. package/dist/ui/tables/DealListTab.smoke.test.d.ts +1 -0
  87. package/package.json +29 -14
  88. package/src/crm-pipeline.feature.ts +86 -86
  89. package/src/deal/deal.enum.ts +8 -8
  90. package/src/deal/deal.operation.ts +255 -255
  91. package/src/deal/deal.schema.ts +92 -92
  92. package/src/deal/deal.test-spec.ts +48 -48
  93. package/src/deal/index.ts +17 -19
  94. package/src/docs/crm-pipeline.docblock.ts +44 -44
  95. package/src/entities/company.entity.ts +52 -52
  96. package/src/entities/contact.entity.ts +67 -67
  97. package/src/entities/deal.entity.ts +134 -134
  98. package/src/entities/index.ts +27 -27
  99. package/src/entities/task.entity.ts +105 -105
  100. package/src/events/contact.event.ts +22 -22
  101. package/src/events/deal.event.ts +77 -77
  102. package/src/events/task.event.ts +19 -19
  103. package/src/example.ts +32 -32
  104. package/src/handlers/crm.handlers.ts +375 -357
  105. package/src/handlers/deal.handlers.ts +179 -179
  106. package/src/handlers/index.ts +18 -19
  107. package/src/handlers/mock-data.ts +167 -167
  108. package/src/index.ts +11 -11
  109. package/src/operations/index.ts +16 -16
  110. package/src/presentations/dashboard.presentation.ts +45 -45
  111. package/src/presentations/pipeline.presentation.ts +90 -90
  112. package/src/seeders/index.ts +26 -26
  113. package/src/shared/overlay-types.ts +23 -23
  114. package/src/ui/CrmDashboard.tsx +210 -279
  115. package/src/ui/CrmDealCard.tsx +64 -64
  116. package/src/ui/CrmPipelineBoard.tsx +105 -105
  117. package/src/ui/hooks/index.ts +3 -3
  118. package/src/ui/hooks/useDealList.ts +113 -85
  119. package/src/ui/hooks/useDealMutations.ts +151 -150
  120. package/src/ui/index.ts +5 -10
  121. package/src/ui/modals/CreateDealModal.tsx +217 -217
  122. package/src/ui/modals/DealActionsModal.tsx +390 -390
  123. package/src/ui/overlays/demo-overlays.ts +43 -43
  124. package/src/ui/renderers/index.ts +4 -3
  125. package/src/ui/renderers/pipeline.markdown.ts +165 -165
  126. package/src/ui/renderers/pipeline.renderer.tsx +17 -16
  127. package/src/ui/tables/DealListTab.smoke.test.tsx +149 -0
  128. package/src/ui/tables/DealListTab.tsx +276 -0
  129. package/tsconfig.json +7 -8
  130. package/tsdown.config.js +7 -3
@@ -1,86 +1,4 @@
1
1
  // @bun
2
- // src/ui/hooks/useDealList.ts
3
- import { useCallback, useEffect, useMemo, useState } from "react";
4
- import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
5
- "use client";
6
- function useDealList(options = {}) {
7
- const { handlers, projectId } = useTemplateRuntime();
8
- const { crm } = handlers;
9
- const [data, setData] = useState(null);
10
- const [dealsByStage, setDealsByStage] = useState({});
11
- const [stages, setStages] = useState([]);
12
- const [loading, setLoading] = useState(true);
13
- const [error, setError] = useState(null);
14
- const [page, setPage] = useState(1);
15
- const pipelineId = options.pipelineId ?? "pipeline-1";
16
- const fetchData = useCallback(async () => {
17
- setLoading(true);
18
- setError(null);
19
- try {
20
- const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
21
- crm.listDeals({
22
- projectId,
23
- pipelineId,
24
- stageId: options.stageId,
25
- status: options.status === "all" ? undefined : options.status,
26
- search: options.search,
27
- limit: options.limit ?? 50,
28
- offset: (page - 1) * (options.limit ?? 50)
29
- }),
30
- crm.getDealsByStage({ projectId, pipelineId }),
31
- crm.getPipelineStages({ pipelineId })
32
- ]);
33
- setData(dealsResult);
34
- setDealsByStage(stageDealsResult);
35
- setStages(stagesResult);
36
- } catch (err) {
37
- setError(err instanceof Error ? err : new Error("Unknown error"));
38
- } finally {
39
- setLoading(false);
40
- }
41
- }, [
42
- crm,
43
- projectId,
44
- pipelineId,
45
- options.stageId,
46
- options.status,
47
- options.search,
48
- options.limit,
49
- page
50
- ]);
51
- useEffect(() => {
52
- fetchData();
53
- }, [fetchData]);
54
- const stats = useMemo(() => {
55
- if (!data)
56
- return null;
57
- const open = data.deals.filter((d) => d.status === "OPEN");
58
- const won = data.deals.filter((d) => d.status === "WON");
59
- const lost = data.deals.filter((d) => d.status === "LOST");
60
- return {
61
- total: data.total,
62
- totalValue: data.totalValue,
63
- openCount: open.length,
64
- openValue: open.reduce((sum, d) => sum + d.value, 0),
65
- wonCount: won.length,
66
- wonValue: won.reduce((sum, d) => sum + d.value, 0),
67
- lostCount: lost.length
68
- };
69
- }, [data]);
70
- return {
71
- data,
72
- dealsByStage,
73
- stages,
74
- loading,
75
- error,
76
- stats,
77
- page,
78
- refetch: fetchData,
79
- nextPage: () => setPage((p) => p + 1),
80
- prevPage: () => page > 1 && setPage((p) => p - 1)
81
- };
82
- }
83
-
84
2
  // src/ui/CrmDealCard.tsx
85
3
  import { jsxDEV } from "react/jsx-dev-runtime";
86
4
  "use client";
@@ -96,7 +14,7 @@ function CrmDealCard({ deal, onClick }) {
96
14
  const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
97
15
  return /* @__PURE__ */ jsxDEV("div", {
98
16
  onClick,
99
- className: "border-border bg-card cursor-pointer rounded-lg border p-3 shadow-sm transition-shadow hover:shadow-md",
17
+ className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
100
18
  role: "button",
101
19
  tabIndex: 0,
102
20
  onKeyDown: (e) => {
@@ -105,22 +23,22 @@ function CrmDealCard({ deal, onClick }) {
105
23
  },
106
24
  children: [
107
25
  /* @__PURE__ */ jsxDEV("h4", {
108
- className: "leading-snug font-medium",
26
+ className: "font-medium leading-snug",
109
27
  children: deal.name
110
28
  }, undefined, false, undefined, this),
111
29
  /* @__PURE__ */ jsxDEV("div", {
112
- className: "text-primary mt-2 text-lg font-semibold",
30
+ className: "mt-2 font-semibold text-lg text-primary",
113
31
  children: formatCurrency(deal.value, deal.currency)
114
32
  }, undefined, false, undefined, this),
115
33
  /* @__PURE__ */ jsxDEV("div", {
116
- className: "text-muted-foreground mt-3 flex items-center justify-between text-xs",
34
+ className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
117
35
  children: [
118
36
  daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
119
37
  className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
120
38
  children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
121
39
  }, undefined, false, undefined, this),
122
40
  /* @__PURE__ */ jsxDEV("span", {
123
- className: `rounded px-1.5 py-0.5 text-xs font-medium ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
41
+ 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"}`,
124
42
  children: deal.status
125
43
  }, undefined, false, undefined, this)
126
44
  ]
@@ -130,7 +48,7 @@ function CrmDealCard({ deal, onClick }) {
130
48
  }
131
49
 
132
50
  // src/ui/CrmPipelineBoard.tsx
133
- import { useState as useState2 } from "react";
51
+ import { useState } from "react";
134
52
  import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
135
53
  "use client";
136
54
  function formatCurrency2(value) {
@@ -146,7 +64,7 @@ function CrmPipelineBoard({
146
64
  onDealClick,
147
65
  onDealMove
148
66
  }) {
149
- const [quickMoveOpen, setQuickMoveOpen] = useState2(null);
67
+ const [quickMoveOpen, setQuickMoveOpen] = useState(null);
150
68
  const sortedStages = [...stages].sort((a, b) => a.position - b.position);
151
69
  const handleQuickMove = (dealId, toStageId) => {
152
70
  onDealMove?.(dealId, toStageId);
@@ -158,10 +76,10 @@ function CrmPipelineBoard({
158
76
  const deals = dealsByStage[stage.id] ?? [];
159
77
  const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
160
78
  return /* @__PURE__ */ jsxDEV2("div", {
161
- className: "bg-muted/30 flex w-72 flex-shrink-0 flex-col rounded-lg",
79
+ className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
162
80
  children: [
163
81
  /* @__PURE__ */ jsxDEV2("div", {
164
- className: "border-border flex items-center justify-between border-b px-3 py-2",
82
+ className: "flex items-center justify-between border-border border-b px-3 py-2",
165
83
  children: [
166
84
  /* @__PURE__ */ jsxDEV2("div", {
167
85
  children: [
@@ -180,7 +98,7 @@ function CrmPipelineBoard({
180
98
  ]
181
99
  }, undefined, true, undefined, this),
182
100
  /* @__PURE__ */ jsxDEV2("span", {
183
- className: "bg-muted flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium",
101
+ className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
184
102
  children: deals.length
185
103
  }, undefined, false, undefined, this)
186
104
  ]
@@ -188,7 +106,7 @@ function CrmPipelineBoard({
188
106
  /* @__PURE__ */ jsxDEV2("div", {
189
107
  className: "flex flex-1 flex-col gap-2 p-2",
190
108
  children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
191
- className: "border-muted-foreground/20 text-muted-foreground flex h-24 items-center justify-center rounded-md border-2 border-dashed text-xs",
109
+ className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
192
110
  children: "No deals"
193
111
  }, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
194
112
  className: "group relative",
@@ -206,15 +124,15 @@ function CrmPipelineBoard({
206
124
  e.stopPropagation();
207
125
  setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
208
126
  },
209
- className: "bg-background border-border hover:bg-muted flex h-6 w-6 items-center justify-center rounded border text-xs shadow-sm",
127
+ className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
210
128
  title: "Quick move",
211
129
  children: "\u27A1\uFE0F"
212
130
  }, undefined, false, undefined, this),
213
131
  quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
214
- className: "bg-card border-border absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border py-1 shadow-lg",
132
+ className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
215
133
  children: [
216
134
  /* @__PURE__ */ jsxDEV2("p", {
217
- className: "text-muted-foreground px-3 py-1 text-xs font-medium",
135
+ className: "px-3 py-1 font-medium text-muted-foreground text-xs",
218
136
  children: "Move to:"
219
137
  }, undefined, false, undefined, this),
220
138
  sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
@@ -223,7 +141,7 @@ function CrmPipelineBoard({
223
141
  e.stopPropagation();
224
142
  handleQuickMove(deal.id, s.id);
225
143
  },
226
- className: "hover:bg-muted w-full px-3 py-1.5 text-left text-sm",
144
+ className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
227
145
  children: s.name
228
146
  }, s.id, false, undefined, this))
229
147
  ]
@@ -239,6 +157,99 @@ function CrmPipelineBoard({
239
157
  }, undefined, false, undefined, this);
240
158
  }
241
159
 
160
+ // src/ui/hooks/useDealList.ts
161
+ import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
162
+ import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
163
+ "use client";
164
+ function useDealList(options = {}) {
165
+ const { handlers, projectId } = useTemplateRuntime();
166
+ const { crm } = handlers;
167
+ const [data, setData] = useState2(null);
168
+ const [dealsByStage, setDealsByStage] = useState2({});
169
+ const [stages, setStages] = useState2([]);
170
+ const [loading, setLoading] = useState2(true);
171
+ const [error, setError] = useState2(null);
172
+ const [internalPage, setInternalPage] = useState2(0);
173
+ const pipelineId = options.pipelineId ?? "pipeline-1";
174
+ const pageIndex = options.pageIndex ?? internalPage;
175
+ const pageSize = options.pageSize ?? options.limit ?? 50;
176
+ const [sort] = options.sorting ?? [];
177
+ const sortBy = sort?.id;
178
+ const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
179
+ const fetchData = useCallback(async () => {
180
+ setLoading(true);
181
+ setError(null);
182
+ try {
183
+ const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
184
+ crm.listDeals({
185
+ projectId,
186
+ pipelineId,
187
+ stageId: options.stageId,
188
+ status: options.status === "all" ? undefined : options.status,
189
+ search: options.search,
190
+ limit: pageSize,
191
+ offset: pageIndex * pageSize,
192
+ sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
193
+ sortDirection
194
+ }),
195
+ crm.getDealsByStage({ projectId, pipelineId }),
196
+ crm.getPipelineStages({ pipelineId })
197
+ ]);
198
+ setData(dealsResult);
199
+ setDealsByStage(stageDealsResult);
200
+ setStages(stagesResult);
201
+ } catch (err) {
202
+ setError(err instanceof Error ? err : new Error("Unknown error"));
203
+ } finally {
204
+ setLoading(false);
205
+ }
206
+ }, [
207
+ crm,
208
+ projectId,
209
+ pipelineId,
210
+ options.stageId,
211
+ options.status,
212
+ options.search,
213
+ pageIndex,
214
+ pageSize,
215
+ sortBy,
216
+ sortDirection
217
+ ]);
218
+ useEffect(() => {
219
+ fetchData();
220
+ }, [fetchData]);
221
+ const stats = useMemo(() => {
222
+ if (!data)
223
+ return null;
224
+ const open = data.deals.filter((d) => d.status === "OPEN");
225
+ const won = data.deals.filter((d) => d.status === "WON");
226
+ const lost = data.deals.filter((d) => d.status === "LOST");
227
+ return {
228
+ total: data.total,
229
+ totalValue: data.totalValue,
230
+ openCount: open.length,
231
+ openValue: open.reduce((sum, d) => sum + d.value, 0),
232
+ wonCount: won.length,
233
+ wonValue: won.reduce((sum, d) => sum + d.value, 0),
234
+ lostCount: lost.length
235
+ };
236
+ }, [data]);
237
+ return {
238
+ data,
239
+ dealsByStage,
240
+ stages,
241
+ loading,
242
+ error,
243
+ stats,
244
+ page: pageIndex + 1,
245
+ pageIndex,
246
+ pageSize,
247
+ refetch: fetchData,
248
+ nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
249
+ prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
250
+ };
251
+ }
252
+
242
253
  // src/ui/renderers/pipeline.renderer.tsx
243
254
  import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
244
255
  function CrmPipelineBoardWrapper() {
@@ -0,0 +1,20 @@
1
+ import type { ContractTableSort } from '@contractspec/lib.presentation-runtime-core';
2
+ import { type Deal } from '../hooks/useDealList';
3
+ export interface DealListDataTableProps {
4
+ deals: Deal[];
5
+ totalItems: number;
6
+ pageIndex: number;
7
+ pageSize: number;
8
+ sorting: ContractTableSort[];
9
+ loading?: boolean;
10
+ onSortingChange: (sorting: ContractTableSort[]) => void;
11
+ onPaginationChange: (pagination: {
12
+ pageIndex: number;
13
+ pageSize: number;
14
+ }) => void;
15
+ onDealClick?: (dealId: string) => void;
16
+ }
17
+ export declare function DealListDataTable({ deals, totalItems, pageIndex, pageSize, sorting, loading, onSortingChange, onPaginationChange, onDealClick, }: DealListDataTableProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare function DealListTab({ onDealClick, }: {
19
+ onDealClick?: (dealId: string) => void;
20
+ }): import("react/jsx-runtime").JSX.Element;