@contractspec/example.crm-pipeline 3.7.7 → 3.7.12
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 +72 -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.d.ts +1 -1
- package/dist/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/ui/renderers/pipeline.renderer.d.ts +1 -1
- 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 +29 -13
- 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/renderers/pipeline.markdown.ts +1 -1
- package/src/ui/renderers/pipeline.renderer.tsx +1 -1
- 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
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Imports handlers from the hooks module to ensure correct build order.
|
|
5
5
|
*/
|
|
6
|
-
import type { PresentationRenderer } from '@contractspec/lib.
|
|
6
|
+
import type { PresentationRenderer } from '@contractspec/lib.presentation-runtime-core/transform-engine';
|
|
7
7
|
/**
|
|
8
8
|
* Markdown renderer for CRM Pipeline Kanban view (crm-pipeline.deal.pipeline)
|
|
9
9
|
* Only handles PipelineKanbanView component
|
|
@@ -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,
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* Renders the CRM pipeline board component.
|
|
5
5
|
* Data is fetched via the CrmPipelineBoard component's internal hooks.
|
|
6
6
|
*/
|
|
7
|
-
import type { PresentationRenderer } from '@contractspec/lib.
|
|
7
|
+
import type { PresentationRenderer } from '@contractspec/lib.presentation-runtime-core/transform-engine';
|
|
8
8
|
import * as React from 'react';
|
|
9
9
|
export declare const crmPipelineReactRenderer: PresentationRenderer<React.ReactElement>;
|
|
@@ -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 {};
|