@contractspec/example.crm-pipeline 3.7.17 → 3.7.18
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 +135 -135
- package/CHANGELOG.md +20 -0
- package/dist/browser/crm-pipeline.feature.js +1 -82
- package/dist/browser/deal/deal.enum.js +1 -18
- package/dist/browser/deal/deal.operation.js +1 -396
- package/dist/browser/deal/deal.schema.js +1 -141
- package/dist/browser/deal/deal.test-spec.js +1 -58
- package/dist/browser/deal/index.js +1 -408
- package/dist/browser/docs/crm-pipeline.docblock.js +5 -49
- package/dist/browser/docs/index.js +5 -49
- package/dist/browser/entities/company.entity.js +1 -52
- package/dist/browser/entities/contact.entity.js +1 -66
- package/dist/browser/entities/deal.entity.js +1 -107
- package/dist/browser/entities/index.js +1 -343
- package/dist/browser/entities/task.entity.js +1 -99
- package/dist/browser/events/contact.event.js +1 -31
- package/dist/browser/events/deal.event.js +1 -101
- package/dist/browser/events/index.js +1 -158
- package/dist/browser/events/task.event.js +1 -28
- package/dist/browser/example.js +1 -39
- package/dist/browser/handlers/crm.handlers.js +2 -171
- package/dist/browser/handlers/deal.handlers.js +1 -293
- package/dist/browser/handlers/index.js +2 -467
- package/dist/browser/handlers/mock-data.js +1 -165
- package/dist/browser/index.js +8 -3461
- package/dist/browser/operations/index.js +1 -407
- package/dist/browser/presentations/dashboard.presentation.js +1 -55
- package/dist/browser/presentations/index.js +1 -290
- package/dist/browser/presentations/pipeline.presentation.js +1 -236
- package/dist/browser/seeders/index.js +1 -22
- package/dist/browser/ui/CrmDashboard.js +1 -1547
- package/dist/browser/ui/CrmDealCard.js +1 -50
- package/dist/browser/ui/CrmPipelineBoard.js +1 -160
- package/dist/browser/ui/hooks/index.js +1 -197
- package/dist/browser/ui/hooks/useDealList.js +1 -95
- package/dist/browser/ui/hooks/useDealMutations.js +1 -100
- package/dist/browser/ui/index.js +4 -2205
- package/dist/browser/ui/modals/CreateDealModal.js +1 -211
- package/dist/browser/ui/modals/DealActionsModal.js +1 -428
- package/dist/browser/ui/modals/index.js +1 -638
- package/dist/browser/ui/overlays/demo-overlays.js +1 -55
- package/dist/browser/ui/overlays/index.js +1 -55
- package/dist/browser/ui/renderers/index.js +4 -849
- package/dist/browser/ui/renderers/pipeline.markdown.js +4 -575
- package/dist/browser/ui/renderers/pipeline.renderer.js +1 -275
- package/dist/browser/ui/tables/DealListTab.js +1 -390
- package/dist/crm-pipeline.feature.js +1 -82
- package/dist/deal/deal.enum.js +1 -18
- package/dist/deal/deal.operation.js +1 -396
- package/dist/deal/deal.schema.js +1 -141
- package/dist/deal/deal.test-spec.js +1 -58
- package/dist/deal/index.js +1 -408
- package/dist/docs/crm-pipeline.docblock.js +5 -49
- package/dist/docs/index.js +5 -49
- package/dist/entities/company.entity.js +1 -52
- package/dist/entities/contact.entity.js +1 -66
- package/dist/entities/deal.entity.js +1 -107
- package/dist/entities/index.js +1 -343
- package/dist/entities/task.entity.js +1 -99
- package/dist/events/contact.event.js +1 -31
- package/dist/events/deal.event.js +1 -101
- package/dist/events/index.js +1 -158
- package/dist/events/task.event.js +1 -28
- package/dist/example.js +1 -39
- package/dist/handlers/crm.handlers.js +2 -171
- package/dist/handlers/deal.handlers.js +1 -293
- package/dist/handlers/index.js +2 -467
- package/dist/handlers/mock-data.js +1 -165
- package/dist/index.js +8 -3461
- package/dist/node/crm-pipeline.feature.js +1 -82
- package/dist/node/deal/deal.enum.js +1 -18
- package/dist/node/deal/deal.operation.js +1 -396
- package/dist/node/deal/deal.schema.js +1 -141
- package/dist/node/deal/deal.test-spec.js +1 -58
- package/dist/node/deal/index.js +1 -408
- package/dist/node/docs/crm-pipeline.docblock.js +5 -49
- package/dist/node/docs/index.js +5 -49
- package/dist/node/entities/company.entity.js +1 -52
- package/dist/node/entities/contact.entity.js +1 -66
- package/dist/node/entities/deal.entity.js +1 -107
- package/dist/node/entities/index.js +1 -343
- package/dist/node/entities/task.entity.js +1 -99
- package/dist/node/events/contact.event.js +1 -31
- package/dist/node/events/deal.event.js +1 -101
- package/dist/node/events/index.js +1 -158
- package/dist/node/events/task.event.js +1 -28
- package/dist/node/example.js +1 -39
- package/dist/node/handlers/crm.handlers.js +2 -171
- package/dist/node/handlers/deal.handlers.js +1 -293
- package/dist/node/handlers/index.js +2 -467
- package/dist/node/handlers/mock-data.js +1 -165
- package/dist/node/index.js +8 -3461
- package/dist/node/operations/index.js +1 -407
- package/dist/node/presentations/dashboard.presentation.js +1 -55
- package/dist/node/presentations/index.js +1 -290
- package/dist/node/presentations/pipeline.presentation.js +1 -236
- package/dist/node/seeders/index.js +1 -22
- package/dist/node/ui/CrmDashboard.js +1 -1547
- package/dist/node/ui/CrmDealCard.js +1 -50
- package/dist/node/ui/CrmPipelineBoard.js +1 -160
- package/dist/node/ui/hooks/index.js +1 -197
- package/dist/node/ui/hooks/useDealList.js +1 -95
- package/dist/node/ui/hooks/useDealMutations.js +1 -100
- package/dist/node/ui/index.js +4 -2205
- package/dist/node/ui/modals/CreateDealModal.js +1 -211
- package/dist/node/ui/modals/DealActionsModal.js +1 -428
- package/dist/node/ui/modals/index.js +1 -638
- package/dist/node/ui/overlays/demo-overlays.js +1 -55
- package/dist/node/ui/overlays/index.js +1 -55
- package/dist/node/ui/renderers/index.js +4 -849
- package/dist/node/ui/renderers/pipeline.markdown.js +4 -575
- package/dist/node/ui/renderers/pipeline.renderer.js +1 -275
- package/dist/node/ui/tables/DealListTab.js +1 -390
- package/dist/operations/index.js +1 -407
- package/dist/presentations/dashboard.presentation.js +1 -55
- package/dist/presentations/index.js +1 -290
- package/dist/presentations/pipeline.presentation.js +1 -236
- package/dist/seeders/index.js +1 -22
- package/dist/ui/CrmDashboard.js +1 -1547
- package/dist/ui/CrmDealCard.js +1 -50
- package/dist/ui/CrmPipelineBoard.js +1 -160
- package/dist/ui/hooks/index.js +1 -197
- package/dist/ui/hooks/useDealList.js +1 -95
- package/dist/ui/hooks/useDealMutations.js +1 -100
- package/dist/ui/index.js +4 -2205
- package/dist/ui/modals/CreateDealModal.js +1 -211
- package/dist/ui/modals/DealActionsModal.js +1 -428
- package/dist/ui/modals/index.js +1 -638
- package/dist/ui/overlays/demo-overlays.js +1 -55
- package/dist/ui/overlays/index.js +1 -55
- package/dist/ui/renderers/index.js +4 -849
- package/dist/ui/renderers/pipeline.markdown.js +4 -575
- package/dist/ui/renderers/pipeline.renderer.js +1 -275
- package/dist/ui/tables/DealListTab.js +1 -390
- package/package.json +13 -13
|
@@ -1,276 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
4
|
-
"use client";
|
|
5
|
-
function formatCurrency(value, currency) {
|
|
6
|
-
return new Intl.NumberFormat("en-US", {
|
|
7
|
-
style: "currency",
|
|
8
|
-
currency,
|
|
9
|
-
minimumFractionDigits: 0,
|
|
10
|
-
maximumFractionDigits: 0
|
|
11
|
-
}).format(value);
|
|
12
|
-
}
|
|
13
|
-
function CrmDealCard({ deal, onClick }) {
|
|
14
|
-
const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
|
|
15
|
-
return /* @__PURE__ */ jsxDEV("div", {
|
|
16
|
-
onClick,
|
|
17
|
-
className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
|
|
18
|
-
role: "button",
|
|
19
|
-
tabIndex: 0,
|
|
20
|
-
onKeyDown: (e) => {
|
|
21
|
-
if (e.key === "Enter" || e.key === " ")
|
|
22
|
-
onClick?.();
|
|
23
|
-
},
|
|
24
|
-
children: [
|
|
25
|
-
/* @__PURE__ */ jsxDEV("h4", {
|
|
26
|
-
className: "font-medium leading-snug",
|
|
27
|
-
children: deal.name
|
|
28
|
-
}, undefined, false, undefined, this),
|
|
29
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
30
|
-
className: "mt-2 font-semibold text-lg text-primary",
|
|
31
|
-
children: formatCurrency(deal.value, deal.currency)
|
|
32
|
-
}, undefined, false, undefined, this),
|
|
33
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
34
|
-
className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
|
|
35
|
-
children: [
|
|
36
|
-
daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
|
|
37
|
-
className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
|
|
38
|
-
children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
|
|
39
|
-
}, undefined, false, undefined, this),
|
|
40
|
-
/* @__PURE__ */ jsxDEV("span", {
|
|
41
|
-
className: `rounded px-1.5 py-0.5 font-medium text-xs ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
|
|
42
|
-
children: deal.status
|
|
43
|
-
}, undefined, false, undefined, this)
|
|
44
|
-
]
|
|
45
|
-
}, undefined, true, undefined, this)
|
|
46
|
-
]
|
|
47
|
-
}, undefined, true, undefined, this);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// src/ui/CrmPipelineBoard.tsx
|
|
51
|
-
import { useState } from "react";
|
|
52
|
-
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
53
|
-
"use client";
|
|
54
|
-
function formatCurrency2(value) {
|
|
55
|
-
if (value >= 1e6)
|
|
56
|
-
return `$${(value / 1e6).toFixed(1)}M`;
|
|
57
|
-
if (value >= 1000)
|
|
58
|
-
return `$${(value / 1000).toFixed(0)}K`;
|
|
59
|
-
return `$${value}`;
|
|
60
|
-
}
|
|
61
|
-
function CrmPipelineBoard({
|
|
62
|
-
dealsByStage,
|
|
63
|
-
stages,
|
|
64
|
-
onDealClick,
|
|
65
|
-
onDealMove
|
|
66
|
-
}) {
|
|
67
|
-
const [quickMoveOpen, setQuickMoveOpen] = useState(null);
|
|
68
|
-
const sortedStages = [...stages].sort((a, b) => a.position - b.position);
|
|
69
|
-
const handleQuickMove = (dealId, toStageId) => {
|
|
70
|
-
onDealMove?.(dealId, toStageId);
|
|
71
|
-
setQuickMoveOpen(null);
|
|
72
|
-
};
|
|
73
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
74
|
-
className: "flex gap-4 overflow-x-auto pb-4",
|
|
75
|
-
children: sortedStages.map((stage) => {
|
|
76
|
-
const deals = dealsByStage[stage.id] ?? [];
|
|
77
|
-
const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
|
|
78
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
79
|
-
className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
|
|
80
|
-
children: [
|
|
81
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
82
|
-
className: "flex items-center justify-between border-border border-b px-3 py-2",
|
|
83
|
-
children: [
|
|
84
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
85
|
-
children: [
|
|
86
|
-
/* @__PURE__ */ jsxDEV2("h3", {
|
|
87
|
-
className: "font-medium",
|
|
88
|
-
children: stage.name
|
|
89
|
-
}, undefined, false, undefined, this),
|
|
90
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
91
|
-
className: "text-muted-foreground text-xs",
|
|
92
|
-
children: [
|
|
93
|
-
deals.length,
|
|
94
|
-
" deals \xB7 ",
|
|
95
|
-
formatCurrency2(stageValue)
|
|
96
|
-
]
|
|
97
|
-
}, undefined, true, undefined, this)
|
|
98
|
-
]
|
|
99
|
-
}, undefined, true, undefined, this),
|
|
100
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
101
|
-
className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
|
|
102
|
-
children: deals.length
|
|
103
|
-
}, undefined, false, undefined, this)
|
|
104
|
-
]
|
|
105
|
-
}, undefined, true, undefined, this),
|
|
106
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
107
|
-
className: "flex flex-1 flex-col gap-2 p-2",
|
|
108
|
-
children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
|
|
109
|
-
className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
|
|
110
|
-
children: "No deals"
|
|
111
|
-
}, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
|
|
112
|
-
className: "group relative",
|
|
113
|
-
children: [
|
|
114
|
-
/* @__PURE__ */ jsxDEV2(CrmDealCard, {
|
|
115
|
-
deal,
|
|
116
|
-
onClick: () => onDealClick?.(deal.id)
|
|
117
|
-
}, undefined, false, undefined, this),
|
|
118
|
-
deal.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
|
|
119
|
-
className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
|
|
120
|
-
children: [
|
|
121
|
-
/* @__PURE__ */ jsxDEV2("button", {
|
|
122
|
-
type: "button",
|
|
123
|
-
onClick: (e) => {
|
|
124
|
-
e.stopPropagation();
|
|
125
|
-
setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
|
|
126
|
-
},
|
|
127
|
-
className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
|
|
128
|
-
title: "Quick move",
|
|
129
|
-
children: "\u27A1\uFE0F"
|
|
130
|
-
}, undefined, false, undefined, this),
|
|
131
|
-
quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
|
|
132
|
-
className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
|
|
133
|
-
children: [
|
|
134
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
135
|
-
className: "px-3 py-1 font-medium text-muted-foreground text-xs",
|
|
136
|
-
children: "Move to:"
|
|
137
|
-
}, undefined, false, undefined, this),
|
|
138
|
-
sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
|
|
139
|
-
type: "button",
|
|
140
|
-
onClick: (e) => {
|
|
141
|
-
e.stopPropagation();
|
|
142
|
-
handleQuickMove(deal.id, s.id);
|
|
143
|
-
},
|
|
144
|
-
className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
|
|
145
|
-
children: s.name
|
|
146
|
-
}, s.id, false, undefined, this))
|
|
147
|
-
]
|
|
148
|
-
}, undefined, true, undefined, this)
|
|
149
|
-
]
|
|
150
|
-
}, undefined, true, undefined, this)
|
|
151
|
-
]
|
|
152
|
-
}, deal.id, true, undefined, this))
|
|
153
|
-
}, undefined, false, undefined, this)
|
|
154
|
-
]
|
|
155
|
-
}, stage.id, true, undefined, this);
|
|
156
|
-
})
|
|
157
|
-
}, undefined, false, undefined, this);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// src/ui/hooks/useDealList.ts
|
|
161
|
-
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
162
|
-
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
163
|
-
"use client";
|
|
164
|
-
function useDealList(options = {}) {
|
|
165
|
-
const { handlers, projectId } = useTemplateRuntime();
|
|
166
|
-
const { crm } = handlers;
|
|
167
|
-
const [data, setData] = useState2(null);
|
|
168
|
-
const [dealsByStage, setDealsByStage] = useState2({});
|
|
169
|
-
const [stages, setStages] = useState2([]);
|
|
170
|
-
const [loading, setLoading] = useState2(true);
|
|
171
|
-
const [error, setError] = useState2(null);
|
|
172
|
-
const [internalPage, setInternalPage] = useState2(0);
|
|
173
|
-
const pipelineId = options.pipelineId ?? "pipeline-1";
|
|
174
|
-
const pageIndex = options.pageIndex ?? internalPage;
|
|
175
|
-
const pageSize = options.pageSize ?? options.limit ?? 50;
|
|
176
|
-
const [sort] = options.sorting ?? [];
|
|
177
|
-
const sortBy = sort?.id;
|
|
178
|
-
const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
|
|
179
|
-
const fetchData = useCallback(async () => {
|
|
180
|
-
setLoading(true);
|
|
181
|
-
setError(null);
|
|
182
|
-
try {
|
|
183
|
-
const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
|
|
184
|
-
crm.listDeals({
|
|
185
|
-
projectId,
|
|
186
|
-
pipelineId,
|
|
187
|
-
stageId: options.stageId,
|
|
188
|
-
status: options.status === "all" ? undefined : options.status,
|
|
189
|
-
search: options.search,
|
|
190
|
-
limit: pageSize,
|
|
191
|
-
offset: pageIndex * pageSize,
|
|
192
|
-
sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
|
|
193
|
-
sortDirection
|
|
194
|
-
}),
|
|
195
|
-
crm.getDealsByStage({ projectId, pipelineId }),
|
|
196
|
-
crm.getPipelineStages({ pipelineId })
|
|
197
|
-
]);
|
|
198
|
-
setData(dealsResult);
|
|
199
|
-
setDealsByStage(stageDealsResult);
|
|
200
|
-
setStages(stagesResult);
|
|
201
|
-
} catch (err) {
|
|
202
|
-
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
203
|
-
} finally {
|
|
204
|
-
setLoading(false);
|
|
205
|
-
}
|
|
206
|
-
}, [
|
|
207
|
-
crm,
|
|
208
|
-
projectId,
|
|
209
|
-
pipelineId,
|
|
210
|
-
options.stageId,
|
|
211
|
-
options.status,
|
|
212
|
-
options.search,
|
|
213
|
-
pageIndex,
|
|
214
|
-
pageSize,
|
|
215
|
-
sortBy,
|
|
216
|
-
sortDirection
|
|
217
|
-
]);
|
|
218
|
-
useEffect(() => {
|
|
219
|
-
fetchData();
|
|
220
|
-
}, [fetchData]);
|
|
221
|
-
const stats = useMemo(() => {
|
|
222
|
-
if (!data)
|
|
223
|
-
return null;
|
|
224
|
-
const open = data.deals.filter((d) => d.status === "OPEN");
|
|
225
|
-
const won = data.deals.filter((d) => d.status === "WON");
|
|
226
|
-
const lost = data.deals.filter((d) => d.status === "LOST");
|
|
227
|
-
return {
|
|
228
|
-
total: data.total,
|
|
229
|
-
totalValue: data.totalValue,
|
|
230
|
-
openCount: open.length,
|
|
231
|
-
openValue: open.reduce((sum, d) => sum + d.value, 0),
|
|
232
|
-
wonCount: won.length,
|
|
233
|
-
wonValue: won.reduce((sum, d) => sum + d.value, 0),
|
|
234
|
-
lostCount: lost.length
|
|
235
|
-
};
|
|
236
|
-
}, [data]);
|
|
237
|
-
return {
|
|
238
|
-
data,
|
|
239
|
-
dealsByStage,
|
|
240
|
-
stages,
|
|
241
|
-
loading,
|
|
242
|
-
error,
|
|
243
|
-
stats,
|
|
244
|
-
page: pageIndex + 1,
|
|
245
|
-
pageIndex,
|
|
246
|
-
pageSize,
|
|
247
|
-
refetch: fetchData,
|
|
248
|
-
nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
|
|
249
|
-
prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// src/ui/renderers/pipeline.renderer.tsx
|
|
254
|
-
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
255
|
-
function CrmPipelineBoardWrapper() {
|
|
256
|
-
const { dealsByStage, stages } = useDealList();
|
|
257
|
-
return /* @__PURE__ */ jsxDEV3(CrmPipelineBoard, {
|
|
258
|
-
dealsByStage,
|
|
259
|
-
stages
|
|
260
|
-
}, undefined, false, undefined, this);
|
|
261
|
-
}
|
|
262
|
-
var crmPipelineReactRenderer = {
|
|
263
|
-
target: "react",
|
|
264
|
-
render: async (desc, _ctx) => {
|
|
265
|
-
if (desc.source.type !== "component") {
|
|
266
|
-
throw new Error("Invalid source type");
|
|
267
|
-
}
|
|
268
|
-
if (desc.source.componentKey !== "CrmPipelineView") {
|
|
269
|
-
throw new Error(`Unknown component: ${desc.source.componentKey}`);
|
|
270
|
-
}
|
|
271
|
-
return /* @__PURE__ */ jsxDEV3(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
export {
|
|
275
|
-
crmPipelineReactRenderer
|
|
276
|
-
};
|
|
2
|
+
import{jsx as P,jsxs as I}from"react/jsx-runtime";function j(A,J){return new Intl.NumberFormat("en-US",{style:"currency",currency:J,minimumFractionDigits:0,maximumFractionDigits:0}).format(A)}function D({deal:A,onClick:J}){let G=A.expectedCloseDate?Math.ceil((A.expectedCloseDate.getTime()-Date.now())/86400000):null;return I("div",{onClick:J,className:"cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",role:"button",tabIndex:0,onKeyDown:(_)=>{if(_.key==="Enter"||_.key===" ")J?.()},children:[P("h4",{className:"font-medium leading-snug",children:A.name}),P("div",{className:"mt-2 font-semibold text-lg text-primary",children:j(A.value,A.currency)}),I("div",{className:"mt-3 flex items-center justify-between text-muted-foreground text-xs",children:[G!==null&&P("span",{className:G<0?"text-red-500":G<=7?"text-yellow-600 dark:text-yellow-500":"",children:G<0?`${Math.abs(G)}d overdue`:G===0?"Due today":`${G}d left`}),P("span",{className:`rounded px-1.5 py-0.5 font-medium text-xs ${A.status==="WON"?"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400":A.status==="LOST"?"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400":"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,children:A.status})]})]})}import{useState as x}from"react";import{jsx as W,jsxs as V}from"react/jsx-runtime";function g(A){if(A>=1e6)return`$${(A/1e6).toFixed(1)}M`;if(A>=1000)return`$${(A/1000).toFixed(0)}K`;return`$${A}`}function S({dealsByStage:A,stages:J,onDealClick:G,onDealMove:_}){let[K,z]=x(null),E=[...J].sort(($,X)=>$.position-X.position),b=($,X)=>{_?.($,X),z(null)};return W("div",{className:"flex gap-4 overflow-x-auto pb-4",children:E.map(($)=>{let X=A[$.id]??[],w=X.reduce((F,Y)=>F+Y.value,0);return V("div",{className:"flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",children:[V("div",{className:"flex items-center justify-between border-border border-b px-3 py-2",children:[V("div",{children:[W("h3",{className:"font-medium",children:$.name}),V("p",{className:"text-muted-foreground text-xs",children:[X.length," deals \xB7 ",g(w)]})]}),W("span",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",children:X.length})]}),W("div",{className:"flex flex-1 flex-col gap-2 p-2",children:X.length===0?W("div",{className:"flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",children:"No deals"}):X.map((F)=>V("div",{className:"group relative",children:[W(D,{deal:F,onClick:()=>G?.(F.id)}),F.status==="OPEN"&&_&&V("div",{className:"absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",children:[W("button",{type:"button",onClick:(Y)=>{Y.stopPropagation(),z(K===F.id?null:F.id)},className:"flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",title:"Quick move",children:"\u27A1\uFE0F"}),K===F.id&&V("div",{className:"absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",children:[W("p",{className:"px-3 py-1 font-medium text-muted-foreground text-xs",children:"Move to:"}),E.filter((Y)=>Y.id!==F.stageId).map((Y)=>W("button",{type:"button",onClick:(L)=>{L.stopPropagation(),b(F.id,Y.id)},className:"w-full px-3 py-1.5 text-left text-sm hover:bg-muted",children:Y.name},Y.id))]})]})]},F.id))})]},$.id)})})}import{useTemplateRuntime as c}from"@contractspec/lib.example-shared-ui";import{useCallback as u,useEffect as p,useMemo as r,useState as q}from"react";function v(A={}){let{handlers:J,projectId:G}=c(),{crm:_}=J,[K,z]=q(null),[E,b]=q({}),[$,X]=q([]),[w,F]=q(!0),[Y,L]=q(null),[C,B]=q(0),O=A.pipelineId??"pipeline-1",T=A.pageIndex??C,Q=A.pageSize??A.limit??50,[U]=A.sorting??[],N=U?.id,k=U?U.desc?"desc":"asc":void 0,f=u(async()=>{F(!0),L(null);try{let[Z,R,h]=await Promise.all([_.listDeals({projectId:G,pipelineId:O,stageId:A.stageId,status:A.status==="all"?void 0:A.status,search:A.search,limit:Q,offset:T*Q,sortBy:N==="name"||N==="value"||N==="status"||N==="expectedCloseDate"||N==="updatedAt"?N:void 0,sortDirection:k}),_.getDealsByStage({projectId:G,pipelineId:O}),_.getPipelineStages({pipelineId:O})]);z(Z),b(R),X(h)}catch(Z){L(Z instanceof Error?Z:Error("Unknown error"))}finally{F(!1)}},[_,G,O,A.stageId,A.status,A.search,T,Q,N,k]);p(()=>{f()},[f]);let m=r(()=>{if(!K)return null;let Z=K.deals.filter((H)=>H.status==="OPEN"),R=K.deals.filter((H)=>H.status==="WON"),h=K.deals.filter((H)=>H.status==="LOST");return{total:K.total,totalValue:K.totalValue,openCount:Z.length,openValue:Z.reduce((H,M)=>H+M.value,0),wonCount:R.length,wonValue:R.reduce((H,M)=>H+M.value,0),lostCount:h.length}},[K]);return{data:K,dealsByStage:E,stages:$,loading:w,error:Y,stats:m,page:T+1,pageIndex:T,pageSize:Q,refetch:f,nextPage:A.pageIndex===void 0?()=>B((Z)=>Z+1):void 0,prevPage:A.pageIndex===void 0?()=>T>0&&B((Z)=>Z-1):void 0}}import{jsx as y}from"react/jsx-runtime";function i(){let{dealsByStage:A,stages:J}=v();return y(S,{dealsByStage:A,stages:J})}var JA={target:"react",render:async(A,J)=>{if(A.source.type!=="component")throw Error("Invalid source type");if(A.source.componentKey!=="CrmPipelineView")throw Error(`Unknown component: ${A.source.componentKey}`);return y(i,{})}};export{JA as crmPipelineReactRenderer};
|
|
@@ -1,391 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
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
|
-
};
|
|
2
|
+
import{useTemplateRuntime as j}from"@contractspec/lib.example-shared-ui";import{useCallback as m,useEffect as u,useMemo as c,useState as $}from"react";function D(A={}){let{handlers:Q,projectId:W}=j(),{crm:K}=Q,[J,X]=$(null),[Z,M]=$({}),[w,_]=$([]),[q,C]=$(!0),[T,y]=$(null),[I,B]=$(0),V=A.pipelineId??"pipeline-1",E=A.pageIndex??I,H=A.pageSize??A.limit??50,[v]=A.sorting??[],Y=v?.id,k=v?v.desc?"desc":"asc":void 0,z=m(async()=>{C(!0),y(null);try{let[N,f,L]=await Promise.all([K.listDeals({projectId:W,pipelineId:V,stageId:A.stageId,status:A.status==="all"?void 0:A.status,search:A.search,limit:H,offset:E*H,sortBy:Y==="name"||Y==="value"||Y==="status"||Y==="expectedCloseDate"||Y==="updatedAt"?Y:void 0,sortDirection:k}),K.getDealsByStage({projectId:W,pipelineId:V}),K.getPipelineStages({pipelineId:V})]);X(N),M(f),_(L)}catch(N){y(N instanceof Error?N:Error("Unknown error"))}finally{C(!1)}},[K,W,V,A.stageId,A.status,A.search,E,H,Y,k]);u(()=>{z()},[z]);let S=c(()=>{if(!J)return null;let N=J.deals.filter((U)=>U.status==="OPEN"),f=J.deals.filter((U)=>U.status==="WON"),L=J.deals.filter((U)=>U.status==="LOST");return{total:J.total,totalValue:J.totalValue,openCount:N.length,openValue:N.reduce((U,b)=>U+b.value,0),wonCount:f.length,wonValue:f.reduce((U,b)=>U+b.value,0),lostCount:L.length}},[J]);return{data:J,dealsByStage:Z,stages:w,loading:q,error:T,stats:S,page:E+1,pageIndex:E,pageSize:H,refetch:z,nextPage:A.pageIndex===void 0?()=>B((N)=>N+1):void 0,prevPage:A.pageIndex===void 0?()=>E>0&&B((N)=>N-1):void 0}}import{Button as x,DataTable as g,LoaderBlock as p}from"@contractspec/lib.design-system";import{useContractTable as d}from"@contractspec/lib.presentation-runtime-react";import{Badge as r}from"@contractspec/lib.ui-kit-web/ui/badge";import{HStack as R,VStack as P}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as G}from"@contractspec/lib.ui-kit-web/ui/text";import*as h from"react";import{jsx as F,jsxs as O}from"react/jsx-runtime";function l(A,Q="USD"){return new Intl.NumberFormat("en-US",{style:"currency",currency:Q,minimumFractionDigits:0,maximumFractionDigits:0}).format(A)}function i(A){switch(A){case"WON":return"default";case"LOST":return"destructive";case"STALE":return"outline";default:return"secondary"}}function n({deals:A,totalItems:Q,pageIndex:W,pageSize:K,sorting:J,loading:X,onSortingChange:Z,onPaginationChange:M,onDealClick:w}){let _=d({data:A,columns:[{id:"deal",header:"Deal",label:"Deal",accessor:(q)=>q.name,cell:({item:q})=>O(P,{gap:"xs",children:[F(G,{className:"font-medium text-sm",children:q.name}),F(G,{className:"text-muted-foreground text-xs",children:q.companyId??"Unassigned company"})]}),size:240,minSize:180,canSort:!0,canPin:!0,canResize:!0},{id:"value",header:"Value",label:"Value",accessorKey:"value",cell:({item:q})=>l(q.value,q.currency),align:"right",size:140,canSort:!0,canResize:!0},{id:"status",header:"Status",label:"Status",accessorKey:"status",cell:({value:q})=>F(r,{variant:i(q),children:String(q)}),size:130,canSort:!0,canHide:!0,canPin:!0,canResize:!0},{id:"expectedCloseDate",header:"Expected Close",label:"Expected Close",accessor:(q)=>q.expectedCloseDate?.toISOString()??"",cell:({item:q})=>q.expectedCloseDate?.toLocaleDateString()??"Not scheduled",size:170,canSort:!0,canHide:!0,canResize:!0},{id:"updatedAt",header:"Updated",label:"Updated",accessor:(q)=>q.updatedAt.toISOString(),cell:({item:q})=>q.updatedAt.toLocaleDateString(),size:140,canSort:!0,canHide:!0,canResize:!0},{id:"actions",header:"Actions",label:"Actions",accessor:(q)=>q.id,cell:({item:q})=>F(x,{variant:"ghost",size:"sm",onPress:()=>w?.(q.id),children:"Actions"}),size:120,canSort:!1,canHide:!1,canPin:!1,canResize:!1}],executionMode:"server",selectionMode:"multiple",totalItems:Q,state:{sorting:J,pagination:{pageIndex:W,pageSize:K}},onSortingChange:Z,onPaginationChange:M,initialState:{columnVisibility:{updatedAt:!1},columnPinning:{left:["deal","status"],right:[]}},renderExpandedContent:(q)=>O(P,{gap:"sm",className:"py-2",children:[O(R,{justify:"between",children:[F(G,{className:"font-medium text-sm",children:"Owner"}),F(G,{className:"text-muted-foreground text-sm",children:q.ownerId})]}),O(R,{justify:"between",children:[F(G,{className:"font-medium text-sm",children:"Contact"}),F(G,{className:"text-muted-foreground text-sm",children:q.contactId??"No linked contact"})]}),q.wonSource?O(R,{justify:"between",children:[F(G,{className:"font-medium text-sm",children:"Won Source"}),F(G,{className:"text-muted-foreground text-sm",children:q.wonSource})]}):null,q.lostReason?O(R,{justify:"between",children:[F(G,{className:"font-medium text-sm",children:"Lost Reason"}),F(G,{className:"text-muted-foreground text-sm",children:q.lostReason})]}):null,q.notes?O(P,{gap:"xs",children:[F(G,{className:"font-medium text-sm",children:"Notes"}),F(G,{className:"text-muted-foreground text-sm",children:q.notes})]}):null]}),getCanExpand:()=>!0});return F(g,{controller:_,title:"All Deals",description:"Server-mode table using the shared ContractSpec controller.",loading:X,toolbar:O(R,{gap:"sm",className:"flex-wrap",children:[O(G,{className:"text-muted-foreground text-sm",children:["Selected ",_.selectedRowIds.length]}),O(G,{className:"text-muted-foreground text-sm",children:[Q," total deals"]})]}),footer:`Page ${_.pageIndex+1} of ${_.pageCount}`,emptyState:F("div",{className:"rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",children:"No deals found"})})}function Jq({onDealClick:A}){let[Q,W]=h.useState([{id:"value",desc:!0}]),[K,J]=h.useState({pageIndex:0,pageSize:3}),{data:X,loading:Z}=D({pageIndex:K.pageIndex,pageSize:K.pageSize,sorting:Q});if(Z&&!X)return F(p,{label:"Loading deals..."});return F(n,{deals:X?.deals??[],totalItems:X?.total??0,pageIndex:K.pageIndex,pageSize:K.pageSize,sorting:Q,loading:Z,onSortingChange:(M)=>{W(M),J((w)=>({...w,pageIndex:0}))},onPaginationChange:J,onDealClick:A})}export{Jq as DealListTab,n as DealListDataTable};
|