@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
|
@@ -22,6 +22,13 @@ function rowToDeal(row) {
|
|
|
22
22
|
updatedAt: new Date(row.updatedAt)
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
var DEAL_SORT_COLUMNS = {
|
|
26
|
+
name: "name",
|
|
27
|
+
value: "value",
|
|
28
|
+
status: "status",
|
|
29
|
+
expectedCloseDate: "expectedCloseDate",
|
|
30
|
+
updatedAt: "updatedAt"
|
|
31
|
+
};
|
|
25
32
|
function createCrmHandlers(db) {
|
|
26
33
|
async function listDeals(input) {
|
|
27
34
|
const {
|
|
@@ -32,7 +39,9 @@ function createCrmHandlers(db) {
|
|
|
32
39
|
ownerId,
|
|
33
40
|
search,
|
|
34
41
|
limit = 20,
|
|
35
|
-
offset = 0
|
|
42
|
+
offset = 0,
|
|
43
|
+
sortBy = "value",
|
|
44
|
+
sortDirection = "desc"
|
|
36
45
|
} = input;
|
|
37
46
|
let whereClause = "WHERE projectId = ?";
|
|
38
47
|
const params = [projectId];
|
|
@@ -60,7 +69,9 @@ function createCrmHandlers(db) {
|
|
|
60
69
|
const total = countResult[0]?.count ?? 0;
|
|
61
70
|
const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
|
|
62
71
|
const totalValue = valueResult[0]?.total ?? 0;
|
|
63
|
-
const
|
|
72
|
+
const orderByColumn = DEAL_SORT_COLUMNS[sortBy] ?? DEAL_SORT_COLUMNS.value;
|
|
73
|
+
const orderByDirection = sortDirection === "asc" ? "ASC" : "DESC";
|
|
74
|
+
const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY ${orderByColumn} ${orderByDirection} LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
|
|
64
75
|
return {
|
|
65
76
|
deals: dealRows.map(rowToDeal),
|
|
66
77
|
total,
|
|
@@ -610,8 +621,13 @@ function useDealList(options = {}) {
|
|
|
610
621
|
const [stages, setStages] = useState2([]);
|
|
611
622
|
const [loading, setLoading] = useState2(true);
|
|
612
623
|
const [error, setError] = useState2(null);
|
|
613
|
-
const [
|
|
624
|
+
const [internalPage, setInternalPage] = useState2(0);
|
|
614
625
|
const pipelineId = options.pipelineId ?? "pipeline-1";
|
|
626
|
+
const pageIndex = options.pageIndex ?? internalPage;
|
|
627
|
+
const pageSize = options.pageSize ?? options.limit ?? 50;
|
|
628
|
+
const [sort] = options.sorting ?? [];
|
|
629
|
+
const sortBy = sort?.id;
|
|
630
|
+
const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
|
|
615
631
|
const fetchData = useCallback(async () => {
|
|
616
632
|
setLoading(true);
|
|
617
633
|
setError(null);
|
|
@@ -623,8 +639,10 @@ function useDealList(options = {}) {
|
|
|
623
639
|
stageId: options.stageId,
|
|
624
640
|
status: options.status === "all" ? undefined : options.status,
|
|
625
641
|
search: options.search,
|
|
626
|
-
limit:
|
|
627
|
-
offset:
|
|
642
|
+
limit: pageSize,
|
|
643
|
+
offset: pageIndex * pageSize,
|
|
644
|
+
sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
|
|
645
|
+
sortDirection
|
|
628
646
|
}),
|
|
629
647
|
crm2.getDealsByStage({ projectId, pipelineId }),
|
|
630
648
|
crm2.getPipelineStages({ pipelineId })
|
|
@@ -644,8 +662,10 @@ function useDealList(options = {}) {
|
|
|
644
662
|
options.stageId,
|
|
645
663
|
options.status,
|
|
646
664
|
options.search,
|
|
647
|
-
|
|
648
|
-
|
|
665
|
+
pageIndex,
|
|
666
|
+
pageSize,
|
|
667
|
+
sortBy,
|
|
668
|
+
sortDirection
|
|
649
669
|
]);
|
|
650
670
|
useEffect(() => {
|
|
651
671
|
fetchData();
|
|
@@ -673,10 +693,12 @@ function useDealList(options = {}) {
|
|
|
673
693
|
loading,
|
|
674
694
|
error,
|
|
675
695
|
stats,
|
|
676
|
-
page,
|
|
696
|
+
page: pageIndex + 1,
|
|
697
|
+
pageIndex,
|
|
698
|
+
pageSize,
|
|
677
699
|
refetch: fetchData,
|
|
678
|
-
nextPage: () =>
|
|
679
|
-
prevPage: () =>
|
|
700
|
+
nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
|
|
701
|
+
prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
|
|
680
702
|
};
|
|
681
703
|
}
|
|
682
704
|
|
|
@@ -22,6 +22,13 @@ function rowToDeal(row) {
|
|
|
22
22
|
updatedAt: new Date(row.updatedAt)
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
var DEAL_SORT_COLUMNS = {
|
|
26
|
+
name: "name",
|
|
27
|
+
value: "value",
|
|
28
|
+
status: "status",
|
|
29
|
+
expectedCloseDate: "expectedCloseDate",
|
|
30
|
+
updatedAt: "updatedAt"
|
|
31
|
+
};
|
|
25
32
|
function createCrmHandlers(db) {
|
|
26
33
|
async function listDeals(input) {
|
|
27
34
|
const {
|
|
@@ -32,7 +39,9 @@ function createCrmHandlers(db) {
|
|
|
32
39
|
ownerId,
|
|
33
40
|
search,
|
|
34
41
|
limit = 20,
|
|
35
|
-
offset = 0
|
|
42
|
+
offset = 0,
|
|
43
|
+
sortBy = "value",
|
|
44
|
+
sortDirection = "desc"
|
|
36
45
|
} = input;
|
|
37
46
|
let whereClause = "WHERE projectId = ?";
|
|
38
47
|
const params = [projectId];
|
|
@@ -60,7 +69,9 @@ function createCrmHandlers(db) {
|
|
|
60
69
|
const total = countResult[0]?.count ?? 0;
|
|
61
70
|
const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
|
|
62
71
|
const totalValue = valueResult[0]?.total ?? 0;
|
|
63
|
-
const
|
|
72
|
+
const orderByColumn = DEAL_SORT_COLUMNS[sortBy] ?? DEAL_SORT_COLUMNS.value;
|
|
73
|
+
const orderByDirection = sortDirection === "asc" ? "ASC" : "DESC";
|
|
74
|
+
const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY ${orderByColumn} ${orderByDirection} LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
|
|
64
75
|
return {
|
|
65
76
|
deals: dealRows.map(rowToDeal),
|
|
66
77
|
total,
|
|
@@ -168,8 +168,13 @@ function useDealList(options = {}) {
|
|
|
168
168
|
const [stages, setStages] = useState2([]);
|
|
169
169
|
const [loading, setLoading] = useState2(true);
|
|
170
170
|
const [error, setError] = useState2(null);
|
|
171
|
-
const [
|
|
171
|
+
const [internalPage, setInternalPage] = useState2(0);
|
|
172
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;
|
|
173
178
|
const fetchData = useCallback(async () => {
|
|
174
179
|
setLoading(true);
|
|
175
180
|
setError(null);
|
|
@@ -181,8 +186,10 @@ function useDealList(options = {}) {
|
|
|
181
186
|
stageId: options.stageId,
|
|
182
187
|
status: options.status === "all" ? undefined : options.status,
|
|
183
188
|
search: options.search,
|
|
184
|
-
limit:
|
|
185
|
-
offset:
|
|
189
|
+
limit: pageSize,
|
|
190
|
+
offset: pageIndex * pageSize,
|
|
191
|
+
sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
|
|
192
|
+
sortDirection
|
|
186
193
|
}),
|
|
187
194
|
crm.getDealsByStage({ projectId, pipelineId }),
|
|
188
195
|
crm.getPipelineStages({ pipelineId })
|
|
@@ -202,8 +209,10 @@ function useDealList(options = {}) {
|
|
|
202
209
|
options.stageId,
|
|
203
210
|
options.status,
|
|
204
211
|
options.search,
|
|
205
|
-
|
|
206
|
-
|
|
212
|
+
pageIndex,
|
|
213
|
+
pageSize,
|
|
214
|
+
sortBy,
|
|
215
|
+
sortDirection
|
|
207
216
|
]);
|
|
208
217
|
useEffect(() => {
|
|
209
218
|
fetchData();
|
|
@@ -231,10 +240,12 @@ function useDealList(options = {}) {
|
|
|
231
240
|
loading,
|
|
232
241
|
error,
|
|
233
242
|
stats,
|
|
234
|
-
page,
|
|
243
|
+
page: pageIndex + 1,
|
|
244
|
+
pageIndex,
|
|
245
|
+
pageSize,
|
|
235
246
|
refetch: fetchData,
|
|
236
|
-
nextPage: () =>
|
|
237
|
-
prevPage: () =>
|
|
247
|
+
nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
|
|
248
|
+
prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
|
|
238
249
|
};
|
|
239
250
|
}
|
|
240
251
|
|
|
@@ -0,0 +1,390 @@
|
|
|
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
|
+
};
|