@elevasis/ui 2.3.0 → 2.4.0

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.
Files changed (48) hide show
  1. package/dist/{chunk-F6RBK7NJ.js → chunk-22UVE3RA.js} +1 -1
  2. package/dist/{chunk-OCP2MBTY.js → chunk-27COZ5AH.js} +3 -118
  3. package/dist/chunk-2DZACNOX.js +1111 -0
  4. package/dist/chunk-3ONP2CEB.js +1842 -0
  5. package/dist/{chunk-ZY4MWZW2.js → chunk-5XGBMKUY.js} +3 -3
  6. package/dist/chunk-BZZCNLT6.js +12 -0
  7. package/dist/{chunk-SWIAK47F.js → chunk-G3G2QEB6.js} +5 -5
  8. package/dist/chunk-IDACMRGQ.js +115 -0
  9. package/dist/{chunk-2XWEOJSX.js → chunk-IPRMGSCV.js} +1 -1
  10. package/dist/chunk-J5KWNRSD.js +45 -0
  11. package/dist/{chunk-NKV5MEWQ.js → chunk-KRTZTBVP.js} +10 -8
  12. package/dist/{chunk-Q3FTQP2M.js → chunk-PEZ4WOPF.js} +1 -1
  13. package/dist/chunk-TUMSNGTX.js +35 -0
  14. package/dist/{chunk-PEATQEEP.js → chunk-WN764MR7.js} +2 -2
  15. package/dist/chunk-WSL5MNAI.js +955 -0
  16. package/dist/{chunk-IWFIKQR5.js → chunk-ZG7MLOBE.js} +1 -1
  17. package/dist/components/index.css +10 -14
  18. package/dist/components/index.js +46 -3922
  19. package/dist/features/auth/index.css +10 -14
  20. package/dist/features/crm/index.css +589 -0
  21. package/dist/features/crm/index.d.ts +2833 -0
  22. package/dist/features/crm/index.js +33 -0
  23. package/dist/features/dashboard/index.css +10 -14
  24. package/dist/features/dashboard/index.js +5 -5
  25. package/dist/features/delivery/index.css +589 -0
  26. package/dist/features/delivery/index.d.ts +2648 -0
  27. package/dist/features/delivery/index.js +33 -0
  28. package/dist/features/lead-gen/index.css +589 -0
  29. package/dist/features/lead-gen/index.d.ts +451 -0
  30. package/dist/features/lead-gen/index.js +42 -0
  31. package/dist/features/monitoring/index.css +10 -14
  32. package/dist/features/monitoring/index.js +6 -6
  33. package/dist/features/operations/index.css +10 -14
  34. package/dist/features/operations/index.js +10 -8
  35. package/dist/features/seo/index.d.ts +41 -0
  36. package/dist/features/seo/index.js +5 -0
  37. package/dist/features/settings/index.css +10 -14
  38. package/dist/features/settings/index.js +4 -4
  39. package/dist/graph/index.css +0 -14
  40. package/dist/graph/index.js +1 -1
  41. package/dist/hooks/index.css +10 -14
  42. package/dist/hooks/index.js +3 -3
  43. package/dist/hooks/published.css +10 -14
  44. package/dist/hooks/published.js +2 -2
  45. package/dist/index.css +10 -14
  46. package/dist/index.js +4 -4
  47. package/dist/layout/index.js +3 -1
  48. package/package.json +19 -3
@@ -0,0 +1,1111 @@
1
+ import { TableSelectionToolbar, SortableHeader } from './chunk-TUMSNGTX.js';
2
+ import { PageContainer } from './chunk-BZZCNLT6.js';
3
+ import { SubshellSidebarSection, SubshellNavItem } from './chunk-27COZ5AH.js';
4
+ import { FilterBar } from './chunk-PDHTXPSF.js';
5
+ import { CustomModal } from './chunk-GBMNCNHX.js';
6
+ import { useDealTasksDue, useDeals, useCreateDealTask, useDeleteDeal, useTableSort, sortData, usePaginationState, useTableSelection, useDealDetail } from './chunk-IPRMGSCV.js';
7
+ import { SubshellContentContainer } from './chunk-RX4UWZZR.js';
8
+ import { CenteredErrorState, CardHeader, PageTitleCaption, EmptyState, ActivityTimeline } from './chunk-Y3D3WFJG.js';
9
+ import { PAGE_SIZE_DEFAULT, formatTimeAgo } from './chunk-IOKL7BKE.js';
10
+ import { useElevasisServices } from './chunk-QEPXAWE2.js';
11
+ import { useRouterContext } from './chunk-Q7DJKLEN.js';
12
+ import { Box, Stack, Group, Text, Badge, Center, Loader, UnstyledButton, Button, Modal, Select, TextInput, Textarea, Paper, Alert, SimpleGrid, Table, Checkbox, Pagination, Title, ActionIcon, Tabs, Card, Code, Divider, Anchor } from '@mantine/core';
13
+ import { IconAddressBook, IconTrophy, IconClockExclamation, IconUser, IconPlus, IconLayoutGrid, IconColumns, IconFileInvoice, IconChecklist, IconAlertCircle, IconHistory, IconSearch, IconTargetArrow, IconAlertTriangle, IconArrowLeft, IconFileText, IconTrash, IconX, IconCheckbox, IconCalendar, IconMail, IconPhone, IconArrowRight, IconNote } from '@tabler/icons-react';
14
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
+ import { useState, useMemo, useEffect } from 'react';
16
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
17
+ import { useNavigate } from '@tanstack/react-router';
18
+
19
+ var CrmSidebarTop = () => {
20
+ return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconAddressBook, label: "CRM" });
21
+ };
22
+
23
+ // src/features/crm/workbench/constants.ts
24
+ var SAVED_VIEW_PRESETS = [
25
+ {
26
+ id: "my-deals",
27
+ label: "My Deals",
28
+ iconName: "IconUser",
29
+ target: "/crm/deals"
30
+ },
31
+ {
32
+ id: "overdue",
33
+ label: "Overdue",
34
+ iconName: "IconClockExclamation",
35
+ target: "/crm/deals"
36
+ },
37
+ {
38
+ id: "won-this-month",
39
+ label: "Won this month",
40
+ iconName: "IconTrophy",
41
+ target: "/crm/deals",
42
+ urlFilters: { stage: "closed_won" }
43
+ }
44
+ ];
45
+ var KIND_ICONS = {
46
+ call: IconPhone,
47
+ email: IconMail,
48
+ meeting: IconCalendar,
49
+ other: IconCheckbox
50
+ };
51
+ function formatDueLabel(dueAt) {
52
+ if (!dueAt) return "";
53
+ const date = new Date(dueAt);
54
+ const now = /* @__PURE__ */ new Date();
55
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
56
+ const dueDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
57
+ const diffDays = Math.round((dueDay.getTime() - today.getTime()) / 864e5);
58
+ if (diffDays < 0) return `${Math.abs(diffDays)}d overdue`;
59
+ if (diffDays === 0) {
60
+ return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
61
+ }
62
+ return `in ${diffDays}d`;
63
+ }
64
+ function TaskRow({ task, onClick }) {
65
+ const KindIcon2 = KIND_ICONS[task.kind];
66
+ const dueLabel = formatDueLabel(task.dueAt);
67
+ const isOverdue = task.dueAt !== null && new Date(task.dueAt) < /* @__PURE__ */ new Date();
68
+ return /* @__PURE__ */ jsx(
69
+ UnstyledButton,
70
+ {
71
+ onClick,
72
+ style: {
73
+ display: "block",
74
+ width: "100%",
75
+ padding: "4px 6px",
76
+ borderRadius: "var(--mantine-radius-sm)",
77
+ transition: `background-color var(--duration-fast) var(--easing)`
78
+ },
79
+ onMouseEnter: (e) => {
80
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
81
+ },
82
+ onMouseLeave: (e) => {
83
+ e.currentTarget.style.backgroundColor = "transparent";
84
+ },
85
+ children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
86
+ /* @__PURE__ */ jsx(KindIcon2, { size: 14, style: { color: "var(--color-text-dimmed)", flexShrink: 0 } }),
87
+ /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1, minWidth: 0 }, children: task.title }),
88
+ dueLabel && /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: isOverdue ? "red" : "gray", style: { flexShrink: 0 }, children: dueLabel })
89
+ ] })
90
+ }
91
+ );
92
+ }
93
+ function MyTasksPanel({
94
+ onTaskClick,
95
+ onSeeAll,
96
+ footer,
97
+ showSectionLabel = true
98
+ }) {
99
+ const { data, isLoading, isError } = useDealTasksDue({ window: "today_and_overdue" });
100
+ const tasks = data ?? [];
101
+ const total = tasks.length;
102
+ const visible = tasks.slice(0, 5);
103
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
104
+ showSectionLabel && /* @__PURE__ */ jsxs(Group, { justify: "space-between", mb: 4, children: [
105
+ /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, children: "My Tasks" }),
106
+ total > 0 && /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", children: total })
107
+ ] }),
108
+ isLoading && /* @__PURE__ */ jsx(Center, { py: 4, children: /* @__PURE__ */ jsx(Loader, { size: "xs" }) }),
109
+ isError && /* @__PURE__ */ jsx(Text, { size: "xs", c: "red", children: "Failed to load tasks" }),
110
+ !isLoading && !isError && visible.length === 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "No tasks due - you're caught up." }),
111
+ !isLoading && !isError && visible.map((task) => /* @__PURE__ */ jsx(TaskRow, { task, onClick: () => onTaskClick(task.dealId) }, task.id)),
112
+ onSeeAll && total > 5 && /* @__PURE__ */ jsx(UnstyledButton, { onClick: onSeeAll, mt: 2, style: { display: "inline-block" }, children: /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", style: { textDecoration: "underline" }, children: [
113
+ "See all (",
114
+ total,
115
+ ")"
116
+ ] }) }),
117
+ footer
118
+ ] }) });
119
+ }
120
+ var ICON_MAP = {
121
+ IconUser,
122
+ IconClockExclamation,
123
+ IconTrophy
124
+ };
125
+ function SavedViewsPanel({ onViewClick, showSectionLabel = true }) {
126
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
127
+ showSectionLabel && /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, mb: 4, children: "Saved Views" }),
128
+ SAVED_VIEW_PRESETS.map((preset) => {
129
+ const Icon = ICON_MAP[preset.iconName];
130
+ return /* @__PURE__ */ jsx(
131
+ UnstyledButton,
132
+ {
133
+ onClick: () => onViewClick(preset),
134
+ style: {
135
+ display: "block",
136
+ width: "100%",
137
+ padding: "4px 6px",
138
+ borderRadius: "var(--mantine-radius-sm)",
139
+ transition: `background-color var(--duration-fast) var(--easing)`
140
+ },
141
+ onMouseEnter: (e) => {
142
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
143
+ },
144
+ onMouseLeave: (e) => {
145
+ e.currentTarget.style.backgroundColor = "transparent";
146
+ },
147
+ children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
148
+ /* @__PURE__ */ jsx(Icon, { size: 14, style: { color: "var(--color-text-dimmed)", flexShrink: 0 } }),
149
+ /* @__PURE__ */ jsx(Text, { size: "xs", children: preset.label })
150
+ ] })
151
+ },
152
+ preset.id
153
+ );
154
+ })
155
+ ] }) });
156
+ }
157
+ var KIND_OPTIONS = [
158
+ { value: "call", label: "Call" },
159
+ { value: "email", label: "Email" },
160
+ { value: "meeting", label: "Meeting" },
161
+ { value: "other", label: "Other" }
162
+ ];
163
+ function buildDealLabel(deal) {
164
+ const contact = deal.contact;
165
+ if (!contact) return `Deal ${deal.id.slice(0, 8)}`;
166
+ const name = `${contact.first_name ?? ""} ${contact.last_name ?? ""}`.trim();
167
+ const company = contact.company?.name ?? "\u2014";
168
+ return name ? `${name} \u2013 ${company}` : `Deal ${deal.id.slice(0, 8)}`;
169
+ }
170
+ function QuickCreateActions({ showSectionLabel = true }) {
171
+ const [open, setOpen] = useState(false);
172
+ const [dealId, setDealId] = useState(null);
173
+ const [title, setTitle] = useState("");
174
+ const [description, setDescription] = useState("");
175
+ const [kind, setKind] = useState("other");
176
+ const [dueAt, setDueAt] = useState("");
177
+ const { data: deals } = useDeals();
178
+ const createTask = useCreateDealTask();
179
+ const dealOptions = (deals ?? []).map((deal) => ({
180
+ value: deal.id,
181
+ label: buildDealLabel(deal)
182
+ }));
183
+ function resetForm() {
184
+ setDealId(null);
185
+ setTitle("");
186
+ setDescription("");
187
+ setKind("other");
188
+ setDueAt("");
189
+ }
190
+ function handleClose() {
191
+ setOpen(false);
192
+ resetForm();
193
+ }
194
+ async function handleSubmit() {
195
+ if (!dealId || !title) return;
196
+ await createTask.mutateAsync({
197
+ dealId,
198
+ title,
199
+ description: description || null,
200
+ kind,
201
+ dueAt: dueAt ? new Date(dueAt).toISOString() : null
202
+ });
203
+ handleClose();
204
+ }
205
+ const isSubmitDisabled = !dealId || !title || createTask.isPending;
206
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
207
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
208
+ showSectionLabel && /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, children: "TASKS" }),
209
+ /* @__PURE__ */ jsx(Button, { variant: "light", size: "xs", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 14 }), fullWidth: true, onClick: () => setOpen(true), children: "New Task" })
210
+ ] }),
211
+ /* @__PURE__ */ jsxs(Modal, { opened: open, onClose: handleClose, title: "New Task", size: "md", children: [
212
+ /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
213
+ /* @__PURE__ */ jsx(
214
+ Select,
215
+ {
216
+ label: "Deal",
217
+ placeholder: "Select a deal",
218
+ data: dealOptions,
219
+ value: dealId,
220
+ onChange: setDealId,
221
+ searchable: true,
222
+ required: true
223
+ }
224
+ ),
225
+ /* @__PURE__ */ jsx(
226
+ TextInput,
227
+ {
228
+ label: "Title",
229
+ placeholder: "Task title",
230
+ value: title,
231
+ onChange: (e) => setTitle(e.currentTarget.value),
232
+ required: true
233
+ }
234
+ ),
235
+ /* @__PURE__ */ jsx(
236
+ Textarea,
237
+ {
238
+ label: "Description",
239
+ placeholder: "Optional description",
240
+ value: description,
241
+ onChange: (e) => setDescription(e.currentTarget.value),
242
+ autosize: true,
243
+ minRows: 2,
244
+ maxRows: 5
245
+ }
246
+ ),
247
+ /* @__PURE__ */ jsx(
248
+ Select,
249
+ {
250
+ label: "Kind",
251
+ data: KIND_OPTIONS,
252
+ value: kind,
253
+ onChange: (v) => setKind(v ?? "other")
254
+ }
255
+ ),
256
+ /* @__PURE__ */ jsx(
257
+ TextInput,
258
+ {
259
+ type: "datetime-local",
260
+ label: "Due At",
261
+ value: dueAt,
262
+ onChange: (e) => setDueAt(e.currentTarget.value)
263
+ }
264
+ )
265
+ ] }),
266
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
267
+ /* @__PURE__ */ jsx(Button, { variant: "default", onClick: handleClose, children: "Cancel" }),
268
+ /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, loading: createTask.isPending, disabled: isSubmitDisabled, children: "Create" })
269
+ ] })
270
+ ] })
271
+ ] });
272
+ }
273
+ var CRM_ITEMS = [
274
+ { label: "Overview", to: "/crm", icon: IconLayoutGrid, exact: true },
275
+ { label: "Pipeline", to: "/crm/pipeline", icon: IconColumns, exact: false },
276
+ { label: "Deals", to: "/crm/deals", icon: IconFileInvoice, exact: false }
277
+ ];
278
+ var CrmSidebarMiddle = () => {
279
+ const { currentPath, navigate } = useRouterContext();
280
+ return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { flex: 1, overflowY: "auto" }, children: [
281
+ /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: CRM_ITEMS.map((item) => {
282
+ const isActive = item.exact ? currentPath === item.to || currentPath === `${item.to}/` : currentPath.startsWith(item.to);
283
+ return /* @__PURE__ */ jsx(
284
+ SubshellNavItem,
285
+ {
286
+ icon: item.icon,
287
+ label: item.label,
288
+ isActive,
289
+ onClick: () => navigate(item.to)
290
+ },
291
+ item.to
292
+ );
293
+ }) }),
294
+ /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconChecklist, label: "My Tasks", withTopBorder: true }),
295
+ /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: /* @__PURE__ */ jsx(
296
+ MyTasksPanel,
297
+ {
298
+ onTaskClick: (dealId) => navigate(`/crm/deals/${dealId}`),
299
+ onSeeAll: () => navigate("/crm/deals"),
300
+ showSectionLabel: false,
301
+ footer: /* @__PURE__ */ jsx(QuickCreateActions, { showSectionLabel: false })
302
+ }
303
+ ) })
304
+ ] });
305
+ };
306
+ var CrmSidebar = () => {
307
+ return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
308
+ /* @__PURE__ */ jsx(CrmSidebarTop, {}),
309
+ /* @__PURE__ */ jsx(CrmSidebarMiddle, {})
310
+ ] });
311
+ };
312
+ var PIPELINE_FUNNEL_ORDER = [
313
+ "interested",
314
+ "proposal",
315
+ "closing",
316
+ "closed_won",
317
+ "closed_lost",
318
+ "nurturing"
319
+ ];
320
+ var defaultValueOf = (deal) => deal.initial_fee ?? 0;
321
+ function useCrmPipelineSummary(opts) {
322
+ const getValue = opts?.getDealValue ?? defaultValueOf;
323
+ const { data: deals, isLoading, error } = useDeals();
324
+ const data = useMemo(() => {
325
+ const dealList = deals ?? [];
326
+ const stageMap = /* @__PURE__ */ new Map();
327
+ for (const stage of PIPELINE_FUNNEL_ORDER) {
328
+ stageMap.set(stage, { count: 0, totalValue: 0 });
329
+ }
330
+ for (const deal of dealList) {
331
+ const stage = deal.cached_stage;
332
+ if (!stage || !stageMap.has(stage)) continue;
333
+ const entry = stageMap.get(stage);
334
+ entry.count += 1;
335
+ entry.totalValue += getValue(deal);
336
+ }
337
+ return PIPELINE_FUNNEL_ORDER.map((stage) => {
338
+ const entry = stageMap.get(stage);
339
+ return { stage, count: entry.count, totalValue: entry.totalValue };
340
+ });
341
+ }, [deals, getValue]);
342
+ return { data, isLoading, error };
343
+ }
344
+ var CLOSED_STAGES = ["closed_won", "closed_lost"];
345
+ var OPEN_EXCLUDED = CLOSED_STAGES;
346
+ var ZERO_METRICS = {
347
+ totalDeals: 0,
348
+ openDeals: 0,
349
+ wonDeals: 0,
350
+ winRate: 0,
351
+ avgDealSize: 0,
352
+ totalPipelineValue: 0
353
+ };
354
+ function useCrmQuickMetrics() {
355
+ const { data: deals, isLoading, error } = useDeals();
356
+ const data = useMemo(() => {
357
+ const dealList = deals ?? [];
358
+ if (dealList.length === 0) return ZERO_METRICS;
359
+ let openDeals = 0;
360
+ let wonDeals = 0;
361
+ let lostDeals = 0;
362
+ let wonFeeSum = 0;
363
+ let wonFeeCount = 0;
364
+ let pipelineValue = 0;
365
+ for (const deal of dealList) {
366
+ const stage = deal.cached_stage;
367
+ const isOpen = !stage || !OPEN_EXCLUDED.includes(stage);
368
+ const isWon = stage === "closed_won";
369
+ const isLost = stage === "closed_lost";
370
+ if (isOpen) {
371
+ openDeals += 1;
372
+ pipelineValue += deal.initial_fee ?? 0;
373
+ }
374
+ if (isWon) {
375
+ wonDeals += 1;
376
+ if (deal.initial_fee != null) {
377
+ wonFeeSum += deal.initial_fee;
378
+ wonFeeCount += 1;
379
+ }
380
+ }
381
+ if (isLost) {
382
+ lostDeals += 1;
383
+ }
384
+ }
385
+ const winRateDenominator = wonDeals + lostDeals;
386
+ const winRate = winRateDenominator === 0 ? 0 : wonDeals / winRateDenominator;
387
+ const avgDealSize = wonFeeCount === 0 ? 0 : wonFeeSum / wonFeeCount;
388
+ return {
389
+ totalDeals: dealList.length,
390
+ openDeals,
391
+ wonDeals,
392
+ winRate,
393
+ avgDealSize,
394
+ totalPipelineValue: pipelineValue
395
+ };
396
+ }, [deals]);
397
+ return { data, isLoading, error };
398
+ }
399
+ function useRecentCrmActivity(opts) {
400
+ const { apiRequest, isReady, organizationId } = useElevasisServices();
401
+ const limit = opts?.limit ?? 20;
402
+ const query = useQuery({
403
+ queryKey: ["recent-crm-activity", organizationId, limit],
404
+ queryFn: () => apiRequest(`/crm/recent-activity?limit=${limit}`),
405
+ enabled: isReady
406
+ });
407
+ return {
408
+ data: query.data?.entries ?? [],
409
+ isLoading: query.isLoading,
410
+ error: query.error
411
+ };
412
+ }
413
+ var currencyFormatter = new Intl.NumberFormat("en-US", {
414
+ style: "currency",
415
+ currency: "USD",
416
+ maximumFractionDigits: 0
417
+ });
418
+ var STAGE_LABELS = {
419
+ interested: "Interested",
420
+ proposal: "Proposal",
421
+ closing: "Closing",
422
+ closed_won: "Closed Won",
423
+ closed_lost: "Closed Lost",
424
+ nurturing: "Nurturing"
425
+ };
426
+ function PipelineFunnelWidget({ onStageClick, getDealValue }) {
427
+ const { data, isLoading, error } = useCrmPipelineSummary({ getDealValue });
428
+ if (isLoading) {
429
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
430
+ }
431
+ if (error) {
432
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load pipeline data" }) });
433
+ }
434
+ const totalDeals = data.reduce((sum, s) => sum + s.count, 0);
435
+ const maxCount = Math.max(...data.map((s) => s.count), 1);
436
+ if (totalDeals === 0) {
437
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
438
+ /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }), title: "Pipeline" }),
439
+ /* @__PURE__ */ jsx(Center, { h: 200, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No deals in the pipeline yet" }) })
440
+ ] });
441
+ }
442
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
443
+ /* @__PURE__ */ jsx(
444
+ CardHeader,
445
+ {
446
+ icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }),
447
+ title: "Pipeline",
448
+ subtitle: `${totalDeals} deal${totalDeals !== 1 ? "s" : ""} total`
449
+ }
450
+ ),
451
+ /* @__PURE__ */ jsx(Box, { children: PIPELINE_FUNNEL_ORDER.map((stage) => {
452
+ const summary = data.find((s) => s.stage === stage);
453
+ const isEmpty = summary.count === 0;
454
+ const barWidth = isEmpty ? 2 : Math.max(4, summary.count / maxCount * 100);
455
+ return /* @__PURE__ */ jsx(
456
+ Box,
457
+ {
458
+ onClick: () => onStageClick(stage),
459
+ style: {
460
+ cursor: "pointer",
461
+ borderRadius: "var(--mantine-radius-sm)",
462
+ padding: "6px 8px",
463
+ transition: `background-color var(--duration-fast) var(--easing)`,
464
+ marginBottom: 4
465
+ },
466
+ onMouseEnter: (e) => {
467
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
468
+ },
469
+ onMouseLeave: (e) => {
470
+ e.currentTarget.style.backgroundColor = "transparent";
471
+ },
472
+ children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", children: [
473
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: isEmpty ? "dimmed" : void 0, style: { width: 130, flexShrink: 0 }, children: STAGE_LABELS[stage] }),
474
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: isEmpty ? "gray" : void 0, style: { flexShrink: 0 }, children: summary.count }),
475
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "outline", color: isEmpty ? "gray" : "teal", style: { flexShrink: 0 }, children: currencyFormatter.format(summary.totalValue) }),
476
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx(
477
+ Box,
478
+ {
479
+ style: {
480
+ height: 8,
481
+ width: `${barWidth}%`,
482
+ borderRadius: 4,
483
+ backgroundColor: isEmpty ? "var(--color-border)" : "color-mix(in srgb, var(--color-primary) 70%, transparent)",
484
+ opacity: isEmpty ? 0.4 : 1,
485
+ transition: `width var(--duration-normal) var(--easing)`
486
+ }
487
+ }
488
+ ) })
489
+ ] })
490
+ },
491
+ stage
492
+ );
493
+ }) })
494
+ ] });
495
+ }
496
+ var MAX_VISIBLE = 5;
497
+ function KindIcon({ kind }) {
498
+ const size = 16;
499
+ switch (kind) {
500
+ case "call":
501
+ return /* @__PURE__ */ jsx(IconPhone, { size });
502
+ case "email":
503
+ return /* @__PURE__ */ jsx(IconMail, { size });
504
+ case "meeting":
505
+ return /* @__PURE__ */ jsx(IconCalendar, { size });
506
+ default:
507
+ return /* @__PURE__ */ jsx(IconCheckbox, { size });
508
+ }
509
+ }
510
+ function formatDueDate(dueAt) {
511
+ if (!dueAt) return "No due date";
512
+ return new Date(dueAt).toLocaleDateString();
513
+ }
514
+ function TasksDueWidget({ onTaskClick, onSeeAll }) {
515
+ const { data: tasks, isLoading, error } = useDealTasksDue({ window: "today_and_overdue" });
516
+ if (isLoading) {
517
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
518
+ }
519
+ if (error) {
520
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load tasks" }) });
521
+ }
522
+ const totalCount = tasks?.length ?? 0;
523
+ const visibleTasks = (tasks ?? []).slice(0, MAX_VISIBLE);
524
+ const hasMore = totalCount > MAX_VISIBLE;
525
+ const seeAllLink = onSeeAll && hasMore ? /* @__PURE__ */ jsxs(Anchor, { size: "sm", onClick: onSeeAll, style: { cursor: "pointer" }, children: [
526
+ "See all (",
527
+ totalCount,
528
+ ")"
529
+ ] }) : void 0;
530
+ if (totalCount === 0) {
531
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
532
+ /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconChecklist, { size: 16 }), title: "Tasks Due" }),
533
+ /* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No tasks due today" }) })
534
+ ] });
535
+ }
536
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
537
+ /* @__PURE__ */ jsx(
538
+ CardHeader,
539
+ {
540
+ icon: /* @__PURE__ */ jsx(IconChecklist, { size: 16 }),
541
+ title: "Tasks Due",
542
+ subtitle: `${totalCount} task${totalCount !== 1 ? "s" : ""}`,
543
+ rightSection: seeAllLink
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: visibleTasks.map((task) => /* @__PURE__ */ jsx(
547
+ Box,
548
+ {
549
+ onClick: () => onTaskClick(task.dealId),
550
+ style: {
551
+ cursor: "pointer",
552
+ borderRadius: "var(--mantine-radius-sm)",
553
+ padding: "6px 8px",
554
+ transition: `background-color var(--duration-fast) var(--easing)`
555
+ },
556
+ onMouseEnter: (e) => {
557
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
558
+ },
559
+ onMouseLeave: (e) => {
560
+ e.currentTarget.style.backgroundColor = "transparent";
561
+ },
562
+ children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", children: [
563
+ /* @__PURE__ */ jsx(Text, { c: "dimmed", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx(KindIcon, { kind: task.kind }) }),
564
+ /* @__PURE__ */ jsx(Text, { size: "sm", style: { flex: 1, minWidth: 0 }, truncate: true, children: task.title }),
565
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: "gray", style: { flexShrink: 0 }, children: formatDueDate(task.dueAt) })
566
+ ] })
567
+ },
568
+ task.id
569
+ )) })
570
+ ] });
571
+ }
572
+ function ActivityKindIcon({ kind }) {
573
+ const size = 16;
574
+ switch (kind) {
575
+ case "note":
576
+ return /* @__PURE__ */ jsx(IconNote, { size });
577
+ case "stage_change":
578
+ return /* @__PURE__ */ jsx(IconArrowRight, { size });
579
+ case "deal_created":
580
+ return /* @__PURE__ */ jsx(IconPlus, { size });
581
+ }
582
+ }
583
+ function formatRelativeTime(occurredAt) {
584
+ const date = new Date(occurredAt);
585
+ const now = /* @__PURE__ */ new Date();
586
+ const diffMs = now.getTime() - date.getTime();
587
+ const diffMin = Math.floor(diffMs / 6e4);
588
+ const diffHr = Math.floor(diffMin / 60);
589
+ const diffDay = Math.floor(diffHr / 24);
590
+ if (diffMin < 1) return "just now";
591
+ if (diffMin < 60) return `${diffMin}m ago`;
592
+ if (diffHr < 24) return `${diffHr}h ago`;
593
+ if (diffDay < 7) return `${diffDay}d ago`;
594
+ return date.toLocaleDateString();
595
+ }
596
+ function ActivityFeedWidget({ onDealClick, limit }) {
597
+ const { data, isLoading, error } = useRecentCrmActivity({ limit: limit ?? 15 });
598
+ if (isLoading) {
599
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
600
+ }
601
+ if (error) {
602
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load activity" }) });
603
+ }
604
+ if (!data.length) {
605
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
606
+ /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
607
+ /* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No recent activity" }) })
608
+ ] });
609
+ }
610
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
611
+ /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
612
+ /* @__PURE__ */ jsx(Stack, { gap: 4, children: data.map((entry) => {
613
+ const name = entry.contactName ?? entry.companyName ?? "Unknown";
614
+ return /* @__PURE__ */ jsx(
615
+ Box,
616
+ {
617
+ onClick: () => onDealClick(entry.dealId),
618
+ style: {
619
+ cursor: "pointer",
620
+ borderRadius: "var(--mantine-radius-sm)",
621
+ padding: "6px 8px",
622
+ transition: `background-color var(--duration-fast) var(--easing)`
623
+ },
624
+ onMouseEnter: (e) => {
625
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
626
+ },
627
+ onMouseLeave: (e) => {
628
+ e.currentTarget.style.backgroundColor = "transparent";
629
+ },
630
+ children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", align: "flex-start", children: [
631
+ /* @__PURE__ */ jsx(Text, { c: "dimmed", style: { flexShrink: 0, paddingTop: 2 }, children: /* @__PURE__ */ jsx(ActivityKindIcon, { kind: entry.kind }) }),
632
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
633
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, truncate: true, style: { flexShrink: 0, maxWidth: 140 }, children: name }),
634
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", truncate: true, style: { flex: 1, minWidth: 0 }, children: entry.description })
635
+ ] }) }),
636
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { flexShrink: 0, whiteSpace: "nowrap" }, children: formatRelativeTime(entry.occurredAt) })
637
+ ] })
638
+ },
639
+ entry.id
640
+ );
641
+ }) })
642
+ ] });
643
+ }
644
+ var currencyFormatter2 = new Intl.NumberFormat("en-US", {
645
+ style: "currency",
646
+ currency: "USD",
647
+ maximumFractionDigits: 0
648
+ });
649
+ function formatPercent(value) {
650
+ return `${Math.round(value * 100)}%`;
651
+ }
652
+ function StatTile({ label, value }) {
653
+ return /* @__PURE__ */ jsx(Card, { padding: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
654
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: label }),
655
+ /* @__PURE__ */ jsx(Text, { fw: 700, size: "xl", children: value })
656
+ ] }) });
657
+ }
658
+ function MetricsStrip() {
659
+ const { data } = useCrmQuickMetrics();
660
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 2, sm: 4 }, children: [
661
+ /* @__PURE__ */ jsx(StatTile, { label: "Total Pipeline Value", value: currencyFormatter2.format(data.totalPipelineValue) }),
662
+ /* @__PURE__ */ jsx(StatTile, { label: "Win Rate", value: formatPercent(data.winRate) }),
663
+ /* @__PURE__ */ jsx(StatTile, { label: "Open Deals", value: String(data.openDeals) }),
664
+ /* @__PURE__ */ jsx(StatTile, { label: "Won This Period", value: String(data.wonDeals) })
665
+ ] }) });
666
+ }
667
+ function CrmOverview({
668
+ onStageClick,
669
+ onDealClick,
670
+ onGoToPipeline,
671
+ getDealValue,
672
+ renderActions
673
+ }) {
674
+ const rightSection = renderActions ? renderActions() : /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconColumns, { size: 16 }), variant: "light", size: "sm", onClick: onGoToPipeline, children: "Go to Pipeline" });
675
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
676
+ /* @__PURE__ */ jsx(
677
+ PageTitleCaption,
678
+ {
679
+ title: "CRM Overview",
680
+ caption: "Pipeline health, tasks, and recent activity at a glance.",
681
+ rightSection
682
+ }
683
+ ),
684
+ /* @__PURE__ */ jsx(MetricsStrip, {}),
685
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, lg: 2 }, spacing: "md", children: [
686
+ /* @__PURE__ */ jsx(PipelineFunnelWidget, { onStageClick, getDealValue }),
687
+ /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
688
+ /* @__PURE__ */ jsx(TasksDueWidget, { onTaskClick: onDealClick }),
689
+ /* @__PURE__ */ jsx(ActivityFeedWidget, { onDealClick })
690
+ ] })
691
+ ] })
692
+ ] });
693
+ }
694
+ var crmManifest = {
695
+ key: "crm",
696
+ label: "CRM",
697
+ sidebar: CrmSidebar,
698
+ subshellRoutes: ["/crm"],
699
+ navEntry: {
700
+ label: "CRM",
701
+ icon: IconAddressBook,
702
+ link: "/crm",
703
+ featureKey: "acquisition"
704
+ }
705
+ };
706
+
707
+ // src/features/crm/pages/shared.ts
708
+ var DEAL_STAGE_COLORS = {
709
+ interested: "blue",
710
+ proposal: "yellow",
711
+ closing: "orange",
712
+ closed_won: "green",
713
+ closed_lost: "red",
714
+ nurturing: "grape"
715
+ };
716
+ var DEAL_STAGE_OPTIONS = [
717
+ { value: "interested", label: "Interested" },
718
+ { value: "proposal", label: "Proposal" },
719
+ { value: "closing", label: "Closing" },
720
+ { value: "closed_won", label: "Closed Won" },
721
+ { value: "closed_lost", label: "Closed Lost" },
722
+ { value: "nurturing", label: "Nurturing" }
723
+ ];
724
+ function formatDealStageLabel(stage) {
725
+ if (!stage) return "Unknown";
726
+ return stage.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
727
+ }
728
+ var sortAccessors = {
729
+ company: (deal) => deal.contact?.company?.name || deal.discovery_data?.company || deal.contact_email?.split("@")[1] || "",
730
+ contact: (deal) => [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ") || "",
731
+ email: (deal) => deal.contact_email || "",
732
+ stage: (deal) => deal.cached_stage || "",
733
+ updated: (deal) => deal.updated_at || ""
734
+ };
735
+ function DealsListPage() {
736
+ const navigate = useNavigate();
737
+ const queryClient = useQueryClient();
738
+ const deleteDeal = useDeleteDeal();
739
+ const [stageFilter, setStageFilter] = useState(null);
740
+ const [searchQuery, setSearchQuery] = useState("");
741
+ const [showBatchDelete, setShowBatchDelete] = useState(false);
742
+ const { data: deals, isLoading, error } = useDeals({
743
+ stage: stageFilter || void 0,
744
+ search: searchQuery || void 0
745
+ });
746
+ const { sort, toggleSort } = useTableSort("updated");
747
+ const sortedDeals = useMemo(() => sortData(deals ?? [], sort, sortAccessors), [deals, sort]);
748
+ const pagination = usePaginationState(PAGE_SIZE_DEFAULT, [stageFilter, searchQuery], sortedDeals.length);
749
+ const paginatedDeals = useMemo(
750
+ () => sortedDeals.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT),
751
+ [sortedDeals, pagination.offset]
752
+ );
753
+ const selection = useTableSelection(paginatedDeals, sortedDeals);
754
+ const handleDeleteSelected = async () => {
755
+ await Promise.all([...selection.selectedIds].map((dealId) => deleteDeal.mutateAsync(dealId)));
756
+ setShowBatchDelete(false);
757
+ selection.clear();
758
+ await queryClient.invalidateQueries({ queryKey: ["deals"] });
759
+ };
760
+ return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
761
+ /* @__PURE__ */ jsxs(PageContainer, { children: [
762
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Deals", caption: "Deal pipeline and stage tracking" }) }),
763
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
764
+ /* @__PURE__ */ jsxs(
765
+ FilterBar,
766
+ {
767
+ actions: /* @__PURE__ */ jsx(
768
+ TableSelectionToolbar,
769
+ {
770
+ selectedCount: selection.selectedCount,
771
+ onDelete: () => setShowBatchDelete(true),
772
+ isDeleting: deleteDeal.isPending
773
+ }
774
+ ),
775
+ children: [
776
+ /* @__PURE__ */ jsx(
777
+ Select,
778
+ {
779
+ placeholder: "All Stages",
780
+ data: DEAL_STAGE_OPTIONS,
781
+ style: { minWidth: 180 },
782
+ size: "sm",
783
+ clearable: true,
784
+ value: stageFilter,
785
+ onChange: (value) => setStageFilter(value ?? null)
786
+ }
787
+ ),
788
+ /* @__PURE__ */ jsx(
789
+ TextInput,
790
+ {
791
+ placeholder: "Search by email...",
792
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
793
+ style: { minWidth: 250 },
794
+ size: "sm",
795
+ value: searchQuery,
796
+ onChange: (e) => setSearchQuery(e.currentTarget.value)
797
+ }
798
+ )
799
+ ]
800
+ }
801
+ ),
802
+ isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : error ? /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load deals" }) : !sortedDeals.length ? /* @__PURE__ */ jsx(EmptyState, { icon: IconTargetArrow, title: "No deals found" }) : /* @__PURE__ */ jsxs(Table, { children: [
803
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
804
+ /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
805
+ Checkbox,
806
+ {
807
+ checked: selection.isPageAllSelected,
808
+ indeterminate: selection.isPagePartiallySelected,
809
+ onChange: selection.togglePage
810
+ }
811
+ ) }),
812
+ /* @__PURE__ */ jsx(SortableHeader, { column: "company", sort, onToggle: toggleSort, children: "Company" }),
813
+ /* @__PURE__ */ jsx(SortableHeader, { column: "contact", sort, onToggle: toggleSort, children: "Contact" }),
814
+ /* @__PURE__ */ jsx(SortableHeader, { column: "email", sort, onToggle: toggleSort, children: "Email" }),
815
+ /* @__PURE__ */ jsx(SortableHeader, { column: "stage", sort, onToggle: toggleSort, children: "Stage" }),
816
+ /* @__PURE__ */ jsx(SortableHeader, { column: "updated", sort, onToggle: toggleSort, children: "Updated" })
817
+ ] }) }),
818
+ /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedDeals.map((deal) => {
819
+ const discoveryData = deal.discovery_data;
820
+ const companyName = deal.contact?.company?.name || discoveryData?.company || deal.contact_email?.split("@")[1] || "-";
821
+ const contactName = [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ");
822
+ return /* @__PURE__ */ jsxs(
823
+ Table.Tr,
824
+ {
825
+ style: { cursor: "pointer" },
826
+ onClick: () => navigate({
827
+ to: "/crm/deals/$dealId",
828
+ params: { dealId: deal.id }
829
+ }),
830
+ children: [
831
+ /* @__PURE__ */ jsx(Table.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
832
+ Checkbox,
833
+ {
834
+ checked: selection.isSelected(deal.id),
835
+ onChange: () => selection.toggle(deal.id)
836
+ }
837
+ ) }),
838
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: companyName }) }),
839
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: contactName || "-" }) }),
840
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: deal.contact_email || "-" }) }),
841
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: DEAL_STAGE_COLORS[deal.cached_stage || ""] || "gray", size: "sm", children: formatDealStageLabel(deal.cached_stage) }) }),
842
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(deal.updated_at) }) })
843
+ ]
844
+ },
845
+ deal.id
846
+ );
847
+ }) })
848
+ ] }),
849
+ sortedDeals.length > PAGE_SIZE_DEFAULT && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
850
+ Pagination,
851
+ {
852
+ value: pagination.page,
853
+ onChange: pagination.setPage,
854
+ total: pagination.totalPages(sortedDeals.length),
855
+ size: "sm"
856
+ }
857
+ ) })
858
+ ] }) })
859
+ ] }),
860
+ /* @__PURE__ */ jsx(
861
+ CustomModal,
862
+ {
863
+ opened: showBatchDelete,
864
+ onClose: () => !deleteDeal.isPending && setShowBatchDelete(false),
865
+ size: "sm",
866
+ loading: deleteDeal.isPending,
867
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
868
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
869
+ /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
870
+ /* @__PURE__ */ jsxs(Title, { order: 4, children: [
871
+ "Delete ",
872
+ selection.selectedCount,
873
+ " Deals"
874
+ ] })
875
+ ] }),
876
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
877
+ "Are you sure you want to delete",
878
+ " ",
879
+ /* @__PURE__ */ jsx(Text, { span: true, fw: 600, children: selection.selectedCount }),
880
+ " ",
881
+ "selected deals?"
882
+ ] }),
883
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone." }),
884
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
885
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: () => setShowBatchDelete(false), disabled: deleteDeal.isPending, children: "Cancel" }),
886
+ /* @__PURE__ */ jsx(Button, { color: "red", loading: deleteDeal.isPending, onClick: () => void handleDeleteSelected(), children: "Delete" })
887
+ ] })
888
+ ] })
889
+ }
890
+ )
891
+ ] });
892
+ }
893
+ function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
894
+ const navigate = useNavigate();
895
+ const deleteDeal = useDeleteDeal();
896
+ const { data: deal, isLoading, error } = useDealDetail(dealId);
897
+ const [deleteModalOpen, setDeleteModalOpen] = useState(false);
898
+ useEffect(() => {
899
+ if (deal) onDealLoaded?.(deal);
900
+ }, [deal, onDealLoaded]);
901
+ const title = deal ? `${[deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ") || "Unknown"} Deal` : "Deal Detail";
902
+ const contactName = useMemo(
903
+ () => deal ? [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ") : "",
904
+ [deal]
905
+ );
906
+ const companyName = useMemo(() => {
907
+ if (!deal) return null;
908
+ return deal.contact?.company?.name || deal.discovery_data?.company || deal.contact_email?.split("@")[1] || "Unknown";
909
+ }, [deal]);
910
+ const headerActions = deal ? /* @__PURE__ */ jsxs(Group, { children: [
911
+ /* @__PURE__ */ jsx(
912
+ Button,
913
+ {
914
+ variant: "light",
915
+ size: "sm",
916
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
917
+ onClick: () => navigate({ to: "/crm/deals" }),
918
+ children: "Deals"
919
+ }
920
+ ),
921
+ deal.proposal_pdf_url && /* @__PURE__ */ jsx(
922
+ Button,
923
+ {
924
+ variant: "light",
925
+ leftSection: /* @__PURE__ */ jsx(IconFileText, { size: 16 }),
926
+ onClick: () => window.open(deal.proposal_pdf_url, "_blank"),
927
+ children: "View Proposal"
928
+ }
929
+ ),
930
+ renderActions?.(deal),
931
+ /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", color: "red", onClick: () => setDeleteModalOpen(true), children: /* @__PURE__ */ jsx(IconTrash, { size: 16 }) })
932
+ ] }) : /* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }), onClick: () => navigate({ to: "/crm/deals" }), children: "Deals" });
933
+ if (isLoading) {
934
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
935
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Loading deal details...", rightSection: headerActions }) }),
936
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
937
+ ] }) });
938
+ }
939
+ if (error) {
940
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
941
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Unable to load deal details", rightSection: headerActions }) }),
942
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load deal" }) })
943
+ ] }) });
944
+ }
945
+ if (!deal) {
946
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
947
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Deal not found", rightSection: headerActions }) }),
948
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(EmptyState, { icon: IconTrash, title: "Deal not found", description: "The selected deal no longer exists." }) })
949
+ ] }) });
950
+ }
951
+ const activityLog = deal.activity_log || [];
952
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
953
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(
954
+ PageTitleCaption,
955
+ {
956
+ title,
957
+ caption: `${companyName || "Unknown"} - ${formatDealStageLabel(deal.cached_stage)}`,
958
+ rightSection: headerActions
959
+ }
960
+ ) }),
961
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "details", children: [
962
+ /* @__PURE__ */ jsxs(Tabs.List, { children: [
963
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "details", children: "Details" }),
964
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "discovery", children: "Discovery Data" }),
965
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "activity", children: "Activity" })
966
+ ] }),
967
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "details", pt: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
968
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
969
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Deal" }),
970
+ /* @__PURE__ */ jsxs(Group, { children: [
971
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Stage:" }),
972
+ /* @__PURE__ */ jsx(Badge, { color: DEAL_STAGE_COLORS[deal.cached_stage || ""] || "gray", children: formatDealStageLabel(deal.cached_stage) })
973
+ ] }),
974
+ /* @__PURE__ */ jsxs(Group, { children: [
975
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Sent:" }),
976
+ /* @__PURE__ */ jsx(Text, { children: deal.proposal_sent_at ? new Date(deal.proposal_sent_at).toLocaleString() : "N/A" })
977
+ ] }),
978
+ /* @__PURE__ */ jsxs(Group, { children: [
979
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Envelope ID:" }),
980
+ /* @__PURE__ */ jsx(Text, { children: deal.signature_envelope_id || "N/A" })
981
+ ] })
982
+ ] }) }),
983
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
984
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Contact" }),
985
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
986
+ /* @__PURE__ */ jsxs(Group, { children: [
987
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Name:" }),
988
+ /* @__PURE__ */ jsx(Text, { children: contactName || "N/A" })
989
+ ] }),
990
+ /* @__PURE__ */ jsxs(Group, { children: [
991
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Email:" }),
992
+ /* @__PURE__ */ jsx(Text, { children: deal.contact?.email || deal.contact_email || "N/A" })
993
+ ] }),
994
+ /* @__PURE__ */ jsxs(Group, { children: [
995
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Title:" }),
996
+ /* @__PURE__ */ jsx(Text, { children: deal.contact?.title || "N/A" })
997
+ ] }),
998
+ /* @__PURE__ */ jsxs(Group, { children: [
999
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Pipeline Status:" }),
1000
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: deal.contact?.pipeline_status ? Object.entries(deal.contact.pipeline_status).map(([key, val]) => {
1001
+ const status = val?.status;
1002
+ return status ? `${key}: ${status}` : null;
1003
+ }).filter(Boolean).join(", ") || "N/A" : "N/A" })
1004
+ ] }),
1005
+ deal.contact?.headline && /* @__PURE__ */ jsxs(Group, { children: [
1006
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Headline:" }),
1007
+ /* @__PURE__ */ jsx(Text, { children: deal.contact.headline })
1008
+ ] }),
1009
+ deal.contact?.linkedin_url && /* @__PURE__ */ jsxs(Group, { children: [
1010
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "LinkedIn:" }),
1011
+ /* @__PURE__ */ jsx(Text, { component: "a", href: deal.contact.linkedin_url, target: "_blank", c: "blue", children: "View Profile" })
1012
+ ] })
1013
+ ] })
1014
+ ] }) }),
1015
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1016
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Company" }),
1017
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
1018
+ /* @__PURE__ */ jsxs(Group, { children: [
1019
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Name:" }),
1020
+ /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.name || "N/A" })
1021
+ ] }),
1022
+ /* @__PURE__ */ jsxs(Group, { children: [
1023
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Domain:" }),
1024
+ /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.domain || "N/A" })
1025
+ ] }),
1026
+ /* @__PURE__ */ jsxs(Group, { children: [
1027
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Segment:" }),
1028
+ /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.segment || "N/A" })
1029
+ ] }),
1030
+ /* @__PURE__ */ jsxs(Group, { children: [
1031
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Category:" }),
1032
+ /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.category || "N/A" })
1033
+ ] }),
1034
+ /* @__PURE__ */ jsxs(Group, { children: [
1035
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Employees:" }),
1036
+ /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.num_employees || "N/A" })
1037
+ ] }),
1038
+ deal.contact?.company?.website && /* @__PURE__ */ jsxs(Group, { children: [
1039
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Website:" }),
1040
+ /* @__PURE__ */ jsx(Text, { component: "a", href: deal.contact.company.website, target: "_blank", c: "blue", children: deal.contact.company.website })
1041
+ ] })
1042
+ ] })
1043
+ ] }) }),
1044
+ ["proposal_signed", "payment_sent", "closed_won"].includes(deal.cached_stage || "") && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1045
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Payment" }),
1046
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
1047
+ /* @__PURE__ */ jsxs(Group, { children: [
1048
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Initial Fee:" }),
1049
+ /* @__PURE__ */ jsxs(Text, { children: [
1050
+ "$",
1051
+ deal.initial_fee?.toLocaleString() || "Not set"
1052
+ ] })
1053
+ ] }),
1054
+ /* @__PURE__ */ jsxs(Group, { children: [
1055
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Monthly Fee:" }),
1056
+ /* @__PURE__ */ jsxs(Text, { children: [
1057
+ "$",
1058
+ deal.monthly_fee?.toLocaleString() || "Not set",
1059
+ "/mo"
1060
+ ] })
1061
+ ] })
1062
+ ] }),
1063
+ deal.stripe_payment_link && /* @__PURE__ */ jsxs(Group, { children: [
1064
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: "Payment Link:" }),
1065
+ /* @__PURE__ */ jsx(Text, { component: "a", href: deal.stripe_payment_link, target: "_blank", c: "blue", children: deal.stripe_payment_link })
1066
+ ] })
1067
+ ] }) })
1068
+ ] }) }),
1069
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "discovery", pt: "md", children: /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsx(Code, { block: true, children: JSON.stringify(deal.discovery_data, null, 2) }) }) }),
1070
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "activity", pt: "md", children: /* @__PURE__ */ jsx(ActivityTimeline, { activities: activityLog }) })
1071
+ ] }) }),
1072
+ /* @__PURE__ */ jsx(CustomModal, { opened: deleteModalOpen, onClose: () => setDeleteModalOpen(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1073
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
1074
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Delete deal" }),
1075
+ /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: () => setDeleteModalOpen(false), children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1078
+ "Are you sure you want to delete the deal for ",
1079
+ /* @__PURE__ */ jsx("strong", { children: contactName || deal.contact_email }),
1080
+ "? This will cancel any active schedules and pending tasks for this contact."
1081
+ ] }),
1082
+ (deal.signature_envelope_id || deal.stripe_payment_link) && /* @__PURE__ */ jsxs(Alert, { color: "yellow", variant: "light", children: [
1083
+ "This deal has ",
1084
+ deal.signature_envelope_id ? "a signed contract" : "",
1085
+ deal.signature_envelope_id && deal.stripe_payment_link ? " and " : "",
1086
+ deal.stripe_payment_link ? "an active payment link" : "",
1087
+ " that will not be automatically cleaned up."
1088
+ ] }),
1089
+ /* @__PURE__ */ jsx(Divider, {}),
1090
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
1091
+ /* @__PURE__ */ jsx(Button, { variant: "default", onClick: () => setDeleteModalOpen(false), disabled: deleteDeal.isPending, children: "Cancel" }),
1092
+ /* @__PURE__ */ jsx(
1093
+ Button,
1094
+ {
1095
+ color: "red",
1096
+ loading: deleteDeal.isPending,
1097
+ onClick: () => deleteDeal.mutate(deal.id, {
1098
+ onSuccess: () => {
1099
+ setDeleteModalOpen(false);
1100
+ void navigate({ to: "/crm/deals" });
1101
+ }
1102
+ }),
1103
+ children: "Delete deal"
1104
+ }
1105
+ )
1106
+ ] })
1107
+ ] }) })
1108
+ ] }) });
1109
+ }
1110
+
1111
+ export { ActivityFeedWidget, CrmOverview, CrmSidebar, CrmSidebarMiddle, CrmSidebarTop, DEAL_STAGE_COLORS, DEAL_STAGE_OPTIONS, DealDetailPage, DealsListPage, MetricsStrip, MyTasksPanel, PIPELINE_FUNNEL_ORDER, PipelineFunnelWidget, QuickCreateActions, SAVED_VIEW_PRESETS, SavedViewsPanel, TasksDueWidget, crmManifest, formatDealStageLabel, useCrmPipelineSummary, useCrmQuickMetrics, useRecentCrmActivity };