@contractspec/example.crm-pipeline 3.7.6 → 3.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +45 -42
- package/AGENTS.md +51 -33
- package/CHANGELOG.md +36 -0
- package/README.md +67 -148
- package/dist/browser/docs/crm-pipeline.docblock.js +1 -1
- package/dist/browser/docs/index.js +1 -1
- package/dist/browser/events/contact.event.js +1 -1
- package/dist/browser/events/deal.event.js +1 -1
- package/dist/browser/events/index.js +3 -3
- package/dist/browser/events/task.event.js +1 -1
- package/dist/browser/handlers/crm.handlers.js +13 -2
- package/dist/browser/handlers/index.js +13 -2
- package/dist/browser/index.js +680 -447
- package/dist/browser/ui/CrmDashboard.js +574 -352
- package/dist/browser/ui/CrmDealCard.js +5 -5
- package/dist/browser/ui/CrmPipelineBoard.js +13 -13
- package/dist/browser/ui/hooks/index.js +21 -10
- package/dist/browser/ui/hooks/useDealList.js +20 -9
- package/dist/browser/ui/hooks/useDealMutations.js +1 -1
- package/dist/browser/ui/index.js +683 -450
- package/dist/browser/ui/modals/CreateDealModal.js +12 -12
- package/dist/browser/ui/modals/DealActionsModal.js +21 -21
- package/dist/browser/ui/modals/index.js +33 -33
- package/dist/browser/ui/renderers/index.js +140 -118
- package/dist/browser/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/browser/ui/renderers/pipeline.renderer.js +108 -97
- package/dist/browser/ui/tables/DealListTab.js +390 -0
- package/dist/deal/index.d.ts +2 -2
- package/dist/docs/crm-pipeline.docblock.js +1 -1
- package/dist/docs/index.js +1 -1
- package/dist/events/contact.event.js +1 -1
- package/dist/events/deal.event.js +1 -1
- package/dist/events/index.js +3 -3
- package/dist/events/task.event.js +1 -1
- package/dist/handlers/crm.handlers.d.ts +2 -0
- package/dist/handlers/crm.handlers.js +13 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +13 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +680 -447
- package/dist/node/docs/crm-pipeline.docblock.js +1 -1
- package/dist/node/docs/index.js +1 -1
- package/dist/node/events/contact.event.js +1 -1
- package/dist/node/events/deal.event.js +1 -1
- package/dist/node/events/index.js +3 -3
- package/dist/node/events/task.event.js +1 -1
- package/dist/node/handlers/crm.handlers.js +13 -2
- package/dist/node/handlers/index.js +13 -2
- package/dist/node/index.js +680 -447
- package/dist/node/ui/CrmDashboard.js +574 -352
- package/dist/node/ui/CrmDealCard.js +5 -5
- package/dist/node/ui/CrmPipelineBoard.js +13 -13
- package/dist/node/ui/hooks/index.js +21 -10
- package/dist/node/ui/hooks/useDealList.js +20 -9
- package/dist/node/ui/hooks/useDealMutations.js +1 -1
- package/dist/node/ui/index.js +683 -450
- package/dist/node/ui/modals/CreateDealModal.js +12 -12
- package/dist/node/ui/modals/DealActionsModal.js +21 -21
- package/dist/node/ui/modals/index.js +33 -33
- package/dist/node/ui/renderers/index.js +140 -118
- package/dist/node/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/node/ui/renderers/pipeline.renderer.js +108 -97
- package/dist/node/ui/tables/DealListTab.js +390 -0
- package/dist/operations/index.d.ts +1 -1
- package/dist/ui/CrmDashboard.js +574 -352
- package/dist/ui/CrmDealCard.js +5 -5
- package/dist/ui/CrmPipelineBoard.js +13 -13
- package/dist/ui/hooks/index.d.ts +2 -2
- package/dist/ui/hooks/index.js +21 -10
- package/dist/ui/hooks/useDealList.d.ts +8 -2
- package/dist/ui/hooks/useDealList.js +20 -9
- package/dist/ui/hooks/useDealMutations.d.ts +9 -0
- package/dist/ui/hooks/useDealMutations.js +1 -1
- package/dist/ui/index.d.ts +3 -3
- package/dist/ui/index.js +683 -450
- package/dist/ui/modals/CreateDealModal.js +12 -12
- package/dist/ui/modals/DealActionsModal.js +21 -21
- package/dist/ui/modals/index.js +33 -33
- package/dist/ui/renderers/index.d.ts +1 -1
- package/dist/ui/renderers/index.js +140 -118
- package/dist/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/ui/renderers/pipeline.renderer.d.ts +1 -1
- package/dist/ui/renderers/pipeline.renderer.js +108 -97
- package/dist/ui/tables/DealListTab.d.ts +20 -0
- package/dist/ui/tables/DealListTab.js +391 -0
- package/dist/ui/tables/DealListTab.smoke.test.d.ts +1 -0
- package/package.json +29 -14
- package/src/crm-pipeline.feature.ts +86 -86
- package/src/deal/deal.enum.ts +8 -8
- package/src/deal/deal.operation.ts +255 -255
- package/src/deal/deal.schema.ts +92 -92
- package/src/deal/deal.test-spec.ts +48 -48
- package/src/deal/index.ts +17 -19
- package/src/docs/crm-pipeline.docblock.ts +44 -44
- package/src/entities/company.entity.ts +52 -52
- package/src/entities/contact.entity.ts +67 -67
- package/src/entities/deal.entity.ts +134 -134
- package/src/entities/index.ts +27 -27
- package/src/entities/task.entity.ts +105 -105
- package/src/events/contact.event.ts +22 -22
- package/src/events/deal.event.ts +77 -77
- package/src/events/task.event.ts +19 -19
- package/src/example.ts +32 -32
- package/src/handlers/crm.handlers.ts +375 -357
- package/src/handlers/deal.handlers.ts +179 -179
- package/src/handlers/index.ts +18 -19
- package/src/handlers/mock-data.ts +167 -167
- package/src/index.ts +11 -11
- package/src/operations/index.ts +16 -16
- package/src/presentations/dashboard.presentation.ts +45 -45
- package/src/presentations/pipeline.presentation.ts +90 -90
- package/src/seeders/index.ts +26 -26
- package/src/shared/overlay-types.ts +23 -23
- package/src/ui/CrmDashboard.tsx +210 -279
- package/src/ui/CrmDealCard.tsx +64 -64
- package/src/ui/CrmPipelineBoard.tsx +105 -105
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useDealList.ts +113 -85
- package/src/ui/hooks/useDealMutations.ts +151 -150
- package/src/ui/index.ts +5 -10
- package/src/ui/modals/CreateDealModal.tsx +217 -217
- package/src/ui/modals/DealActionsModal.tsx +390 -390
- package/src/ui/overlays/demo-overlays.ts +43 -43
- package/src/ui/renderers/index.ts +4 -3
- package/src/ui/renderers/pipeline.markdown.ts +165 -165
- package/src/ui/renderers/pipeline.renderer.tsx +17 -16
- package/src/ui/tables/DealListTab.smoke.test.tsx +149 -0
- package/src/ui/tables/DealListTab.tsx +276 -0
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -3
|
@@ -1,17 +1,180 @@
|
|
|
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
|
+
|
|
1
159
|
// src/ui/hooks/useDealList.ts
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
160
|
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
161
|
+
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
4
162
|
"use client";
|
|
5
163
|
function useDealList(options = {}) {
|
|
6
164
|
const { handlers, projectId } = useTemplateRuntime();
|
|
7
165
|
const { crm } = handlers;
|
|
8
|
-
const [data, setData] =
|
|
9
|
-
const [dealsByStage, setDealsByStage] =
|
|
10
|
-
const [stages, setStages] =
|
|
11
|
-
const [loading, setLoading] =
|
|
12
|
-
const [error, setError] =
|
|
13
|
-
const [
|
|
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);
|
|
14
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;
|
|
15
178
|
const fetchData = useCallback(async () => {
|
|
16
179
|
setLoading(true);
|
|
17
180
|
setError(null);
|
|
@@ -23,8 +186,10 @@ function useDealList(options = {}) {
|
|
|
23
186
|
stageId: options.stageId,
|
|
24
187
|
status: options.status === "all" ? undefined : options.status,
|
|
25
188
|
search: options.search,
|
|
26
|
-
limit:
|
|
27
|
-
offset:
|
|
189
|
+
limit: pageSize,
|
|
190
|
+
offset: pageIndex * pageSize,
|
|
191
|
+
sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
|
|
192
|
+
sortDirection
|
|
28
193
|
}),
|
|
29
194
|
crm.getDealsByStage({ projectId, pipelineId }),
|
|
30
195
|
crm.getPipelineStages({ pipelineId })
|
|
@@ -44,8 +209,10 @@ function useDealList(options = {}) {
|
|
|
44
209
|
options.stageId,
|
|
45
210
|
options.status,
|
|
46
211
|
options.search,
|
|
47
|
-
|
|
48
|
-
|
|
212
|
+
pageIndex,
|
|
213
|
+
pageSize,
|
|
214
|
+
sortBy,
|
|
215
|
+
sortDirection
|
|
49
216
|
]);
|
|
50
217
|
useEffect(() => {
|
|
51
218
|
fetchData();
|
|
@@ -73,35 +240,37 @@ function useDealList(options = {}) {
|
|
|
73
240
|
loading,
|
|
74
241
|
error,
|
|
75
242
|
stats,
|
|
76
|
-
page,
|
|
243
|
+
page: pageIndex + 1,
|
|
244
|
+
pageIndex,
|
|
245
|
+
pageSize,
|
|
77
246
|
refetch: fetchData,
|
|
78
|
-
nextPage: () =>
|
|
79
|
-
prevPage: () =>
|
|
247
|
+
nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
|
|
248
|
+
prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
|
|
80
249
|
};
|
|
81
250
|
}
|
|
82
251
|
|
|
83
252
|
// src/ui/hooks/useDealMutations.ts
|
|
84
|
-
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
85
253
|
import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
|
|
254
|
+
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
86
255
|
function useDealMutations(options = {}) {
|
|
87
256
|
const { handlers, projectId } = useTemplateRuntime2();
|
|
88
257
|
const { crm } = handlers;
|
|
89
|
-
const [createState, setCreateState] =
|
|
258
|
+
const [createState, setCreateState] = useState3({
|
|
90
259
|
loading: false,
|
|
91
260
|
error: null,
|
|
92
261
|
data: null
|
|
93
262
|
});
|
|
94
|
-
const [moveState, setMoveState] =
|
|
263
|
+
const [moveState, setMoveState] = useState3({
|
|
95
264
|
loading: false,
|
|
96
265
|
error: null,
|
|
97
266
|
data: null
|
|
98
267
|
});
|
|
99
|
-
const [winState, setWinState] =
|
|
268
|
+
const [winState, setWinState] = useState3({
|
|
100
269
|
loading: false,
|
|
101
270
|
error: null,
|
|
102
271
|
data: null
|
|
103
272
|
});
|
|
104
|
-
const [loseState, setLoseState] =
|
|
273
|
+
const [loseState, setLoseState] = useState3({
|
|
105
274
|
loading: false,
|
|
106
275
|
error: null,
|
|
107
276
|
data: null
|
|
@@ -178,167 +347,9 @@ function useDealMutations(options = {}) {
|
|
|
178
347
|
};
|
|
179
348
|
}
|
|
180
349
|
|
|
181
|
-
// src/ui/CrmDealCard.tsx
|
|
182
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
183
|
-
"use client";
|
|
184
|
-
function formatCurrency(value, currency) {
|
|
185
|
-
return new Intl.NumberFormat("en-US", {
|
|
186
|
-
style: "currency",
|
|
187
|
-
currency,
|
|
188
|
-
minimumFractionDigits: 0,
|
|
189
|
-
maximumFractionDigits: 0
|
|
190
|
-
}).format(value);
|
|
191
|
-
}
|
|
192
|
-
function CrmDealCard({ deal, onClick }) {
|
|
193
|
-
const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
|
|
194
|
-
return /* @__PURE__ */ jsxDEV("div", {
|
|
195
|
-
onClick,
|
|
196
|
-
className: "border-border bg-card cursor-pointer rounded-lg border p-3 shadow-sm transition-shadow hover:shadow-md",
|
|
197
|
-
role: "button",
|
|
198
|
-
tabIndex: 0,
|
|
199
|
-
onKeyDown: (e) => {
|
|
200
|
-
if (e.key === "Enter" || e.key === " ")
|
|
201
|
-
onClick?.();
|
|
202
|
-
},
|
|
203
|
-
children: [
|
|
204
|
-
/* @__PURE__ */ jsxDEV("h4", {
|
|
205
|
-
className: "leading-snug font-medium",
|
|
206
|
-
children: deal.name
|
|
207
|
-
}, undefined, false, undefined, this),
|
|
208
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
209
|
-
className: "text-primary mt-2 text-lg font-semibold",
|
|
210
|
-
children: formatCurrency(deal.value, deal.currency)
|
|
211
|
-
}, undefined, false, undefined, this),
|
|
212
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
213
|
-
className: "text-muted-foreground mt-3 flex items-center justify-between text-xs",
|
|
214
|
-
children: [
|
|
215
|
-
daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
|
|
216
|
-
className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
|
|
217
|
-
children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
|
|
218
|
-
}, undefined, false, undefined, this),
|
|
219
|
-
/* @__PURE__ */ jsxDEV("span", {
|
|
220
|
-
className: `rounded px-1.5 py-0.5 text-xs font-medium ${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"}`,
|
|
221
|
-
children: deal.status
|
|
222
|
-
}, undefined, false, undefined, this)
|
|
223
|
-
]
|
|
224
|
-
}, undefined, true, undefined, this)
|
|
225
|
-
]
|
|
226
|
-
}, undefined, true, undefined, this);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// src/ui/CrmPipelineBoard.tsx
|
|
230
|
-
import { useState as useState3 } from "react";
|
|
231
|
-
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
232
|
-
"use client";
|
|
233
|
-
function formatCurrency2(value) {
|
|
234
|
-
if (value >= 1e6)
|
|
235
|
-
return `$${(value / 1e6).toFixed(1)}M`;
|
|
236
|
-
if (value >= 1000)
|
|
237
|
-
return `$${(value / 1000).toFixed(0)}K`;
|
|
238
|
-
return `$${value}`;
|
|
239
|
-
}
|
|
240
|
-
function CrmPipelineBoard({
|
|
241
|
-
dealsByStage,
|
|
242
|
-
stages,
|
|
243
|
-
onDealClick,
|
|
244
|
-
onDealMove
|
|
245
|
-
}) {
|
|
246
|
-
const [quickMoveOpen, setQuickMoveOpen] = useState3(null);
|
|
247
|
-
const sortedStages = [...stages].sort((a, b) => a.position - b.position);
|
|
248
|
-
const handleQuickMove = (dealId, toStageId) => {
|
|
249
|
-
onDealMove?.(dealId, toStageId);
|
|
250
|
-
setQuickMoveOpen(null);
|
|
251
|
-
};
|
|
252
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
253
|
-
className: "flex gap-4 overflow-x-auto pb-4",
|
|
254
|
-
children: sortedStages.map((stage) => {
|
|
255
|
-
const deals = dealsByStage[stage.id] ?? [];
|
|
256
|
-
const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
|
|
257
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
258
|
-
className: "bg-muted/30 flex w-72 flex-shrink-0 flex-col rounded-lg",
|
|
259
|
-
children: [
|
|
260
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
261
|
-
className: "border-border flex items-center justify-between border-b px-3 py-2",
|
|
262
|
-
children: [
|
|
263
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
264
|
-
children: [
|
|
265
|
-
/* @__PURE__ */ jsxDEV2("h3", {
|
|
266
|
-
className: "font-medium",
|
|
267
|
-
children: stage.name
|
|
268
|
-
}, undefined, false, undefined, this),
|
|
269
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
270
|
-
className: "text-muted-foreground text-xs",
|
|
271
|
-
children: [
|
|
272
|
-
deals.length,
|
|
273
|
-
" deals · ",
|
|
274
|
-
formatCurrency2(stageValue)
|
|
275
|
-
]
|
|
276
|
-
}, undefined, true, undefined, this)
|
|
277
|
-
]
|
|
278
|
-
}, undefined, true, undefined, this),
|
|
279
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
280
|
-
className: "bg-muted flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium",
|
|
281
|
-
children: deals.length
|
|
282
|
-
}, undefined, false, undefined, this)
|
|
283
|
-
]
|
|
284
|
-
}, undefined, true, undefined, this),
|
|
285
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
286
|
-
className: "flex flex-1 flex-col gap-2 p-2",
|
|
287
|
-
children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
|
|
288
|
-
className: "border-muted-foreground/20 text-muted-foreground flex h-24 items-center justify-center rounded-md border-2 border-dashed text-xs",
|
|
289
|
-
children: "No deals"
|
|
290
|
-
}, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
|
|
291
|
-
className: "group relative",
|
|
292
|
-
children: [
|
|
293
|
-
/* @__PURE__ */ jsxDEV2(CrmDealCard, {
|
|
294
|
-
deal,
|
|
295
|
-
onClick: () => onDealClick?.(deal.id)
|
|
296
|
-
}, undefined, false, undefined, this),
|
|
297
|
-
deal.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
|
|
298
|
-
className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
|
|
299
|
-
children: [
|
|
300
|
-
/* @__PURE__ */ jsxDEV2("button", {
|
|
301
|
-
type: "button",
|
|
302
|
-
onClick: (e) => {
|
|
303
|
-
e.stopPropagation();
|
|
304
|
-
setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
|
|
305
|
-
},
|
|
306
|
-
className: "bg-background border-border hover:bg-muted flex h-6 w-6 items-center justify-center rounded border text-xs shadow-sm",
|
|
307
|
-
title: "Quick move",
|
|
308
|
-
children: "➡️"
|
|
309
|
-
}, undefined, false, undefined, this),
|
|
310
|
-
quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
|
|
311
|
-
className: "bg-card border-border absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border py-1 shadow-lg",
|
|
312
|
-
children: [
|
|
313
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
314
|
-
className: "text-muted-foreground px-3 py-1 text-xs font-medium",
|
|
315
|
-
children: "Move to:"
|
|
316
|
-
}, undefined, false, undefined, this),
|
|
317
|
-
sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
|
|
318
|
-
type: "button",
|
|
319
|
-
onClick: (e) => {
|
|
320
|
-
e.stopPropagation();
|
|
321
|
-
handleQuickMove(deal.id, s.id);
|
|
322
|
-
},
|
|
323
|
-
className: "hover:bg-muted w-full px-3 py-1.5 text-left text-sm",
|
|
324
|
-
children: s.name
|
|
325
|
-
}, s.id, false, undefined, this))
|
|
326
|
-
]
|
|
327
|
-
}, undefined, true, undefined, this)
|
|
328
|
-
]
|
|
329
|
-
}, undefined, true, undefined, this)
|
|
330
|
-
]
|
|
331
|
-
}, deal.id, true, undefined, this))
|
|
332
|
-
}, undefined, false, undefined, this)
|
|
333
|
-
]
|
|
334
|
-
}, stage.id, true, undefined, this);
|
|
335
|
-
})
|
|
336
|
-
}, undefined, false, undefined, this);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
350
|
// src/ui/modals/CreateDealModal.tsx
|
|
340
|
-
import { useState as useState4 } from "react";
|
|
341
351
|
import { Button, Input } from "@contractspec/lib.design-system";
|
|
352
|
+
import { useState as useState4 } from "react";
|
|
342
353
|
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
343
354
|
"use client";
|
|
344
355
|
var CURRENCIES = ["USD", "EUR", "GBP", "CAD"];
|
|
@@ -397,7 +408,7 @@ function CreateDealModal({
|
|
|
397
408
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
398
409
|
children: [
|
|
399
410
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
400
|
-
className: "bg-background/80
|
|
411
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
401
412
|
onClick: onClose,
|
|
402
413
|
role: "button",
|
|
403
414
|
tabIndex: 0,
|
|
@@ -408,10 +419,10 @@ function CreateDealModal({
|
|
|
408
419
|
"aria-label": "Close modal"
|
|
409
420
|
}, undefined, false, undefined, this),
|
|
410
421
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
411
|
-
className: "
|
|
422
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
412
423
|
children: [
|
|
413
424
|
/* @__PURE__ */ jsxDEV3("h2", {
|
|
414
|
-
className: "mb-4 text-xl
|
|
425
|
+
className: "mb-4 font-semibold text-xl",
|
|
415
426
|
children: "Create New Deal"
|
|
416
427
|
}, undefined, false, undefined, this),
|
|
417
428
|
/* @__PURE__ */ jsxDEV3("form", {
|
|
@@ -422,7 +433,7 @@ function CreateDealModal({
|
|
|
422
433
|
children: [
|
|
423
434
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
424
435
|
htmlFor: "deal-name",
|
|
425
|
-
className: "
|
|
436
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
426
437
|
children: "Deal Name *"
|
|
427
438
|
}, undefined, false, undefined, this),
|
|
428
439
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -442,7 +453,7 @@ function CreateDealModal({
|
|
|
442
453
|
children: [
|
|
443
454
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
444
455
|
htmlFor: "deal-value",
|
|
445
|
-
className: "
|
|
456
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
446
457
|
children: "Value *"
|
|
447
458
|
}, undefined, false, undefined, this),
|
|
448
459
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -462,7 +473,7 @@ function CreateDealModal({
|
|
|
462
473
|
children: [
|
|
463
474
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
464
475
|
htmlFor: "deal-currency",
|
|
465
|
-
className: "
|
|
476
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
466
477
|
children: "Currency"
|
|
467
478
|
}, undefined, false, undefined, this),
|
|
468
479
|
/* @__PURE__ */ jsxDEV3("select", {
|
|
@@ -470,7 +481,7 @@ function CreateDealModal({
|
|
|
470
481
|
value: currency,
|
|
471
482
|
onChange: (e) => setCurrency(e.target.value),
|
|
472
483
|
disabled: isLoading,
|
|
473
|
-
className: "
|
|
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",
|
|
474
485
|
children: CURRENCIES.map((c) => /* @__PURE__ */ jsxDEV3("option", {
|
|
475
486
|
value: c,
|
|
476
487
|
children: c
|
|
@@ -484,7 +495,7 @@ function CreateDealModal({
|
|
|
484
495
|
children: [
|
|
485
496
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
486
497
|
htmlFor: "deal-stage",
|
|
487
|
-
className: "
|
|
498
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
488
499
|
children: "Pipeline Stage *"
|
|
489
500
|
}, undefined, false, undefined, this),
|
|
490
501
|
/* @__PURE__ */ jsxDEV3("select", {
|
|
@@ -492,7 +503,7 @@ function CreateDealModal({
|
|
|
492
503
|
value: stageId,
|
|
493
504
|
onChange: (e) => setStageId(e.target.value),
|
|
494
505
|
disabled: isLoading,
|
|
495
|
-
className: "
|
|
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",
|
|
496
507
|
children: stages.map((stage) => /* @__PURE__ */ jsxDEV3("option", {
|
|
497
508
|
value: stage.id,
|
|
498
509
|
children: stage.name
|
|
@@ -504,7 +515,7 @@ function CreateDealModal({
|
|
|
504
515
|
children: [
|
|
505
516
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
506
517
|
htmlFor: "deal-close-date",
|
|
507
|
-
className: "
|
|
518
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
508
519
|
children: "Expected Close Date"
|
|
509
520
|
}, undefined, false, undefined, this),
|
|
510
521
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -517,7 +528,7 @@ function CreateDealModal({
|
|
|
517
528
|
]
|
|
518
529
|
}, undefined, true, undefined, this),
|
|
519
530
|
error && /* @__PURE__ */ jsxDEV3("div", {
|
|
520
|
-
className: "bg-destructive/10
|
|
531
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
521
532
|
children: error
|
|
522
533
|
}, undefined, false, undefined, this),
|
|
523
534
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
@@ -546,8 +557,8 @@ function CreateDealModal({
|
|
|
546
557
|
}
|
|
547
558
|
|
|
548
559
|
// src/ui/modals/DealActionsModal.tsx
|
|
549
|
-
import { useState as useState5 } from "react";
|
|
550
560
|
import { Button as Button2 } from "@contractspec/lib.design-system";
|
|
561
|
+
import { useState as useState5 } from "react";
|
|
551
562
|
import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
|
|
552
563
|
"use client";
|
|
553
564
|
function formatCurrency3(value, currency) {
|
|
@@ -648,7 +659,7 @@ function DealActionsModal({
|
|
|
648
659
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
649
660
|
children: [
|
|
650
661
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
651
|
-
className: "bg-background/80
|
|
662
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
652
663
|
onClick: handleClose,
|
|
653
664
|
role: "button",
|
|
654
665
|
tabIndex: 0,
|
|
@@ -659,21 +670,21 @@ function DealActionsModal({
|
|
|
659
670
|
"aria-label": "Close modal"
|
|
660
671
|
}, undefined, false, undefined, this),
|
|
661
672
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
662
|
-
className: "
|
|
673
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
663
674
|
children: [
|
|
664
675
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
665
|
-
className: "
|
|
676
|
+
className: "mb-4 border-border border-b pb-4",
|
|
666
677
|
children: [
|
|
667
678
|
/* @__PURE__ */ jsxDEV4("h2", {
|
|
668
|
-
className: "text-xl
|
|
679
|
+
className: "font-semibold text-xl",
|
|
669
680
|
children: deal.name
|
|
670
681
|
}, undefined, false, undefined, this),
|
|
671
682
|
/* @__PURE__ */ jsxDEV4("p", {
|
|
672
|
-
className: "
|
|
683
|
+
className: "font-medium text-lg text-primary",
|
|
673
684
|
children: formatCurrency3(deal.value, deal.currency)
|
|
674
685
|
}, undefined, false, undefined, this),
|
|
675
686
|
/* @__PURE__ */ jsxDEV4("span", {
|
|
676
|
-
className: `mt-2 inline-flex rounded-full px-2 py-0.5 text-xs
|
|
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"}`,
|
|
677
688
|
children: deal.status
|
|
678
689
|
}, undefined, false, undefined, this)
|
|
679
690
|
]
|
|
@@ -725,7 +736,7 @@ function DealActionsModal({
|
|
|
725
736
|
]
|
|
726
737
|
}, undefined, true, undefined, this),
|
|
727
738
|
deal.status !== "OPEN" && /* @__PURE__ */ jsxDEV4("p", {
|
|
728
|
-
className: "
|
|
739
|
+
className: "py-4 text-center text-muted-foreground",
|
|
729
740
|
children: [
|
|
730
741
|
"This deal is already ",
|
|
731
742
|
deal.status.toLowerCase(),
|
|
@@ -750,14 +761,14 @@ function DealActionsModal({
|
|
|
750
761
|
children: [
|
|
751
762
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
752
763
|
htmlFor: "won-source",
|
|
753
|
-
className: "
|
|
764
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
754
765
|
children: "How did you win this deal?"
|
|
755
766
|
}, undefined, false, undefined, this),
|
|
756
767
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
757
768
|
id: "won-source",
|
|
758
769
|
value: wonSource,
|
|
759
770
|
onChange: (e) => setWonSource(e.target.value),
|
|
760
|
-
className: "
|
|
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",
|
|
761
772
|
children: [
|
|
762
773
|
/* @__PURE__ */ jsxDEV4("option", {
|
|
763
774
|
value: "",
|
|
@@ -791,7 +802,7 @@ function DealActionsModal({
|
|
|
791
802
|
children: [
|
|
792
803
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
793
804
|
htmlFor: "win-notes",
|
|
794
|
-
className: "
|
|
805
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
795
806
|
children: "Notes (optional)"
|
|
796
807
|
}, undefined, false, undefined, this),
|
|
797
808
|
/* @__PURE__ */ jsxDEV4("textarea", {
|
|
@@ -800,12 +811,12 @@ function DealActionsModal({
|
|
|
800
811
|
onChange: (e) => setNotes(e.target.value),
|
|
801
812
|
placeholder: "Any additional notes about the win...",
|
|
802
813
|
rows: 3,
|
|
803
|
-
className: "
|
|
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"
|
|
804
815
|
}, undefined, false, undefined, this)
|
|
805
816
|
]
|
|
806
817
|
}, undefined, true, undefined, this),
|
|
807
818
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
808
|
-
className: "bg-destructive/10
|
|
819
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
809
820
|
children: error
|
|
810
821
|
}, undefined, false, undefined, this),
|
|
811
822
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -833,14 +844,14 @@ function DealActionsModal({
|
|
|
833
844
|
children: [
|
|
834
845
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
835
846
|
htmlFor: "lost-reason",
|
|
836
|
-
className: "
|
|
847
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
837
848
|
children: "Why was this deal lost? *"
|
|
838
849
|
}, undefined, false, undefined, this),
|
|
839
850
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
840
851
|
id: "lost-reason",
|
|
841
852
|
value: lostReason,
|
|
842
853
|
onChange: (e) => setLostReason(e.target.value),
|
|
843
|
-
className: "
|
|
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",
|
|
844
855
|
children: [
|
|
845
856
|
/* @__PURE__ */ jsxDEV4("option", {
|
|
846
857
|
value: "",
|
|
@@ -882,7 +893,7 @@ function DealActionsModal({
|
|
|
882
893
|
children: [
|
|
883
894
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
884
895
|
htmlFor: "lose-notes",
|
|
885
|
-
className: "
|
|
896
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
886
897
|
children: "Notes (optional)"
|
|
887
898
|
}, undefined, false, undefined, this),
|
|
888
899
|
/* @__PURE__ */ jsxDEV4("textarea", {
|
|
@@ -891,12 +902,12 @@ function DealActionsModal({
|
|
|
891
902
|
onChange: (e) => setNotes(e.target.value),
|
|
892
903
|
placeholder: "Any additional details...",
|
|
893
904
|
rows: 3,
|
|
894
|
-
className: "
|
|
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"
|
|
895
906
|
}, undefined, false, undefined, this)
|
|
896
907
|
]
|
|
897
908
|
}, undefined, true, undefined, this),
|
|
898
909
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
899
|
-
className: "bg-destructive/10
|
|
910
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
900
911
|
children: error
|
|
901
912
|
}, undefined, false, undefined, this),
|
|
902
913
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -925,14 +936,14 @@ function DealActionsModal({
|
|
|
925
936
|
children: [
|
|
926
937
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
927
938
|
htmlFor: "move-stage",
|
|
928
|
-
className: "
|
|
939
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
929
940
|
children: "Move to Stage"
|
|
930
941
|
}, undefined, false, undefined, this),
|
|
931
942
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
932
943
|
id: "move-stage",
|
|
933
944
|
value: selectedStageId,
|
|
934
945
|
onChange: (e) => setSelectedStageId(e.target.value),
|
|
935
|
-
className: "
|
|
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",
|
|
936
947
|
children: stages.map((stage) => /* @__PURE__ */ jsxDEV4("option", {
|
|
937
948
|
value: stage.id,
|
|
938
949
|
children: [
|
|
@@ -944,7 +955,7 @@ function DealActionsModal({
|
|
|
944
955
|
]
|
|
945
956
|
}, undefined, true, undefined, this),
|
|
946
957
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
947
|
-
className: "bg-destructive/10
|
|
958
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
948
959
|
children: error
|
|
949
960
|
}, undefined, false, undefined, this),
|
|
950
961
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -971,12 +982,305 @@ function DealActionsModal({
|
|
|
971
982
|
}, undefined, true, undefined, this);
|
|
972
983
|
}
|
|
973
984
|
|
|
974
|
-
// src/ui/
|
|
975
|
-
import { useCallback as useCallback3, useState as useState6 } from "react";
|
|
985
|
+
// src/ui/tables/DealListTab.tsx
|
|
976
986
|
import {
|
|
977
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,
|
|
978
1282
|
ErrorState,
|
|
979
|
-
LoaderBlock,
|
|
1283
|
+
LoaderBlock as LoaderBlock2,
|
|
980
1284
|
StatCard,
|
|
981
1285
|
StatCardGroup
|
|
982
1286
|
} from "@contractspec/lib.design-system";
|
|
@@ -986,9 +1290,10 @@ import {
|
|
|
986
1290
|
TabsList,
|
|
987
1291
|
TabsTrigger
|
|
988
1292
|
} from "@contractspec/lib.ui-kit-web/ui/tabs";
|
|
989
|
-
import {
|
|
1293
|
+
import { useCallback as useCallback3, useState as useState7 } from "react";
|
|
1294
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
990
1295
|
"use client";
|
|
991
|
-
function
|
|
1296
|
+
function formatCurrency5(value, currency = "USD") {
|
|
992
1297
|
return new Intl.NumberFormat("en-US", {
|
|
993
1298
|
style: "currency",
|
|
994
1299
|
currency,
|
|
@@ -997,9 +1302,9 @@ function formatCurrency4(value, currency = "USD") {
|
|
|
997
1302
|
}).format(value);
|
|
998
1303
|
}
|
|
999
1304
|
function CrmDashboard() {
|
|
1000
|
-
const [isCreateModalOpen, setIsCreateModalOpen] =
|
|
1001
|
-
const [selectedDeal, setSelectedDeal] =
|
|
1002
|
-
const [isDealActionsOpen, setIsDealActionsOpen] =
|
|
1305
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState7(false);
|
|
1306
|
+
const [selectedDeal, setSelectedDeal] = useState7(null);
|
|
1307
|
+
const [isDealActionsOpen, setIsDealActionsOpen] = useState7(false);
|
|
1003
1308
|
const { data, dealsByStage, stages, loading, error, stats, refetch } = useDealList();
|
|
1004
1309
|
const mutations = useDealMutations({
|
|
1005
1310
|
onSuccess: () => {
|
|
@@ -1017,32 +1322,32 @@ function CrmDashboard() {
|
|
|
1017
1322
|
await mutations.moveDeal({ dealId, stageId: toStageId });
|
|
1018
1323
|
}, [mutations]);
|
|
1019
1324
|
if (loading && !data) {
|
|
1020
|
-
return /* @__PURE__ */
|
|
1325
|
+
return /* @__PURE__ */ jsxDEV6(LoaderBlock2, {
|
|
1021
1326
|
label: "Loading CRM..."
|
|
1022
1327
|
}, undefined, false, undefined, this);
|
|
1023
1328
|
}
|
|
1024
1329
|
if (error) {
|
|
1025
|
-
return /* @__PURE__ */
|
|
1330
|
+
return /* @__PURE__ */ jsxDEV6(ErrorState, {
|
|
1026
1331
|
title: "Failed to load CRM",
|
|
1027
1332
|
description: error.message,
|
|
1028
1333
|
onRetry: refetch,
|
|
1029
1334
|
retryLabel: "Retry"
|
|
1030
1335
|
}, undefined, false, undefined, this);
|
|
1031
1336
|
}
|
|
1032
|
-
return /* @__PURE__ */
|
|
1337
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1033
1338
|
className: "space-y-6",
|
|
1034
1339
|
children: [
|
|
1035
|
-
/* @__PURE__ */
|
|
1340
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1036
1341
|
className: "flex items-center justify-between",
|
|
1037
1342
|
children: [
|
|
1038
|
-
/* @__PURE__ */
|
|
1039
|
-
className: "text-2xl
|
|
1343
|
+
/* @__PURE__ */ jsxDEV6("h2", {
|
|
1344
|
+
className: "font-bold text-2xl",
|
|
1040
1345
|
children: "CRM Pipeline"
|
|
1041
1346
|
}, undefined, false, undefined, this),
|
|
1042
|
-
/* @__PURE__ */
|
|
1347
|
+
/* @__PURE__ */ jsxDEV6(Button4, {
|
|
1043
1348
|
onClick: () => setIsCreateModalOpen(true),
|
|
1044
1349
|
children: [
|
|
1045
|
-
/* @__PURE__ */
|
|
1350
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1046
1351
|
className: "mr-2",
|
|
1047
1352
|
children: "+"
|
|
1048
1353
|
}, undefined, false, undefined, this),
|
|
@@ -1051,60 +1356,60 @@ function CrmDashboard() {
|
|
|
1051
1356
|
}, undefined, true, undefined, this)
|
|
1052
1357
|
]
|
|
1053
1358
|
}, undefined, true, undefined, this),
|
|
1054
|
-
stats && /* @__PURE__ */
|
|
1359
|
+
stats && /* @__PURE__ */ jsxDEV6(StatCardGroup, {
|
|
1055
1360
|
children: [
|
|
1056
|
-
/* @__PURE__ */
|
|
1361
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1057
1362
|
label: "Total Pipeline",
|
|
1058
|
-
value:
|
|
1363
|
+
value: formatCurrency5(stats.totalValue),
|
|
1059
1364
|
hint: `${stats.total} deals`
|
|
1060
1365
|
}, undefined, false, undefined, this),
|
|
1061
|
-
/* @__PURE__ */
|
|
1366
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1062
1367
|
label: "Open Deals",
|
|
1063
|
-
value:
|
|
1368
|
+
value: formatCurrency5(stats.openValue),
|
|
1064
1369
|
hint: `${stats.openCount} active`
|
|
1065
1370
|
}, undefined, false, undefined, this),
|
|
1066
|
-
/* @__PURE__ */
|
|
1371
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1067
1372
|
label: "Won",
|
|
1068
|
-
value:
|
|
1373
|
+
value: formatCurrency5(stats.wonValue),
|
|
1069
1374
|
hint: `${stats.wonCount} closed`
|
|
1070
1375
|
}, undefined, false, undefined, this),
|
|
1071
|
-
/* @__PURE__ */
|
|
1376
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1072
1377
|
label: "Lost",
|
|
1073
1378
|
value: stats.lostCount,
|
|
1074
1379
|
hint: "deals lost"
|
|
1075
1380
|
}, undefined, false, undefined, this)
|
|
1076
1381
|
]
|
|
1077
1382
|
}, undefined, true, undefined, this),
|
|
1078
|
-
/* @__PURE__ */
|
|
1383
|
+
/* @__PURE__ */ jsxDEV6(Tabs, {
|
|
1079
1384
|
defaultValue: "pipeline",
|
|
1080
1385
|
className: "w-full",
|
|
1081
1386
|
children: [
|
|
1082
|
-
/* @__PURE__ */
|
|
1387
|
+
/* @__PURE__ */ jsxDEV6(TabsList, {
|
|
1083
1388
|
children: [
|
|
1084
|
-
/* @__PURE__ */
|
|
1389
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1085
1390
|
value: "pipeline",
|
|
1086
1391
|
children: [
|
|
1087
|
-
/* @__PURE__ */
|
|
1392
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1088
1393
|
className: "mr-2",
|
|
1089
1394
|
children: "\uD83D\uDCCA"
|
|
1090
1395
|
}, undefined, false, undefined, this),
|
|
1091
1396
|
"Pipeline"
|
|
1092
1397
|
]
|
|
1093
1398
|
}, undefined, true, undefined, this),
|
|
1094
|
-
/* @__PURE__ */
|
|
1399
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1095
1400
|
value: "list",
|
|
1096
1401
|
children: [
|
|
1097
|
-
/* @__PURE__ */
|
|
1402
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1098
1403
|
className: "mr-2",
|
|
1099
1404
|
children: "\uD83D\uDCCB"
|
|
1100
1405
|
}, undefined, false, undefined, this),
|
|
1101
1406
|
"All Deals"
|
|
1102
1407
|
]
|
|
1103
1408
|
}, undefined, true, undefined, this),
|
|
1104
|
-
/* @__PURE__ */
|
|
1409
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1105
1410
|
value: "metrics",
|
|
1106
1411
|
children: [
|
|
1107
|
-
/* @__PURE__ */
|
|
1412
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1108
1413
|
className: "mr-2",
|
|
1109
1414
|
children: "\uD83D\uDCC8"
|
|
1110
1415
|
}, undefined, false, undefined, this),
|
|
@@ -1113,34 +1418,33 @@ function CrmDashboard() {
|
|
|
1113
1418
|
}, undefined, true, undefined, this)
|
|
1114
1419
|
]
|
|
1115
1420
|
}, undefined, true, undefined, this),
|
|
1116
|
-
/* @__PURE__ */
|
|
1421
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1117
1422
|
value: "pipeline",
|
|
1118
1423
|
className: "min-h-[400px]",
|
|
1119
|
-
children: /* @__PURE__ */
|
|
1424
|
+
children: /* @__PURE__ */ jsxDEV6(CrmPipelineBoard, {
|
|
1120
1425
|
dealsByStage,
|
|
1121
1426
|
stages,
|
|
1122
1427
|
onDealClick: handleDealClick,
|
|
1123
1428
|
onDealMove: handleDealMove
|
|
1124
1429
|
}, undefined, false, undefined, this)
|
|
1125
1430
|
}, undefined, false, undefined, this),
|
|
1126
|
-
/* @__PURE__ */
|
|
1431
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1127
1432
|
value: "list",
|
|
1128
1433
|
className: "min-h-[400px]",
|
|
1129
|
-
children: /* @__PURE__ */
|
|
1130
|
-
data,
|
|
1434
|
+
children: /* @__PURE__ */ jsxDEV6(DealListTab, {
|
|
1131
1435
|
onDealClick: handleDealClick
|
|
1132
1436
|
}, undefined, false, undefined, this)
|
|
1133
1437
|
}, undefined, false, undefined, this),
|
|
1134
|
-
/* @__PURE__ */
|
|
1438
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1135
1439
|
value: "metrics",
|
|
1136
1440
|
className: "min-h-[400px]",
|
|
1137
|
-
children: /* @__PURE__ */
|
|
1441
|
+
children: /* @__PURE__ */ jsxDEV6(MetricsTab, {
|
|
1138
1442
|
stats
|
|
1139
1443
|
}, undefined, false, undefined, this)
|
|
1140
1444
|
}, undefined, false, undefined, this)
|
|
1141
1445
|
]
|
|
1142
1446
|
}, undefined, true, undefined, this),
|
|
1143
|
-
/* @__PURE__ */
|
|
1447
|
+
/* @__PURE__ */ jsxDEV6(CreateDealModal, {
|
|
1144
1448
|
isOpen: isCreateModalOpen,
|
|
1145
1449
|
onClose: () => setIsCreateModalOpen(false),
|
|
1146
1450
|
onSubmit: async (input) => {
|
|
@@ -1149,7 +1453,7 @@ function CrmDashboard() {
|
|
|
1149
1453
|
stages,
|
|
1150
1454
|
isLoading: mutations.createState.loading
|
|
1151
1455
|
}, undefined, false, undefined, this),
|
|
1152
|
-
/* @__PURE__ */
|
|
1456
|
+
/* @__PURE__ */ jsxDEV6(DealActionsModal, {
|
|
1153
1457
|
isOpen: isDealActionsOpen,
|
|
1154
1458
|
deal: selectedDeal,
|
|
1155
1459
|
stages,
|
|
@@ -1172,113 +1476,31 @@ function CrmDashboard() {
|
|
|
1172
1476
|
]
|
|
1173
1477
|
}, undefined, true, undefined, this);
|
|
1174
1478
|
}
|
|
1175
|
-
function DealListTab({ data, onDealClick }) {
|
|
1176
|
-
if (!data?.deals.length) {
|
|
1177
|
-
return /* @__PURE__ */ jsxDEV5("div", {
|
|
1178
|
-
className: "text-muted-foreground flex h-64 items-center justify-center",
|
|
1179
|
-
children: "No deals found"
|
|
1180
|
-
}, undefined, false, undefined, this);
|
|
1181
|
-
}
|
|
1182
|
-
return /* @__PURE__ */ jsxDEV5("div", {
|
|
1183
|
-
className: "border-border rounded-lg border",
|
|
1184
|
-
children: /* @__PURE__ */ jsxDEV5("table", {
|
|
1185
|
-
className: "w-full",
|
|
1186
|
-
children: [
|
|
1187
|
-
/* @__PURE__ */ jsxDEV5("thead", {
|
|
1188
|
-
className: "border-border bg-muted/30 border-b",
|
|
1189
|
-
children: /* @__PURE__ */ jsxDEV5("tr", {
|
|
1190
|
-
children: [
|
|
1191
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1192
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1193
|
-
children: "Deal"
|
|
1194
|
-
}, undefined, false, undefined, this),
|
|
1195
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1196
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1197
|
-
children: "Value"
|
|
1198
|
-
}, undefined, false, undefined, this),
|
|
1199
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1200
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1201
|
-
children: "Status"
|
|
1202
|
-
}, undefined, false, undefined, this),
|
|
1203
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1204
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1205
|
-
children: "Expected Close"
|
|
1206
|
-
}, undefined, false, undefined, this),
|
|
1207
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1208
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1209
|
-
children: "Actions"
|
|
1210
|
-
}, undefined, false, undefined, this)
|
|
1211
|
-
]
|
|
1212
|
-
}, undefined, true, undefined, this)
|
|
1213
|
-
}, undefined, false, undefined, this),
|
|
1214
|
-
/* @__PURE__ */ jsxDEV5("tbody", {
|
|
1215
|
-
className: "divide-border divide-y",
|
|
1216
|
-
children: data.deals.map((deal) => /* @__PURE__ */ jsxDEV5("tr", {
|
|
1217
|
-
className: "hover:bg-muted/50",
|
|
1218
|
-
children: [
|
|
1219
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1220
|
-
className: "px-4 py-3",
|
|
1221
|
-
children: /* @__PURE__ */ jsxDEV5("div", {
|
|
1222
|
-
className: "font-medium",
|
|
1223
|
-
children: deal.name
|
|
1224
|
-
}, undefined, false, undefined, this)
|
|
1225
|
-
}, undefined, false, undefined, this),
|
|
1226
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1227
|
-
className: "px-4 py-3 font-mono",
|
|
1228
|
-
children: formatCurrency4(deal.value, deal.currency)
|
|
1229
|
-
}, undefined, false, undefined, this),
|
|
1230
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1231
|
-
className: "px-4 py-3",
|
|
1232
|
-
children: /* @__PURE__ */ jsxDEV5("span", {
|
|
1233
|
-
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${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"}`,
|
|
1234
|
-
children: deal.status
|
|
1235
|
-
}, undefined, false, undefined, this)
|
|
1236
|
-
}, undefined, false, undefined, this),
|
|
1237
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1238
|
-
className: "text-muted-foreground px-4 py-3",
|
|
1239
|
-
children: deal.expectedCloseDate?.toLocaleDateString() ?? "-"
|
|
1240
|
-
}, undefined, false, undefined, this),
|
|
1241
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1242
|
-
className: "px-4 py-3",
|
|
1243
|
-
children: /* @__PURE__ */ jsxDEV5(Button3, {
|
|
1244
|
-
variant: "ghost",
|
|
1245
|
-
size: "sm",
|
|
1246
|
-
onPress: () => onDealClick?.(deal.id),
|
|
1247
|
-
children: "Actions"
|
|
1248
|
-
}, undefined, false, undefined, this)
|
|
1249
|
-
}, undefined, false, undefined, this)
|
|
1250
|
-
]
|
|
1251
|
-
}, deal.id, true, undefined, this))
|
|
1252
|
-
}, undefined, false, undefined, this)
|
|
1253
|
-
]
|
|
1254
|
-
}, undefined, true, undefined, this)
|
|
1255
|
-
}, undefined, false, undefined, this);
|
|
1256
|
-
}
|
|
1257
1479
|
function MetricsTab({
|
|
1258
1480
|
stats
|
|
1259
1481
|
}) {
|
|
1260
1482
|
if (!stats)
|
|
1261
1483
|
return null;
|
|
1262
|
-
return /* @__PURE__ */
|
|
1484
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1263
1485
|
className: "space-y-6",
|
|
1264
|
-
children: /* @__PURE__ */
|
|
1265
|
-
className: "border-border bg-card
|
|
1486
|
+
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
1487
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
1266
1488
|
children: [
|
|
1267
|
-
/* @__PURE__ */
|
|
1268
|
-
className: "mb-4 text-lg
|
|
1489
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
1490
|
+
className: "mb-4 font-semibold text-lg",
|
|
1269
1491
|
children: "Pipeline Overview"
|
|
1270
1492
|
}, undefined, false, undefined, this),
|
|
1271
|
-
/* @__PURE__ */
|
|
1493
|
+
/* @__PURE__ */ jsxDEV6("dl", {
|
|
1272
1494
|
className: "grid gap-4 sm:grid-cols-3",
|
|
1273
1495
|
children: [
|
|
1274
|
-
/* @__PURE__ */
|
|
1496
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1275
1497
|
children: [
|
|
1276
|
-
/* @__PURE__ */
|
|
1498
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1277
1499
|
className: "text-muted-foreground text-sm",
|
|
1278
1500
|
children: "Win Rate"
|
|
1279
1501
|
}, undefined, false, undefined, this),
|
|
1280
|
-
/* @__PURE__ */
|
|
1281
|
-
className: "text-2xl
|
|
1502
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1503
|
+
className: "font-semibold text-2xl",
|
|
1282
1504
|
children: [
|
|
1283
1505
|
stats.total > 0 ? (stats.wonCount / stats.total * 100).toFixed(0) : 0,
|
|
1284
1506
|
"%"
|
|
@@ -1286,26 +1508,26 @@ function MetricsTab({
|
|
|
1286
1508
|
}, undefined, true, undefined, this)
|
|
1287
1509
|
]
|
|
1288
1510
|
}, undefined, true, undefined, this),
|
|
1289
|
-
/* @__PURE__ */
|
|
1511
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1290
1512
|
children: [
|
|
1291
|
-
/* @__PURE__ */
|
|
1513
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1292
1514
|
className: "text-muted-foreground text-sm",
|
|
1293
1515
|
children: "Avg Deal Size"
|
|
1294
1516
|
}, undefined, false, undefined, this),
|
|
1295
|
-
/* @__PURE__ */
|
|
1296
|
-
className: "text-2xl
|
|
1297
|
-
children:
|
|
1517
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1518
|
+
className: "font-semibold text-2xl",
|
|
1519
|
+
children: formatCurrency5(stats.total > 0 ? stats.totalValue / stats.total : 0)
|
|
1298
1520
|
}, undefined, false, undefined, this)
|
|
1299
1521
|
]
|
|
1300
1522
|
}, undefined, true, undefined, this),
|
|
1301
|
-
/* @__PURE__ */
|
|
1523
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1302
1524
|
children: [
|
|
1303
|
-
/* @__PURE__ */
|
|
1525
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1304
1526
|
className: "text-muted-foreground text-sm",
|
|
1305
1527
|
children: "Conversion"
|
|
1306
1528
|
}, undefined, false, undefined, this),
|
|
1307
|
-
/* @__PURE__ */
|
|
1308
|
-
className: "text-2xl
|
|
1529
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1530
|
+
className: "font-semibold text-2xl",
|
|
1309
1531
|
children: [
|
|
1310
1532
|
stats.wonCount,
|
|
1311
1533
|
" / ",
|