@contractspec/example.crm-pipeline 3.7.16 → 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 +40 -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 +16 -16
|
@@ -1,1547 +1 @@
|
|
|
1
|
-
// src/ui/CrmDealCard.tsx
|
|
2
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
3
|
-
"use client";
|
|
4
|
-
function formatCurrency(value, currency) {
|
|
5
|
-
return new Intl.NumberFormat("en-US", {
|
|
6
|
-
style: "currency",
|
|
7
|
-
currency,
|
|
8
|
-
minimumFractionDigits: 0,
|
|
9
|
-
maximumFractionDigits: 0
|
|
10
|
-
}).format(value);
|
|
11
|
-
}
|
|
12
|
-
function CrmDealCard({ deal, onClick }) {
|
|
13
|
-
const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
|
|
14
|
-
return /* @__PURE__ */ jsxDEV("div", {
|
|
15
|
-
onClick,
|
|
16
|
-
className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
|
|
17
|
-
role: "button",
|
|
18
|
-
tabIndex: 0,
|
|
19
|
-
onKeyDown: (e) => {
|
|
20
|
-
if (e.key === "Enter" || e.key === " ")
|
|
21
|
-
onClick?.();
|
|
22
|
-
},
|
|
23
|
-
children: [
|
|
24
|
-
/* @__PURE__ */ jsxDEV("h4", {
|
|
25
|
-
className: "font-medium leading-snug",
|
|
26
|
-
children: deal.name
|
|
27
|
-
}, undefined, false, undefined, this),
|
|
28
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
29
|
-
className: "mt-2 font-semibold text-lg text-primary",
|
|
30
|
-
children: formatCurrency(deal.value, deal.currency)
|
|
31
|
-
}, undefined, false, undefined, this),
|
|
32
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
33
|
-
className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
|
|
34
|
-
children: [
|
|
35
|
-
daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
|
|
36
|
-
className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
|
|
37
|
-
children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
|
|
38
|
-
}, undefined, false, undefined, this),
|
|
39
|
-
/* @__PURE__ */ jsxDEV("span", {
|
|
40
|
-
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"}`,
|
|
41
|
-
children: deal.status
|
|
42
|
-
}, undefined, false, undefined, this)
|
|
43
|
-
]
|
|
44
|
-
}, undefined, true, undefined, this)
|
|
45
|
-
]
|
|
46
|
-
}, undefined, true, undefined, this);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// src/ui/CrmPipelineBoard.tsx
|
|
50
|
-
import { useState } from "react";
|
|
51
|
-
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
52
|
-
"use client";
|
|
53
|
-
function formatCurrency2(value) {
|
|
54
|
-
if (value >= 1e6)
|
|
55
|
-
return `$${(value / 1e6).toFixed(1)}M`;
|
|
56
|
-
if (value >= 1000)
|
|
57
|
-
return `$${(value / 1000).toFixed(0)}K`;
|
|
58
|
-
return `$${value}`;
|
|
59
|
-
}
|
|
60
|
-
function CrmPipelineBoard({
|
|
61
|
-
dealsByStage,
|
|
62
|
-
stages,
|
|
63
|
-
onDealClick,
|
|
64
|
-
onDealMove
|
|
65
|
-
}) {
|
|
66
|
-
const [quickMoveOpen, setQuickMoveOpen] = useState(null);
|
|
67
|
-
const sortedStages = [...stages].sort((a, b) => a.position - b.position);
|
|
68
|
-
const handleQuickMove = (dealId, toStageId) => {
|
|
69
|
-
onDealMove?.(dealId, toStageId);
|
|
70
|
-
setQuickMoveOpen(null);
|
|
71
|
-
};
|
|
72
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
73
|
-
className: "flex gap-4 overflow-x-auto pb-4",
|
|
74
|
-
children: sortedStages.map((stage) => {
|
|
75
|
-
const deals = dealsByStage[stage.id] ?? [];
|
|
76
|
-
const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
|
|
77
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
78
|
-
className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
|
|
79
|
-
children: [
|
|
80
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
81
|
-
className: "flex items-center justify-between border-border border-b px-3 py-2",
|
|
82
|
-
children: [
|
|
83
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
84
|
-
children: [
|
|
85
|
-
/* @__PURE__ */ jsxDEV2("h3", {
|
|
86
|
-
className: "font-medium",
|
|
87
|
-
children: stage.name
|
|
88
|
-
}, undefined, false, undefined, this),
|
|
89
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
90
|
-
className: "text-muted-foreground text-xs",
|
|
91
|
-
children: [
|
|
92
|
-
deals.length,
|
|
93
|
-
" deals · ",
|
|
94
|
-
formatCurrency2(stageValue)
|
|
95
|
-
]
|
|
96
|
-
}, undefined, true, undefined, this)
|
|
97
|
-
]
|
|
98
|
-
}, undefined, true, undefined, this),
|
|
99
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
100
|
-
className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
|
|
101
|
-
children: deals.length
|
|
102
|
-
}, undefined, false, undefined, this)
|
|
103
|
-
]
|
|
104
|
-
}, undefined, true, undefined, this),
|
|
105
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
106
|
-
className: "flex flex-1 flex-col gap-2 p-2",
|
|
107
|
-
children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
|
|
108
|
-
className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
|
|
109
|
-
children: "No deals"
|
|
110
|
-
}, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
|
|
111
|
-
className: "group relative",
|
|
112
|
-
children: [
|
|
113
|
-
/* @__PURE__ */ jsxDEV2(CrmDealCard, {
|
|
114
|
-
deal,
|
|
115
|
-
onClick: () => onDealClick?.(deal.id)
|
|
116
|
-
}, undefined, false, undefined, this),
|
|
117
|
-
deal.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
|
|
118
|
-
className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
|
|
119
|
-
children: [
|
|
120
|
-
/* @__PURE__ */ jsxDEV2("button", {
|
|
121
|
-
type: "button",
|
|
122
|
-
onClick: (e) => {
|
|
123
|
-
e.stopPropagation();
|
|
124
|
-
setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
|
|
125
|
-
},
|
|
126
|
-
className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
|
|
127
|
-
title: "Quick move",
|
|
128
|
-
children: "➡️"
|
|
129
|
-
}, undefined, false, undefined, this),
|
|
130
|
-
quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
|
|
131
|
-
className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
|
|
132
|
-
children: [
|
|
133
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
134
|
-
className: "px-3 py-1 font-medium text-muted-foreground text-xs",
|
|
135
|
-
children: "Move to:"
|
|
136
|
-
}, undefined, false, undefined, this),
|
|
137
|
-
sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
|
|
138
|
-
type: "button",
|
|
139
|
-
onClick: (e) => {
|
|
140
|
-
e.stopPropagation();
|
|
141
|
-
handleQuickMove(deal.id, s.id);
|
|
142
|
-
},
|
|
143
|
-
className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
|
|
144
|
-
children: s.name
|
|
145
|
-
}, s.id, false, undefined, this))
|
|
146
|
-
]
|
|
147
|
-
}, undefined, true, undefined, this)
|
|
148
|
-
]
|
|
149
|
-
}, undefined, true, undefined, this)
|
|
150
|
-
]
|
|
151
|
-
}, deal.id, true, undefined, this))
|
|
152
|
-
}, undefined, false, undefined, this)
|
|
153
|
-
]
|
|
154
|
-
}, stage.id, true, undefined, this);
|
|
155
|
-
})
|
|
156
|
-
}, undefined, false, undefined, this);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// src/ui/hooks/useDealList.ts
|
|
160
|
-
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
161
|
-
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
162
|
-
"use client";
|
|
163
|
-
function useDealList(options = {}) {
|
|
164
|
-
const { handlers, projectId } = useTemplateRuntime();
|
|
165
|
-
const { crm } = handlers;
|
|
166
|
-
const [data, setData] = useState2(null);
|
|
167
|
-
const [dealsByStage, setDealsByStage] = useState2({});
|
|
168
|
-
const [stages, setStages] = useState2([]);
|
|
169
|
-
const [loading, setLoading] = useState2(true);
|
|
170
|
-
const [error, setError] = useState2(null);
|
|
171
|
-
const [internalPage, setInternalPage] = useState2(0);
|
|
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;
|
|
178
|
-
const fetchData = useCallback(async () => {
|
|
179
|
-
setLoading(true);
|
|
180
|
-
setError(null);
|
|
181
|
-
try {
|
|
182
|
-
const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
|
|
183
|
-
crm.listDeals({
|
|
184
|
-
projectId,
|
|
185
|
-
pipelineId,
|
|
186
|
-
stageId: options.stageId,
|
|
187
|
-
status: options.status === "all" ? undefined : options.status,
|
|
188
|
-
search: options.search,
|
|
189
|
-
limit: pageSize,
|
|
190
|
-
offset: pageIndex * pageSize,
|
|
191
|
-
sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
|
|
192
|
-
sortDirection
|
|
193
|
-
}),
|
|
194
|
-
crm.getDealsByStage({ projectId, pipelineId }),
|
|
195
|
-
crm.getPipelineStages({ pipelineId })
|
|
196
|
-
]);
|
|
197
|
-
setData(dealsResult);
|
|
198
|
-
setDealsByStage(stageDealsResult);
|
|
199
|
-
setStages(stagesResult);
|
|
200
|
-
} catch (err) {
|
|
201
|
-
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
202
|
-
} finally {
|
|
203
|
-
setLoading(false);
|
|
204
|
-
}
|
|
205
|
-
}, [
|
|
206
|
-
crm,
|
|
207
|
-
projectId,
|
|
208
|
-
pipelineId,
|
|
209
|
-
options.stageId,
|
|
210
|
-
options.status,
|
|
211
|
-
options.search,
|
|
212
|
-
pageIndex,
|
|
213
|
-
pageSize,
|
|
214
|
-
sortBy,
|
|
215
|
-
sortDirection
|
|
216
|
-
]);
|
|
217
|
-
useEffect(() => {
|
|
218
|
-
fetchData();
|
|
219
|
-
}, [fetchData]);
|
|
220
|
-
const stats = useMemo(() => {
|
|
221
|
-
if (!data)
|
|
222
|
-
return null;
|
|
223
|
-
const open = data.deals.filter((d) => d.status === "OPEN");
|
|
224
|
-
const won = data.deals.filter((d) => d.status === "WON");
|
|
225
|
-
const lost = data.deals.filter((d) => d.status === "LOST");
|
|
226
|
-
return {
|
|
227
|
-
total: data.total,
|
|
228
|
-
totalValue: data.totalValue,
|
|
229
|
-
openCount: open.length,
|
|
230
|
-
openValue: open.reduce((sum, d) => sum + d.value, 0),
|
|
231
|
-
wonCount: won.length,
|
|
232
|
-
wonValue: won.reduce((sum, d) => sum + d.value, 0),
|
|
233
|
-
lostCount: lost.length
|
|
234
|
-
};
|
|
235
|
-
}, [data]);
|
|
236
|
-
return {
|
|
237
|
-
data,
|
|
238
|
-
dealsByStage,
|
|
239
|
-
stages,
|
|
240
|
-
loading,
|
|
241
|
-
error,
|
|
242
|
-
stats,
|
|
243
|
-
page: pageIndex + 1,
|
|
244
|
-
pageIndex,
|
|
245
|
-
pageSize,
|
|
246
|
-
refetch: fetchData,
|
|
247
|
-
nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
|
|
248
|
-
prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// src/ui/hooks/useDealMutations.ts
|
|
253
|
-
import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
|
|
254
|
-
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
255
|
-
function useDealMutations(options = {}) {
|
|
256
|
-
const { handlers, projectId } = useTemplateRuntime2();
|
|
257
|
-
const { crm } = handlers;
|
|
258
|
-
const [createState, setCreateState] = useState3({
|
|
259
|
-
loading: false,
|
|
260
|
-
error: null,
|
|
261
|
-
data: null
|
|
262
|
-
});
|
|
263
|
-
const [moveState, setMoveState] = useState3({
|
|
264
|
-
loading: false,
|
|
265
|
-
error: null,
|
|
266
|
-
data: null
|
|
267
|
-
});
|
|
268
|
-
const [winState, setWinState] = useState3({
|
|
269
|
-
loading: false,
|
|
270
|
-
error: null,
|
|
271
|
-
data: null
|
|
272
|
-
});
|
|
273
|
-
const [loseState, setLoseState] = useState3({
|
|
274
|
-
loading: false,
|
|
275
|
-
error: null,
|
|
276
|
-
data: null
|
|
277
|
-
});
|
|
278
|
-
const createDeal = useCallback2(async (input) => {
|
|
279
|
-
setCreateState({ loading: true, error: null, data: null });
|
|
280
|
-
try {
|
|
281
|
-
const result = await crm.createDeal(input, {
|
|
282
|
-
projectId,
|
|
283
|
-
ownerId: "user-1"
|
|
284
|
-
});
|
|
285
|
-
setCreateState({ loading: false, error: null, data: result });
|
|
286
|
-
options.onSuccess?.();
|
|
287
|
-
return result;
|
|
288
|
-
} catch (err) {
|
|
289
|
-
const error = err instanceof Error ? err : new Error("Failed to create deal");
|
|
290
|
-
setCreateState({ loading: false, error, data: null });
|
|
291
|
-
options.onError?.(error);
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
}, [crm, projectId, options]);
|
|
295
|
-
const moveDeal = useCallback2(async (input) => {
|
|
296
|
-
setMoveState({ loading: true, error: null, data: null });
|
|
297
|
-
try {
|
|
298
|
-
const result = await crm.moveDeal(input);
|
|
299
|
-
setMoveState({ loading: false, error: null, data: result });
|
|
300
|
-
options.onSuccess?.();
|
|
301
|
-
return result;
|
|
302
|
-
} catch (err) {
|
|
303
|
-
const error = err instanceof Error ? err : new Error("Failed to move deal");
|
|
304
|
-
setMoveState({ loading: false, error, data: null });
|
|
305
|
-
options.onError?.(error);
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
}, [crm, options]);
|
|
309
|
-
const winDeal = useCallback2(async (input) => {
|
|
310
|
-
setWinState({ loading: true, error: null, data: null });
|
|
311
|
-
try {
|
|
312
|
-
const result = await crm.winDeal(input);
|
|
313
|
-
setWinState({ loading: false, error: null, data: result });
|
|
314
|
-
options.onSuccess?.();
|
|
315
|
-
return result;
|
|
316
|
-
} catch (err) {
|
|
317
|
-
const error = err instanceof Error ? err : new Error("Failed to mark deal as won");
|
|
318
|
-
setWinState({ loading: false, error, data: null });
|
|
319
|
-
options.onError?.(error);
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
}, [crm, options]);
|
|
323
|
-
const loseDeal = useCallback2(async (input) => {
|
|
324
|
-
setLoseState({ loading: true, error: null, data: null });
|
|
325
|
-
try {
|
|
326
|
-
const result = await crm.loseDeal(input);
|
|
327
|
-
setLoseState({ loading: false, error: null, data: result });
|
|
328
|
-
options.onSuccess?.();
|
|
329
|
-
return result;
|
|
330
|
-
} catch (err) {
|
|
331
|
-
const error = err instanceof Error ? err : new Error("Failed to mark deal as lost");
|
|
332
|
-
setLoseState({ loading: false, error, data: null });
|
|
333
|
-
options.onError?.(error);
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
}, [crm, options]);
|
|
337
|
-
return {
|
|
338
|
-
createDeal,
|
|
339
|
-
moveDeal,
|
|
340
|
-
winDeal,
|
|
341
|
-
loseDeal,
|
|
342
|
-
createState,
|
|
343
|
-
moveState,
|
|
344
|
-
winState,
|
|
345
|
-
loseState,
|
|
346
|
-
isLoading: createState.loading || moveState.loading || winState.loading || loseState.loading
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// src/ui/modals/CreateDealModal.tsx
|
|
351
|
-
import { Button, Input } from "@contractspec/lib.design-system";
|
|
352
|
-
import { useState as useState4 } from "react";
|
|
353
|
-
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
354
|
-
"use client";
|
|
355
|
-
var CURRENCIES = ["USD", "EUR", "GBP", "CAD"];
|
|
356
|
-
var DEFAULT_PIPELINE_ID = "pipeline-1";
|
|
357
|
-
function CreateDealModal({
|
|
358
|
-
isOpen,
|
|
359
|
-
onClose,
|
|
360
|
-
onSubmit,
|
|
361
|
-
stages,
|
|
362
|
-
isLoading = false
|
|
363
|
-
}) {
|
|
364
|
-
const [name, setName] = useState4("");
|
|
365
|
-
const [value, setValue] = useState4("");
|
|
366
|
-
const [currency, setCurrency] = useState4("USD");
|
|
367
|
-
const [stageId, setStageId] = useState4(stages[0]?.id ?? "");
|
|
368
|
-
const [expectedCloseDate, setExpectedCloseDate] = useState4("");
|
|
369
|
-
const [error, setError] = useState4(null);
|
|
370
|
-
const handleSubmit = async (e) => {
|
|
371
|
-
e.preventDefault();
|
|
372
|
-
setError(null);
|
|
373
|
-
if (!name.trim()) {
|
|
374
|
-
setError("Deal name is required");
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
const numericValue = parseFloat(value);
|
|
378
|
-
if (isNaN(numericValue) || numericValue <= 0) {
|
|
379
|
-
setError("Value must be a positive number");
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
if (!stageId) {
|
|
383
|
-
setError("Please select a pipeline stage");
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
try {
|
|
387
|
-
await onSubmit({
|
|
388
|
-
name: name.trim(),
|
|
389
|
-
value: numericValue,
|
|
390
|
-
currency,
|
|
391
|
-
pipelineId: DEFAULT_PIPELINE_ID,
|
|
392
|
-
stageId,
|
|
393
|
-
expectedCloseDate: expectedCloseDate ? new Date(expectedCloseDate) : undefined
|
|
394
|
-
});
|
|
395
|
-
setName("");
|
|
396
|
-
setValue("");
|
|
397
|
-
setCurrency("USD");
|
|
398
|
-
setStageId(stages[0]?.id ?? "");
|
|
399
|
-
setExpectedCloseDate("");
|
|
400
|
-
onClose();
|
|
401
|
-
} catch (err) {
|
|
402
|
-
setError(err instanceof Error ? err.message : "Failed to create deal");
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
if (!isOpen)
|
|
406
|
-
return null;
|
|
407
|
-
return /* @__PURE__ */ jsxDEV3("div", {
|
|
408
|
-
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
409
|
-
children: [
|
|
410
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
411
|
-
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
412
|
-
onClick: onClose,
|
|
413
|
-
role: "button",
|
|
414
|
-
tabIndex: 0,
|
|
415
|
-
onKeyDown: (e) => {
|
|
416
|
-
if (e.key === "Enter" || e.key === " ")
|
|
417
|
-
onClose();
|
|
418
|
-
},
|
|
419
|
-
"aria-label": "Close modal"
|
|
420
|
-
}, undefined, false, undefined, this),
|
|
421
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
422
|
-
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
423
|
-
children: [
|
|
424
|
-
/* @__PURE__ */ jsxDEV3("h2", {
|
|
425
|
-
className: "mb-4 font-semibold text-xl",
|
|
426
|
-
children: "Create New Deal"
|
|
427
|
-
}, undefined, false, undefined, this),
|
|
428
|
-
/* @__PURE__ */ jsxDEV3("form", {
|
|
429
|
-
onSubmit: handleSubmit,
|
|
430
|
-
className: "space-y-4",
|
|
431
|
-
children: [
|
|
432
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
433
|
-
children: [
|
|
434
|
-
/* @__PURE__ */ jsxDEV3("label", {
|
|
435
|
-
htmlFor: "deal-name",
|
|
436
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
437
|
-
children: "Deal Name *"
|
|
438
|
-
}, undefined, false, undefined, this),
|
|
439
|
-
/* @__PURE__ */ jsxDEV3(Input, {
|
|
440
|
-
id: "deal-name",
|
|
441
|
-
value: name,
|
|
442
|
-
onChange: (e) => setName(e.target.value),
|
|
443
|
-
placeholder: "e.g., Enterprise License - Acme Corp",
|
|
444
|
-
disabled: isLoading
|
|
445
|
-
}, undefined, false, undefined, this)
|
|
446
|
-
]
|
|
447
|
-
}, undefined, true, undefined, this),
|
|
448
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
449
|
-
className: "flex gap-3",
|
|
450
|
-
children: [
|
|
451
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
452
|
-
className: "flex-1",
|
|
453
|
-
children: [
|
|
454
|
-
/* @__PURE__ */ jsxDEV3("label", {
|
|
455
|
-
htmlFor: "deal-value",
|
|
456
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
457
|
-
children: "Value *"
|
|
458
|
-
}, undefined, false, undefined, this),
|
|
459
|
-
/* @__PURE__ */ jsxDEV3(Input, {
|
|
460
|
-
id: "deal-value",
|
|
461
|
-
type: "number",
|
|
462
|
-
min: "0",
|
|
463
|
-
step: "0.01",
|
|
464
|
-
value,
|
|
465
|
-
onChange: (e) => setValue(e.target.value),
|
|
466
|
-
placeholder: "50000",
|
|
467
|
-
disabled: isLoading
|
|
468
|
-
}, undefined, false, undefined, this)
|
|
469
|
-
]
|
|
470
|
-
}, undefined, true, undefined, this),
|
|
471
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
472
|
-
className: "w-24",
|
|
473
|
-
children: [
|
|
474
|
-
/* @__PURE__ */ jsxDEV3("label", {
|
|
475
|
-
htmlFor: "deal-currency",
|
|
476
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
477
|
-
children: "Currency"
|
|
478
|
-
}, undefined, false, undefined, this),
|
|
479
|
-
/* @__PURE__ */ jsxDEV3("select", {
|
|
480
|
-
id: "deal-currency",
|
|
481
|
-
value: currency,
|
|
482
|
-
onChange: (e) => setCurrency(e.target.value),
|
|
483
|
-
disabled: isLoading,
|
|
484
|
-
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
|
|
485
|
-
children: CURRENCIES.map((c) => /* @__PURE__ */ jsxDEV3("option", {
|
|
486
|
-
value: c,
|
|
487
|
-
children: c
|
|
488
|
-
}, c, false, undefined, this))
|
|
489
|
-
}, undefined, false, undefined, this)
|
|
490
|
-
]
|
|
491
|
-
}, undefined, true, undefined, this)
|
|
492
|
-
]
|
|
493
|
-
}, undefined, true, undefined, this),
|
|
494
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
495
|
-
children: [
|
|
496
|
-
/* @__PURE__ */ jsxDEV3("label", {
|
|
497
|
-
htmlFor: "deal-stage",
|
|
498
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
499
|
-
children: "Pipeline Stage *"
|
|
500
|
-
}, undefined, false, undefined, this),
|
|
501
|
-
/* @__PURE__ */ jsxDEV3("select", {
|
|
502
|
-
id: "deal-stage",
|
|
503
|
-
value: stageId,
|
|
504
|
-
onChange: (e) => setStageId(e.target.value),
|
|
505
|
-
disabled: isLoading,
|
|
506
|
-
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
|
|
507
|
-
children: stages.map((stage) => /* @__PURE__ */ jsxDEV3("option", {
|
|
508
|
-
value: stage.id,
|
|
509
|
-
children: stage.name
|
|
510
|
-
}, stage.id, false, undefined, this))
|
|
511
|
-
}, undefined, false, undefined, this)
|
|
512
|
-
]
|
|
513
|
-
}, undefined, true, undefined, this),
|
|
514
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
515
|
-
children: [
|
|
516
|
-
/* @__PURE__ */ jsxDEV3("label", {
|
|
517
|
-
htmlFor: "deal-close-date",
|
|
518
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
519
|
-
children: "Expected Close Date"
|
|
520
|
-
}, undefined, false, undefined, this),
|
|
521
|
-
/* @__PURE__ */ jsxDEV3(Input, {
|
|
522
|
-
id: "deal-close-date",
|
|
523
|
-
type: "date",
|
|
524
|
-
value: expectedCloseDate,
|
|
525
|
-
onChange: (e) => setExpectedCloseDate(e.target.value),
|
|
526
|
-
disabled: isLoading
|
|
527
|
-
}, undefined, false, undefined, this)
|
|
528
|
-
]
|
|
529
|
-
}, undefined, true, undefined, this),
|
|
530
|
-
error && /* @__PURE__ */ jsxDEV3("div", {
|
|
531
|
-
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
532
|
-
children: error
|
|
533
|
-
}, undefined, false, undefined, this),
|
|
534
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
535
|
-
className: "flex justify-end gap-3 pt-2",
|
|
536
|
-
children: [
|
|
537
|
-
/* @__PURE__ */ jsxDEV3(Button, {
|
|
538
|
-
type: "button",
|
|
539
|
-
variant: "ghost",
|
|
540
|
-
onPress: onClose,
|
|
541
|
-
disabled: isLoading,
|
|
542
|
-
children: "Cancel"
|
|
543
|
-
}, undefined, false, undefined, this),
|
|
544
|
-
/* @__PURE__ */ jsxDEV3(Button, {
|
|
545
|
-
type: "submit",
|
|
546
|
-
disabled: isLoading,
|
|
547
|
-
children: isLoading ? "Creating..." : "Create Deal"
|
|
548
|
-
}, undefined, false, undefined, this)
|
|
549
|
-
]
|
|
550
|
-
}, undefined, true, undefined, this)
|
|
551
|
-
]
|
|
552
|
-
}, undefined, true, undefined, this)
|
|
553
|
-
]
|
|
554
|
-
}, undefined, true, undefined, this)
|
|
555
|
-
]
|
|
556
|
-
}, undefined, true, undefined, this);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// src/ui/modals/DealActionsModal.tsx
|
|
560
|
-
import { Button as Button2 } from "@contractspec/lib.design-system";
|
|
561
|
-
import { useState as useState5 } from "react";
|
|
562
|
-
import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
|
|
563
|
-
"use client";
|
|
564
|
-
function formatCurrency3(value, currency) {
|
|
565
|
-
return new Intl.NumberFormat("en-US", {
|
|
566
|
-
style: "currency",
|
|
567
|
-
currency,
|
|
568
|
-
minimumFractionDigits: 0,
|
|
569
|
-
maximumFractionDigits: 0
|
|
570
|
-
}).format(value);
|
|
571
|
-
}
|
|
572
|
-
function DealActionsModal({
|
|
573
|
-
isOpen,
|
|
574
|
-
deal,
|
|
575
|
-
stages,
|
|
576
|
-
onClose,
|
|
577
|
-
onWin,
|
|
578
|
-
onLose,
|
|
579
|
-
onMove,
|
|
580
|
-
isLoading = false
|
|
581
|
-
}) {
|
|
582
|
-
const [mode, setMode] = useState5("menu");
|
|
583
|
-
const [wonSource, setWonSource] = useState5("");
|
|
584
|
-
const [lostReason, setLostReason] = useState5("");
|
|
585
|
-
const [notes, setNotes] = useState5("");
|
|
586
|
-
const [selectedStageId, setSelectedStageId] = useState5("");
|
|
587
|
-
const [error, setError] = useState5(null);
|
|
588
|
-
const resetForm = () => {
|
|
589
|
-
setMode("menu");
|
|
590
|
-
setWonSource("");
|
|
591
|
-
setLostReason("");
|
|
592
|
-
setNotes("");
|
|
593
|
-
setSelectedStageId("");
|
|
594
|
-
setError(null);
|
|
595
|
-
};
|
|
596
|
-
const handleClose = () => {
|
|
597
|
-
resetForm();
|
|
598
|
-
onClose();
|
|
599
|
-
};
|
|
600
|
-
const handleWin = async () => {
|
|
601
|
-
if (!deal)
|
|
602
|
-
return;
|
|
603
|
-
setError(null);
|
|
604
|
-
try {
|
|
605
|
-
await onWin({
|
|
606
|
-
dealId: deal.id,
|
|
607
|
-
wonSource: wonSource.trim() || undefined,
|
|
608
|
-
notes: notes.trim() || undefined
|
|
609
|
-
});
|
|
610
|
-
handleClose();
|
|
611
|
-
} catch (err) {
|
|
612
|
-
setError(err instanceof Error ? err.message : "Failed to mark deal as won");
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
const handleLose = async () => {
|
|
616
|
-
if (!deal)
|
|
617
|
-
return;
|
|
618
|
-
setError(null);
|
|
619
|
-
if (!lostReason.trim()) {
|
|
620
|
-
setError("Please provide a reason for losing the deal");
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
try {
|
|
624
|
-
await onLose({
|
|
625
|
-
dealId: deal.id,
|
|
626
|
-
lostReason: lostReason.trim(),
|
|
627
|
-
notes: notes.trim() || undefined
|
|
628
|
-
});
|
|
629
|
-
handleClose();
|
|
630
|
-
} catch (err) {
|
|
631
|
-
setError(err instanceof Error ? err.message : "Failed to mark deal as lost");
|
|
632
|
-
}
|
|
633
|
-
};
|
|
634
|
-
const handleMove = async () => {
|
|
635
|
-
if (!deal)
|
|
636
|
-
return;
|
|
637
|
-
setError(null);
|
|
638
|
-
if (!selectedStageId) {
|
|
639
|
-
setError("Please select a stage");
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
if (selectedStageId === deal.stageId) {
|
|
643
|
-
setError("Deal is already in this stage");
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
try {
|
|
647
|
-
await onMove({
|
|
648
|
-
dealId: deal.id,
|
|
649
|
-
stageId: selectedStageId
|
|
650
|
-
});
|
|
651
|
-
handleClose();
|
|
652
|
-
} catch (err) {
|
|
653
|
-
setError(err instanceof Error ? err.message : "Failed to move deal");
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
if (!isOpen || !deal)
|
|
657
|
-
return null;
|
|
658
|
-
return /* @__PURE__ */ jsxDEV4("div", {
|
|
659
|
-
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
660
|
-
children: [
|
|
661
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
662
|
-
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
663
|
-
onClick: handleClose,
|
|
664
|
-
role: "button",
|
|
665
|
-
tabIndex: 0,
|
|
666
|
-
onKeyDown: (e) => {
|
|
667
|
-
if (e.key === "Enter" || e.key === " ")
|
|
668
|
-
handleClose();
|
|
669
|
-
},
|
|
670
|
-
"aria-label": "Close modal"
|
|
671
|
-
}, undefined, false, undefined, this),
|
|
672
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
673
|
-
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
674
|
-
children: [
|
|
675
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
676
|
-
className: "mb-4 border-border border-b pb-4",
|
|
677
|
-
children: [
|
|
678
|
-
/* @__PURE__ */ jsxDEV4("h2", {
|
|
679
|
-
className: "font-semibold text-xl",
|
|
680
|
-
children: deal.name
|
|
681
|
-
}, undefined, false, undefined, this),
|
|
682
|
-
/* @__PURE__ */ jsxDEV4("p", {
|
|
683
|
-
className: "font-medium text-lg text-primary",
|
|
684
|
-
children: formatCurrency3(deal.value, deal.currency)
|
|
685
|
-
}, undefined, false, undefined, this),
|
|
686
|
-
/* @__PURE__ */ jsxDEV4("span", {
|
|
687
|
-
className: `mt-2 inline-flex rounded-full px-2 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"}`,
|
|
688
|
-
children: deal.status
|
|
689
|
-
}, undefined, false, undefined, this)
|
|
690
|
-
]
|
|
691
|
-
}, undefined, true, undefined, this),
|
|
692
|
-
mode === "menu" && /* @__PURE__ */ jsxDEV4("div", {
|
|
693
|
-
className: "space-y-3",
|
|
694
|
-
children: [
|
|
695
|
-
deal.status === "OPEN" && /* @__PURE__ */ jsxDEV4(Fragment, {
|
|
696
|
-
children: [
|
|
697
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
698
|
-
className: "w-full justify-start",
|
|
699
|
-
variant: "ghost",
|
|
700
|
-
onPress: () => setMode("win"),
|
|
701
|
-
children: [
|
|
702
|
-
/* @__PURE__ */ jsxDEV4("span", {
|
|
703
|
-
className: "mr-2",
|
|
704
|
-
children: "\uD83C\uDFC6"
|
|
705
|
-
}, undefined, false, undefined, this),
|
|
706
|
-
" Mark as Won"
|
|
707
|
-
]
|
|
708
|
-
}, undefined, true, undefined, this),
|
|
709
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
710
|
-
className: "w-full justify-start",
|
|
711
|
-
variant: "ghost",
|
|
712
|
-
onPress: () => setMode("lose"),
|
|
713
|
-
children: [
|
|
714
|
-
/* @__PURE__ */ jsxDEV4("span", {
|
|
715
|
-
className: "mr-2",
|
|
716
|
-
children: "❌"
|
|
717
|
-
}, undefined, false, undefined, this),
|
|
718
|
-
" Mark as Lost"
|
|
719
|
-
]
|
|
720
|
-
}, undefined, true, undefined, this),
|
|
721
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
722
|
-
className: "w-full justify-start",
|
|
723
|
-
variant: "ghost",
|
|
724
|
-
onPress: () => {
|
|
725
|
-
setSelectedStageId(deal.stageId);
|
|
726
|
-
setMode("move");
|
|
727
|
-
},
|
|
728
|
-
children: [
|
|
729
|
-
/* @__PURE__ */ jsxDEV4("span", {
|
|
730
|
-
className: "mr-2",
|
|
731
|
-
children: "➡️"
|
|
732
|
-
}, undefined, false, undefined, this),
|
|
733
|
-
" Move to Stage"
|
|
734
|
-
]
|
|
735
|
-
}, undefined, true, undefined, this)
|
|
736
|
-
]
|
|
737
|
-
}, undefined, true, undefined, this),
|
|
738
|
-
deal.status !== "OPEN" && /* @__PURE__ */ jsxDEV4("p", {
|
|
739
|
-
className: "py-4 text-center text-muted-foreground",
|
|
740
|
-
children: [
|
|
741
|
-
"This deal is already ",
|
|
742
|
-
deal.status.toLowerCase(),
|
|
743
|
-
". No actions available."
|
|
744
|
-
]
|
|
745
|
-
}, undefined, true, undefined, this),
|
|
746
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
747
|
-
className: "border-border border-t pt-3",
|
|
748
|
-
children: /* @__PURE__ */ jsxDEV4(Button2, {
|
|
749
|
-
className: "w-full",
|
|
750
|
-
variant: "outline",
|
|
751
|
-
onPress: handleClose,
|
|
752
|
-
children: "Close"
|
|
753
|
-
}, undefined, false, undefined, this)
|
|
754
|
-
}, undefined, false, undefined, this)
|
|
755
|
-
]
|
|
756
|
-
}, undefined, true, undefined, this),
|
|
757
|
-
mode === "win" && /* @__PURE__ */ jsxDEV4("div", {
|
|
758
|
-
className: "space-y-4",
|
|
759
|
-
children: [
|
|
760
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
761
|
-
children: [
|
|
762
|
-
/* @__PURE__ */ jsxDEV4("label", {
|
|
763
|
-
htmlFor: "won-source",
|
|
764
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
765
|
-
children: "How did you win this deal?"
|
|
766
|
-
}, undefined, false, undefined, this),
|
|
767
|
-
/* @__PURE__ */ jsxDEV4("select", {
|
|
768
|
-
id: "won-source",
|
|
769
|
-
value: wonSource,
|
|
770
|
-
onChange: (e) => setWonSource(e.target.value),
|
|
771
|
-
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
772
|
-
children: [
|
|
773
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
774
|
-
value: "",
|
|
775
|
-
children: "Select a source..."
|
|
776
|
-
}, undefined, false, undefined, this),
|
|
777
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
778
|
-
value: "referral",
|
|
779
|
-
children: "Referral"
|
|
780
|
-
}, undefined, false, undefined, this),
|
|
781
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
782
|
-
value: "cold_outreach",
|
|
783
|
-
children: "Cold Outreach"
|
|
784
|
-
}, undefined, false, undefined, this),
|
|
785
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
786
|
-
value: "inbound",
|
|
787
|
-
children: "Inbound Lead"
|
|
788
|
-
}, undefined, false, undefined, this),
|
|
789
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
790
|
-
value: "upsell",
|
|
791
|
-
children: "Upsell"
|
|
792
|
-
}, undefined, false, undefined, this),
|
|
793
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
794
|
-
value: "other",
|
|
795
|
-
children: "Other"
|
|
796
|
-
}, undefined, false, undefined, this)
|
|
797
|
-
]
|
|
798
|
-
}, undefined, true, undefined, this)
|
|
799
|
-
]
|
|
800
|
-
}, undefined, true, undefined, this),
|
|
801
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
802
|
-
children: [
|
|
803
|
-
/* @__PURE__ */ jsxDEV4("label", {
|
|
804
|
-
htmlFor: "win-notes",
|
|
805
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
806
|
-
children: "Notes (optional)"
|
|
807
|
-
}, undefined, false, undefined, this),
|
|
808
|
-
/* @__PURE__ */ jsxDEV4("textarea", {
|
|
809
|
-
id: "win-notes",
|
|
810
|
-
value: notes,
|
|
811
|
-
onChange: (e) => setNotes(e.target.value),
|
|
812
|
-
placeholder: "Any additional notes about the win...",
|
|
813
|
-
rows: 3,
|
|
814
|
-
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
815
|
-
}, undefined, false, undefined, this)
|
|
816
|
-
]
|
|
817
|
-
}, undefined, true, undefined, this),
|
|
818
|
-
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
819
|
-
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
820
|
-
children: error
|
|
821
|
-
}, undefined, false, undefined, this),
|
|
822
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
823
|
-
className: "flex justify-end gap-3 pt-2",
|
|
824
|
-
children: [
|
|
825
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
826
|
-
variant: "ghost",
|
|
827
|
-
onPress: () => setMode("menu"),
|
|
828
|
-
disabled: isLoading,
|
|
829
|
-
children: "Back"
|
|
830
|
-
}, undefined, false, undefined, this),
|
|
831
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
832
|
-
onPress: handleWin,
|
|
833
|
-
disabled: isLoading,
|
|
834
|
-
children: isLoading ? "Processing..." : "\uD83C\uDFC6 Confirm Win"
|
|
835
|
-
}, undefined, false, undefined, this)
|
|
836
|
-
]
|
|
837
|
-
}, undefined, true, undefined, this)
|
|
838
|
-
]
|
|
839
|
-
}, undefined, true, undefined, this),
|
|
840
|
-
mode === "lose" && /* @__PURE__ */ jsxDEV4("div", {
|
|
841
|
-
className: "space-y-4",
|
|
842
|
-
children: [
|
|
843
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
844
|
-
children: [
|
|
845
|
-
/* @__PURE__ */ jsxDEV4("label", {
|
|
846
|
-
htmlFor: "lost-reason",
|
|
847
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
848
|
-
children: "Why was this deal lost? *"
|
|
849
|
-
}, undefined, false, undefined, this),
|
|
850
|
-
/* @__PURE__ */ jsxDEV4("select", {
|
|
851
|
-
id: "lost-reason",
|
|
852
|
-
value: lostReason,
|
|
853
|
-
onChange: (e) => setLostReason(e.target.value),
|
|
854
|
-
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
855
|
-
children: [
|
|
856
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
857
|
-
value: "",
|
|
858
|
-
children: "Select a reason..."
|
|
859
|
-
}, undefined, false, undefined, this),
|
|
860
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
861
|
-
value: "price",
|
|
862
|
-
children: "Price too high"
|
|
863
|
-
}, undefined, false, undefined, this),
|
|
864
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
865
|
-
value: "competitor",
|
|
866
|
-
children: "Lost to competitor"
|
|
867
|
-
}, undefined, false, undefined, this),
|
|
868
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
869
|
-
value: "no_budget",
|
|
870
|
-
children: "No budget"
|
|
871
|
-
}, undefined, false, undefined, this),
|
|
872
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
873
|
-
value: "no_decision",
|
|
874
|
-
children: "No decision made"
|
|
875
|
-
}, undefined, false, undefined, this),
|
|
876
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
877
|
-
value: "timing",
|
|
878
|
-
children: "Bad timing"
|
|
879
|
-
}, undefined, false, undefined, this),
|
|
880
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
881
|
-
value: "product_fit",
|
|
882
|
-
children: "Product not a fit"
|
|
883
|
-
}, undefined, false, undefined, this),
|
|
884
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
885
|
-
value: "other",
|
|
886
|
-
children: "Other"
|
|
887
|
-
}, undefined, false, undefined, this)
|
|
888
|
-
]
|
|
889
|
-
}, undefined, true, undefined, this)
|
|
890
|
-
]
|
|
891
|
-
}, undefined, true, undefined, this),
|
|
892
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
893
|
-
children: [
|
|
894
|
-
/* @__PURE__ */ jsxDEV4("label", {
|
|
895
|
-
htmlFor: "lose-notes",
|
|
896
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
897
|
-
children: "Notes (optional)"
|
|
898
|
-
}, undefined, false, undefined, this),
|
|
899
|
-
/* @__PURE__ */ jsxDEV4("textarea", {
|
|
900
|
-
id: "lose-notes",
|
|
901
|
-
value: notes,
|
|
902
|
-
onChange: (e) => setNotes(e.target.value),
|
|
903
|
-
placeholder: "Any additional details...",
|
|
904
|
-
rows: 3,
|
|
905
|
-
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
906
|
-
}, undefined, false, undefined, this)
|
|
907
|
-
]
|
|
908
|
-
}, undefined, true, undefined, this),
|
|
909
|
-
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
910
|
-
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
911
|
-
children: error
|
|
912
|
-
}, undefined, false, undefined, this),
|
|
913
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
914
|
-
className: "flex justify-end gap-3 pt-2",
|
|
915
|
-
children: [
|
|
916
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
917
|
-
variant: "ghost",
|
|
918
|
-
onPress: () => setMode("menu"),
|
|
919
|
-
disabled: isLoading,
|
|
920
|
-
children: "Back"
|
|
921
|
-
}, undefined, false, undefined, this),
|
|
922
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
923
|
-
variant: "destructive",
|
|
924
|
-
onPress: handleLose,
|
|
925
|
-
disabled: isLoading,
|
|
926
|
-
children: isLoading ? "Processing..." : "❌ Confirm Loss"
|
|
927
|
-
}, undefined, false, undefined, this)
|
|
928
|
-
]
|
|
929
|
-
}, undefined, true, undefined, this)
|
|
930
|
-
]
|
|
931
|
-
}, undefined, true, undefined, this),
|
|
932
|
-
mode === "move" && /* @__PURE__ */ jsxDEV4("div", {
|
|
933
|
-
className: "space-y-4",
|
|
934
|
-
children: [
|
|
935
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
936
|
-
children: [
|
|
937
|
-
/* @__PURE__ */ jsxDEV4("label", {
|
|
938
|
-
htmlFor: "move-stage",
|
|
939
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
940
|
-
children: "Move to Stage"
|
|
941
|
-
}, undefined, false, undefined, this),
|
|
942
|
-
/* @__PURE__ */ jsxDEV4("select", {
|
|
943
|
-
id: "move-stage",
|
|
944
|
-
value: selectedStageId,
|
|
945
|
-
onChange: (e) => setSelectedStageId(e.target.value),
|
|
946
|
-
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
947
|
-
children: stages.map((stage) => /* @__PURE__ */ jsxDEV4("option", {
|
|
948
|
-
value: stage.id,
|
|
949
|
-
children: [
|
|
950
|
-
stage.name,
|
|
951
|
-
stage.id === deal.stageId ? " (current)" : ""
|
|
952
|
-
]
|
|
953
|
-
}, stage.id, true, undefined, this))
|
|
954
|
-
}, undefined, false, undefined, this)
|
|
955
|
-
]
|
|
956
|
-
}, undefined, true, undefined, this),
|
|
957
|
-
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
958
|
-
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
959
|
-
children: error
|
|
960
|
-
}, undefined, false, undefined, this),
|
|
961
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
962
|
-
className: "flex justify-end gap-3 pt-2",
|
|
963
|
-
children: [
|
|
964
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
965
|
-
variant: "ghost",
|
|
966
|
-
onPress: () => setMode("menu"),
|
|
967
|
-
disabled: isLoading,
|
|
968
|
-
children: "Back"
|
|
969
|
-
}, undefined, false, undefined, this),
|
|
970
|
-
/* @__PURE__ */ jsxDEV4(Button2, {
|
|
971
|
-
onPress: handleMove,
|
|
972
|
-
disabled: isLoading,
|
|
973
|
-
children: isLoading ? "Moving..." : "➡️ Move Deal"
|
|
974
|
-
}, undefined, false, undefined, this)
|
|
975
|
-
]
|
|
976
|
-
}, undefined, true, undefined, this)
|
|
977
|
-
]
|
|
978
|
-
}, undefined, true, undefined, this)
|
|
979
|
-
]
|
|
980
|
-
}, undefined, true, undefined, this)
|
|
981
|
-
]
|
|
982
|
-
}, undefined, true, undefined, this);
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// src/ui/tables/DealListTab.tsx
|
|
986
|
-
import {
|
|
987
|
-
Button as Button3,
|
|
988
|
-
DataTable,
|
|
989
|
-
LoaderBlock
|
|
990
|
-
} from "@contractspec/lib.design-system";
|
|
991
|
-
import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
|
|
992
|
-
import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
993
|
-
import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
|
|
994
|
-
import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
|
|
995
|
-
import * as React from "react";
|
|
996
|
-
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
997
|
-
"use client";
|
|
998
|
-
function formatCurrency4(value, currency = "USD") {
|
|
999
|
-
return new Intl.NumberFormat("en-US", {
|
|
1000
|
-
style: "currency",
|
|
1001
|
-
currency,
|
|
1002
|
-
minimumFractionDigits: 0,
|
|
1003
|
-
maximumFractionDigits: 0
|
|
1004
|
-
}).format(value);
|
|
1005
|
-
}
|
|
1006
|
-
function statusVariant(status) {
|
|
1007
|
-
switch (status) {
|
|
1008
|
-
case "WON":
|
|
1009
|
-
return "default";
|
|
1010
|
-
case "LOST":
|
|
1011
|
-
return "destructive";
|
|
1012
|
-
case "STALE":
|
|
1013
|
-
return "outline";
|
|
1014
|
-
default:
|
|
1015
|
-
return "secondary";
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
function DealListDataTable({
|
|
1019
|
-
deals,
|
|
1020
|
-
totalItems,
|
|
1021
|
-
pageIndex,
|
|
1022
|
-
pageSize,
|
|
1023
|
-
sorting,
|
|
1024
|
-
loading,
|
|
1025
|
-
onSortingChange,
|
|
1026
|
-
onPaginationChange,
|
|
1027
|
-
onDealClick
|
|
1028
|
-
}) {
|
|
1029
|
-
const controller = useContractTable({
|
|
1030
|
-
data: deals,
|
|
1031
|
-
columns: [
|
|
1032
|
-
{
|
|
1033
|
-
id: "deal",
|
|
1034
|
-
header: "Deal",
|
|
1035
|
-
label: "Deal",
|
|
1036
|
-
accessor: (deal) => deal.name,
|
|
1037
|
-
cell: ({ item }) => /* @__PURE__ */ jsxDEV5(VStack, {
|
|
1038
|
-
gap: "xs",
|
|
1039
|
-
children: [
|
|
1040
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1041
|
-
className: "font-medium text-sm",
|
|
1042
|
-
children: item.name
|
|
1043
|
-
}, undefined, false, undefined, this),
|
|
1044
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1045
|
-
className: "text-muted-foreground text-xs",
|
|
1046
|
-
children: item.companyId ?? "Unassigned company"
|
|
1047
|
-
}, undefined, false, undefined, this)
|
|
1048
|
-
]
|
|
1049
|
-
}, undefined, true, undefined, this),
|
|
1050
|
-
size: 240,
|
|
1051
|
-
minSize: 180,
|
|
1052
|
-
canSort: true,
|
|
1053
|
-
canPin: true,
|
|
1054
|
-
canResize: true
|
|
1055
|
-
},
|
|
1056
|
-
{
|
|
1057
|
-
id: "value",
|
|
1058
|
-
header: "Value",
|
|
1059
|
-
label: "Value",
|
|
1060
|
-
accessorKey: "value",
|
|
1061
|
-
cell: ({ item }) => formatCurrency4(item.value, item.currency),
|
|
1062
|
-
align: "right",
|
|
1063
|
-
size: 140,
|
|
1064
|
-
canSort: true,
|
|
1065
|
-
canResize: true
|
|
1066
|
-
},
|
|
1067
|
-
{
|
|
1068
|
-
id: "status",
|
|
1069
|
-
header: "Status",
|
|
1070
|
-
label: "Status",
|
|
1071
|
-
accessorKey: "status",
|
|
1072
|
-
cell: ({ value }) => /* @__PURE__ */ jsxDEV5(Badge, {
|
|
1073
|
-
variant: statusVariant(value),
|
|
1074
|
-
children: String(value)
|
|
1075
|
-
}, undefined, false, undefined, this),
|
|
1076
|
-
size: 130,
|
|
1077
|
-
canSort: true,
|
|
1078
|
-
canHide: true,
|
|
1079
|
-
canPin: true,
|
|
1080
|
-
canResize: true
|
|
1081
|
-
},
|
|
1082
|
-
{
|
|
1083
|
-
id: "expectedCloseDate",
|
|
1084
|
-
header: "Expected Close",
|
|
1085
|
-
label: "Expected Close",
|
|
1086
|
-
accessor: (deal) => deal.expectedCloseDate?.toISOString() ?? "",
|
|
1087
|
-
cell: ({ item }) => item.expectedCloseDate?.toLocaleDateString() ?? "Not scheduled",
|
|
1088
|
-
size: 170,
|
|
1089
|
-
canSort: true,
|
|
1090
|
-
canHide: true,
|
|
1091
|
-
canResize: true
|
|
1092
|
-
},
|
|
1093
|
-
{
|
|
1094
|
-
id: "updatedAt",
|
|
1095
|
-
header: "Updated",
|
|
1096
|
-
label: "Updated",
|
|
1097
|
-
accessor: (deal) => deal.updatedAt.toISOString(),
|
|
1098
|
-
cell: ({ item }) => item.updatedAt.toLocaleDateString(),
|
|
1099
|
-
size: 140,
|
|
1100
|
-
canSort: true,
|
|
1101
|
-
canHide: true,
|
|
1102
|
-
canResize: true
|
|
1103
|
-
},
|
|
1104
|
-
{
|
|
1105
|
-
id: "actions",
|
|
1106
|
-
header: "Actions",
|
|
1107
|
-
label: "Actions",
|
|
1108
|
-
accessor: (deal) => deal.id,
|
|
1109
|
-
cell: ({ item }) => /* @__PURE__ */ jsxDEV5(Button3, {
|
|
1110
|
-
variant: "ghost",
|
|
1111
|
-
size: "sm",
|
|
1112
|
-
onPress: () => onDealClick?.(item.id),
|
|
1113
|
-
children: "Actions"
|
|
1114
|
-
}, undefined, false, undefined, this),
|
|
1115
|
-
size: 120,
|
|
1116
|
-
canSort: false,
|
|
1117
|
-
canHide: false,
|
|
1118
|
-
canPin: false,
|
|
1119
|
-
canResize: false
|
|
1120
|
-
}
|
|
1121
|
-
],
|
|
1122
|
-
executionMode: "server",
|
|
1123
|
-
selectionMode: "multiple",
|
|
1124
|
-
totalItems,
|
|
1125
|
-
state: {
|
|
1126
|
-
sorting,
|
|
1127
|
-
pagination: {
|
|
1128
|
-
pageIndex,
|
|
1129
|
-
pageSize
|
|
1130
|
-
}
|
|
1131
|
-
},
|
|
1132
|
-
onSortingChange,
|
|
1133
|
-
onPaginationChange,
|
|
1134
|
-
initialState: {
|
|
1135
|
-
columnVisibility: { updatedAt: false },
|
|
1136
|
-
columnPinning: { left: ["deal", "status"], right: [] }
|
|
1137
|
-
},
|
|
1138
|
-
renderExpandedContent: (deal) => /* @__PURE__ */ jsxDEV5(VStack, {
|
|
1139
|
-
gap: "sm",
|
|
1140
|
-
className: "py-2",
|
|
1141
|
-
children: [
|
|
1142
|
-
/* @__PURE__ */ jsxDEV5(HStack, {
|
|
1143
|
-
justify: "between",
|
|
1144
|
-
children: [
|
|
1145
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1146
|
-
className: "font-medium text-sm",
|
|
1147
|
-
children: "Owner"
|
|
1148
|
-
}, undefined, false, undefined, this),
|
|
1149
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1150
|
-
className: "text-muted-foreground text-sm",
|
|
1151
|
-
children: deal.ownerId
|
|
1152
|
-
}, undefined, false, undefined, this)
|
|
1153
|
-
]
|
|
1154
|
-
}, undefined, true, undefined, this),
|
|
1155
|
-
/* @__PURE__ */ jsxDEV5(HStack, {
|
|
1156
|
-
justify: "between",
|
|
1157
|
-
children: [
|
|
1158
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1159
|
-
className: "font-medium text-sm",
|
|
1160
|
-
children: "Contact"
|
|
1161
|
-
}, undefined, false, undefined, this),
|
|
1162
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1163
|
-
className: "text-muted-foreground text-sm",
|
|
1164
|
-
children: deal.contactId ?? "No linked contact"
|
|
1165
|
-
}, undefined, false, undefined, this)
|
|
1166
|
-
]
|
|
1167
|
-
}, undefined, true, undefined, this),
|
|
1168
|
-
deal.wonSource ? /* @__PURE__ */ jsxDEV5(HStack, {
|
|
1169
|
-
justify: "between",
|
|
1170
|
-
children: [
|
|
1171
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1172
|
-
className: "font-medium text-sm",
|
|
1173
|
-
children: "Won Source"
|
|
1174
|
-
}, undefined, false, undefined, this),
|
|
1175
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1176
|
-
className: "text-muted-foreground text-sm",
|
|
1177
|
-
children: deal.wonSource
|
|
1178
|
-
}, undefined, false, undefined, this)
|
|
1179
|
-
]
|
|
1180
|
-
}, undefined, true, undefined, this) : null,
|
|
1181
|
-
deal.lostReason ? /* @__PURE__ */ jsxDEV5(HStack, {
|
|
1182
|
-
justify: "between",
|
|
1183
|
-
children: [
|
|
1184
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1185
|
-
className: "font-medium text-sm",
|
|
1186
|
-
children: "Lost Reason"
|
|
1187
|
-
}, undefined, false, undefined, this),
|
|
1188
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1189
|
-
className: "text-muted-foreground text-sm",
|
|
1190
|
-
children: deal.lostReason
|
|
1191
|
-
}, undefined, false, undefined, this)
|
|
1192
|
-
]
|
|
1193
|
-
}, undefined, true, undefined, this) : null,
|
|
1194
|
-
deal.notes ? /* @__PURE__ */ jsxDEV5(VStack, {
|
|
1195
|
-
gap: "xs",
|
|
1196
|
-
children: [
|
|
1197
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1198
|
-
className: "font-medium text-sm",
|
|
1199
|
-
children: "Notes"
|
|
1200
|
-
}, undefined, false, undefined, this),
|
|
1201
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1202
|
-
className: "text-muted-foreground text-sm",
|
|
1203
|
-
children: deal.notes
|
|
1204
|
-
}, undefined, false, undefined, this)
|
|
1205
|
-
]
|
|
1206
|
-
}, undefined, true, undefined, this) : null
|
|
1207
|
-
]
|
|
1208
|
-
}, undefined, true, undefined, this),
|
|
1209
|
-
getCanExpand: () => true
|
|
1210
|
-
});
|
|
1211
|
-
return /* @__PURE__ */ jsxDEV5(DataTable, {
|
|
1212
|
-
controller,
|
|
1213
|
-
title: "All Deals",
|
|
1214
|
-
description: "Server-mode table using the shared ContractSpec controller.",
|
|
1215
|
-
loading,
|
|
1216
|
-
toolbar: /* @__PURE__ */ jsxDEV5(HStack, {
|
|
1217
|
-
gap: "sm",
|
|
1218
|
-
className: "flex-wrap",
|
|
1219
|
-
children: [
|
|
1220
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1221
|
-
className: "text-muted-foreground text-sm",
|
|
1222
|
-
children: [
|
|
1223
|
-
"Selected ",
|
|
1224
|
-
controller.selectedRowIds.length
|
|
1225
|
-
]
|
|
1226
|
-
}, undefined, true, undefined, this),
|
|
1227
|
-
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1228
|
-
className: "text-muted-foreground text-sm",
|
|
1229
|
-
children: [
|
|
1230
|
-
totalItems,
|
|
1231
|
-
" total deals"
|
|
1232
|
-
]
|
|
1233
|
-
}, undefined, true, undefined, this)
|
|
1234
|
-
]
|
|
1235
|
-
}, undefined, true, undefined, this),
|
|
1236
|
-
footer: `Page ${controller.pageIndex + 1} of ${controller.pageCount}`,
|
|
1237
|
-
emptyState: /* @__PURE__ */ jsxDEV5("div", {
|
|
1238
|
-
className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
|
|
1239
|
-
children: "No deals found"
|
|
1240
|
-
}, undefined, false, undefined, this)
|
|
1241
|
-
}, undefined, false, undefined, this);
|
|
1242
|
-
}
|
|
1243
|
-
function DealListTab({
|
|
1244
|
-
onDealClick
|
|
1245
|
-
}) {
|
|
1246
|
-
const [sorting, setSorting] = React.useState([
|
|
1247
|
-
{ id: "value", desc: true }
|
|
1248
|
-
]);
|
|
1249
|
-
const [pagination, setPagination] = React.useState({
|
|
1250
|
-
pageIndex: 0,
|
|
1251
|
-
pageSize: 3
|
|
1252
|
-
});
|
|
1253
|
-
const { data, loading } = useDealList({
|
|
1254
|
-
pageIndex: pagination.pageIndex,
|
|
1255
|
-
pageSize: pagination.pageSize,
|
|
1256
|
-
sorting
|
|
1257
|
-
});
|
|
1258
|
-
if (loading && !data) {
|
|
1259
|
-
return /* @__PURE__ */ jsxDEV5(LoaderBlock, {
|
|
1260
|
-
label: "Loading deals..."
|
|
1261
|
-
}, undefined, false, undefined, this);
|
|
1262
|
-
}
|
|
1263
|
-
return /* @__PURE__ */ jsxDEV5(DealListDataTable, {
|
|
1264
|
-
deals: data?.deals ?? [],
|
|
1265
|
-
totalItems: data?.total ?? 0,
|
|
1266
|
-
pageIndex: pagination.pageIndex,
|
|
1267
|
-
pageSize: pagination.pageSize,
|
|
1268
|
-
sorting,
|
|
1269
|
-
loading,
|
|
1270
|
-
onSortingChange: (nextSorting) => {
|
|
1271
|
-
setSorting(nextSorting);
|
|
1272
|
-
setPagination((current) => ({ ...current, pageIndex: 0 }));
|
|
1273
|
-
},
|
|
1274
|
-
onPaginationChange: setPagination,
|
|
1275
|
-
onDealClick
|
|
1276
|
-
}, undefined, false, undefined, this);
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// src/ui/CrmDashboard.tsx
|
|
1280
|
-
import {
|
|
1281
|
-
Button as Button4,
|
|
1282
|
-
ErrorState,
|
|
1283
|
-
LoaderBlock as LoaderBlock2,
|
|
1284
|
-
StatCard,
|
|
1285
|
-
StatCardGroup
|
|
1286
|
-
} from "@contractspec/lib.design-system";
|
|
1287
|
-
import {
|
|
1288
|
-
Tabs,
|
|
1289
|
-
TabsContent,
|
|
1290
|
-
TabsList,
|
|
1291
|
-
TabsTrigger
|
|
1292
|
-
} from "@contractspec/lib.ui-kit-web/ui/tabs";
|
|
1293
|
-
import { useCallback as useCallback3, useState as useState7 } from "react";
|
|
1294
|
-
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
1295
|
-
"use client";
|
|
1296
|
-
function formatCurrency5(value, currency = "USD") {
|
|
1297
|
-
return new Intl.NumberFormat("en-US", {
|
|
1298
|
-
style: "currency",
|
|
1299
|
-
currency,
|
|
1300
|
-
minimumFractionDigits: 0,
|
|
1301
|
-
maximumFractionDigits: 0
|
|
1302
|
-
}).format(value);
|
|
1303
|
-
}
|
|
1304
|
-
function CrmDashboard() {
|
|
1305
|
-
const [isCreateModalOpen, setIsCreateModalOpen] = useState7(false);
|
|
1306
|
-
const [selectedDeal, setSelectedDeal] = useState7(null);
|
|
1307
|
-
const [isDealActionsOpen, setIsDealActionsOpen] = useState7(false);
|
|
1308
|
-
const { data, dealsByStage, stages, loading, error, stats, refetch } = useDealList();
|
|
1309
|
-
const mutations = useDealMutations({
|
|
1310
|
-
onSuccess: () => {
|
|
1311
|
-
refetch();
|
|
1312
|
-
}
|
|
1313
|
-
});
|
|
1314
|
-
const handleDealClick = useCallback3((dealId) => {
|
|
1315
|
-
const deal = dealsByStage ? Object.values(dealsByStage).flat().find((d) => d.id === dealId) : null;
|
|
1316
|
-
if (deal) {
|
|
1317
|
-
setSelectedDeal(deal);
|
|
1318
|
-
setIsDealActionsOpen(true);
|
|
1319
|
-
}
|
|
1320
|
-
}, [dealsByStage]);
|
|
1321
|
-
const handleDealMove = useCallback3(async (dealId, toStageId) => {
|
|
1322
|
-
await mutations.moveDeal({ dealId, stageId: toStageId });
|
|
1323
|
-
}, [mutations]);
|
|
1324
|
-
if (loading && !data) {
|
|
1325
|
-
return /* @__PURE__ */ jsxDEV6(LoaderBlock2, {
|
|
1326
|
-
label: "Loading CRM..."
|
|
1327
|
-
}, undefined, false, undefined, this);
|
|
1328
|
-
}
|
|
1329
|
-
if (error) {
|
|
1330
|
-
return /* @__PURE__ */ jsxDEV6(ErrorState, {
|
|
1331
|
-
title: "Failed to load CRM",
|
|
1332
|
-
description: error.message,
|
|
1333
|
-
onRetry: refetch,
|
|
1334
|
-
retryLabel: "Retry"
|
|
1335
|
-
}, undefined, false, undefined, this);
|
|
1336
|
-
}
|
|
1337
|
-
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1338
|
-
className: "space-y-6",
|
|
1339
|
-
children: [
|
|
1340
|
-
/* @__PURE__ */ jsxDEV6("div", {
|
|
1341
|
-
className: "flex items-center justify-between",
|
|
1342
|
-
children: [
|
|
1343
|
-
/* @__PURE__ */ jsxDEV6("h2", {
|
|
1344
|
-
className: "font-bold text-2xl",
|
|
1345
|
-
children: "CRM Pipeline"
|
|
1346
|
-
}, undefined, false, undefined, this),
|
|
1347
|
-
/* @__PURE__ */ jsxDEV6(Button4, {
|
|
1348
|
-
onClick: () => setIsCreateModalOpen(true),
|
|
1349
|
-
children: [
|
|
1350
|
-
/* @__PURE__ */ jsxDEV6("span", {
|
|
1351
|
-
className: "mr-2",
|
|
1352
|
-
children: "+"
|
|
1353
|
-
}, undefined, false, undefined, this),
|
|
1354
|
-
" Create Deal"
|
|
1355
|
-
]
|
|
1356
|
-
}, undefined, true, undefined, this)
|
|
1357
|
-
]
|
|
1358
|
-
}, undefined, true, undefined, this),
|
|
1359
|
-
stats && /* @__PURE__ */ jsxDEV6(StatCardGroup, {
|
|
1360
|
-
children: [
|
|
1361
|
-
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1362
|
-
label: "Total Pipeline",
|
|
1363
|
-
value: formatCurrency5(stats.totalValue),
|
|
1364
|
-
hint: `${stats.total} deals`
|
|
1365
|
-
}, undefined, false, undefined, this),
|
|
1366
|
-
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1367
|
-
label: "Open Deals",
|
|
1368
|
-
value: formatCurrency5(stats.openValue),
|
|
1369
|
-
hint: `${stats.openCount} active`
|
|
1370
|
-
}, undefined, false, undefined, this),
|
|
1371
|
-
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1372
|
-
label: "Won",
|
|
1373
|
-
value: formatCurrency5(stats.wonValue),
|
|
1374
|
-
hint: `${stats.wonCount} closed`
|
|
1375
|
-
}, undefined, false, undefined, this),
|
|
1376
|
-
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1377
|
-
label: "Lost",
|
|
1378
|
-
value: stats.lostCount,
|
|
1379
|
-
hint: "deals lost"
|
|
1380
|
-
}, undefined, false, undefined, this)
|
|
1381
|
-
]
|
|
1382
|
-
}, undefined, true, undefined, this),
|
|
1383
|
-
/* @__PURE__ */ jsxDEV6(Tabs, {
|
|
1384
|
-
defaultValue: "pipeline",
|
|
1385
|
-
className: "w-full",
|
|
1386
|
-
children: [
|
|
1387
|
-
/* @__PURE__ */ jsxDEV6(TabsList, {
|
|
1388
|
-
children: [
|
|
1389
|
-
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1390
|
-
value: "pipeline",
|
|
1391
|
-
children: [
|
|
1392
|
-
/* @__PURE__ */ jsxDEV6("span", {
|
|
1393
|
-
className: "mr-2",
|
|
1394
|
-
children: "\uD83D\uDCCA"
|
|
1395
|
-
}, undefined, false, undefined, this),
|
|
1396
|
-
"Pipeline"
|
|
1397
|
-
]
|
|
1398
|
-
}, undefined, true, undefined, this),
|
|
1399
|
-
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1400
|
-
value: "list",
|
|
1401
|
-
children: [
|
|
1402
|
-
/* @__PURE__ */ jsxDEV6("span", {
|
|
1403
|
-
className: "mr-2",
|
|
1404
|
-
children: "\uD83D\uDCCB"
|
|
1405
|
-
}, undefined, false, undefined, this),
|
|
1406
|
-
"All Deals"
|
|
1407
|
-
]
|
|
1408
|
-
}, undefined, true, undefined, this),
|
|
1409
|
-
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1410
|
-
value: "metrics",
|
|
1411
|
-
children: [
|
|
1412
|
-
/* @__PURE__ */ jsxDEV6("span", {
|
|
1413
|
-
className: "mr-2",
|
|
1414
|
-
children: "\uD83D\uDCC8"
|
|
1415
|
-
}, undefined, false, undefined, this),
|
|
1416
|
-
"Metrics"
|
|
1417
|
-
]
|
|
1418
|
-
}, undefined, true, undefined, this)
|
|
1419
|
-
]
|
|
1420
|
-
}, undefined, true, undefined, this),
|
|
1421
|
-
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1422
|
-
value: "pipeline",
|
|
1423
|
-
className: "min-h-[400px]",
|
|
1424
|
-
children: /* @__PURE__ */ jsxDEV6(CrmPipelineBoard, {
|
|
1425
|
-
dealsByStage,
|
|
1426
|
-
stages,
|
|
1427
|
-
onDealClick: handleDealClick,
|
|
1428
|
-
onDealMove: handleDealMove
|
|
1429
|
-
}, undefined, false, undefined, this)
|
|
1430
|
-
}, undefined, false, undefined, this),
|
|
1431
|
-
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1432
|
-
value: "list",
|
|
1433
|
-
className: "min-h-[400px]",
|
|
1434
|
-
children: /* @__PURE__ */ jsxDEV6(DealListTab, {
|
|
1435
|
-
onDealClick: handleDealClick
|
|
1436
|
-
}, undefined, false, undefined, this)
|
|
1437
|
-
}, undefined, false, undefined, this),
|
|
1438
|
-
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1439
|
-
value: "metrics",
|
|
1440
|
-
className: "min-h-[400px]",
|
|
1441
|
-
children: /* @__PURE__ */ jsxDEV6(MetricsTab, {
|
|
1442
|
-
stats
|
|
1443
|
-
}, undefined, false, undefined, this)
|
|
1444
|
-
}, undefined, false, undefined, this)
|
|
1445
|
-
]
|
|
1446
|
-
}, undefined, true, undefined, this),
|
|
1447
|
-
/* @__PURE__ */ jsxDEV6(CreateDealModal, {
|
|
1448
|
-
isOpen: isCreateModalOpen,
|
|
1449
|
-
onClose: () => setIsCreateModalOpen(false),
|
|
1450
|
-
onSubmit: async (input) => {
|
|
1451
|
-
await mutations.createDeal(input);
|
|
1452
|
-
},
|
|
1453
|
-
stages,
|
|
1454
|
-
isLoading: mutations.createState.loading
|
|
1455
|
-
}, undefined, false, undefined, this),
|
|
1456
|
-
/* @__PURE__ */ jsxDEV6(DealActionsModal, {
|
|
1457
|
-
isOpen: isDealActionsOpen,
|
|
1458
|
-
deal: selectedDeal,
|
|
1459
|
-
stages,
|
|
1460
|
-
onClose: () => {
|
|
1461
|
-
setIsDealActionsOpen(false);
|
|
1462
|
-
setSelectedDeal(null);
|
|
1463
|
-
},
|
|
1464
|
-
onWin: async (input) => {
|
|
1465
|
-
await mutations.winDeal(input);
|
|
1466
|
-
},
|
|
1467
|
-
onLose: async (input) => {
|
|
1468
|
-
await mutations.loseDeal(input);
|
|
1469
|
-
},
|
|
1470
|
-
onMove: async (input) => {
|
|
1471
|
-
await mutations.moveDeal(input);
|
|
1472
|
-
refetch();
|
|
1473
|
-
},
|
|
1474
|
-
isLoading: mutations.isLoading
|
|
1475
|
-
}, undefined, false, undefined, this)
|
|
1476
|
-
]
|
|
1477
|
-
}, undefined, true, undefined, this);
|
|
1478
|
-
}
|
|
1479
|
-
function MetricsTab({
|
|
1480
|
-
stats
|
|
1481
|
-
}) {
|
|
1482
|
-
if (!stats)
|
|
1483
|
-
return null;
|
|
1484
|
-
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1485
|
-
className: "space-y-6",
|
|
1486
|
-
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
1487
|
-
className: "rounded-xl border border-border bg-card p-6",
|
|
1488
|
-
children: [
|
|
1489
|
-
/* @__PURE__ */ jsxDEV6("h3", {
|
|
1490
|
-
className: "mb-4 font-semibold text-lg",
|
|
1491
|
-
children: "Pipeline Overview"
|
|
1492
|
-
}, undefined, false, undefined, this),
|
|
1493
|
-
/* @__PURE__ */ jsxDEV6("dl", {
|
|
1494
|
-
className: "grid gap-4 sm:grid-cols-3",
|
|
1495
|
-
children: [
|
|
1496
|
-
/* @__PURE__ */ jsxDEV6("div", {
|
|
1497
|
-
children: [
|
|
1498
|
-
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1499
|
-
className: "text-muted-foreground text-sm",
|
|
1500
|
-
children: "Win Rate"
|
|
1501
|
-
}, undefined, false, undefined, this),
|
|
1502
|
-
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1503
|
-
className: "font-semibold text-2xl",
|
|
1504
|
-
children: [
|
|
1505
|
-
stats.total > 0 ? (stats.wonCount / stats.total * 100).toFixed(0) : 0,
|
|
1506
|
-
"%"
|
|
1507
|
-
]
|
|
1508
|
-
}, undefined, true, undefined, this)
|
|
1509
|
-
]
|
|
1510
|
-
}, undefined, true, undefined, this),
|
|
1511
|
-
/* @__PURE__ */ jsxDEV6("div", {
|
|
1512
|
-
children: [
|
|
1513
|
-
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1514
|
-
className: "text-muted-foreground text-sm",
|
|
1515
|
-
children: "Avg Deal Size"
|
|
1516
|
-
}, undefined, false, undefined, this),
|
|
1517
|
-
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1518
|
-
className: "font-semibold text-2xl",
|
|
1519
|
-
children: formatCurrency5(stats.total > 0 ? stats.totalValue / stats.total : 0)
|
|
1520
|
-
}, undefined, false, undefined, this)
|
|
1521
|
-
]
|
|
1522
|
-
}, undefined, true, undefined, this),
|
|
1523
|
-
/* @__PURE__ */ jsxDEV6("div", {
|
|
1524
|
-
children: [
|
|
1525
|
-
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1526
|
-
className: "text-muted-foreground text-sm",
|
|
1527
|
-
children: "Conversion"
|
|
1528
|
-
}, undefined, false, undefined, this),
|
|
1529
|
-
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1530
|
-
className: "font-semibold text-2xl",
|
|
1531
|
-
children: [
|
|
1532
|
-
stats.wonCount,
|
|
1533
|
-
" / ",
|
|
1534
|
-
stats.total
|
|
1535
|
-
]
|
|
1536
|
-
}, undefined, true, undefined, this)
|
|
1537
|
-
]
|
|
1538
|
-
}, undefined, true, undefined, this)
|
|
1539
|
-
]
|
|
1540
|
-
}, undefined, true, undefined, this)
|
|
1541
|
-
]
|
|
1542
|
-
}, undefined, true, undefined, this)
|
|
1543
|
-
}, undefined, false, undefined, this);
|
|
1544
|
-
}
|
|
1545
|
-
export {
|
|
1546
|
-
CrmDashboard
|
|
1547
|
-
};
|
|
1
|
+
import{jsx as u,jsxs as YJ}from"react/jsx-runtime";function AJ(J,K){return new Intl.NumberFormat("en-US",{style:"currency",currency:K,minimumFractionDigits:0,maximumFractionDigits:0}).format(J)}function ZJ({deal:J,onClick:K}){let N=J.expectedCloseDate?Math.ceil((J.expectedCloseDate.getTime()-Date.now())/86400000):null;return YJ("div",{onClick:K,className:"cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",role:"button",tabIndex:0,onKeyDown:(G)=>{if(G.key==="Enter"||G.key===" ")K?.()},children:[u("h4",{className:"font-medium leading-snug",children:J.name}),u("div",{className:"mt-2 font-semibold text-lg text-primary",children:AJ(J.value,J.currency)}),YJ("div",{className:"mt-3 flex items-center justify-between text-muted-foreground text-xs",children:[N!==null&&u("span",{className:N<0?"text-red-500":N<=7?"text-yellow-600 dark:text-yellow-500":"",children:N<0?`${Math.abs(N)}d overdue`:N===0?"Due today":`${N}d left`}),u("span",{className:`rounded px-1.5 py-0.5 font-medium text-xs ${J.status==="WON"?"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400":J.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:J.status})]})]})}import{useState as HJ}from"react";import{jsx as C,jsxs as m}from"react/jsx-runtime";function NJ(J){if(J>=1e6)return`$${(J/1e6).toFixed(1)}M`;if(J>=1000)return`$${(J/1000).toFixed(0)}K`;return`$${J}`}function $J({dealsByStage:J,stages:K,onDealClick:N,onDealMove:G}){let[z,P]=HJ(null),O=[...K].sort((H,Q)=>H.position-Q.position),A=(H,Q)=>{G?.(H,Q),P(null)};return C("div",{className:"flex gap-4 overflow-x-auto pb-4",children:O.map((H)=>{let Q=J[H.id]??[],Y=Q.reduce((_,W)=>_+W.value,0);return m("div",{className:"flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",children:[m("div",{className:"flex items-center justify-between border-border border-b px-3 py-2",children:[m("div",{children:[C("h3",{className:"font-medium",children:H.name}),m("p",{className:"text-muted-foreground text-xs",children:[Q.length," deals · ",NJ(Y)]})]}),C("span",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",children:Q.length})]}),C("div",{className:"flex flex-1 flex-col gap-2 p-2",children:Q.length===0?C("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"}):Q.map((_)=>m("div",{className:"group relative",children:[C(ZJ,{deal:_,onClick:()=>N?.(_.id)}),_.status==="OPEN"&&G&&m("div",{className:"absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",children:[C("button",{type:"button",onClick:(W)=>{W.stopPropagation(),P(z===_.id?null:_.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:"➡️"}),z===_.id&&m("div",{className:"absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",children:[C("p",{className:"px-3 py-1 font-medium text-muted-foreground text-xs",children:"Move to:"}),O.filter((W)=>W.id!==_.stageId).map((W)=>C("button",{type:"button",onClick:(V)=>{V.stopPropagation(),A(_.id,W.id)},className:"w-full px-3 py-1.5 text-left text-sm hover:bg-muted",children:W.name},W.id))]})]})]},_.id))})]},H.id)})})}import{useTemplateRuntime as UJ}from"@contractspec/lib.example-shared-ui";import{useCallback as WJ,useEffect as PJ,useMemo as wJ,useState as S}from"react";function x(J={}){let{handlers:K,projectId:N}=UJ(),{crm:G}=K,[z,P]=S(null),[O,A]=S({}),[H,Q]=S([]),[Y,_]=S(!0),[W,V]=S(null),[T,b]=S(0),q=J.pipelineId??"pipeline-1",Z=J.pageIndex??T,X=J.pageSize??J.limit??50,[R]=J.sorting??[],k=R?.id,L=R?R.desc?"desc":"asc":void 0,c=WJ(async()=>{_(!0),V(null);try{let[M,F,o]=await Promise.all([G.listDeals({projectId:N,pipelineId:q,stageId:J.stageId,status:J.status==="all"?void 0:J.status,search:J.search,limit:X,offset:Z*X,sortBy:k==="name"||k==="value"||k==="status"||k==="expectedCloseDate"||k==="updatedAt"?k:void 0,sortDirection:L}),G.getDealsByStage({projectId:N,pipelineId:q}),G.getPipelineStages({pipelineId:q})]);P(M),A(F),Q(o)}catch(M){V(M instanceof Error?M:Error("Unknown error"))}finally{_(!1)}},[G,N,q,J.stageId,J.status,J.search,Z,X,k,L]);PJ(()=>{c()},[c]);let n=wJ(()=>{if(!z)return null;let M=z.deals.filter((D)=>D.status==="OPEN"),F=z.deals.filter((D)=>D.status==="WON"),o=z.deals.filter((D)=>D.status==="LOST");return{total:z.total,totalValue:z.totalValue,openCount:M.length,openValue:M.reduce((D,a)=>D+a.value,0),wonCount:F.length,wonValue:F.reduce((D,a)=>D+a.value,0),lostCount:o.length}},[z]);return{data:z,dealsByStage:O,stages:H,loading:Y,error:W,stats:n,page:Z+1,pageIndex:Z,pageSize:X,refetch:c,nextPage:J.pageIndex===void 0?()=>b((M)=>M+1):void 0,prevPage:J.pageIndex===void 0?()=>Z>0&&b((M)=>M-1):void 0}}import{useTemplateRuntime as VJ}from"@contractspec/lib.example-shared-ui";import{useCallback as i,useState as d}from"react";function GJ(J={}){let{handlers:K,projectId:N}=VJ(),{crm:G}=K,[z,P]=d({loading:!1,error:null,data:null}),[O,A]=d({loading:!1,error:null,data:null}),[H,Q]=d({loading:!1,error:null,data:null}),[Y,_]=d({loading:!1,error:null,data:null}),W=i(async(q)=>{P({loading:!0,error:null,data:null});try{let Z=await G.createDeal(q,{projectId:N,ownerId:"user-1"});return P({loading:!1,error:null,data:Z}),J.onSuccess?.(),Z}catch(Z){let X=Z instanceof Error?Z:Error("Failed to create deal");return P({loading:!1,error:X,data:null}),J.onError?.(X),null}},[G,N,J]),V=i(async(q)=>{A({loading:!0,error:null,data:null});try{let Z=await G.moveDeal(q);return A({loading:!1,error:null,data:Z}),J.onSuccess?.(),Z}catch(Z){let X=Z instanceof Error?Z:Error("Failed to move deal");return A({loading:!1,error:X,data:null}),J.onError?.(X),null}},[G,J]),T=i(async(q)=>{Q({loading:!0,error:null,data:null});try{let Z=await G.winDeal(q);return Q({loading:!1,error:null,data:Z}),J.onSuccess?.(),Z}catch(Z){let X=Z instanceof Error?Z:Error("Failed to mark deal as won");return Q({loading:!1,error:X,data:null}),J.onError?.(X),null}},[G,J]),b=i(async(q)=>{_({loading:!0,error:null,data:null});try{let Z=await G.loseDeal(q);return _({loading:!1,error:null,data:Z}),J.onSuccess?.(),Z}catch(Z){let X=Z instanceof Error?Z:Error("Failed to mark deal as lost");return _({loading:!1,error:X,data:null}),J.onError?.(X),null}},[G,J]);return{createDeal:W,moveDeal:V,winDeal:T,loseDeal:b,createState:z,moveState:O,winState:H,loseState:Y,isLoading:z.loading||O.loading||H.loading||Y.loading}}import{Button as _J,Input as t}from"@contractspec/lib.design-system";import{useState as p}from"react";import{jsx as E,jsxs as v}from"react/jsx-runtime";var OJ=["USD","EUR","GBP","CAD"],RJ="pipeline-1";function qJ({isOpen:J,onClose:K,onSubmit:N,stages:G,isLoading:z=!1}){let[P,O]=p(""),[A,H]=p(""),[Q,Y]=p("USD"),[_,W]=p(G[0]?.id??""),[V,T]=p(""),[b,q]=p(null),Z=async(X)=>{if(X.preventDefault(),q(null),!P.trim()){q("Deal name is required");return}let R=parseFloat(A);if(isNaN(R)||R<=0){q("Value must be a positive number");return}if(!_){q("Please select a pipeline stage");return}try{await N({name:P.trim(),value:R,currency:Q,pipelineId:RJ,stageId:_,expectedCloseDate:V?new Date(V):void 0}),O(""),H(""),Y("USD"),W(G[0]?.id??""),T(""),K()}catch(k){q(k instanceof Error?k.message:"Failed to create deal")}};if(!J)return null;return v("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[E("div",{className:"absolute inset-0 bg-background/80 backdrop-blur-sm",onClick:K,role:"button",tabIndex:0,onKeyDown:(X)=>{if(X.key==="Enter"||X.key===" ")K()},"aria-label":"Close modal"}),v("div",{className:"relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",children:[E("h2",{className:"mb-4 font-semibold text-xl",children:"Create New Deal"}),v("form",{onSubmit:Z,className:"space-y-4",children:[v("div",{children:[E("label",{htmlFor:"deal-name",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Deal Name *"}),E(t,{id:"deal-name",value:P,onChange:(X)=>O(X.target.value),placeholder:"e.g., Enterprise License - Acme Corp",disabled:z})]}),v("div",{className:"flex gap-3",children:[v("div",{className:"flex-1",children:[E("label",{htmlFor:"deal-value",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Value *"}),E(t,{id:"deal-value",type:"number",min:"0",step:"0.01",value:A,onChange:(X)=>H(X.target.value),placeholder:"50000",disabled:z})]}),v("div",{className:"w-24",children:[E("label",{htmlFor:"deal-currency",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Currency"}),E("select",{id:"deal-currency",value:Q,onChange:(X)=>Y(X.target.value),disabled:z,className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",children:OJ.map((X)=>E("option",{value:X,children:X},X))})]})]}),v("div",{children:[E("label",{htmlFor:"deal-stage",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Pipeline Stage *"}),E("select",{id:"deal-stage",value:_,onChange:(X)=>W(X.target.value),disabled:z,className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",children:G.map((X)=>E("option",{value:X.id,children:X.name},X.id))})]}),v("div",{children:[E("label",{htmlFor:"deal-close-date",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Expected Close Date"}),E(t,{id:"deal-close-date",type:"date",value:V,onChange:(X)=>T(X.target.value),disabled:z})]}),b&&E("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:b}),v("div",{className:"flex justify-end gap-3 pt-2",children:[E(_J,{type:"button",variant:"ghost",onPress:K,disabled:z,children:"Cancel"}),E(_J,{type:"submit",disabled:z,children:z?"Creating...":"Create Deal"})]})]})]})]})}import{Button as y}from"@contractspec/lib.design-system";import{useState as g}from"react";import{jsx as $,jsxs as w,Fragment as EJ}from"react/jsx-runtime";function fJ(J,K){return new Intl.NumberFormat("en-US",{style:"currency",currency:K,minimumFractionDigits:0,maximumFractionDigits:0}).format(J)}function QJ({isOpen:J,deal:K,stages:N,onClose:G,onWin:z,onLose:P,onMove:O,isLoading:A=!1}){let[H,Q]=g("menu"),[Y,_]=g(""),[W,V]=g(""),[T,b]=g(""),[q,Z]=g(""),[X,R]=g(null),k=()=>{Q("menu"),_(""),V(""),b(""),Z(""),R(null)},L=()=>{k(),G()},c=async()=>{if(!K)return;R(null);try{await z({dealId:K.id,wonSource:Y.trim()||void 0,notes:T.trim()||void 0}),L()}catch(F){R(F instanceof Error?F.message:"Failed to mark deal as won")}},n=async()=>{if(!K)return;if(R(null),!W.trim()){R("Please provide a reason for losing the deal");return}try{await P({dealId:K.id,lostReason:W.trim(),notes:T.trim()||void 0}),L()}catch(F){R(F instanceof Error?F.message:"Failed to mark deal as lost")}},M=async()=>{if(!K)return;if(R(null),!q){R("Please select a stage");return}if(q===K.stageId){R("Deal is already in this stage");return}try{await O({dealId:K.id,stageId:q}),L()}catch(F){R(F instanceof Error?F.message:"Failed to move deal")}};if(!J||!K)return null;return w("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[$("div",{className:"absolute inset-0 bg-background/80 backdrop-blur-sm",onClick:L,role:"button",tabIndex:0,onKeyDown:(F)=>{if(F.key==="Enter"||F.key===" ")L()},"aria-label":"Close modal"}),w("div",{className:"relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",children:[w("div",{className:"mb-4 border-border border-b pb-4",children:[$("h2",{className:"font-semibold text-xl",children:K.name}),$("p",{className:"font-medium text-lg text-primary",children:fJ(K.value,K.currency)}),$("span",{className:`mt-2 inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${K.status==="WON"?"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400":K.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:K.status})]}),H==="menu"&&w("div",{className:"space-y-3",children:[K.status==="OPEN"&&w(EJ,{children:[w(y,{className:"w-full justify-start",variant:"ghost",onPress:()=>Q("win"),children:[$("span",{className:"mr-2",children:"\uD83C\uDFC6"})," Mark as Won"]}),w(y,{className:"w-full justify-start",variant:"ghost",onPress:()=>Q("lose"),children:[$("span",{className:"mr-2",children:"❌"})," Mark as Lost"]}),w(y,{className:"w-full justify-start",variant:"ghost",onPress:()=>{Z(K.stageId),Q("move")},children:[$("span",{className:"mr-2",children:"➡️"})," Move to Stage"]})]}),K.status!=="OPEN"&&w("p",{className:"py-4 text-center text-muted-foreground",children:["This deal is already ",K.status.toLowerCase(),". No actions available."]}),$("div",{className:"border-border border-t pt-3",children:$(y,{className:"w-full",variant:"outline",onPress:L,children:"Close"})})]}),H==="win"&&w("div",{className:"space-y-4",children:[w("div",{children:[$("label",{htmlFor:"won-source",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"How did you win this deal?"}),w("select",{id:"won-source",value:Y,onChange:(F)=>_(F.target.value),className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",children:[$("option",{value:"",children:"Select a source..."}),$("option",{value:"referral",children:"Referral"}),$("option",{value:"cold_outreach",children:"Cold Outreach"}),$("option",{value:"inbound",children:"Inbound Lead"}),$("option",{value:"upsell",children:"Upsell"}),$("option",{value:"other",children:"Other"})]})]}),w("div",{children:[$("label",{htmlFor:"win-notes",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Notes (optional)"}),$("textarea",{id:"win-notes",value:T,onChange:(F)=>b(F.target.value),placeholder:"Any additional notes about the win...",rows:3,className:"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"})]}),X&&$("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:X}),w("div",{className:"flex justify-end gap-3 pt-2",children:[$(y,{variant:"ghost",onPress:()=>Q("menu"),disabled:A,children:"Back"}),$(y,{onPress:c,disabled:A,children:A?"Processing...":"\uD83C\uDFC6 Confirm Win"})]})]}),H==="lose"&&w("div",{className:"space-y-4",children:[w("div",{children:[$("label",{htmlFor:"lost-reason",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Why was this deal lost? *"}),w("select",{id:"lost-reason",value:W,onChange:(F)=>V(F.target.value),className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",children:[$("option",{value:"",children:"Select a reason..."}),$("option",{value:"price",children:"Price too high"}),$("option",{value:"competitor",children:"Lost to competitor"}),$("option",{value:"no_budget",children:"No budget"}),$("option",{value:"no_decision",children:"No decision made"}),$("option",{value:"timing",children:"Bad timing"}),$("option",{value:"product_fit",children:"Product not a fit"}),$("option",{value:"other",children:"Other"})]})]}),w("div",{children:[$("label",{htmlFor:"lose-notes",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Notes (optional)"}),$("textarea",{id:"lose-notes",value:T,onChange:(F)=>b(F.target.value),placeholder:"Any additional details...",rows:3,className:"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"})]}),X&&$("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:X}),w("div",{className:"flex justify-end gap-3 pt-2",children:[$(y,{variant:"ghost",onPress:()=>Q("menu"),disabled:A,children:"Back"}),$(y,{variant:"destructive",onPress:n,disabled:A,children:A?"Processing...":"❌ Confirm Loss"})]})]}),H==="move"&&w("div",{className:"space-y-4",children:[w("div",{children:[$("label",{htmlFor:"move-stage",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Move to Stage"}),$("select",{id:"move-stage",value:q,onChange:(F)=>Z(F.target.value),className:"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",children:N.map((F)=>w("option",{value:F.id,children:[F.name,F.id===K.stageId?" (current)":""]},F.id))})]}),X&&$("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:X}),w("div",{className:"flex justify-end gap-3 pt-2",children:[$(y,{variant:"ghost",onPress:()=>Q("menu"),disabled:A,children:"Back"}),$(y,{onPress:M,disabled:A,children:A?"Moving...":"➡️ Move Deal"})]})]})]})]})}import{Button as hJ,DataTable as TJ,LoaderBlock as BJ}from"@contractspec/lib.design-system";import{useContractTable as bJ}from"@contractspec/lib.presentation-runtime-react";import{Badge as MJ}from"@contractspec/lib.ui-kit-web/ui/badge";import{HStack as j,VStack as s}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as B}from"@contractspec/lib.ui-kit-web/ui/text";import*as e from"react";import{jsx as f,jsxs as I}from"react/jsx-runtime";function kJ(J,K="USD"){return new Intl.NumberFormat("en-US",{style:"currency",currency:K,minimumFractionDigits:0,maximumFractionDigits:0}).format(J)}function vJ(J){switch(J){case"WON":return"default";case"LOST":return"destructive";case"STALE":return"outline";default:return"secondary"}}function yJ({deals:J,totalItems:K,pageIndex:N,pageSize:G,sorting:z,loading:P,onSortingChange:O,onPaginationChange:A,onDealClick:H}){let Q=bJ({data:J,columns:[{id:"deal",header:"Deal",label:"Deal",accessor:(Y)=>Y.name,cell:({item:Y})=>I(s,{gap:"xs",children:[f(B,{className:"font-medium text-sm",children:Y.name}),f(B,{className:"text-muted-foreground text-xs",children:Y.companyId??"Unassigned company"})]}),size:240,minSize:180,canSort:!0,canPin:!0,canResize:!0},{id:"value",header:"Value",label:"Value",accessorKey:"value",cell:({item:Y})=>kJ(Y.value,Y.currency),align:"right",size:140,canSort:!0,canResize:!0},{id:"status",header:"Status",label:"Status",accessorKey:"status",cell:({value:Y})=>f(MJ,{variant:vJ(Y),children:String(Y)}),size:130,canSort:!0,canHide:!0,canPin:!0,canResize:!0},{id:"expectedCloseDate",header:"Expected Close",label:"Expected Close",accessor:(Y)=>Y.expectedCloseDate?.toISOString()??"",cell:({item:Y})=>Y.expectedCloseDate?.toLocaleDateString()??"Not scheduled",size:170,canSort:!0,canHide:!0,canResize:!0},{id:"updatedAt",header:"Updated",label:"Updated",accessor:(Y)=>Y.updatedAt.toISOString(),cell:({item:Y})=>Y.updatedAt.toLocaleDateString(),size:140,canSort:!0,canHide:!0,canResize:!0},{id:"actions",header:"Actions",label:"Actions",accessor:(Y)=>Y.id,cell:({item:Y})=>f(hJ,{variant:"ghost",size:"sm",onPress:()=>H?.(Y.id),children:"Actions"}),size:120,canSort:!1,canHide:!1,canPin:!1,canResize:!1}],executionMode:"server",selectionMode:"multiple",totalItems:K,state:{sorting:z,pagination:{pageIndex:N,pageSize:G}},onSortingChange:O,onPaginationChange:A,initialState:{columnVisibility:{updatedAt:!1},columnPinning:{left:["deal","status"],right:[]}},renderExpandedContent:(Y)=>I(s,{gap:"sm",className:"py-2",children:[I(j,{justify:"between",children:[f(B,{className:"font-medium text-sm",children:"Owner"}),f(B,{className:"text-muted-foreground text-sm",children:Y.ownerId})]}),I(j,{justify:"between",children:[f(B,{className:"font-medium text-sm",children:"Contact"}),f(B,{className:"text-muted-foreground text-sm",children:Y.contactId??"No linked contact"})]}),Y.wonSource?I(j,{justify:"between",children:[f(B,{className:"font-medium text-sm",children:"Won Source"}),f(B,{className:"text-muted-foreground text-sm",children:Y.wonSource})]}):null,Y.lostReason?I(j,{justify:"between",children:[f(B,{className:"font-medium text-sm",children:"Lost Reason"}),f(B,{className:"text-muted-foreground text-sm",children:Y.lostReason})]}):null,Y.notes?I(s,{gap:"xs",children:[f(B,{className:"font-medium text-sm",children:"Notes"}),f(B,{className:"text-muted-foreground text-sm",children:Y.notes})]}):null]}),getCanExpand:()=>!0});return f(TJ,{controller:Q,title:"All Deals",description:"Server-mode table using the shared ContractSpec controller.",loading:P,toolbar:I(j,{gap:"sm",className:"flex-wrap",children:[I(B,{className:"text-muted-foreground text-sm",children:["Selected ",Q.selectedRowIds.length]}),I(B,{className:"text-muted-foreground text-sm",children:[K," total deals"]})]}),footer:`Page ${Q.pageIndex+1} of ${Q.pageCount}`,emptyState:f("div",{className:"rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",children:"No deals found"})})}function zJ({onDealClick:J}){let[K,N]=e.useState([{id:"value",desc:!0}]),[G,z]=e.useState({pageIndex:0,pageSize:3}),{data:P,loading:O}=x({pageIndex:G.pageIndex,pageSize:G.pageSize,sorting:K});if(O&&!P)return f(BJ,{label:"Loading deals..."});return f(yJ,{deals:P?.deals??[],totalItems:P?.total??0,pageIndex:G.pageIndex,pageSize:G.pageSize,sorting:K,loading:O,onSortingChange:(A)=>{N(A),z((H)=>({...H,pageIndex:0}))},onPaginationChange:z,onDealClick:J})}import{Button as IJ,ErrorState as LJ,LoaderBlock as DJ,StatCard as r,StatCardGroup as CJ}from"@contractspec/lib.design-system";import{Tabs as mJ,TabsContent as JJ,TabsList as SJ,TabsTrigger as KJ}from"@contractspec/lib.ui-kit-web/ui/tabs";import{useCallback as FJ,useState as XJ}from"react";import{jsx as U,jsxs as h}from"react/jsx-runtime";function l(J,K="USD"){return new Intl.NumberFormat("en-US",{style:"currency",currency:K,minimumFractionDigits:0,maximumFractionDigits:0}).format(J)}function fK(){let[J,K]=XJ(!1),[N,G]=XJ(null),[z,P]=XJ(!1),{data:O,dealsByStage:A,stages:H,loading:Q,error:Y,stats:_,refetch:W}=x(),V=GJ({onSuccess:()=>{W()}}),T=FJ((q)=>{let Z=A?Object.values(A).flat().find((X)=>X.id===q):null;if(Z)G(Z),P(!0)},[A]),b=FJ(async(q,Z)=>{await V.moveDeal({dealId:q,stageId:Z})},[V]);if(Q&&!O)return U(DJ,{label:"Loading CRM..."});if(Y)return U(LJ,{title:"Failed to load CRM",description:Y.message,onRetry:W,retryLabel:"Retry"});return h("div",{className:"space-y-6",children:[h("div",{className:"flex items-center justify-between",children:[U("h2",{className:"font-bold text-2xl",children:"CRM Pipeline"}),h(IJ,{onClick:()=>K(!0),children:[U("span",{className:"mr-2",children:"+"})," Create Deal"]})]}),_&&h(CJ,{children:[U(r,{label:"Total Pipeline",value:l(_.totalValue),hint:`${_.total} deals`}),U(r,{label:"Open Deals",value:l(_.openValue),hint:`${_.openCount} active`}),U(r,{label:"Won",value:l(_.wonValue),hint:`${_.wonCount} closed`}),U(r,{label:"Lost",value:_.lostCount,hint:"deals lost"})]}),h(mJ,{defaultValue:"pipeline",className:"w-full",children:[h(SJ,{children:[h(KJ,{value:"pipeline",children:[U("span",{className:"mr-2",children:"\uD83D\uDCCA"}),"Pipeline"]}),h(KJ,{value:"list",children:[U("span",{className:"mr-2",children:"\uD83D\uDCCB"}),"All Deals"]}),h(KJ,{value:"metrics",children:[U("span",{className:"mr-2",children:"\uD83D\uDCC8"}),"Metrics"]})]}),U(JJ,{value:"pipeline",className:"min-h-[400px]",children:U($J,{dealsByStage:A,stages:H,onDealClick:T,onDealMove:b})}),U(JJ,{value:"list",className:"min-h-[400px]",children:U(zJ,{onDealClick:T})}),U(JJ,{value:"metrics",className:"min-h-[400px]",children:U(pJ,{stats:_})})]}),U(qJ,{isOpen:J,onClose:()=>K(!1),onSubmit:async(q)=>{await V.createDeal(q)},stages:H,isLoading:V.createState.loading}),U(QJ,{isOpen:z,deal:N,stages:H,onClose:()=>{P(!1),G(null)},onWin:async(q)=>{await V.winDeal(q)},onLose:async(q)=>{await V.loseDeal(q)},onMove:async(q)=>{await V.moveDeal(q),W()},isLoading:V.isLoading})]})}function pJ({stats:J}){if(!J)return null;return U("div",{className:"space-y-6",children:h("div",{className:"rounded-xl border border-border bg-card p-6",children:[U("h3",{className:"mb-4 font-semibold text-lg",children:"Pipeline Overview"}),h("dl",{className:"grid gap-4 sm:grid-cols-3",children:[h("div",{children:[U("dt",{className:"text-muted-foreground text-sm",children:"Win Rate"}),h("dd",{className:"font-semibold text-2xl",children:[J.total>0?(J.wonCount/J.total*100).toFixed(0):0,"%"]})]}),h("div",{children:[U("dt",{className:"text-muted-foreground text-sm",children:"Avg Deal Size"}),U("dd",{className:"font-semibold text-2xl",children:l(J.total>0?J.totalValue/J.total:0)})]}),h("div",{children:[U("dt",{className:"text-muted-foreground text-sm",children:"Conversion"}),h("dd",{className:"font-semibold text-2xl",children:[J.wonCount," / ",J.total]})]})]})]})})}export{fK as CrmDashboard};
|