@elevasis/ui 2.34.0 → 2.35.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 (106) hide show
  1. package/dist/api/index.js +3 -3
  2. package/dist/app/index.d.ts +12 -12
  3. package/dist/app/index.js +25 -23
  4. package/dist/charts/index.js +3 -5
  5. package/dist/chunk-26HFM4MH.js +41449 -0
  6. package/dist/{chunk-DTFKWZ7A.js → chunk-4U3XAWCN.js} +502 -484
  7. package/dist/{chunk-ND5TDV2J.js → chunk-57OZ3AEG.js} +1 -1
  8. package/dist/{chunk-E4WQGJNS.js → chunk-7FPLLSHN.js} +14 -1
  9. package/dist/{chunk-RQA2EVN3.js → chunk-AKW7KISS.js} +39 -3
  10. package/dist/chunk-AUDNF2Q7.js +2050 -0
  11. package/dist/{chunk-TYRUKGGD.js → chunk-GX6XBRRF.js} +1 -2
  12. package/dist/{chunk-V6SZ4ECN.js → chunk-LUYVRATI.js} +257 -6
  13. package/dist/{chunk-X4WBGKJQ.js → chunk-R3VCBZDC.js} +50 -3
  14. package/dist/chunk-SIQ3P4OR.js +1764 -0
  15. package/dist/{chunk-RIAXZ6AH.js → chunk-VDOOGGBA.js} +1 -1
  16. package/dist/{chunk-3FV6HBXS.js → chunk-WF7CONXF.js} +23 -23
  17. package/dist/{chunk-3QXJK5IY.js → chunk-YYX7OPZQ.js} +1 -1
  18. package/dist/components/index.d.ts +69 -69
  19. package/dist/components/index.js +20 -2795
  20. package/dist/components/navigation/index.js +25 -5
  21. package/dist/execution/index.d.ts +9 -9
  22. package/dist/execution/index.js +1 -2
  23. package/dist/features/auth/index.js +23 -2
  24. package/dist/features/clients/index.js +20 -26
  25. package/dist/features/crm/index.js +20 -30
  26. package/dist/features/dashboard/index.d.ts +68 -68
  27. package/dist/features/dashboard/index.js +20 -28
  28. package/dist/features/delivery/index.js +20 -30
  29. package/dist/features/knowledge/index.js +25 -9
  30. package/dist/features/lead-gen/index.d.ts +9 -9
  31. package/dist/features/lead-gen/index.js +20 -31
  32. package/dist/features/monitoring/index.js +20 -30
  33. package/dist/features/monitoring/requests/index.js +20 -25
  34. package/dist/features/operations/index.d.ts +153 -153
  35. package/dist/features/operations/index.js +18 -37
  36. package/dist/features/seo/index.js +3 -4
  37. package/dist/features/settings/index.js +20 -27
  38. package/dist/graph/index.js +1 -1
  39. package/dist/hooks/delivery/index.js +30 -2
  40. package/dist/hooks/index.d.ts +85 -85
  41. package/dist/hooks/index.js +20 -21
  42. package/dist/hooks/operations/command-view/utils/transformCommandViewData.d.ts +35 -35
  43. package/dist/hooks/published.d.ts +85 -85
  44. package/dist/hooks/published.js +20 -20
  45. package/dist/index.css +532 -532
  46. package/dist/index.d.ts +9256 -5803
  47. package/dist/index.js +22 -26
  48. package/dist/knowledge/index.d.ts +21 -21
  49. package/dist/knowledge/index.js +8 -15
  50. package/dist/layout/index.js +4 -10
  51. package/dist/organization/index.js +27 -1
  52. package/dist/provider/index.d.ts +47 -21
  53. package/dist/provider/index.js +20 -15
  54. package/dist/provider/published.d.ts +15 -16
  55. package/dist/provider/published.js +20 -11
  56. package/dist/test-utils/index.js +3 -3
  57. package/dist/theme/index.js +2 -3
  58. package/dist/theme/presets/index.d.ts +28 -3
  59. package/dist/theme/presets/index.js +1 -1
  60. package/dist/typeform/index.js +1 -2049
  61. package/dist/types/index.d.ts +68 -68
  62. package/dist/utils/index.d.ts +46 -46
  63. package/dist/utils/index.js +1 -1
  64. package/dist/zustand/index.d.ts +6 -6
  65. package/dist/zustand/index.js +0 -3
  66. package/package.json +5 -5
  67. package/dist/chunk-3AJVNMY5.js +0 -4769
  68. package/dist/chunk-3MEXPLWT.js +0 -265
  69. package/dist/chunk-3ZMAGTWF.js +0 -18
  70. package/dist/chunk-4O4MII5S.js +0 -4716
  71. package/dist/chunk-5EYJ2GIN.js +0 -122
  72. package/dist/chunk-7M2VOCYN.js +0 -1
  73. package/dist/chunk-BPQVTIUP.js +0 -105
  74. package/dist/chunk-BZZCNLT6.js +0 -12
  75. package/dist/chunk-CLDCYJQT.js +0 -1
  76. package/dist/chunk-E565XMTQ.js +0 -17
  77. package/dist/chunk-HRWLKKWM.js +0 -758
  78. package/dist/chunk-IGDYWFNE.js +0 -5198
  79. package/dist/chunk-IIMU5YAJ.js +0 -53
  80. package/dist/chunk-IVGI4GDL.js +0 -1593
  81. package/dist/chunk-JFL3GRD4.js +0 -39
  82. package/dist/chunk-LAWLB6CT.js +0 -951
  83. package/dist/chunk-LGKLC5MG.js +0 -44
  84. package/dist/chunk-LRWTWOGP.js +0 -1778
  85. package/dist/chunk-MP3GPBPX.js +0 -1874
  86. package/dist/chunk-N55DVMAG.js +0 -14
  87. package/dist/chunk-NLBQTDOW.js +0 -12051
  88. package/dist/chunk-O6JXQ6UQ.js +0 -468
  89. package/dist/chunk-OBBQ2JCM.js +0 -68
  90. package/dist/chunk-PDHTXPSF.js +0 -12
  91. package/dist/chunk-PLP3NYPL.js +0 -356
  92. package/dist/chunk-R2XR4FCV.js +0 -48
  93. package/dist/chunk-R66W5UDG.js +0 -26
  94. package/dist/chunk-RYTEQBAO.js +0 -37
  95. package/dist/chunk-SDXSB3HN.js +0 -425
  96. package/dist/chunk-TKAYX2SP.js +0 -204
  97. package/dist/chunk-TUMSNGTX.js +0 -35
  98. package/dist/chunk-VNAZTCHA.js +0 -65
  99. package/dist/chunk-VNFR57DF.js +0 -87
  100. package/dist/chunk-VTXTZXAU.js +0 -539
  101. package/dist/chunk-W73ZABT6.js +0 -85
  102. package/dist/chunk-WU4FNWCW.js +0 -2281
  103. package/dist/chunk-XZGSCABI.js +0 -383
  104. package/dist/chunk-YNWZIWJL.js +0 -1863
  105. /package/dist/{chunk-2RJMVWFJ.js → chunk-GEFWMU26.js} +0 -0
  106. /package/dist/{chunk-22UVE3RA.js → chunk-HENXLGVD.js} +0 -0
@@ -1,1863 +0,0 @@
1
- import { CRM_PIPELINE_DEFINITION, SalesSidebar, CrmSidebarMiddle, DEFAULT_CRM_PRIORITY_RULE_CONFIG, CRM_PRIORITY_BUCKETS } from './chunk-4O4MII5S.js';
2
- import { SubshellSidebarSection } from './chunk-IIMU5YAJ.js';
3
- import { PageContainer } from './chunk-BZZCNLT6.js';
4
- import { TableSelectionToolbar, SortableHeader } from './chunk-TUMSNGTX.js';
5
- import { FilterBar } from './chunk-PDHTXPSF.js';
6
- import { CustomModal } from './chunk-R66W5UDG.js';
7
- import { useDealsSummary, useDeleteDeal, usePaginationState, useDeals, useTableSort, sortData, useTableSelection, useDealDetail, useCompany, dealKeys, useContact, useExecuteAction } from './chunk-IGDYWFNE.js';
8
- import { showApiErrorNotification, showSuccessNotification } from './chunk-XZGSCABI.js';
9
- import { useCrmActions, deriveActions } from './chunk-PLP3NYPL.js';
10
- import { CenteredErrorState, CardHeader, StatCard, PageTitleCaption, EmptyState, ActivityTimeline } from './chunk-HRWLKKWM.js';
11
- import { SubshellContentContainer } from './chunk-TKAYX2SP.js';
12
- import { PAGE_SIZE_DEFAULT, formatTimeAgo } from './chunk-2RJMVWFJ.js';
13
- import { useElevasisServices } from './chunk-KJ3QUBNU.js';
14
- import { Stack, Paper, Center, Loader, Group, Box, Text, Alert, Table, SimpleGrid, Button, Select, TextInput, Tabs, Checkbox, Badge, Tooltip, Pagination, Title, Code, Card, Switch, NumberInput, ColorSwatch, Popover, ActionIcon, Anchor, CopyButton, Divider, Textarea } from '@mantine/core';
15
- import { IconAddressBook, IconColumns, IconHistory, IconAlertCircle, IconCurrencyDollar, IconChartBar, IconBriefcase, IconTrophy, IconSearch, IconTargetArrow, IconAlertTriangle, IconMessages, IconArrowLeft, IconFileText, IconInbox, IconUser, IconBolt, IconBuilding, IconSettings, IconRestore, IconInfoCircle, IconMailForward, IconPlus, IconArrowRight, IconNote, IconCheck, IconCopy } from '@tabler/icons-react';
16
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
- import { useMemo, useState, useEffect } from 'react';
18
- import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
19
- import { useNavigate } from '@tanstack/react-router';
20
- import { useForm } from '@mantine/form';
21
-
22
- var CrmSidebarTop = () => {
23
- return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconAddressBook, label: "CRM" });
24
- };
25
-
26
- // src/lib/crm/pipeline.ts
27
- function getCrmStageOptions() {
28
- return CRM_PIPELINE_DEFINITION.stages.map((stage) => ({
29
- value: stage.stageKey,
30
- label: stage.label
31
- }));
32
- }
33
- function getCrmStageColors() {
34
- return CRM_PIPELINE_DEFINITION.stages.reduce((colors, stage) => {
35
- if (stage.color) colors[stage.stageKey] = stage.color;
36
- return colors;
37
- }, {});
38
- }
39
- function getCrmStageLabel(stageKey) {
40
- const normalized = normalizeCatalogKey(stageKey);
41
- if (!normalized) return "Unknown";
42
- return CRM_PIPELINE_DEFINITION.stages.find((stage) => stage.stageKey.toLowerCase() === normalized.toLowerCase())?.label ?? formatReadableKey(normalized);
43
- }
44
- function getCrmStateLabel(stateKey) {
45
- const normalized = normalizeCatalogKey(stateKey);
46
- if (!normalized) return "Unknown";
47
- for (const stage of CRM_PIPELINE_DEFINITION.stages) {
48
- const state = stage.states.find((candidate) => candidate.stateKey.toLowerCase() === normalized.toLowerCase());
49
- if (state) return state.label;
50
- }
51
- return formatReadableKey(normalized);
52
- }
53
- function normalizeCatalogKey(value) {
54
- const normalized = value?.trim();
55
- return normalized ? normalized : null;
56
- }
57
- function formatReadableKey(value) {
58
- const words = value.split("_").map((word) => word.trim().toLowerCase()).filter(Boolean);
59
- if (!words.length) return "Unknown";
60
- return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
61
- }
62
-
63
- // src/features/crm/pages/shared.ts
64
- var DEAL_REFERRER_STORAGE_KEY = "crm:dealReferrer";
65
- function setDealReferrer(referrer) {
66
- if (typeof window === "undefined") return;
67
- try {
68
- window.sessionStorage.setItem(DEAL_REFERRER_STORAGE_KEY, referrer);
69
- } catch {
70
- }
71
- }
72
- function getDealReferrer() {
73
- if (typeof window === "undefined") return "deals";
74
- try {
75
- const value = window.sessionStorage.getItem(DEAL_REFERRER_STORAGE_KEY);
76
- return value === "pipeline" ? "pipeline" : "deals";
77
- } catch {
78
- return "deals";
79
- }
80
- }
81
- var DEAL_STAGE_COLORS = getCrmStageColors();
82
- var DEAL_STAGE_OPTIONS = getCrmStageOptions().map((option) => ({
83
- value: option.value,
84
- label: option.label
85
- }));
86
- var DEAL_PRIORITY_OPTIONS = [
87
- { value: "needs_response", label: "Needs response" },
88
- { value: "follow_up_due", label: "Follow-up due" },
89
- { value: "waiting", label: "Waiting" },
90
- { value: "stale", label: "Stale" },
91
- { value: "closed_low", label: "Closed low" }
92
- ];
93
- function compareDealsByPriority(a, b, direction = "asc") {
94
- const rankComparison = comparePriorityRank(a.priority, b.priority, direction);
95
- if (rankComparison !== 0) return rankComparison;
96
- const aActivity = getPriorityActivityTimestamp(a);
97
- const bActivity = getPriorityActivityTimestamp(b);
98
- return bActivity - aActivity;
99
- }
100
- function compareDealsByPriorityThenUpdated(a, b, direction = "asc") {
101
- const rankComparison = comparePriorityRank(a.priority, b.priority, direction);
102
- if (rankComparison !== 0) return rankComparison;
103
- return getUpdatedTimestamp(b) - getUpdatedTimestamp(a);
104
- }
105
- function formatDealStageLabel(stage) {
106
- return getCrmStageLabel(stage);
107
- }
108
- function formatDealStateLabel(state) {
109
- return getCrmStateLabel(state);
110
- }
111
- function comparePriorityRank(a, b, direction) {
112
- const comparison = a.rank - b.rank;
113
- return direction === "asc" ? comparison : -comparison;
114
- }
115
- function getPriorityActivityTimestamp(deal) {
116
- const value = deal.priority.latestActivityAt ?? deal.updated_at;
117
- const timestamp = new Date(value).getTime();
118
- return Number.isNaN(timestamp) ? 0 : timestamp;
119
- }
120
- function getUpdatedTimestamp(deal) {
121
- const timestamp = new Date(deal.updated_at).getTime();
122
- return Number.isNaN(timestamp) ? 0 : timestamp;
123
- }
124
- var BOOKING_PAGE_URL = "https://elevasis.io/inbound/book";
125
- function buildBookingUrl(opts = {}) {
126
- const params = new URLSearchParams();
127
- if (opts.dealId) params.set("dealId", opts.dealId);
128
- if (opts.contactId) params.set("contactId", opts.contactId);
129
- return params.size > 0 ? `${BOOKING_PAGE_URL}?${params.toString()}` : BOOKING_PAGE_URL;
130
- }
131
- var CrmSidebar = ({ children } = {}) => {
132
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
133
- /* @__PURE__ */ jsx(CrmSidebarTop, {}),
134
- children ?? /* @__PURE__ */ jsx(CrmSidebarMiddle, {})
135
- ] });
136
- };
137
- var PIPELINE_FUNNEL_ORDER = [
138
- "interested",
139
- "proposal",
140
- "closing",
141
- "closed_won",
142
- "closed_lost",
143
- "nurturing"
144
- ];
145
- function useCrmPipelineSummary(opts) {
146
- const { data: summary, isLoading, error } = useDealsSummary();
147
- const data = useMemo(() => {
148
- const stageMap = /* @__PURE__ */ new Map();
149
- for (const stage of PIPELINE_FUNNEL_ORDER) {
150
- stageMap.set(stage, { count: 0, totalValue: 0 });
151
- }
152
- for (const stageSummary of summary?.stageSummary ?? []) {
153
- const stage = stageSummary.stage;
154
- if (!stageMap.has(stage)) continue;
155
- const entry = stageMap.get(stage);
156
- entry.count = stageSummary.count;
157
- entry.totalValue = typeof stageSummary.totalValue === "number" ? stageSummary.totalValue : 0;
158
- }
159
- return PIPELINE_FUNNEL_ORDER.map((stage) => {
160
- const entry = stageMap.get(stage);
161
- return { stage, count: entry.count, totalValue: entry.totalValue };
162
- });
163
- }, [summary, opts]);
164
- return { data, isLoading, error };
165
- }
166
-
167
- // src/features/crm/overview/hooks/useCrmQuickMetrics.ts
168
- var ZERO_METRICS = {
169
- totalDeals: 0,
170
- openDeals: 0,
171
- wonDeals: 0,
172
- winRate: 0,
173
- avgDealSize: 0,
174
- totalPipelineValue: 0
175
- };
176
- function useCrmQuickMetrics() {
177
- const { data: summary, isLoading, error } = useDealsSummary();
178
- const data = summary ? {
179
- totalDeals: summary.totalDeals,
180
- openDeals: summary.openDeals,
181
- wonDeals: summary.wonDeals,
182
- winRate: summary.winRate,
183
- avgDealSize: summary.avgDealSize,
184
- totalPipelineValue: summary.totalPipelineValue
185
- } : ZERO_METRICS;
186
- return { data, isLoading, error };
187
- }
188
- function useRecentCrmActivity(opts) {
189
- const { apiRequest, isReady, workOSOrganizationId } = useElevasisServices();
190
- const limit = opts?.limit ?? 20;
191
- const query = useQuery({
192
- queryKey: ["recent-crm-activity", workOSOrganizationId, limit],
193
- queryFn: () => apiRequest(`/crm/recent-activity?limit=${limit}`),
194
- enabled: isReady
195
- });
196
- return {
197
- data: query.data?.entries ?? [],
198
- isLoading: query.isLoading,
199
- error: query.error
200
- };
201
- }
202
- var STAGE_LABELS = {
203
- interested: "Interested",
204
- proposal: "Proposal",
205
- closing: "Closing",
206
- closed_won: "Closed Won",
207
- closed_lost: "Closed Lost",
208
- nurturing: "Nurturing"
209
- };
210
- function PipelineFunnelWidget({ onStageClick, getDealValue }) {
211
- const { data, isLoading, error } = useCrmPipelineSummary({ getDealValue });
212
- if (isLoading) {
213
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
214
- }
215
- if (error) {
216
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load pipeline data" }) });
217
- }
218
- const totalDeals = data.reduce((sum, s) => sum + s.count, 0);
219
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
220
- /* @__PURE__ */ jsx(
221
- CardHeader,
222
- {
223
- icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }),
224
- title: "Pipeline",
225
- subtitle: totalDeals === 0 ? "No deals in the pipeline yet" : `${totalDeals} deal${totalDeals !== 1 ? "s" : ""} total`
226
- }
227
- ),
228
- /* @__PURE__ */ jsx(Group, { gap: "lg", wrap: "nowrap", mt: "md", children: PIPELINE_FUNNEL_ORDER.map((stage) => {
229
- const summary = data.find((s) => s.stage === stage);
230
- const isEmpty = summary.count === 0;
231
- const stageColor = DEAL_STAGE_COLORS[stage] ?? "gray";
232
- const accent = `var(--mantine-color-${stageColor}-6)`;
233
- return /* @__PURE__ */ jsxs(
234
- Box,
235
- {
236
- onClick: () => onStageClick(stage),
237
- style: {
238
- flex: 1,
239
- minWidth: 0,
240
- cursor: "pointer",
241
- padding: "4px 0"
242
- },
243
- children: [
244
- /* @__PURE__ */ jsxs(Group, { gap: 8, wrap: "nowrap", align: "center", mb: 6, children: [
245
- /* @__PURE__ */ jsx(
246
- Box,
247
- {
248
- style: {
249
- width: 8,
250
- height: 8,
251
- borderRadius: "50%",
252
- background: accent,
253
- flexShrink: 0
254
- }
255
- }
256
- ),
257
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", truncate: true, children: STAGE_LABELS[stage] })
258
- ] }),
259
- /* @__PURE__ */ jsx(Text, { size: "xl", fw: 600, c: isEmpty ? "dimmed" : void 0, lh: 1, children: summary.count })
260
- ]
261
- },
262
- stage
263
- );
264
- }) })
265
- ] });
266
- }
267
- function ActivityKindIcon({ kind }) {
268
- const size = 16;
269
- switch (kind) {
270
- case "note":
271
- return /* @__PURE__ */ jsx(IconNote, { size });
272
- case "stage_change":
273
- return /* @__PURE__ */ jsx(IconArrowRight, { size });
274
- case "deal_created":
275
- return /* @__PURE__ */ jsx(IconPlus, { size });
276
- }
277
- }
278
- function formatRelativeTime(occurredAt) {
279
- const date = new Date(occurredAt);
280
- const now = /* @__PURE__ */ new Date();
281
- const diffMs = now.getTime() - date.getTime();
282
- const diffMin = Math.floor(diffMs / 6e4);
283
- const diffHr = Math.floor(diffMin / 60);
284
- const diffDay = Math.floor(diffHr / 24);
285
- if (diffMin < 1) return "just now";
286
- if (diffMin < 60) return `${diffMin}m ago`;
287
- if (diffHr < 24) return `${diffHr}h ago`;
288
- if (diffDay < 7) return `${diffDay}d ago`;
289
- return date.toLocaleDateString();
290
- }
291
- function ActivityFeedWidget({ onDealClick, limit }) {
292
- const { data, isLoading, error } = useRecentCrmActivity({ limit: limit ?? 15 });
293
- if (isLoading) {
294
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
295
- }
296
- if (error) {
297
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load activity" }) });
298
- }
299
- if (!data.length) {
300
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
301
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
302
- /* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No recent activity" }) })
303
- ] });
304
- }
305
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
306
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
307
- /* @__PURE__ */ jsxs(Table, { highlightOnHover: true, style: { tableLayout: "fixed", width: "100%" }, children: [
308
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
309
- /* @__PURE__ */ jsx(Table.Th, { w: 40 }),
310
- /* @__PURE__ */ jsx(Table.Th, { w: 220, children: "Name" }),
311
- /* @__PURE__ */ jsx(Table.Th, { children: "Activity" }),
312
- /* @__PURE__ */ jsx(Table.Th, { w: 120, children: "When" })
313
- ] }) }),
314
- /* @__PURE__ */ jsx(Table.Tbody, { children: data.map((entry) => {
315
- const name = entry.contactName ?? entry.companyName ?? "Unknown";
316
- return /* @__PURE__ */ jsxs(Table.Tr, { style: { cursor: "pointer" }, onClick: () => onDealClick(entry.dealId), children: [
317
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { c: "dimmed", component: "span", children: /* @__PURE__ */ jsx(ActivityKindIcon, { kind: entry.kind }) }) }),
318
- /* @__PURE__ */ jsx(Table.Td, { style: { maxWidth: 220 }, children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, truncate: true, title: name, style: { minWidth: 0 }, children: name }) }),
319
- /* @__PURE__ */ jsx(Table.Td, { style: { minWidth: 0 }, children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", truncate: true, title: entry.description, style: { minWidth: 0 }, children: entry.description }) }),
320
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { whiteSpace: "nowrap" }, children: formatRelativeTime(entry.occurredAt) }) })
321
- ] }, entry.id);
322
- }) })
323
- ] })
324
- ] }) });
325
- }
326
- var currencyFormatter = new Intl.NumberFormat("en-US", {
327
- style: "currency",
328
- currency: "USD",
329
- maximumFractionDigits: 0
330
- });
331
- function formatPercent(value) {
332
- return `${Math.round(value * 100)}%`;
333
- }
334
- function MetricsStrip() {
335
- const { data, isLoading } = useCrmQuickMetrics();
336
- return /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 2, sm: 4 }, children: [
337
- /* @__PURE__ */ jsx(
338
- StatCard,
339
- {
340
- variant: "hero",
341
- icon: IconCurrencyDollar,
342
- value: currencyFormatter.format(data.totalPipelineValue),
343
- label: "Total Pipeline Value",
344
- isLoading
345
- }
346
- ),
347
- /* @__PURE__ */ jsx(
348
- StatCard,
349
- {
350
- variant: "hero",
351
- icon: IconChartBar,
352
- value: formatPercent(data.winRate),
353
- label: "Win Rate",
354
- isLoading
355
- }
356
- ),
357
- /* @__PURE__ */ jsx(
358
- StatCard,
359
- {
360
- variant: "hero",
361
- icon: IconBriefcase,
362
- value: String(data.openDeals),
363
- label: "Open Deals",
364
- isLoading
365
- }
366
- ),
367
- /* @__PURE__ */ jsx(
368
- StatCard,
369
- {
370
- variant: "hero",
371
- icon: IconTrophy,
372
- value: String(data.wonDeals),
373
- label: "Won This Period",
374
- isLoading
375
- }
376
- )
377
- ] });
378
- }
379
- function CrmOverview({
380
- onStageClick,
381
- onDealClick,
382
- onGoToPipeline,
383
- getDealValue,
384
- renderActions
385
- }) {
386
- const rightSection = renderActions ? renderActions() : /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconColumns, { size: 16 }), variant: "light", size: "xs", onClick: onGoToPipeline, children: "Go to Pipeline" });
387
- return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
388
- /* @__PURE__ */ jsx(
389
- PageTitleCaption,
390
- {
391
- title: "CRM Overview",
392
- caption: "Pipeline health, tasks, and recent activity at a glance.",
393
- rightSection
394
- }
395
- ),
396
- /* @__PURE__ */ jsx(MetricsStrip, {}),
397
- /* @__PURE__ */ jsx(PipelineFunnelWidget, { onStageClick, getDealValue }),
398
- /* @__PURE__ */ jsx(ActivityFeedWidget, { onDealClick })
399
- ] });
400
- }
401
- var crmManifest = {
402
- key: "crm",
403
- systemId: "sales.crm",
404
- capabilityIds: ["crm.pipeline.manage"],
405
- icon: IconAddressBook,
406
- sidebar: SalesSidebar
407
- };
408
- var sortAccessors = {
409
- company: (deal) => deal.contact?.company?.name || deal.discovery_data?.company || deal.contact_email?.split("@")[1] || "",
410
- contact: (deal) => [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ") || "",
411
- email: (deal) => deal.contact_email || "",
412
- stage: (deal) => deal.stage_key || "",
413
- state: (deal) => deal.state_key || "",
414
- ownership: (deal) => deal.ownership || "",
415
- updated: (deal) => deal.updated_at || ""
416
- };
417
- var DEAL_OWNERSHIP_BADGE_COPY = {
418
- us: {
419
- color: "red",
420
- label: "Our move",
421
- tooltip: "We owe the next move"
422
- },
423
- them: {
424
- color: "gray",
425
- label: "Their move",
426
- tooltip: "Lead owes the next move"
427
- }
428
- };
429
- function DealsListPage() {
430
- const navigate = useNavigate();
431
- const deleteDeal = useDeleteDeal();
432
- const [stageFilter, setStageFilter] = useState(null);
433
- const [priorityFilter, setPriorityFilter] = useState("all");
434
- const [searchQuery, setSearchQuery] = useState("");
435
- const [showBatchDelete, setShowBatchDelete] = useState(false);
436
- const pagination = usePaginationState(PAGE_SIZE_DEFAULT, [stageFilter, priorityFilter, searchQuery]);
437
- const {
438
- data: deals,
439
- total,
440
- isLoading,
441
- error
442
- } = useDeals({
443
- stage: stageFilter || void 0,
444
- search: searchQuery || void 0,
445
- limit: PAGE_SIZE_DEFAULT,
446
- offset: pagination.offset
447
- });
448
- const { sort, toggleSort } = useTableSort("priority", "asc");
449
- const sortedDeals = useMemo(() => {
450
- const filteredDeals = priorityFilter === "all" ? deals ?? [] : (deals ?? []).filter((deal) => deal.priority.bucketKey === priorityFilter);
451
- if (sort.column === "priority") {
452
- return [...filteredDeals].sort((a, b) => compareDealsByPriorityThenUpdated(a, b, sort.direction));
453
- }
454
- return sortData(filteredDeals, sort, sortAccessors);
455
- }, [deals, priorityFilter, sort]);
456
- const selection = useTableSelection(sortedDeals, sortedDeals);
457
- const handleDeleteSelected = async () => {
458
- await Promise.all([...selection.selectedIds].map((dealId) => deleteDeal.mutateAsync(dealId)));
459
- setShowBatchDelete(false);
460
- selection.clear();
461
- };
462
- return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
463
- /* @__PURE__ */ jsxs(PageContainer, { children: [
464
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Deals", caption: "Deal pipeline and stage tracking" }) }),
465
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
466
- /* @__PURE__ */ jsxs(
467
- FilterBar,
468
- {
469
- actions: /* @__PURE__ */ jsx(
470
- TableSelectionToolbar,
471
- {
472
- selectedCount: selection.selectedCount,
473
- onDelete: () => setShowBatchDelete(true),
474
- isDeleting: deleteDeal.isPending
475
- }
476
- ),
477
- children: [
478
- /* @__PURE__ */ jsx(
479
- Select,
480
- {
481
- placeholder: "All Stages",
482
- data: DEAL_STAGE_OPTIONS,
483
- style: { minWidth: 180 },
484
- size: "sm",
485
- clearable: true,
486
- value: stageFilter,
487
- onChange: (value) => setStageFilter(value ?? null)
488
- }
489
- ),
490
- /* @__PURE__ */ jsx(
491
- TextInput,
492
- {
493
- placeholder: "Search by email...",
494
- leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
495
- style: { minWidth: 250 },
496
- size: "sm",
497
- value: searchQuery,
498
- onChange: (e) => setSearchQuery(e.currentTarget.value)
499
- }
500
- )
501
- ]
502
- }
503
- ),
504
- /* @__PURE__ */ jsx(
505
- Tabs,
506
- {
507
- value: priorityFilter,
508
- onChange: (value) => setPriorityFilter(value ?? "all"),
509
- children: /* @__PURE__ */ jsxs(Tabs.List, { children: [
510
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "all", children: "All priorities" }),
511
- DEAL_PRIORITY_OPTIONS.map((option) => /* @__PURE__ */ jsx(Tabs.Tab, { value: option.value, children: option.label }, option.value))
512
- ] })
513
- }
514
- ),
515
- 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, { style: { tableLayout: "fixed" }, children: [
516
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
517
- /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
518
- Checkbox,
519
- {
520
- checked: selection.isPageAllSelected,
521
- indeterminate: selection.isPagePartiallySelected,
522
- onChange: selection.togglePage
523
- }
524
- ) }),
525
- /* @__PURE__ */ jsx(SortableHeader, { column: "company", sort, onToggle: toggleSort, children: "Company" }),
526
- /* @__PURE__ */ jsx(SortableHeader, { column: "contact", sort, onToggle: toggleSort, children: "Contact" }),
527
- /* @__PURE__ */ jsx(SortableHeader, { column: "email", sort, onToggle: toggleSort, children: "Email" }),
528
- /* @__PURE__ */ jsx(SortableHeader, { column: "stage", sort, onToggle: toggleSort, w: 120, children: "Stage" }),
529
- /* @__PURE__ */ jsx(SortableHeader, { column: "state", sort, onToggle: toggleSort, w: 160, children: "State" }),
530
- /* @__PURE__ */ jsx(SortableHeader, { column: "ownership", sort, onToggle: toggleSort, w: 100, children: "Move" }),
531
- /* @__PURE__ */ jsx(SortableHeader, { column: "priority", sort, onToggle: toggleSort, w: 140, children: "Priority" }),
532
- /* @__PURE__ */ jsx(SortableHeader, { column: "updated", sort, onToggle: toggleSort, w: 100, children: "Updated" })
533
- ] }) }),
534
- /* @__PURE__ */ jsx(Table.Tbody, { children: sortedDeals.map((deal) => {
535
- const discoveryData = deal.discovery_data;
536
- const companyName = deal.contact?.company?.name || discoveryData?.company || deal.contact_email?.split("@")[1] || "-";
537
- const contactName = [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ");
538
- const ownershipBadge = deal.ownership ? DEAL_OWNERSHIP_BADGE_COPY[deal.ownership] : null;
539
- return /* @__PURE__ */ jsxs(
540
- Table.Tr,
541
- {
542
- style: { cursor: "pointer" },
543
- onClick: () => {
544
- setDealReferrer("deals");
545
- navigate({
546
- to: "/crm/deals/$dealId",
547
- params: { dealId: deal.id }
548
- });
549
- },
550
- children: [
551
- /* @__PURE__ */ jsx(Table.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
552
- Checkbox,
553
- {
554
- checked: selection.isSelected(deal.id),
555
- onChange: () => selection.toggle(deal.id)
556
- }
557
- ) }),
558
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, lineClamp: 1, children: companyName }) }),
559
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", lineClamp: 1, children: contactName || "-" }) }),
560
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", lineClamp: 1, children: deal.contact_email || "-" }) }),
561
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: DEAL_STAGE_COLORS[deal.stage_key || ""] || "gray", size: "sm", children: formatDealStageLabel(deal.stage_key) }) }),
562
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
563
- Badge,
564
- {
565
- variant: "light",
566
- color: deal.state_key ? "gray" : "dark",
567
- size: "sm",
568
- style: { maxWidth: "100%", textTransform: "none" },
569
- children: /* @__PURE__ */ jsx(Text, { span: true, size: "xs", truncate: "end", style: { maxWidth: "100%" }, children: formatDealStateLabel(deal.state_key) })
570
- }
571
- ) }),
572
- /* @__PURE__ */ jsx(Table.Td, { children: ownershipBadge ? /* @__PURE__ */ jsx(Tooltip, { label: ownershipBadge.tooltip, withArrow: true, children: /* @__PURE__ */ jsx(
573
- Badge,
574
- {
575
- variant: "light",
576
- color: ownershipBadge.color,
577
- size: "sm",
578
- style: { maxWidth: "100%", textTransform: "none" },
579
- children: /* @__PURE__ */ jsx(Text, { span: true, size: "xs", truncate: "end", style: { maxWidth: "100%" }, children: ownershipBadge.label })
580
- }
581
- ) }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "-" }) }),
582
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Tooltip, { label: deal.priority.reason, withArrow: true, children: /* @__PURE__ */ jsx(
583
- Badge,
584
- {
585
- variant: "light",
586
- color: deal.priority.color,
587
- size: "sm",
588
- style: { maxWidth: "100%", textTransform: "none" },
589
- children: /* @__PURE__ */ jsx(Text, { span: true, size: "xs", truncate: "end", style: { maxWidth: "100%" }, children: deal.priority.label })
590
- }
591
- ) }) }),
592
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", lineClamp: 1, children: formatTimeAgo(deal.updated_at) }) })
593
- ]
594
- },
595
- deal.id
596
- );
597
- }) })
598
- ] }),
599
- total > PAGE_SIZE_DEFAULT && /* @__PURE__ */ jsx(Group, { justify: "flex-start", children: /* @__PURE__ */ jsx(
600
- Pagination,
601
- {
602
- value: pagination.page,
603
- onChange: pagination.setPage,
604
- total: pagination.totalPages(total),
605
- size: "sm"
606
- }
607
- ) })
608
- ] }) })
609
- ] }),
610
- /* @__PURE__ */ jsx(
611
- CustomModal,
612
- {
613
- opened: showBatchDelete,
614
- onClose: () => !deleteDeal.isPending && setShowBatchDelete(false),
615
- size: "sm",
616
- loading: deleteDeal.isPending,
617
- children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
618
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
619
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
620
- /* @__PURE__ */ jsxs(Title, { order: 4, children: [
621
- "Delete ",
622
- selection.selectedCount,
623
- " Deals"
624
- ] })
625
- ] }),
626
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
627
- "Are you sure you want to delete",
628
- " ",
629
- /* @__PURE__ */ jsx(Text, { span: true, fw: 600, children: selection.selectedCount }),
630
- " ",
631
- "selected deals?"
632
- ] }),
633
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone." }),
634
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
635
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: () => setShowBatchDelete(false), disabled: deleteDeal.isPending, children: "Cancel" }),
636
- /* @__PURE__ */ jsx(Button, { color: "red", loading: deleteDeal.isPending, onClick: () => void handleDeleteSelected(), children: "Delete" })
637
- ] })
638
- ] })
639
- }
640
- )
641
- ] });
642
- }
643
- function ActionButton({ action, dealId }) {
644
- const executeAction = useExecuteAction({ dealId });
645
- return /* @__PURE__ */ jsx(
646
- Button,
647
- {
648
- variant: "light",
649
- size: "sm",
650
- loading: executeAction.isPending,
651
- onClick: () => executeAction.mutate({ key: action.key }),
652
- children: action.label
653
- }
654
- );
655
- }
656
- function getType(schema) {
657
- const zodSchema = schema;
658
- return zodSchema._def?.type ?? zodSchema._def?.typeName ?? "";
659
- }
660
- function unwrapField(schema) {
661
- const type = getType(schema);
662
- if (type === "optional" || type === "ZodOptional") {
663
- const inner = schema._def?.innerType;
664
- if (!inner) return { schema, required: false, nullable: false, defaultValue: void 0 };
665
- return { ...unwrapField(inner), required: false };
666
- }
667
- if (type === "nullable" || type === "ZodNullable") {
668
- const inner = schema._def?.innerType;
669
- if (!inner) return { schema, required: true, nullable: true, defaultValue: null };
670
- return { ...unwrapField(inner), nullable: true };
671
- }
672
- if (type === "default" || type === "ZodDefault") {
673
- const def = schema._def;
674
- const inner = def?.innerType;
675
- const defaultValue = typeof def?.defaultValue === "function" ? def.defaultValue() : def?.defaultValue;
676
- if (!inner) return { schema, required: false, nullable: false, defaultValue };
677
- return { ...unwrapField(inner), required: false, defaultValue };
678
- }
679
- return { schema, required: true, nullable: false, defaultValue: void 0 };
680
- }
681
- function getObjectShape(schema) {
682
- const zodSchema = schema;
683
- const type = getType(schema);
684
- if (type !== "object" && type !== "ZodObject") {
685
- return null;
686
- }
687
- const shape = zodSchema.shape;
688
- if (!shape || typeof shape !== "object" || Array.isArray(shape)) {
689
- return null;
690
- }
691
- return shape;
692
- }
693
- function getEnumValues(schema) {
694
- const zodSchema = schema;
695
- const values = zodSchema.options ?? zodSchema._def?.values ?? Object.values(zodSchema._def?.entries ?? {});
696
- if (!Array.isArray(values) || values.some((value) => typeof value !== "string")) {
697
- return null;
698
- }
699
- return values;
700
- }
701
- function labelForField(name) {
702
- return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (s) => s.toUpperCase());
703
- }
704
- function isLongTextField(name) {
705
- return ["body", "message", "reply", "replyBody", "emailBody"].includes(name);
706
- }
707
- function initialValueFor(field, defaultValue) {
708
- if (defaultValue !== void 0) {
709
- if (field.kind === "date" && defaultValue instanceof Date) {
710
- return defaultValue.toISOString().slice(0, 10);
711
- }
712
- return defaultValue;
713
- }
714
- switch (field.kind) {
715
- case "boolean":
716
- return false;
717
- case "number":
718
- return field.required ? 0 : "";
719
- case "enum":
720
- return field.required ? field.enumValues?.[0] ?? "" : "";
721
- case "date":
722
- case "dateString":
723
- case "string":
724
- return "";
725
- }
726
- }
727
- function describeSchema(schema) {
728
- if (!schema) {
729
- return { supported: false, reason: "This action does not define a payload schema." };
730
- }
731
- const shape = getObjectShape(schema);
732
- if (!shape) {
733
- return { supported: false, reason: "This action requires a payload schema that is not a flat object." };
734
- }
735
- const fields = [];
736
- const initialValues = {};
737
- for (const [name, rawFieldSchema] of Object.entries(shape)) {
738
- const { schema: fieldSchema, required, nullable, defaultValue } = unwrapField(rawFieldSchema);
739
- const type = getType(fieldSchema);
740
- const zodField = fieldSchema;
741
- const stringFormat = zodField._def?.format;
742
- let descriptor = null;
743
- if (type === "string" || type === "ZodString") {
744
- descriptor = {
745
- name,
746
- label: labelForField(name),
747
- kind: stringFormat === "date" ? "dateString" : "string",
748
- schema: rawFieldSchema,
749
- required,
750
- nullable
751
- };
752
- } else if (type === "number" || type === "ZodNumber") {
753
- descriptor = { name, label: labelForField(name), kind: "number", schema: rawFieldSchema, required, nullable };
754
- } else if (type === "boolean" || type === "ZodBoolean") {
755
- descriptor = { name, label: labelForField(name), kind: "boolean", schema: rawFieldSchema, required, nullable };
756
- } else if (type === "enum" || type === "ZodEnum") {
757
- const enumValues = getEnumValues(fieldSchema);
758
- if (!enumValues) {
759
- return { supported: false, reason: `Field "${name}" uses an enum shape that is not supported by this form.` };
760
- }
761
- descriptor = {
762
- name,
763
- label: labelForField(name),
764
- kind: "enum",
765
- schema: rawFieldSchema,
766
- required,
767
- nullable,
768
- enumValues
769
- };
770
- } else if (type === "date" || type === "ZodDate") {
771
- descriptor = { name, label: labelForField(name), kind: "date", schema: rawFieldSchema, required, nullable };
772
- }
773
- if (!descriptor) {
774
- return {
775
- supported: false,
776
- reason: `Field "${name}" is not supported. CRM action forms currently support only flat string, number, boolean, enum, and date fields.`
777
- };
778
- }
779
- const field = { ...descriptor, defaultValue: initialValueFor(descriptor, defaultValue) };
780
- fields.push(field);
781
- initialValues[name] = field.defaultValue;
782
- }
783
- return { supported: true, fields, initialValues };
784
- }
785
- function normalizeValue(field, value) {
786
- const isEmpty = value === "" || value === void 0 || value === null;
787
- if (isEmpty) {
788
- if (!field.required) return void 0;
789
- if (field.nullable) return null;
790
- return value;
791
- }
792
- if (field.kind === "date") {
793
- const acceptsString = (field.schema._def?.coerce ?? false) === true;
794
- return acceptsString ? value : new Date(String(value));
795
- }
796
- return value;
797
- }
798
- function buildPayload(fields, values) {
799
- return fields.reduce((payload, field) => {
800
- const value = normalizeValue(field, values[field.name]);
801
- if (value !== void 0 || field.required) {
802
- payload[field.name] = value;
803
- }
804
- return payload;
805
- }, {});
806
- }
807
- function ActionFormButton({ action, isActive, onToggle }) {
808
- return /* @__PURE__ */ jsx(Button, { variant: isActive ? "filled" : "light", size: "sm", onClick: onToggle, children: action.label });
809
- }
810
- function ActionForm({ action, dealId, onClose }) {
811
- const executeAction = useExecuteAction({ dealId });
812
- const schemaDescription = describeSchema(action.payloadSchema);
813
- const form = useForm({
814
- initialValues: schemaDescription.supported ? schemaDescription.initialValues : {}
815
- });
816
- const handleSubmit = form.onSubmit(async (values) => {
817
- if (!schemaDescription.supported || !action.payloadSchema) return;
818
- form.clearErrors();
819
- const payload = buildPayload(schemaDescription.fields, values);
820
- const parsed = action.payloadSchema.safeParse(payload);
821
- if (!parsed.success) {
822
- const formErrors = {};
823
- for (const issue of parsed.error.issues) {
824
- const fieldName = issue.path[0];
825
- if (typeof fieldName === "string" && schemaDescription.fields.some((field) => field.name === fieldName)) {
826
- formErrors[fieldName] = issue.message;
827
- }
828
- }
829
- form.setErrors(formErrors);
830
- return;
831
- }
832
- await executeAction.mutateAsync({ key: action.key, payload: parsed.data });
833
- onClose();
834
- });
835
- return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
836
- /* @__PURE__ */ jsx(Divider, {}),
837
- /* @__PURE__ */ jsx(Title, { order: 3, children: action.label }),
838
- schemaDescription.supported ? /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
839
- schemaDescription.fields.map((field) => {
840
- if (field.kind === "number") {
841
- return /* @__PURE__ */ jsx(
842
- NumberInput,
843
- {
844
- label: field.label,
845
- required: field.required,
846
- disabled: executeAction.isPending,
847
- error: form.errors[field.name],
848
- ...form.getInputProps(field.name)
849
- },
850
- field.name
851
- );
852
- }
853
- if (field.kind === "boolean") {
854
- return /* @__PURE__ */ jsx(
855
- Switch,
856
- {
857
- label: field.label,
858
- disabled: executeAction.isPending,
859
- error: form.errors[field.name],
860
- ...form.getInputProps(field.name, { type: "checkbox" })
861
- },
862
- field.name
863
- );
864
- }
865
- if (field.kind === "enum") {
866
- return /* @__PURE__ */ jsx(
867
- Select,
868
- {
869
- label: field.label,
870
- required: field.required,
871
- disabled: executeAction.isPending,
872
- data: field.enumValues ?? [],
873
- error: form.errors[field.name],
874
- ...form.getInputProps(field.name)
875
- },
876
- field.name
877
- );
878
- }
879
- if (field.kind === "string" && isLongTextField(field.name)) {
880
- return /* @__PURE__ */ jsx(
881
- Textarea,
882
- {
883
- label: field.label,
884
- required: field.required,
885
- disabled: executeAction.isPending,
886
- error: form.errors[field.name],
887
- autosize: true,
888
- minRows: 6,
889
- ...form.getInputProps(field.name)
890
- },
891
- field.name
892
- );
893
- }
894
- return /* @__PURE__ */ jsx(
895
- TextInput,
896
- {
897
- label: field.label,
898
- type: field.kind === "date" || field.kind === "dateString" ? "date" : "text",
899
- required: field.required,
900
- disabled: executeAction.isPending,
901
- error: form.errors[field.name],
902
- ...form.getInputProps(field.name)
903
- },
904
- field.name
905
- );
906
- }),
907
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
908
- /* @__PURE__ */ jsx(Button, { variant: "default", onClick: onClose, disabled: executeAction.isPending, children: "Cancel" }),
909
- /* @__PURE__ */ jsx(Button, { type: "submit", loading: executeAction.isPending, children: action.label })
910
- ] })
911
- ] }) }) : /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
912
- /* @__PURE__ */ jsx(Alert, { color: "yellow", variant: "light", icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Unsupported action form", children: /* @__PURE__ */ jsx(Text, { size: "sm", children: schemaDescription.reason }) }),
913
- /* @__PURE__ */ jsx(Group, { justify: "flex-end", children: /* @__PURE__ */ jsx(Button, { variant: "default", onClick: onClose, disabled: executeAction.isPending, children: "Close" }) })
914
- ] })
915
- ] });
916
- }
917
- var THREAD_INDENT_STEP = 6;
918
- var THREAD_MAX_INDENT = 36;
919
- function formatSentAt(sentAt) {
920
- if (!sentAt) return "Unknown time";
921
- const date = new Date(sentAt);
922
- if (Number.isNaN(date.getTime())) return sentAt;
923
- return date.toLocaleString();
924
- }
925
- function getQuoteDepth(line) {
926
- const match = line.match(/^\s*((?:>\s*)+)(.*)$/);
927
- if (!match) return { depth: 0, text: line };
928
- return {
929
- depth: (match[1].match(/>/g) ?? []).length,
930
- text: match[2]
931
- };
932
- }
933
- function MessageBody({ body }) {
934
- const resolvedBody = body || "Full message body unavailable from Instantly.";
935
- return /* @__PURE__ */ jsx(Stack, { gap: 0, children: resolvedBody.split(/\r?\n/).map((line, index) => {
936
- const { depth, text } = getQuoteDepth(line);
937
- return /* @__PURE__ */ jsxs(
938
- Box,
939
- {
940
- style: {
941
- display: "grid",
942
- gridTemplateColumns: `${depth * 12}px minmax(0, 1fr)`,
943
- minHeight: "1.55em"
944
- },
945
- children: [
946
- /* @__PURE__ */ jsx(
947
- Box,
948
- {
949
- "aria-hidden": "true",
950
- style: {
951
- display: "grid",
952
- gridTemplateColumns: `repeat(${depth}, 12px)`
953
- },
954
- children: Array.from({ length: depth }).map((_, depthIndex) => /* @__PURE__ */ jsx(
955
- Box,
956
- {
957
- style: {
958
- borderLeft: "1px solid var(--color-border)",
959
- marginLeft: 4
960
- }
961
- },
962
- depthIndex
963
- ))
964
- }
965
- ),
966
- /* @__PURE__ */ jsx(Text, { size: "sm", lh: 1.55, style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere" }, children: text || " " })
967
- ]
968
- },
969
- `${index}-${line}`
970
- );
971
- }) });
972
- }
973
- function ConversationThread({ messages }) {
974
- if (messages.length === 0) {
975
- return /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
976
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconMessages, { size: 18 }), title: "Conversation", mb: "xs" }),
977
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No synced email thread is available. Check Activity for local CRM events." })
978
- ] });
979
- }
980
- return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
981
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconMessages, { size: 18 }), title: "Conversation", mb: 0 }),
982
- [...messages].reverse().map((message, index) => {
983
- const isOutbound = message.direction === "outbound";
984
- const participant = isOutbound ? message.toEmail : message.fromEmail;
985
- const olderMessageIndent = Math.min(index * THREAD_INDENT_STEP, THREAD_MAX_INDENT);
986
- const olderMessageLevels = olderMessageIndent / THREAD_INDENT_STEP;
987
- const directionLabel = isOutbound ? "Sent to" : "Received from";
988
- const messageIcon = isOutbound ? /* @__PURE__ */ jsx(IconMailForward, { size: 16 }) : /* @__PURE__ */ jsx(IconInbox, { size: 16 });
989
- return /* @__PURE__ */ jsxs(
990
- Box,
991
- {
992
- style: {
993
- display: "grid",
994
- gridTemplateColumns: `${olderMessageIndent}px minmax(0, 1fr)`
995
- },
996
- children: [
997
- /* @__PURE__ */ jsx(
998
- Box,
999
- {
1000
- "aria-hidden": "true",
1001
- style: {
1002
- display: "grid",
1003
- gridTemplateColumns: `repeat(${olderMessageLevels}, ${THREAD_INDENT_STEP}px)`
1004
- },
1005
- children: Array.from({ length: olderMessageLevels }).map((_, levelIndex) => /* @__PURE__ */ jsx(
1006
- Box,
1007
- {
1008
- style: {
1009
- borderLeft: "1px solid var(--color-border)"
1010
- }
1011
- },
1012
- levelIndex
1013
- ))
1014
- }
1015
- ),
1016
- /* @__PURE__ */ jsx(
1017
- Box,
1018
- {
1019
- pl: "md",
1020
- py: "xs",
1021
- style: {
1022
- borderLeft: `2px solid ${isOutbound ? "var(--mantine-color-blue-5)" : "var(--mantine-color-teal-5)"}`
1023
- },
1024
- children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
1025
- /* @__PURE__ */ jsx(
1026
- CardHeader,
1027
- {
1028
- icon: messageIcon,
1029
- iconColor: isOutbound ? "blue" : "teal",
1030
- title: `${directionLabel} ${participant || "unknown recipient"}`,
1031
- subtitle: message.subject ?? void 0,
1032
- titleOrder: 5,
1033
- mb: 0,
1034
- rightSection: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { flexShrink: 0, textAlign: "right" }, children: formatSentAt(message.sentAt) })
1035
- }
1036
- ),
1037
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(MessageBody, { body: message.body }) })
1038
- ] })
1039
- }
1040
- )
1041
- ]
1042
- },
1043
- message.id
1044
- );
1045
- })
1046
- ] });
1047
- }
1048
- var CANONICAL_ACTION_KEYS = /* @__PURE__ */ new Set(["send_reply"]);
1049
- var DEAL_OWNERSHIP_BADGE_COPY2 = {
1050
- us: {
1051
- color: "red",
1052
- label: "Our move"
1053
- },
1054
- them: {
1055
- color: "gray",
1056
- label: "Their move"
1057
- }
1058
- };
1059
- function getContactName(deal) {
1060
- return [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ");
1061
- }
1062
- function getCompanyName(deal) {
1063
- return deal.contact?.company?.name || deal.discovery_data?.company || deal.contact_email?.split("@")[1] || "Unknown";
1064
- }
1065
- function toAction(actionDef) {
1066
- const { key, label, payloadSchema } = actionDef;
1067
- return { key, label, payloadSchema };
1068
- }
1069
- function getVisibleCanonicalDealActions(deal, crmActions) {
1070
- if (deal.nextAction && CANONICAL_ACTION_KEYS.has(deal.nextAction)) {
1071
- if (deal.nextAction === "send_reply" && deal.ownership === "them") return [];
1072
- const nextAction = crmActions.find((action) => action.key === deal.nextAction);
1073
- return nextAction ? [toAction(nextAction)] : [];
1074
- }
1075
- return deriveActions(deal, crmActions).filter((action) => {
1076
- if (!CANONICAL_ACTION_KEYS.has(action.key)) return false;
1077
- if (action.key === "send_reply" && deal.ownership === "them") return false;
1078
- return true;
1079
- });
1080
- }
1081
- function CopyBookingLinkButton({ dealId, contactId }) {
1082
- const url = buildBookingUrl({ dealId, contactId });
1083
- return /* @__PURE__ */ jsx(CopyButton, { value: url, timeout: 1500, children: ({ copied, copy }) => /* @__PURE__ */ jsx(
1084
- Button,
1085
- {
1086
- variant: "light",
1087
- size: "xs",
1088
- color: copied ? "teal" : void 0,
1089
- leftSection: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 }),
1090
- onClick: copy,
1091
- children: copied ? "Copied" : "Copy Booking Link"
1092
- }
1093
- ) });
1094
- }
1095
- function DetailRow({ label, value }) {
1096
- return /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "flex-start", wrap: "nowrap", children: [
1097
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, style: { width: 120, flexShrink: 0 }, children: label }),
1098
- /* @__PURE__ */ jsx(Text, { size: "sm", style: { minWidth: 0, wordBreak: "break-word", color: "var(--color-text-subtle)" }, children: value || "N/A" })
1099
- ] });
1100
- }
1101
- function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
1102
- const navigate = useNavigate();
1103
- const [referrer] = useState(() => getDealReferrer());
1104
- const backTarget = referrer === "pipeline" ? { to: "/crm/pipeline", label: "Pipeline" } : { to: "/crm/deals", label: "Deals" };
1105
- const { data: deal, isLoading, error } = useDealDetail(dealId);
1106
- const crmActions = useCrmActions();
1107
- const actions = useMemo(
1108
- () => deal ? getVisibleCanonicalDealActions(deal, crmActions) : [],
1109
- [deal, crmActions]
1110
- );
1111
- const [activeActionKey, setActiveActionKey] = useState(null);
1112
- const activeAction = activeActionKey ? actions.find((action) => action.key === activeActionKey) : null;
1113
- useEffect(() => {
1114
- if (activeActionKey && !actions.some((action) => action.key === activeActionKey)) {
1115
- setActiveActionKey(null);
1116
- }
1117
- }, [actions, activeActionKey]);
1118
- useEffect(() => {
1119
- if (deal) onDealLoaded?.(deal);
1120
- }, [deal, onDealLoaded]);
1121
- const titleName = deal ? getContactName(deal) || getCompanyName(deal) : null;
1122
- const title = titleName ? `${titleName} Deal` : "Deal Detail";
1123
- const companyName = deal ? getCompanyName(deal) : "Unknown";
1124
- const ownershipBadge = deal?.ownership ? DEAL_OWNERSHIP_BADGE_COPY2[deal.ownership] : null;
1125
- const activityLog = (deal?.activity_log || []).filter(Boolean);
1126
- const hasDiscoveryData = deal?.discovery_data != null && (typeof deal.discovery_data !== "object" || Array.isArray(deal.discovery_data) || Object.keys(deal.discovery_data).length > 0);
1127
- const headerActions = /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1128
- /* @__PURE__ */ jsx(
1129
- Button,
1130
- {
1131
- variant: "light",
1132
- size: "sm",
1133
- leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1134
- onClick: () => navigate({ to: backTarget.to }),
1135
- children: backTarget.label
1136
- }
1137
- ),
1138
- deal?.proposal_pdf_url && /* @__PURE__ */ jsx(
1139
- Button,
1140
- {
1141
- variant: "light",
1142
- size: "sm",
1143
- leftSection: /* @__PURE__ */ jsx(IconFileText, { size: 16 }),
1144
- onClick: () => window.open(deal.proposal_pdf_url, "_blank"),
1145
- children: "View Proposal"
1146
- }
1147
- ),
1148
- deal && renderActions?.(deal)
1149
- ] });
1150
- if (isLoading) {
1151
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1152
- /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Loading deal details...", rightSection: headerActions }),
1153
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
1154
- ] }) }) });
1155
- }
1156
- if (error) {
1157
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1158
- /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Unable to load deal details", rightSection: headerActions }),
1159
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load deal" }) })
1160
- ] }) }) });
1161
- }
1162
- if (!deal) {
1163
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1164
- /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Deal not found", rightSection: headerActions }),
1165
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(EmptyState, { icon: IconInbox, title: "Deal not found", description: "The selected deal no longer exists." }) })
1166
- ] }) }) });
1167
- }
1168
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1169
- /* @__PURE__ */ jsx(
1170
- PageTitleCaption,
1171
- {
1172
- title,
1173
- caption: `${companyName} - ${formatDealStageLabel(deal.stage_key)}`,
1174
- rightSection: headerActions
1175
- }
1176
- ),
1177
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "lg", children: [
1178
- /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1179
- /* @__PURE__ */ jsx(
1180
- CardHeader,
1181
- {
1182
- icon: /* @__PURE__ */ jsx(IconBriefcase, { size: 18 }),
1183
- title: "Deal",
1184
- mb: 0,
1185
- rightSection: /* @__PURE__ */ jsx(CopyBookingLinkButton, { dealId: deal.id, contactId: deal.contact?.id })
1186
- }
1187
- ),
1188
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
1189
- /* @__PURE__ */ jsx(
1190
- DetailRow,
1191
- {
1192
- label: "Stage",
1193
- value: /* @__PURE__ */ jsx(Badge, { color: DEAL_STAGE_COLORS[deal.stage_key || ""] || "gray", children: formatDealStageLabel(deal.stage_key) })
1194
- }
1195
- ),
1196
- /* @__PURE__ */ jsx(
1197
- DetailRow,
1198
- {
1199
- label: "Priority",
1200
- value: /* @__PURE__ */ jsx(Tooltip, { label: deal.priority.reason, withArrow: true, children: /* @__PURE__ */ jsx(
1201
- Badge,
1202
- {
1203
- variant: "light",
1204
- color: deal.priority.color,
1205
- size: "sm",
1206
- style: { maxWidth: "100%", textTransform: "none" },
1207
- children: /* @__PURE__ */ jsx(Text, { span: true, size: "xs", truncate: "end", style: { maxWidth: "100%" }, children: deal.priority.label })
1208
- }
1209
- ) })
1210
- }
1211
- ),
1212
- /* @__PURE__ */ jsx(
1213
- DetailRow,
1214
- {
1215
- label: "Move",
1216
- value: ownershipBadge ? /* @__PURE__ */ jsx(
1217
- Badge,
1218
- {
1219
- variant: "light",
1220
- color: ownershipBadge.color,
1221
- size: "sm",
1222
- style: { maxWidth: "100%", textTransform: "none" },
1223
- children: /* @__PURE__ */ jsx(Text, { span: true, size: "xs", truncate: "end", style: { maxWidth: "100%" }, children: ownershipBadge.label })
1224
- }
1225
- ) : "N/A"
1226
- }
1227
- ),
1228
- /* @__PURE__ */ jsx(
1229
- DetailRow,
1230
- {
1231
- label: "Sent",
1232
- value: deal.proposal_sent_at ? new Date(deal.proposal_sent_at).toLocaleString() : "N/A"
1233
- }
1234
- ),
1235
- /* @__PURE__ */ jsx(DetailRow, { label: "Envelope ID", value: deal.signature_envelope_id || "N/A" }),
1236
- /* @__PURE__ */ jsx(DetailRow, { label: "Contact Email", value: deal.contact?.email || deal.contact_email || "N/A" })
1237
- ] })
1238
- ] }),
1239
- /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1240
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconUser, { size: 18 }), title: "Contact", mb: 0 }),
1241
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
1242
- /* @__PURE__ */ jsx(DetailRow, { label: "Name", value: getContactName(deal) || "N/A" }),
1243
- /* @__PURE__ */ jsx(DetailRow, { label: "Title", value: deal.contact?.title || "N/A" }),
1244
- /* @__PURE__ */ jsx(DetailRow, { label: "Company", value: deal.contact?.company?.name || "N/A" }),
1245
- /* @__PURE__ */ jsx(DetailRow, { label: "Domain", value: deal.contact?.company?.domain || "N/A" }),
1246
- /* @__PURE__ */ jsx(DetailRow, { label: "Segment", value: deal.contact?.company?.segment || "N/A" }),
1247
- /* @__PURE__ */ jsx(DetailRow, { label: "Category", value: deal.contact?.company?.category || "N/A" })
1248
- ] })
1249
- ] }),
1250
- /* @__PURE__ */ jsxs(Tabs, { defaultValue: "conversation", keepMounted: false, children: [
1251
- /* @__PURE__ */ jsxs(Tabs.List, { children: [
1252
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "conversation", leftSection: /* @__PURE__ */ jsx(IconMessages, { size: 16 }), children: "Conversation" }),
1253
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "activity", leftSection: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), children: "Activity" }),
1254
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "discovery", leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }), children: "Discovery" })
1255
- ] }),
1256
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "conversation", pt: "md", children: /* @__PURE__ */ jsx(ConversationThread, { messages: deal.conversation.messages }) }),
1257
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "activity", pt: "md", children: /* @__PURE__ */ jsx(ActivityTimeline, { activities: activityLog }) }),
1258
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "discovery", pt: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1259
- /* @__PURE__ */ jsx(
1260
- CardHeader,
1261
- {
1262
- icon: /* @__PURE__ */ jsx(IconSearch, { size: 18 }),
1263
- title: "Discovery",
1264
- subtitle: "Structured intake answers and qualification context captured before proposal work.",
1265
- mb: 0
1266
- }
1267
- ),
1268
- hasDiscoveryData ? /* @__PURE__ */ jsx(Code, { block: true, children: JSON.stringify(deal.discovery_data, null, 2) }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No discovery responses have been captured for this deal." })
1269
- ] }) })
1270
- ] })
1271
- ] }) }),
1272
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1273
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconBolt, { size: 18 }), title: "Actions", titleOrder: 2, mb: 0 }),
1274
- actions.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1275
- /* @__PURE__ */ jsx(Group, { gap: "sm", children: actions.map(
1276
- (action) => action.payloadSchema ? /* @__PURE__ */ jsx(
1277
- ActionFormButton,
1278
- {
1279
- action,
1280
- isActive: activeActionKey === action.key,
1281
- onToggle: () => setActiveActionKey((current) => current === action.key ? null : action.key)
1282
- },
1283
- action.key
1284
- ) : /* @__PURE__ */ jsx(ActionButton, { action, dealId: deal.id }, action.key)
1285
- ) }),
1286
- activeAction?.payloadSchema && /* @__PURE__ */ jsx(
1287
- ActionForm,
1288
- {
1289
- action: activeAction,
1290
- dealId: deal.id,
1291
- onClose: () => setActiveActionKey(null)
1292
- },
1293
- activeAction.key
1294
- )
1295
- ] }) : /* @__PURE__ */ jsx(Alert, { color: "gray", variant: "light", children: "No reply actions are currently available for this deal." })
1296
- ] }) })
1297
- ] }) }) });
1298
- }
1299
- function CompanyDetailPage({ companyId }) {
1300
- const navigate = useNavigate();
1301
- const { data: company, isLoading, error } = useCompany(companyId);
1302
- const title = company ? company.name : "Company Detail";
1303
- const headerActions = /* @__PURE__ */ jsx(
1304
- Button,
1305
- {
1306
- variant: "light",
1307
- size: "sm",
1308
- leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1309
- onClick: () => navigate({ to: "/crm" }),
1310
- children: "CRM"
1311
- }
1312
- );
1313
- if (isLoading) {
1314
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1315
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Loading company details...", rightSection: headerActions }) }),
1316
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
1317
- ] }) });
1318
- }
1319
- if (error) {
1320
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1321
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Unable to load company details", rightSection: headerActions }) }),
1322
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load company" }) })
1323
- ] }) });
1324
- }
1325
- if (!company) {
1326
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1327
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Company not found", rightSection: headerActions }) }),
1328
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(
1329
- EmptyState,
1330
- {
1331
- icon: IconBuilding,
1332
- title: "Company not found",
1333
- description: "The selected company no longer exists."
1334
- }
1335
- ) })
1336
- ] }) });
1337
- }
1338
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1339
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(
1340
- PageTitleCaption,
1341
- {
1342
- title: company.name,
1343
- caption: [company.domain, company.locationCity, company.locationState].filter(Boolean).join(" \xB7 ") || "Company",
1344
- rightSection: headerActions
1345
- }
1346
- ) }),
1347
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", p: "md", children: [
1348
- /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1349
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Overview" }),
1350
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
1351
- /* @__PURE__ */ jsxs(Group, { children: [
1352
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Name:" }),
1353
- /* @__PURE__ */ jsx(Text, { children: company.name })
1354
- ] }),
1355
- /* @__PURE__ */ jsxs(Group, { children: [
1356
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Status:" }),
1357
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: "blue", children: company.status })
1358
- ] }),
1359
- company.domain && /* @__PURE__ */ jsxs(Group, { children: [
1360
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Domain:" }),
1361
- /* @__PURE__ */ jsx(Text, { children: company.domain })
1362
- ] }),
1363
- company.website && /* @__PURE__ */ jsxs(Group, { children: [
1364
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Website:" }),
1365
- /* @__PURE__ */ jsx(Text, { component: "a", href: company.website, target: "_blank", c: "blue", children: company.website })
1366
- ] }),
1367
- company.numEmployees != null && /* @__PURE__ */ jsxs(Group, { children: [
1368
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Employees:" }),
1369
- /* @__PURE__ */ jsx(Text, { children: company.numEmployees.toLocaleString() })
1370
- ] }),
1371
- company.foundedYear != null && /* @__PURE__ */ jsxs(Group, { children: [
1372
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Founded:" }),
1373
- /* @__PURE__ */ jsx(Text, { children: company.foundedYear })
1374
- ] }),
1375
- company.contactCount != null && /* @__PURE__ */ jsxs(Group, { children: [
1376
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Contacts:" }),
1377
- /* @__PURE__ */ jsx(Text, { children: company.contactCount })
1378
- ] })
1379
- ] })
1380
- ] }) }),
1381
- (company.locationCity || company.locationState) && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1382
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Location" }),
1383
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
1384
- company.locationCity && /* @__PURE__ */ jsxs(Group, { children: [
1385
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "City:" }),
1386
- /* @__PURE__ */ jsx(Text, { children: company.locationCity })
1387
- ] }),
1388
- company.locationState && /* @__PURE__ */ jsxs(Group, { children: [
1389
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "State:" }),
1390
- /* @__PURE__ */ jsx(Text, { children: company.locationState })
1391
- ] })
1392
- ] })
1393
- ] }) }),
1394
- company.linkedinUrl && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1395
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Social" }),
1396
- /* @__PURE__ */ jsxs(Group, { children: [
1397
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "LinkedIn:" }),
1398
- /* @__PURE__ */ jsx(Text, { component: "a", href: company.linkedinUrl, target: "_blank", c: "blue", children: "View Profile" })
1399
- ] })
1400
- ] }) }),
1401
- (company.segment || company.category) && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1402
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Classification" }),
1403
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
1404
- company.segment && /* @__PURE__ */ jsxs(Group, { children: [
1405
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Segment:" }),
1406
- /* @__PURE__ */ jsx(Text, { children: company.segment })
1407
- ] }),
1408
- company.category && /* @__PURE__ */ jsxs(Group, { children: [
1409
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Category:" }),
1410
- /* @__PURE__ */ jsx(Text, { children: company.category })
1411
- ] })
1412
- ] })
1413
- ] }) }),
1414
- company.verticalResearch && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1415
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Vertical Research" }),
1416
- /* @__PURE__ */ jsx(Text, { size: "sm", style: { whiteSpace: "pre-wrap" }, children: company.verticalResearch })
1417
- ] }) }),
1418
- company.enrichmentData && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1419
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Enrichment Data" }),
1420
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { whiteSpace: "pre-wrap" }, children: JSON.stringify(company.enrichmentData, null, 2) })
1421
- ] }) })
1422
- ] }) })
1423
- ] }) });
1424
- }
1425
- var crmPrioritySettingsKeys = {
1426
- all: ["crm-priority-settings"],
1427
- detail: (organizationId) => [...crmPrioritySettingsKeys.all, organizationId]
1428
- };
1429
- function useCrmPrioritySettings() {
1430
- const { apiRequest, isReady, workOSOrganizationId } = useElevasisServices();
1431
- return useQuery({
1432
- queryKey: crmPrioritySettingsKeys.detail(workOSOrganizationId),
1433
- queryFn: () => apiRequest("/crm/settings/priority"),
1434
- enabled: isReady
1435
- });
1436
- }
1437
- function useUpdateCrmPrioritySettings() {
1438
- const { apiRequest, workOSOrganizationId } = useElevasisServices();
1439
- const queryClient = useQueryClient();
1440
- return useMutation({
1441
- mutationFn: (override) => apiRequest("/crm/settings/priority", {
1442
- method: "PATCH",
1443
- body: JSON.stringify(override)
1444
- }),
1445
- onSuccess: () => {
1446
- queryClient.invalidateQueries({ queryKey: crmPrioritySettingsKeys.detail(workOSOrganizationId) });
1447
- queryClient.invalidateQueries({ queryKey: dealKeys.lists() });
1448
- queryClient.invalidateQueries({ queryKey: dealKeys.details() });
1449
- showSuccessNotification("CRM priority settings saved.");
1450
- },
1451
- onError: (error) => {
1452
- showApiErrorNotification(error);
1453
- }
1454
- });
1455
- }
1456
- function useResetCrmPrioritySettings() {
1457
- const { apiRequest, workOSOrganizationId } = useElevasisServices();
1458
- const queryClient = useQueryClient();
1459
- return useMutation({
1460
- mutationFn: () => apiRequest("/crm/settings/priority", {
1461
- method: "DELETE"
1462
- }),
1463
- onSuccess: () => {
1464
- queryClient.invalidateQueries({ queryKey: crmPrioritySettingsKeys.detail(workOSOrganizationId) });
1465
- queryClient.invalidateQueries({ queryKey: dealKeys.lists() });
1466
- queryClient.invalidateQueries({ queryKey: dealKeys.details() });
1467
- showSuccessNotification("CRM priority settings reset to Org-OS defaults.");
1468
- },
1469
- onError: (error) => {
1470
- showApiErrorNotification(error);
1471
- }
1472
- });
1473
- }
1474
- var BUCKET_KEYS = [
1475
- "needs_response",
1476
- "follow_up_due",
1477
- "waiting",
1478
- "stale",
1479
- "closed_low"
1480
- ];
1481
- var COLOR_OPTIONS = ["red", "orange", "yellow", "blue", "gray", "dark", "green", "teal", "violet", "grape"].map(
1482
- (color) => ({ value: color, label: color })
1483
- );
1484
- var ORDER_OPTIONS = [
1485
- { value: "10", label: "First - most urgent" },
1486
- { value: "20", label: "Second - follow-up queue" },
1487
- { value: "30", label: "Middle - waiting" },
1488
- { value: "40", label: "Later - stale review" },
1489
- { value: "50", label: "Last - closed or low priority" }
1490
- ];
1491
- var BUCKET_RULE_COPY = {
1492
- needs_response: {
1493
- applies: "Current state or latest activity indicates the lead replied or cancelled and needs a human response.",
1494
- example: "A lead replies to the discovery email; the deal moves to Needs Response immediately."
1495
- },
1496
- follow_up_due: {
1497
- applies: "The configured follow-up window for the current state has elapsed.",
1498
- example: "A discovery link was sent 5 days ago and the follow-up window is 3 days."
1499
- },
1500
- waiting: {
1501
- applies: "No response is needed yet, or the next action time is still in the future.",
1502
- example: "A discovery link was sent yesterday and the follow-up window has not elapsed."
1503
- },
1504
- stale: {
1505
- applies: "No meaningful activity has happened for the stale threshold and no stronger bucket applies.",
1506
- example: "A deal has had no activity for 14 days and is not waiting on a scheduled follow-up."
1507
- },
1508
- closed_low: {
1509
- applies: "The deal is in a closed stage, so it should stay out of the active follow-up queue.",
1510
- example: "A deal is marked Closed Won or Closed Lost."
1511
- }
1512
- };
1513
- function getOrderLabel(rank) {
1514
- if (rank <= 10) return "Shown first";
1515
- if (rank <= 20) return "After urgent replies";
1516
- if (rank <= 30) return "Middle";
1517
- if (rank <= 40) return "Later review";
1518
- return "Shown last";
1519
- }
1520
- function toResolvedConfig(config = DEFAULT_CRM_PRIORITY_RULE_CONFIG) {
1521
- return {
1522
- ...config,
1523
- enabled: "enabled" in config ? config.enabled : true,
1524
- buckets: [...config.buckets].sort((a, b) => a.rank - b.rank),
1525
- closedStageKeys: [...config.closedStageKeys],
1526
- followUpAfterDaysByStateKey: { ...config.followUpAfterDaysByStateKey }
1527
- };
1528
- }
1529
- function draftFromConfig(config) {
1530
- const defaultBuckets = new Map(CRM_PRIORITY_BUCKETS.map((bucket) => [bucket.bucketKey, bucket]));
1531
- const resolvedBuckets = new Map(config.buckets.map((bucket) => [bucket.bucketKey, bucket]));
1532
- return {
1533
- enabled: config.enabled,
1534
- staleAfterDays: config.staleAfterDays,
1535
- buckets: Object.fromEntries(
1536
- BUCKET_KEYS.map((bucketKey) => {
1537
- const bucket = resolvedBuckets.get(bucketKey) ?? defaultBuckets.get(bucketKey);
1538
- return [bucketKey, { label: bucket.label, color: bucket.color, rank: bucket.rank }];
1539
- })
1540
- )
1541
- };
1542
- }
1543
- function toOverride(draft) {
1544
- return {
1545
- enabled: draft.enabled,
1546
- staleAfterDays: draft.staleAfterDays,
1547
- buckets: Object.fromEntries(
1548
- BUCKET_KEYS.map((bucketKey) => [
1549
- bucketKey,
1550
- {
1551
- label: draft.buckets[bucketKey].label.trim(),
1552
- color: draft.buckets[bucketKey].color.trim(),
1553
- rank: draft.buckets[bucketKey].rank
1554
- }
1555
- ])
1556
- )
1557
- };
1558
- }
1559
- function CrmSettingsPage() {
1560
- const settingsQuery = useCrmPrioritySettings();
1561
- const updateSettings = useUpdateCrmPrioritySettings();
1562
- const resetSettings = useResetCrmPrioritySettings();
1563
- const resolvedConfig = useMemo(() => toResolvedConfig(settingsQuery.data?.resolved), [settingsQuery.data?.resolved]);
1564
- const defaultConfig = useMemo(() => toResolvedConfig(settingsQuery.data?.defaults), [settingsQuery.data?.defaults]);
1565
- const [draft, setDraft] = useState(() => draftFromConfig(resolvedConfig));
1566
- useEffect(() => {
1567
- if (settingsQuery.data) {
1568
- setDraft(draftFromConfig(toResolvedConfig(settingsQuery.data.resolved)));
1569
- }
1570
- }, [settingsQuery.data]);
1571
- const hasOverride = Boolean(settingsQuery.data?.override);
1572
- const isSaving = updateSettings.isPending || resetSettings.isPending;
1573
- const updateBucket = (bucketKey, field, value) => {
1574
- setDraft((current) => ({
1575
- ...current,
1576
- buckets: {
1577
- ...current.buckets,
1578
- [bucketKey]: {
1579
- ...current.buckets[bucketKey],
1580
- [field]: value
1581
- }
1582
- }
1583
- }));
1584
- };
1585
- const handleReset = async () => {
1586
- const response = await resetSettings.mutateAsync();
1587
- setDraft(draftFromConfig(toResolvedConfig(response.resolved)));
1588
- };
1589
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1590
- /* @__PURE__ */ jsx(
1591
- PageTitleCaption,
1592
- {
1593
- title: "CRM Settings",
1594
- caption: "Scoped CRM controls for priority presentation and thresholds",
1595
- rightSection: !settingsQuery.isLoading && !settingsQuery.error ? /* @__PURE__ */ jsx(
1596
- Switch,
1597
- {
1598
- label: "Priority Enabled",
1599
- checked: draft.enabled,
1600
- onChange: (event) => setDraft((current) => ({ ...current, enabled: event.currentTarget.checked }))
1601
- }
1602
- ) : null
1603
- }
1604
- ),
1605
- settingsQuery.isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : settingsQuery.error ? /* @__PURE__ */ jsx(CenteredErrorState, { error: settingsQuery.error, title: "Failed to load CRM settings" }) : /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
1606
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
1607
- /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1608
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1609
- /* @__PURE__ */ jsx(IconSettings, { size: 18 }),
1610
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Priority Rules" }),
1611
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: hasOverride ? "blue" : "gray", children: hasOverride ? "Custom" : "Org-OS defaults" })
1612
- ] }),
1613
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "These settings are saved for CRM priority evaluation. Deals continue to receive priority from the backend response." })
1614
- ] }),
1615
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1616
- /* @__PURE__ */ jsx(
1617
- Button,
1618
- {
1619
- variant: "light",
1620
- leftSection: /* @__PURE__ */ jsx(IconRestore, { size: 16 }),
1621
- loading: resetSettings.isPending,
1622
- disabled: isSaving,
1623
- onClick: () => void handleReset(),
1624
- children: "Reset defaults"
1625
- }
1626
- ),
1627
- /* @__PURE__ */ jsx(
1628
- Button,
1629
- {
1630
- loading: updateSettings.isPending,
1631
- disabled: isSaving,
1632
- onClick: () => void updateSettings.mutateAsync(toOverride(draft)),
1633
- children: "Save changes"
1634
- }
1635
- )
1636
- ] })
1637
- ] }),
1638
- /* @__PURE__ */ jsx(
1639
- NumberInput,
1640
- {
1641
- label: "Stale threshold",
1642
- description: "Deals with no meaningful activity after this many days can move to Stale.",
1643
- min: 1,
1644
- max: 365,
1645
- value: draft.staleAfterDays,
1646
- onChange: (value) => setDraft((current) => ({ ...current, staleAfterDays: Number(value) || current.staleAfterDays })),
1647
- suffix: " days",
1648
- maw: 320
1649
- }
1650
- ),
1651
- /* @__PURE__ */ jsxs(Table, { style: { tableLayout: "fixed" }, children: [
1652
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1653
- /* @__PURE__ */ jsx(Table.Th, { w: 150, children: "Bucket" }),
1654
- /* @__PURE__ */ jsx(Table.Th, { w: 180, children: "Label" }),
1655
- /* @__PURE__ */ jsx(Table.Th, { w: 170, children: "Color" }),
1656
- /* @__PURE__ */ jsx(Table.Th, { w: 210, children: "Display order" }),
1657
- /* @__PURE__ */ jsx(Table.Th, { w: 80, children: "Logic" })
1658
- ] }) }),
1659
- /* @__PURE__ */ jsx(Table.Tbody, { children: BUCKET_KEYS.map((bucketKey) => {
1660
- const bucket = draft.buckets[bucketKey];
1661
- const defaultBucket = defaultConfig.buckets.find((candidate) => candidate.bucketKey === bucketKey);
1662
- return /* @__PURE__ */ jsxs(Table.Tr, { children: [
1663
- /* @__PURE__ */ jsxs(Table.Td, { children: [
1664
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: bucketKey.replaceAll("_", " ") }),
1665
- /* @__PURE__ */ jsxs(
1666
- Badge,
1667
- {
1668
- mt: 6,
1669
- variant: "light",
1670
- color: defaultBucket?.color ?? "gray",
1671
- style: { textTransform: "none" },
1672
- children: [
1673
- "Default: ",
1674
- defaultBucket ? getOrderLabel(defaultBucket.rank) : "Not set"
1675
- ]
1676
- }
1677
- )
1678
- ] }),
1679
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1680
- TextInput,
1681
- {
1682
- size: "sm",
1683
- value: bucket.label,
1684
- onChange: (event) => updateBucket(bucketKey, "label", event.currentTarget.value)
1685
- }
1686
- ) }),
1687
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
1688
- /* @__PURE__ */ jsx(ColorSwatch, { color: `var(--mantine-color-${bucket.color}-6)`, size: 18 }),
1689
- /* @__PURE__ */ jsx(
1690
- Select,
1691
- {
1692
- size: "sm",
1693
- data: COLOR_OPTIONS,
1694
- value: bucket.color,
1695
- searchable: true,
1696
- allowDeselect: false,
1697
- onChange: (value) => updateBucket(bucketKey, "color", value ?? bucket.color)
1698
- }
1699
- )
1700
- ] }) }),
1701
- /* @__PURE__ */ jsxs(Table.Td, { children: [
1702
- /* @__PURE__ */ jsx(
1703
- Select,
1704
- {
1705
- size: "sm",
1706
- data: ORDER_OPTIONS,
1707
- value: String(bucket.rank),
1708
- allowDeselect: false,
1709
- onChange: (value) => updateBucket(bucketKey, "rank", Number(value) || bucket.rank)
1710
- }
1711
- ),
1712
- /* @__PURE__ */ jsx(Text, { mt: 4, size: "xs", c: "dimmed", children: "Lower order appears earlier in Pipeline and Deals." })
1713
- ] }),
1714
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Popover, { width: 360, position: "left", withArrow: true, shadow: "md", children: [
1715
- /* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", color: "gray", "aria-label": `Show ${bucket.label} logic`, children: /* @__PURE__ */ jsx(IconInfoCircle, { size: 18 }) }) }),
1716
- /* @__PURE__ */ jsx(Popover.Dropdown, { children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1717
- /* @__PURE__ */ jsxs("div", { children: [
1718
- /* @__PURE__ */ jsx(Text, { size: "xs", fw: 700, tt: "uppercase", c: "dimmed", children: "When it applies" }),
1719
- /* @__PURE__ */ jsx(Text, { size: "sm", children: BUCKET_RULE_COPY[bucketKey].applies })
1720
- ] }),
1721
- /* @__PURE__ */ jsxs("div", { children: [
1722
- /* @__PURE__ */ jsx(Text, { size: "xs", fw: 700, tt: "uppercase", c: "dimmed", children: "Example" }),
1723
- /* @__PURE__ */ jsx(Text, { size: "sm", children: BUCKET_RULE_COPY[bucketKey].example })
1724
- ] })
1725
- ] }) })
1726
- ] }) })
1727
- ] }, bucketKey);
1728
- }) })
1729
- ] })
1730
- ] }) })
1731
- ] }) }) });
1732
- }
1733
- function ContactDetailPage({ contactId }) {
1734
- const navigate = useNavigate();
1735
- const { data: contact, isLoading, error } = useContact(contactId);
1736
- const fullName = contact ? [contact.firstName, contact.lastName].filter(Boolean).join(" ") || contact.email : "Contact Detail";
1737
- const title = contact ? fullName : "Contact Detail";
1738
- const headerActions = /* @__PURE__ */ jsx(
1739
- Button,
1740
- {
1741
- variant: "light",
1742
- size: "sm",
1743
- leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1744
- onClick: () => navigate({ to: "/crm" }),
1745
- children: "CRM"
1746
- }
1747
- );
1748
- if (isLoading) {
1749
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1750
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Loading contact details...", rightSection: headerActions }) }),
1751
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
1752
- ] }) });
1753
- }
1754
- if (error) {
1755
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1756
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Unable to load contact details", rightSection: headerActions }) }),
1757
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load contact" }) })
1758
- ] }) });
1759
- }
1760
- if (!contact) {
1761
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1762
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Contact not found", rightSection: headerActions }) }),
1763
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(
1764
- EmptyState,
1765
- {
1766
- icon: IconUser,
1767
- title: "Contact not found",
1768
- description: "The selected contact no longer exists."
1769
- }
1770
- ) })
1771
- ] }) });
1772
- }
1773
- const liData = contact.enrichmentData;
1774
- const caption = [contact.title, contact.email].filter(Boolean).join(" \xB7 ") || [contact.company?.name].filter(Boolean).join("") || "Contact";
1775
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1776
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: fullName, caption, rightSection: headerActions }) }),
1777
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", p: "md", children: [
1778
- /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1779
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Overview" }),
1780
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1781
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: "blue", children: contact.status }),
1782
- contact.openingLine && /* @__PURE__ */ jsx(Badge, { variant: "light", color: "green", children: "Personalized" })
1783
- ] }),
1784
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
1785
- /* @__PURE__ */ jsxs(Group, { children: [
1786
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Email:" }),
1787
- /* @__PURE__ */ jsx(Text, { children: contact.email })
1788
- ] }),
1789
- contact.title && /* @__PURE__ */ jsxs(Group, { children: [
1790
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Title:" }),
1791
- /* @__PURE__ */ jsx(Text, { children: contact.title })
1792
- ] }),
1793
- contact.headline && /* @__PURE__ */ jsxs(Group, { children: [
1794
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Headline:" }),
1795
- /* @__PURE__ */ jsx(Text, { children: contact.headline })
1796
- ] }),
1797
- contact.linkedinUrl && /* @__PURE__ */ jsxs(Group, { children: [
1798
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "LinkedIn:" }),
1799
- /* @__PURE__ */ jsx(Anchor, { href: contact.linkedinUrl, target: "_blank", size: "sm", children: "View Profile" })
1800
- ] })
1801
- ] }),
1802
- /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1803
- "Created: ",
1804
- new Date(contact.createdAt).toLocaleDateString(),
1805
- contact.updatedAt && ` | Updated: ${new Date(contact.updatedAt).toLocaleDateString()}`
1806
- ] })
1807
- ] }) }),
1808
- contact.company && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1809
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Company" }),
1810
- /* @__PURE__ */ jsxs(
1811
- Card,
1812
- {
1813
- withBorder: true,
1814
- style: { cursor: "pointer" },
1815
- onClick: () => navigate({
1816
- to: "/crm/companies/$companyId",
1817
- params: { companyId: contact.company.id }
1818
- }),
1819
- children: [
1820
- /* @__PURE__ */ jsx(Text, { fw: 500, children: contact.company.name }),
1821
- contact.company.domain && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: contact.company.domain })
1822
- ]
1823
- }
1824
- )
1825
- ] }) }),
1826
- (liData?.linkedin?.summary || liData?.linkedin?.pastExperience || liData?.linkedin?.education) && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1827
- /* @__PURE__ */ jsx(Title, { order: 4, children: "LinkedIn Data" }),
1828
- liData.linkedin?.summary && /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1829
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Summary" }),
1830
- /* @__PURE__ */ jsx(Text, { size: "sm", children: liData.linkedin.summary })
1831
- ] }),
1832
- liData.linkedin?.pastExperience && /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1833
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Past Experience" }),
1834
- /* @__PURE__ */ jsx(Text, { size: "sm", children: liData.linkedin.pastExperience })
1835
- ] }),
1836
- liData.linkedin?.education && /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1837
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Education" }),
1838
- /* @__PURE__ */ jsx(Text, { size: "sm", children: liData.linkedin.education })
1839
- ] })
1840
- ] }) }),
1841
- liData?.linkedin?.activity && liData.linkedin.activity.length > 0 && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1842
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Recent Activity" }),
1843
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: liData.linkedin.activity.slice(0, 3).map((activity, i) => /* @__PURE__ */ jsxs(Card, { withBorder: true, children: [
1844
- activity.date && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: activity.date }),
1845
- activity.content && /* @__PURE__ */ jsx(Text, { size: "sm", children: activity.content })
1846
- ] }, i)) })
1847
- ] }) }),
1848
- contact.openingLine && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1849
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Personalization" }),
1850
- /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1851
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Opening Line" }),
1852
- /* @__PURE__ */ jsx(Text, { size: "sm", children: contact.openingLine })
1853
- ] })
1854
- ] }) }),
1855
- contact.filterReason && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1856
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Qualification" }),
1857
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "red", children: contact.filterReason })
1858
- ] }) })
1859
- ] }) })
1860
- ] }) });
1861
- }
1862
-
1863
- export { ActivityFeedWidget, CompanyDetailPage, ContactDetailPage, ConversationThread, CrmOverview, CrmSettingsPage, CrmSidebar, CrmSidebarTop, DEAL_STAGE_COLORS, DEAL_STAGE_OPTIONS, DealDetailPage, DealsListPage, MetricsStrip, PIPELINE_FUNNEL_ORDER, PipelineFunnelWidget, compareDealsByPriority, crmManifest, crmPrioritySettingsKeys, formatDealStageLabel, setDealReferrer, useCrmPipelineSummary, useCrmPrioritySettings, useCrmQuickMetrics, useRecentCrmActivity, useResetCrmPrioritySettings, useUpdateCrmPrioritySettings };