@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.
- package/.turbo/turbo-build.log +45 -42
- package/CHANGELOG.md +36 -0
- package/README.md +2 -1
- package/dist/browser/docs/crm-pipeline.docblock.js +1 -1
- package/dist/browser/docs/index.js +1 -1
- package/dist/browser/handlers/crm.handlers.js +13 -2
- package/dist/browser/handlers/index.js +13 -2
- package/dist/browser/index.js +392 -159
- package/dist/browser/ui/CrmDashboard.js +366 -144
- package/dist/browser/ui/hooks/index.js +19 -8
- package/dist/browser/ui/hooks/useDealList.js +19 -8
- package/dist/browser/ui/index.js +391 -158
- package/dist/browser/ui/renderers/index.js +32 -10
- package/dist/browser/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/browser/ui/renderers/pipeline.renderer.js +19 -8
- package/dist/browser/ui/tables/DealListTab.js +390 -0
- package/dist/docs/crm-pipeline.docblock.js +1 -1
- package/dist/docs/index.js +1 -1
- package/dist/handlers/crm.handlers.d.ts +2 -0
- package/dist/handlers/crm.handlers.js +13 -2
- package/dist/handlers/index.js +13 -2
- package/dist/index.js +392 -159
- package/dist/node/docs/crm-pipeline.docblock.js +1 -1
- package/dist/node/docs/index.js +1 -1
- package/dist/node/handlers/crm.handlers.js +13 -2
- package/dist/node/handlers/index.js +13 -2
- package/dist/node/index.js +392 -159
- package/dist/node/ui/CrmDashboard.js +366 -144
- package/dist/node/ui/hooks/index.js +19 -8
- package/dist/node/ui/hooks/useDealList.js +19 -8
- package/dist/node/ui/index.js +391 -158
- package/dist/node/ui/renderers/index.js +32 -10
- package/dist/node/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/node/ui/renderers/pipeline.renderer.js +19 -8
- package/dist/node/ui/tables/DealListTab.js +390 -0
- package/dist/ui/CrmDashboard.js +366 -144
- package/dist/ui/hooks/index.js +19 -8
- package/dist/ui/hooks/useDealList.d.ts +8 -2
- package/dist/ui/hooks/useDealList.js +19 -8
- package/dist/ui/index.js +391 -158
- package/dist/ui/renderers/index.js +32 -10
- package/dist/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/ui/renderers/pipeline.renderer.js +19 -8
- package/dist/ui/tables/DealListTab.d.ts +20 -0
- package/dist/ui/tables/DealListTab.js +391 -0
- package/dist/ui/tables/DealListTab.smoke.test.d.ts +1 -0
- package/package.json +27 -12
- package/src/docs/crm-pipeline.docblock.ts +1 -1
- package/src/handlers/crm.handlers.ts +18 -1
- package/src/ui/CrmDashboard.tsx +2 -71
- package/src/ui/hooks/useDealList.ts +36 -8
- package/src/ui/tables/DealListTab.smoke.test.tsx +149 -0
- 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
|
|
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 [
|
|
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:
|
|
628
|
-
offset:
|
|
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
|
-
|
|
649
|
-
|
|
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: () =>
|
|
680
|
-
prevPage: () =>
|
|
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
|
|
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 [
|
|
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:
|
|
186
|
-
offset:
|
|
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
|
-
|
|
207
|
-
|
|
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: () =>
|
|
238
|
-
prevPage: () =>
|
|
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.
|
|
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.
|
|
335
|
-
"@contractspec/lib.design-system": "3.8.
|
|
336
|
-
"@contractspec/lib.example-shared-ui": "6.0.
|
|
337
|
-
"@contractspec/lib.identity-rbac": "3.7.
|
|
338
|
-
"@contractspec/lib.runtime-sandbox": "2.7.
|
|
339
|
-
"@contractspec/lib.schema": "3.7.
|
|
340
|
-
"@contractspec/lib.ui-kit-web": "3.
|
|
341
|
-
"@contractspec/module.audit-trail": "3.7.
|
|
342
|
-
"@contractspec/module.notifications": "3.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.
|
|
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.
|
|
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.
|