@contractspec/example.crm-pipeline 3.7.7 → 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 (53) hide show
  1. package/.turbo/turbo-build.log +45 -42
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +2 -1
  4. package/dist/browser/docs/crm-pipeline.docblock.js +1 -1
  5. package/dist/browser/docs/index.js +1 -1
  6. package/dist/browser/handlers/crm.handlers.js +13 -2
  7. package/dist/browser/handlers/index.js +13 -2
  8. package/dist/browser/index.js +392 -159
  9. package/dist/browser/ui/CrmDashboard.js +366 -144
  10. package/dist/browser/ui/hooks/index.js +19 -8
  11. package/dist/browser/ui/hooks/useDealList.js +19 -8
  12. package/dist/browser/ui/index.js +391 -158
  13. package/dist/browser/ui/renderers/index.js +32 -10
  14. package/dist/browser/ui/renderers/pipeline.markdown.js +13 -2
  15. package/dist/browser/ui/renderers/pipeline.renderer.js +19 -8
  16. package/dist/browser/ui/tables/DealListTab.js +390 -0
  17. package/dist/docs/crm-pipeline.docblock.js +1 -1
  18. package/dist/docs/index.js +1 -1
  19. package/dist/handlers/crm.handlers.d.ts +2 -0
  20. package/dist/handlers/crm.handlers.js +13 -2
  21. package/dist/handlers/index.js +13 -2
  22. package/dist/index.js +392 -159
  23. package/dist/node/docs/crm-pipeline.docblock.js +1 -1
  24. package/dist/node/docs/index.js +1 -1
  25. package/dist/node/handlers/crm.handlers.js +13 -2
  26. package/dist/node/handlers/index.js +13 -2
  27. package/dist/node/index.js +392 -159
  28. package/dist/node/ui/CrmDashboard.js +366 -144
  29. package/dist/node/ui/hooks/index.js +19 -8
  30. package/dist/node/ui/hooks/useDealList.js +19 -8
  31. package/dist/node/ui/index.js +391 -158
  32. package/dist/node/ui/renderers/index.js +32 -10
  33. package/dist/node/ui/renderers/pipeline.markdown.js +13 -2
  34. package/dist/node/ui/renderers/pipeline.renderer.js +19 -8
  35. package/dist/node/ui/tables/DealListTab.js +390 -0
  36. package/dist/ui/CrmDashboard.js +366 -144
  37. package/dist/ui/hooks/index.js +19 -8
  38. package/dist/ui/hooks/useDealList.d.ts +8 -2
  39. package/dist/ui/hooks/useDealList.js +19 -8
  40. package/dist/ui/index.js +391 -158
  41. package/dist/ui/renderers/index.js +32 -10
  42. package/dist/ui/renderers/pipeline.markdown.js +13 -2
  43. package/dist/ui/renderers/pipeline.renderer.js +19 -8
  44. package/dist/ui/tables/DealListTab.d.ts +20 -0
  45. package/dist/ui/tables/DealListTab.js +391 -0
  46. package/dist/ui/tables/DealListTab.smoke.test.d.ts +1 -0
  47. package/package.json +27 -12
  48. package/src/docs/crm-pipeline.docblock.ts +1 -1
  49. package/src/handlers/crm.handlers.ts +18 -1
  50. package/src/ui/CrmDashboard.tsx +2 -71
  51. package/src/ui/hooks/useDealList.ts +36 -8
  52. package/src/ui/tables/DealListTab.smoke.test.tsx +149 -0
  53. package/src/ui/tables/DealListTab.tsx +276 -0
@@ -23,6 +23,13 @@ function rowToDeal(row) {
23
23
  updatedAt: new Date(row.updatedAt)
24
24
  };
25
25
  }
26
+ var DEAL_SORT_COLUMNS = {
27
+ name: "name",
28
+ value: "value",
29
+ status: "status",
30
+ expectedCloseDate: "expectedCloseDate",
31
+ updatedAt: "updatedAt"
32
+ };
26
33
  function createCrmHandlers(db) {
27
34
  async function listDeals(input) {
28
35
  const {
@@ -33,7 +40,9 @@ function createCrmHandlers(db) {
33
40
  ownerId,
34
41
  search,
35
42
  limit = 20,
36
- offset = 0
43
+ offset = 0,
44
+ sortBy = "value",
45
+ sortDirection = "desc"
37
46
  } = input;
38
47
  let whereClause = "WHERE projectId = ?";
39
48
  const params = [projectId];
@@ -61,7 +70,9 @@ function createCrmHandlers(db) {
61
70
  const total = countResult[0]?.count ?? 0;
62
71
  const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
63
72
  const totalValue = valueResult[0]?.total ?? 0;
64
- const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY value DESC LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
73
+ const orderByColumn = DEAL_SORT_COLUMNS[sortBy] ?? DEAL_SORT_COLUMNS.value;
74
+ const orderByDirection = sortDirection === "asc" ? "ASC" : "DESC";
75
+ const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY ${orderByColumn} ${orderByDirection} LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
65
76
  return {
66
77
  deals: dealRows.map(rowToDeal),
67
78
  total,
@@ -611,8 +622,13 @@ function useDealList(options = {}) {
611
622
  const [stages, setStages] = useState2([]);
612
623
  const [loading, setLoading] = useState2(true);
613
624
  const [error, setError] = useState2(null);
614
- const [page, setPage] = useState2(1);
625
+ const [internalPage, setInternalPage] = useState2(0);
615
626
  const pipelineId = options.pipelineId ?? "pipeline-1";
627
+ const pageIndex = options.pageIndex ?? internalPage;
628
+ const pageSize = options.pageSize ?? options.limit ?? 50;
629
+ const [sort] = options.sorting ?? [];
630
+ const sortBy = sort?.id;
631
+ const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
616
632
  const fetchData = useCallback(async () => {
617
633
  setLoading(true);
618
634
  setError(null);
@@ -624,8 +640,10 @@ function useDealList(options = {}) {
624
640
  stageId: options.stageId,
625
641
  status: options.status === "all" ? undefined : options.status,
626
642
  search: options.search,
627
- limit: options.limit ?? 50,
628
- offset: (page - 1) * (options.limit ?? 50)
643
+ limit: pageSize,
644
+ offset: pageIndex * pageSize,
645
+ sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
646
+ sortDirection
629
647
  }),
630
648
  crm2.getDealsByStage({ projectId, pipelineId }),
631
649
  crm2.getPipelineStages({ pipelineId })
@@ -645,8 +663,10 @@ function useDealList(options = {}) {
645
663
  options.stageId,
646
664
  options.status,
647
665
  options.search,
648
- options.limit,
649
- page
666
+ pageIndex,
667
+ pageSize,
668
+ sortBy,
669
+ sortDirection
650
670
  ]);
651
671
  useEffect(() => {
652
672
  fetchData();
@@ -674,10 +694,12 @@ function useDealList(options = {}) {
674
694
  loading,
675
695
  error,
676
696
  stats,
677
- page,
697
+ page: pageIndex + 1,
698
+ pageIndex,
699
+ pageSize,
678
700
  refetch: fetchData,
679
- nextPage: () => setPage((p) => p + 1),
680
- prevPage: () => page > 1 && setPage((p) => p - 1)
701
+ nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
702
+ prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
681
703
  };
682
704
  }
683
705
 
@@ -23,6 +23,13 @@ function rowToDeal(row) {
23
23
  updatedAt: new Date(row.updatedAt)
24
24
  };
25
25
  }
26
+ var DEAL_SORT_COLUMNS = {
27
+ name: "name",
28
+ value: "value",
29
+ status: "status",
30
+ expectedCloseDate: "expectedCloseDate",
31
+ updatedAt: "updatedAt"
32
+ };
26
33
  function createCrmHandlers(db) {
27
34
  async function listDeals(input) {
28
35
  const {
@@ -33,7 +40,9 @@ function createCrmHandlers(db) {
33
40
  ownerId,
34
41
  search,
35
42
  limit = 20,
36
- offset = 0
43
+ offset = 0,
44
+ sortBy = "value",
45
+ sortDirection = "desc"
37
46
  } = input;
38
47
  let whereClause = "WHERE projectId = ?";
39
48
  const params = [projectId];
@@ -61,7 +70,9 @@ function createCrmHandlers(db) {
61
70
  const total = countResult[0]?.count ?? 0;
62
71
  const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
63
72
  const totalValue = valueResult[0]?.total ?? 0;
64
- const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY value DESC LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
73
+ const orderByColumn = DEAL_SORT_COLUMNS[sortBy] ?? DEAL_SORT_COLUMNS.value;
74
+ const orderByDirection = sortDirection === "asc" ? "ASC" : "DESC";
75
+ const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY ${orderByColumn} ${orderByDirection} LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
65
76
  return {
66
77
  deals: dealRows.map(rowToDeal),
67
78
  total,
@@ -169,8 +169,13 @@ function useDealList(options = {}) {
169
169
  const [stages, setStages] = useState2([]);
170
170
  const [loading, setLoading] = useState2(true);
171
171
  const [error, setError] = useState2(null);
172
- const [page, setPage] = useState2(1);
172
+ const [internalPage, setInternalPage] = useState2(0);
173
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;
174
179
  const fetchData = useCallback(async () => {
175
180
  setLoading(true);
176
181
  setError(null);
@@ -182,8 +187,10 @@ function useDealList(options = {}) {
182
187
  stageId: options.stageId,
183
188
  status: options.status === "all" ? undefined : options.status,
184
189
  search: options.search,
185
- limit: options.limit ?? 50,
186
- offset: (page - 1) * (options.limit ?? 50)
190
+ limit: pageSize,
191
+ offset: pageIndex * pageSize,
192
+ sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
193
+ sortDirection
187
194
  }),
188
195
  crm.getDealsByStage({ projectId, pipelineId }),
189
196
  crm.getPipelineStages({ pipelineId })
@@ -203,8 +210,10 @@ function useDealList(options = {}) {
203
210
  options.stageId,
204
211
  options.status,
205
212
  options.search,
206
- options.limit,
207
- page
213
+ pageIndex,
214
+ pageSize,
215
+ sortBy,
216
+ sortDirection
208
217
  ]);
209
218
  useEffect(() => {
210
219
  fetchData();
@@ -232,10 +241,12 @@ function useDealList(options = {}) {
232
241
  loading,
233
242
  error,
234
243
  stats,
235
- page,
244
+ page: pageIndex + 1,
245
+ pageIndex,
246
+ pageSize,
236
247
  refetch: fetchData,
237
- nextPage: () => setPage((p) => p + 1),
238
- prevPage: () => page > 1 && setPage((p) => p - 1)
248
+ nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
249
+ prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
239
250
  };
240
251
  }
241
252
 
@@ -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;
@@ -0,0 +1,391 @@
1
+ // @bun
2
+ // src/ui/hooks/useDealList.ts
3
+ import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
4
+ import { useCallback, useEffect, useMemo, useState } from "react";
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 [internalPage, setInternalPage] = useState(0);
15
+ const pipelineId = options.pipelineId ?? "pipeline-1";
16
+ const pageIndex = options.pageIndex ?? internalPage;
17
+ const pageSize = options.pageSize ?? options.limit ?? 50;
18
+ const [sort] = options.sorting ?? [];
19
+ const sortBy = sort?.id;
20
+ const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
21
+ const fetchData = useCallback(async () => {
22
+ setLoading(true);
23
+ setError(null);
24
+ try {
25
+ const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
26
+ crm.listDeals({
27
+ projectId,
28
+ pipelineId,
29
+ stageId: options.stageId,
30
+ status: options.status === "all" ? undefined : options.status,
31
+ search: options.search,
32
+ limit: pageSize,
33
+ offset: pageIndex * pageSize,
34
+ sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
35
+ sortDirection
36
+ }),
37
+ crm.getDealsByStage({ projectId, pipelineId }),
38
+ crm.getPipelineStages({ pipelineId })
39
+ ]);
40
+ setData(dealsResult);
41
+ setDealsByStage(stageDealsResult);
42
+ setStages(stagesResult);
43
+ } catch (err) {
44
+ setError(err instanceof Error ? err : new Error("Unknown error"));
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ }, [
49
+ crm,
50
+ projectId,
51
+ pipelineId,
52
+ options.stageId,
53
+ options.status,
54
+ options.search,
55
+ pageIndex,
56
+ pageSize,
57
+ sortBy,
58
+ sortDirection
59
+ ]);
60
+ useEffect(() => {
61
+ fetchData();
62
+ }, [fetchData]);
63
+ const stats = useMemo(() => {
64
+ if (!data)
65
+ return null;
66
+ const open = data.deals.filter((d) => d.status === "OPEN");
67
+ const won = data.deals.filter((d) => d.status === "WON");
68
+ const lost = data.deals.filter((d) => d.status === "LOST");
69
+ return {
70
+ total: data.total,
71
+ totalValue: data.totalValue,
72
+ openCount: open.length,
73
+ openValue: open.reduce((sum, d) => sum + d.value, 0),
74
+ wonCount: won.length,
75
+ wonValue: won.reduce((sum, d) => sum + d.value, 0),
76
+ lostCount: lost.length
77
+ };
78
+ }, [data]);
79
+ return {
80
+ data,
81
+ dealsByStage,
82
+ stages,
83
+ loading,
84
+ error,
85
+ stats,
86
+ page: pageIndex + 1,
87
+ pageIndex,
88
+ pageSize,
89
+ refetch: fetchData,
90
+ nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
91
+ prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
92
+ };
93
+ }
94
+
95
+ // src/ui/tables/DealListTab.tsx
96
+ import {
97
+ Button,
98
+ DataTable,
99
+ LoaderBlock
100
+ } from "@contractspec/lib.design-system";
101
+ import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
102
+ import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
103
+ import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
104
+ import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
105
+ import * as React from "react";
106
+ import { jsxDEV } from "react/jsx-dev-runtime";
107
+ "use client";
108
+ function formatCurrency(value, currency = "USD") {
109
+ return new Intl.NumberFormat("en-US", {
110
+ style: "currency",
111
+ currency,
112
+ minimumFractionDigits: 0,
113
+ maximumFractionDigits: 0
114
+ }).format(value);
115
+ }
116
+ function statusVariant(status) {
117
+ switch (status) {
118
+ case "WON":
119
+ return "default";
120
+ case "LOST":
121
+ return "destructive";
122
+ case "STALE":
123
+ return "outline";
124
+ default:
125
+ return "secondary";
126
+ }
127
+ }
128
+ function DealListDataTable({
129
+ deals,
130
+ totalItems,
131
+ pageIndex,
132
+ pageSize,
133
+ sorting,
134
+ loading,
135
+ onSortingChange,
136
+ onPaginationChange,
137
+ onDealClick
138
+ }) {
139
+ const controller = useContractTable({
140
+ data: deals,
141
+ columns: [
142
+ {
143
+ id: "deal",
144
+ header: "Deal",
145
+ label: "Deal",
146
+ accessor: (deal) => deal.name,
147
+ cell: ({ item }) => /* @__PURE__ */ jsxDEV(VStack, {
148
+ gap: "xs",
149
+ children: [
150
+ /* @__PURE__ */ jsxDEV(Text, {
151
+ className: "font-medium text-sm",
152
+ children: item.name
153
+ }, undefined, false, undefined, this),
154
+ /* @__PURE__ */ jsxDEV(Text, {
155
+ className: "text-muted-foreground text-xs",
156
+ children: item.companyId ?? "Unassigned company"
157
+ }, undefined, false, undefined, this)
158
+ ]
159
+ }, undefined, true, undefined, this),
160
+ size: 240,
161
+ minSize: 180,
162
+ canSort: true,
163
+ canPin: true,
164
+ canResize: true
165
+ },
166
+ {
167
+ id: "value",
168
+ header: "Value",
169
+ label: "Value",
170
+ accessorKey: "value",
171
+ cell: ({ item }) => formatCurrency(item.value, item.currency),
172
+ align: "right",
173
+ size: 140,
174
+ canSort: true,
175
+ canResize: true
176
+ },
177
+ {
178
+ id: "status",
179
+ header: "Status",
180
+ label: "Status",
181
+ accessorKey: "status",
182
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV(Badge, {
183
+ variant: statusVariant(value),
184
+ children: String(value)
185
+ }, undefined, false, undefined, this),
186
+ size: 130,
187
+ canSort: true,
188
+ canHide: true,
189
+ canPin: true,
190
+ canResize: true
191
+ },
192
+ {
193
+ id: "expectedCloseDate",
194
+ header: "Expected Close",
195
+ label: "Expected Close",
196
+ accessor: (deal) => deal.expectedCloseDate?.toISOString() ?? "",
197
+ cell: ({ item }) => item.expectedCloseDate?.toLocaleDateString() ?? "Not scheduled",
198
+ size: 170,
199
+ canSort: true,
200
+ canHide: true,
201
+ canResize: true
202
+ },
203
+ {
204
+ id: "updatedAt",
205
+ header: "Updated",
206
+ label: "Updated",
207
+ accessor: (deal) => deal.updatedAt.toISOString(),
208
+ cell: ({ item }) => item.updatedAt.toLocaleDateString(),
209
+ size: 140,
210
+ canSort: true,
211
+ canHide: true,
212
+ canResize: true
213
+ },
214
+ {
215
+ id: "actions",
216
+ header: "Actions",
217
+ label: "Actions",
218
+ accessor: (deal) => deal.id,
219
+ cell: ({ item }) => /* @__PURE__ */ jsxDEV(Button, {
220
+ variant: "ghost",
221
+ size: "sm",
222
+ onPress: () => onDealClick?.(item.id),
223
+ children: "Actions"
224
+ }, undefined, false, undefined, this),
225
+ size: 120,
226
+ canSort: false,
227
+ canHide: false,
228
+ canPin: false,
229
+ canResize: false
230
+ }
231
+ ],
232
+ executionMode: "server",
233
+ selectionMode: "multiple",
234
+ totalItems,
235
+ state: {
236
+ sorting,
237
+ pagination: {
238
+ pageIndex,
239
+ pageSize
240
+ }
241
+ },
242
+ onSortingChange,
243
+ onPaginationChange,
244
+ initialState: {
245
+ columnVisibility: { updatedAt: false },
246
+ columnPinning: { left: ["deal", "status"], right: [] }
247
+ },
248
+ renderExpandedContent: (deal) => /* @__PURE__ */ jsxDEV(VStack, {
249
+ gap: "sm",
250
+ className: "py-2",
251
+ children: [
252
+ /* @__PURE__ */ jsxDEV(HStack, {
253
+ justify: "between",
254
+ children: [
255
+ /* @__PURE__ */ jsxDEV(Text, {
256
+ className: "font-medium text-sm",
257
+ children: "Owner"
258
+ }, undefined, false, undefined, this),
259
+ /* @__PURE__ */ jsxDEV(Text, {
260
+ className: "text-muted-foreground text-sm",
261
+ children: deal.ownerId
262
+ }, undefined, false, undefined, this)
263
+ ]
264
+ }, undefined, true, undefined, this),
265
+ /* @__PURE__ */ jsxDEV(HStack, {
266
+ justify: "between",
267
+ children: [
268
+ /* @__PURE__ */ jsxDEV(Text, {
269
+ className: "font-medium text-sm",
270
+ children: "Contact"
271
+ }, undefined, false, undefined, this),
272
+ /* @__PURE__ */ jsxDEV(Text, {
273
+ className: "text-muted-foreground text-sm",
274
+ children: deal.contactId ?? "No linked contact"
275
+ }, undefined, false, undefined, this)
276
+ ]
277
+ }, undefined, true, undefined, this),
278
+ deal.wonSource ? /* @__PURE__ */ jsxDEV(HStack, {
279
+ justify: "between",
280
+ children: [
281
+ /* @__PURE__ */ jsxDEV(Text, {
282
+ className: "font-medium text-sm",
283
+ children: "Won Source"
284
+ }, undefined, false, undefined, this),
285
+ /* @__PURE__ */ jsxDEV(Text, {
286
+ className: "text-muted-foreground text-sm",
287
+ children: deal.wonSource
288
+ }, undefined, false, undefined, this)
289
+ ]
290
+ }, undefined, true, undefined, this) : null,
291
+ deal.lostReason ? /* @__PURE__ */ jsxDEV(HStack, {
292
+ justify: "between",
293
+ children: [
294
+ /* @__PURE__ */ jsxDEV(Text, {
295
+ className: "font-medium text-sm",
296
+ children: "Lost Reason"
297
+ }, undefined, false, undefined, this),
298
+ /* @__PURE__ */ jsxDEV(Text, {
299
+ className: "text-muted-foreground text-sm",
300
+ children: deal.lostReason
301
+ }, undefined, false, undefined, this)
302
+ ]
303
+ }, undefined, true, undefined, this) : null,
304
+ deal.notes ? /* @__PURE__ */ jsxDEV(VStack, {
305
+ gap: "xs",
306
+ children: [
307
+ /* @__PURE__ */ jsxDEV(Text, {
308
+ className: "font-medium text-sm",
309
+ children: "Notes"
310
+ }, undefined, false, undefined, this),
311
+ /* @__PURE__ */ jsxDEV(Text, {
312
+ className: "text-muted-foreground text-sm",
313
+ children: deal.notes
314
+ }, undefined, false, undefined, this)
315
+ ]
316
+ }, undefined, true, undefined, this) : null
317
+ ]
318
+ }, undefined, true, undefined, this),
319
+ getCanExpand: () => true
320
+ });
321
+ return /* @__PURE__ */ jsxDEV(DataTable, {
322
+ controller,
323
+ title: "All Deals",
324
+ description: "Server-mode table using the shared ContractSpec controller.",
325
+ loading,
326
+ toolbar: /* @__PURE__ */ jsxDEV(HStack, {
327
+ gap: "sm",
328
+ className: "flex-wrap",
329
+ children: [
330
+ /* @__PURE__ */ jsxDEV(Text, {
331
+ className: "text-muted-foreground text-sm",
332
+ children: [
333
+ "Selected ",
334
+ controller.selectedRowIds.length
335
+ ]
336
+ }, undefined, true, undefined, this),
337
+ /* @__PURE__ */ jsxDEV(Text, {
338
+ className: "text-muted-foreground text-sm",
339
+ children: [
340
+ totalItems,
341
+ " total deals"
342
+ ]
343
+ }, undefined, true, undefined, this)
344
+ ]
345
+ }, undefined, true, undefined, this),
346
+ footer: `Page ${controller.pageIndex + 1} of ${controller.pageCount}`,
347
+ emptyState: /* @__PURE__ */ jsxDEV("div", {
348
+ className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
349
+ children: "No deals found"
350
+ }, undefined, false, undefined, this)
351
+ }, undefined, false, undefined, this);
352
+ }
353
+ function DealListTab({
354
+ onDealClick
355
+ }) {
356
+ const [sorting, setSorting] = React.useState([
357
+ { id: "value", desc: true }
358
+ ]);
359
+ const [pagination, setPagination] = React.useState({
360
+ pageIndex: 0,
361
+ pageSize: 3
362
+ });
363
+ const { data, loading } = useDealList({
364
+ pageIndex: pagination.pageIndex,
365
+ pageSize: pagination.pageSize,
366
+ sorting
367
+ });
368
+ if (loading && !data) {
369
+ return /* @__PURE__ */ jsxDEV(LoaderBlock, {
370
+ label: "Loading deals..."
371
+ }, undefined, false, undefined, this);
372
+ }
373
+ return /* @__PURE__ */ jsxDEV(DealListDataTable, {
374
+ deals: data?.deals ?? [],
375
+ totalItems: data?.total ?? 0,
376
+ pageIndex: pagination.pageIndex,
377
+ pageSize: pagination.pageSize,
378
+ sorting,
379
+ loading,
380
+ onSortingChange: (nextSorting) => {
381
+ setSorting(nextSorting);
382
+ setPagination((current) => ({ ...current, pageIndex: 0 }));
383
+ },
384
+ onPaginationChange: setPagination,
385
+ onDealClick
386
+ }, undefined, false, undefined, this);
387
+ }
388
+ export {
389
+ DealListTab,
390
+ DealListDataTable
391
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/example.crm-pipeline",
3
- "version": "3.7.7",
3
+ "version": "3.7.10",
4
4
  "description": "CRM Pipeline - Contacts, Companies, Deals, Tasks",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -312,6 +312,13 @@
312
312
  "bun": "./dist/ui/renderers/pipeline.renderer.js",
313
313
  "node": "./dist/node/ui/renderers/pipeline.renderer.js",
314
314
  "default": "./dist/ui/renderers/pipeline.renderer.js"
315
+ },
316
+ "./ui/tables/DealListTab": {
317
+ "types": "./dist/ui/tables/DealListTab.d.ts",
318
+ "browser": "./dist/browser/ui/tables/DealListTab.js",
319
+ "bun": "./dist/ui/tables/DealListTab.js",
320
+ "node": "./dist/node/ui/tables/DealListTab.js",
321
+ "default": "./dist/ui/tables/DealListTab.js"
315
322
  }
316
323
  },
317
324
  "scripts": {
@@ -331,24 +338,25 @@
331
338
  "typecheck": "tsc --noEmit"
332
339
  },
333
340
  "dependencies": {
334
- "@contractspec/lib.contracts-spec": "4.0.0",
335
- "@contractspec/lib.design-system": "3.8.0",
336
- "@contractspec/lib.example-shared-ui": "6.0.7",
337
- "@contractspec/lib.identity-rbac": "3.7.7",
338
- "@contractspec/lib.runtime-sandbox": "2.7.6",
339
- "@contractspec/lib.schema": "3.7.6",
340
- "@contractspec/lib.ui-kit-web": "3.8.0",
341
- "@contractspec/module.audit-trail": "3.7.7",
342
- "@contractspec/module.notifications": "3.7.7",
341
+ "@contractspec/lib.contracts-spec": "4.1.2",
342
+ "@contractspec/lib.design-system": "3.8.3",
343
+ "@contractspec/lib.example-shared-ui": "6.0.10",
344
+ "@contractspec/lib.identity-rbac": "3.7.10",
345
+ "@contractspec/lib.runtime-sandbox": "2.7.9",
346
+ "@contractspec/lib.schema": "3.7.8",
347
+ "@contractspec/lib.ui-kit-web": "3.9.2",
348
+ "@contractspec/module.audit-trail": "3.7.10",
349
+ "@contractspec/module.notifications": "3.7.10",
343
350
  "react": "19.2.0",
344
351
  "react-dom": "19.2.0"
345
352
  },
346
353
  "devDependencies": {
347
- "@contractspec/tool.typescript": "3.7.6",
354
+ "@contractspec/tool.typescript": "3.7.8",
348
355
  "typescript": "^5.9.3",
349
356
  "@types/react": "^19.2.14",
350
357
  "@types/react-dom": "^19.2.2",
351
- "@contractspec/tool.bun": "3.7.6"
358
+ "@contractspec/tool.bun": "3.7.8",
359
+ "happy-dom": "^20.8.4"
352
360
  },
353
361
  "publishConfig": {
354
362
  "exports": {
@@ -659,6 +667,13 @@
659
667
  "bun": "./dist/ui/renderers/pipeline.renderer.js",
660
668
  "node": "./dist/node/ui/renderers/pipeline.renderer.js",
661
669
  "default": "./dist/ui/renderers/pipeline.renderer.js"
670
+ },
671
+ "./ui/tables/DealListTab": {
672
+ "types": "./dist/ui/tables/DealListTab.d.ts",
673
+ "browser": "./dist/browser/ui/tables/DealListTab.js",
674
+ "bun": "./dist/ui/tables/DealListTab.js",
675
+ "node": "./dist/node/ui/tables/DealListTab.js",
676
+ "default": "./dist/ui/tables/DealListTab.js"
662
677
  }
663
678
  },
664
679
  "registry": "https://registry.npmjs.org/",
@@ -84,7 +84,7 @@ const crmPipelineDocBlocks: DocBlock[] = [
84
84
  - deal.created, stage.moved, task.completed, contact.updated.
85
85
 
86
86
  ## Presentations
87
- - Pipelines/kanban, deal detail, contact/company profiles, task lists.
87
+ - Pipelines/kanban, deal detail, contact/company profiles, task lists, and a server-mode shared table for the deal list.
88
88
 
89
89
  ## Notes
90
90
  - Stage definitions should be declarative; enforce via spec and regeneration.