@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,1842 @@
1
+ import { ResourceExecuteDialog } from './chunk-KRTZTBVP.js';
2
+ import { TableSelectionToolbar, SortableHeader } from './chunk-TUMSNGTX.js';
3
+ import { PageContainer } from './chunk-BZZCNLT6.js';
4
+ import { SubshellSidebarSection, SubshellNavItem } from './chunk-27COZ5AH.js';
5
+ import { FilterBar } from './chunk-PDHTXPSF.js';
6
+ import { CustomModal } from './chunk-GBMNCNHX.js';
7
+ import { showApiErrorNotification, acquisitionListKeys, showSuccessNotification, useListsTelemetry, useLists, useCreateList, useTableSort, sortData, usePaginationState, useTableSelection, useList, useListProgress, useListExecutions, useResourceDefinition, useCompanies, useDeleteCompanies, useContacts, useDeleteContacts } from './chunk-IPRMGSCV.js';
8
+ import { SubshellContentContainer } from './chunk-RX4UWZZR.js';
9
+ import { PageTitleCaption, CenteredErrorState, StatCard, EmptyState } from './chunk-Y3D3WFJG.js';
10
+ import { useElevasisServices } from './chunk-QEPXAWE2.js';
11
+ import { useRouterContext } from './chunk-Q7DJKLEN.js';
12
+ import { Stack, Paper, Text, Anchor, Group, Title, ActionIcon, Divider, Box, SimpleGrid, Badge, Card, Button, Center, Loader, Alert, Table, Progress, TextInput, Checkbox, Pagination, Textarea, Select, Code, Modal } from '@mantine/core';
13
+ import { IconTarget, IconLayoutGrid, IconList, IconBuilding, IconAddressBook, IconMailCheck, IconExternalLink, IconX, IconAlertCircle, IconPlayerPlay, IconArrowRight, IconQuestionMark, IconChecklist, IconPlus, IconSearch, IconSparkles, IconAlertTriangle, IconArrowLeft, IconClockHour4, IconBuildingFactory2, IconUsers } from '@tabler/icons-react';
14
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
+ import { Link, useNavigate } from '@tanstack/react-router';
16
+ import { useQueryClient, useMutation } from '@tanstack/react-query';
17
+ import { useState, useMemo } from 'react';
18
+
19
+ var LeadGenSidebarTop = () => {
20
+ return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconTarget, label: "Lead Gen" });
21
+ };
22
+ var LEAD_GEN_ITEMS = [
23
+ { label: "Overview", to: "/lead-gen", icon: IconLayoutGrid, exact: true },
24
+ { label: "Lists", to: "/lead-gen/lists", icon: IconList, exact: false },
25
+ { label: "Companies", to: "/lead-gen/companies", icon: IconBuilding, exact: false },
26
+ { label: "Contacts", to: "/lead-gen/contacts", icon: IconAddressBook, exact: false },
27
+ { label: "Deliverability", to: "/lead-gen/deliverability", icon: IconMailCheck, exact: false }
28
+ ];
29
+ var LeadGenSidebarMiddle = () => {
30
+ const { currentPath, navigate } = useRouterContext();
31
+ return /* @__PURE__ */ jsx(Stack, { gap: 0, style: { flex: 1, overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: LEAD_GEN_ITEMS.map((item) => {
32
+ const isActive = item.exact ? currentPath === item.to || currentPath === `${item.to}/` : currentPath.startsWith(item.to);
33
+ return /* @__PURE__ */ jsx(
34
+ SubshellNavItem,
35
+ {
36
+ icon: item.icon,
37
+ label: item.label,
38
+ isActive,
39
+ onClick: () => navigate(item.to)
40
+ },
41
+ item.to
42
+ );
43
+ }) }) });
44
+ };
45
+ var LeadGenSidebar = () => {
46
+ return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
47
+ /* @__PURE__ */ jsx(LeadGenSidebarTop, {}),
48
+ /* @__PURE__ */ jsx(LeadGenSidebarMiddle, {})
49
+ ] });
50
+ };
51
+ var leadGenManifest = {
52
+ key: "lead-gen",
53
+ label: "Lead Gen",
54
+ sidebar: LeadGenSidebar,
55
+ subshellRoutes: ["/lead-gen"],
56
+ navEntry: {
57
+ label: "Lead Gen",
58
+ icon: IconTarget,
59
+ link: "/lead-gen",
60
+ featureKey: "acquisition"
61
+ }
62
+ };
63
+ var LEAD_GEN_ROUTE_LINKS = [
64
+ { label: "Overview", to: "/lead-gen" },
65
+ { label: "Lists", to: "/lead-gen/lists" },
66
+ { label: "Companies", to: "/lead-gen/companies" },
67
+ { label: "Contacts", to: "/lead-gen/contacts" },
68
+ { label: "Deliverability", to: "/lead-gen/deliverability" }
69
+ ];
70
+ function LeadGenRouteShell({
71
+ title,
72
+ caption,
73
+ body,
74
+ links = LEAD_GEN_ROUTE_LINKS
75
+ }) {
76
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
77
+ /* @__PURE__ */ jsx(PageTitleCaption, { title, caption }),
78
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "lg", children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
79
+ body,
80
+ /* @__PURE__ */ jsx(Text, { children: links.map((link, index) => /* @__PURE__ */ jsxs("span", { children: [
81
+ index > 0 ? " | " : null,
82
+ /* @__PURE__ */ jsx(Anchor, { component: Link, to: link.to, children: link.label })
83
+ ] }, link.to)) })
84
+ ] }) })
85
+ ] }) }) });
86
+ }
87
+ function formatDate(dateValue) {
88
+ const date = typeof dateValue === "string" ? new Date(dateValue) : dateValue;
89
+ return date.toLocaleDateString("en-US", {
90
+ month: "short",
91
+ day: "numeric",
92
+ year: "numeric"
93
+ });
94
+ }
95
+ function getStatusColor(status) {
96
+ return status === "active" ? "green" : status === "invalid" ? "red" : "gray";
97
+ }
98
+ function getEnrichmentColor(status) {
99
+ switch (status) {
100
+ case "complete":
101
+ return "green";
102
+ case "pending":
103
+ return "yellow";
104
+ case "failed":
105
+ return "red";
106
+ default:
107
+ return "gray";
108
+ }
109
+ }
110
+ function getEnrichmentStatus(enrichmentData) {
111
+ if (!enrichmentData || typeof enrichmentData !== "object") return "pending";
112
+ const website = enrichmentData.website;
113
+ const linkedin = enrichmentData.linkedin;
114
+ if (website === "complete" && linkedin === "complete") return "complete";
115
+ if (website === "failed" || linkedin === "failed") return "failed";
116
+ return "pending";
117
+ }
118
+ function formatName(parts, fallback) {
119
+ const name = parts.filter(Boolean).join(" ").trim();
120
+ return name || fallback;
121
+ }
122
+ function CompanyDetailModal({ company, onClose }) {
123
+ return /* @__PURE__ */ jsx(CustomModal, { opened: !!company, onClose, size: "xl", children: company ? /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
124
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
125
+ /* @__PURE__ */ jsxs("div", { children: [
126
+ /* @__PURE__ */ jsx(Title, { order: 3, children: company.name }),
127
+ company.domain ? /* @__PURE__ */ jsxs(Anchor, { href: `https://${company.domain}`, target: "_blank", size: "sm", c: "dimmed", children: [
128
+ company.domain,
129
+ " ",
130
+ /* @__PURE__ */ jsx(IconExternalLink, { size: 12, style: { verticalAlign: "middle" } })
131
+ ] }) : null
132
+ ] }),
133
+ /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: onClose, children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
134
+ ] }),
135
+ /* @__PURE__ */ jsx(Divider, {}),
136
+ /* @__PURE__ */ jsxs(Box, { children: [
137
+ /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Firmographics" }),
138
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: 3, children: [
139
+ /* @__PURE__ */ jsxs("div", { children: [
140
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Segment" }),
141
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: company.segment || "-" })
142
+ ] }),
143
+ /* @__PURE__ */ jsxs("div", { children: [
144
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Category" }),
145
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: company.category || "-" })
146
+ ] }),
147
+ /* @__PURE__ */ jsxs("div", { children: [
148
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Employees" }),
149
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: company.numEmployees || "-" })
150
+ ] }),
151
+ /* @__PURE__ */ jsxs("div", { children: [
152
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Founded" }),
153
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: company.foundedYear || "-" })
154
+ ] }),
155
+ /* @__PURE__ */ jsxs("div", { children: [
156
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Location" }),
157
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: [company.locationCity, company.locationState].filter(Boolean).join(", ") || "-" })
158
+ ] }),
159
+ /* @__PURE__ */ jsxs("div", { children: [
160
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Contacts" }),
161
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: company.contactCount })
162
+ ] })
163
+ ] })
164
+ ] }),
165
+ company.enrichmentData ? /* @__PURE__ */ jsxs(Box, { children: [
166
+ /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Enrichment Data" }),
167
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { whiteSpace: "pre-wrap" }, children: JSON.stringify(company.enrichmentData, null, 2) })
168
+ ] }) : null,
169
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
170
+ company.linkedinUrl ? /* @__PURE__ */ jsxs(Anchor, { href: company.linkedinUrl, target: "_blank", size: "sm", children: [
171
+ "LinkedIn ",
172
+ /* @__PURE__ */ jsx(IconExternalLink, { size: 12 })
173
+ ] }) : null,
174
+ company.website ? /* @__PURE__ */ jsxs(Anchor, { href: company.website, target: "_blank", size: "sm", children: [
175
+ "Website ",
176
+ /* @__PURE__ */ jsx(IconExternalLink, { size: 12 })
177
+ ] }) : null
178
+ ] }),
179
+ /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
180
+ "Created: ",
181
+ formatDate(company.createdAt),
182
+ company.updatedAt && ` | Updated: ${formatDate(company.updatedAt)}`
183
+ ] })
184
+ ] }) : null });
185
+ }
186
+ function ContactDetailModal({ contact, onClose }) {
187
+ return /* @__PURE__ */ jsx(CustomModal, { opened: !!contact, onClose, size: "xl", children: contact ? /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
188
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
189
+ /* @__PURE__ */ jsxs("div", { children: [
190
+ /* @__PURE__ */ jsx(Title, { order: 3, children: formatName([contact.firstName, contact.lastName], contact.email) }),
191
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: contact.email }),
192
+ contact.title ? /* @__PURE__ */ jsx(Text, { size: "sm", children: contact.title }) : null
193
+ ] }),
194
+ /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: onClose, children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
195
+ ] }),
196
+ /* @__PURE__ */ jsx(Divider, {}),
197
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
198
+ /* @__PURE__ */ jsx(Badge, { color: getStatusColor(contact.status), children: contact.status }),
199
+ contact.openingLine ? /* @__PURE__ */ jsx(Badge, { color: "green", children: "Personalized" }) : null
200
+ ] }),
201
+ contact.company ? /* @__PURE__ */ jsxs(Box, { children: [
202
+ /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Company" }),
203
+ /* @__PURE__ */ jsxs(Card, { withBorder: true, children: [
204
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: contact.company.name }),
205
+ contact.company.domain ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: contact.company.domain }) : null
206
+ ] })
207
+ ] }) : null,
208
+ /* @__PURE__ */ jsxs(Box, { children: [
209
+ /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Contact Information" }),
210
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: 2, children: [
211
+ /* @__PURE__ */ jsxs("div", { children: [
212
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Headline" }),
213
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: contact.headline || "-" })
214
+ ] }),
215
+ /* @__PURE__ */ jsxs("div", { children: [
216
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "LinkedIn" }),
217
+ contact.linkedinUrl ? /* @__PURE__ */ jsxs(Anchor, { href: contact.linkedinUrl, target: "_blank", size: "sm", children: [
218
+ "View Profile ",
219
+ /* @__PURE__ */ jsx(IconExternalLink, { size: 12 })
220
+ ] }) : /* @__PURE__ */ jsx(Text, { size: "sm", children: "-" })
221
+ ] })
222
+ ] })
223
+ ] }),
224
+ contact.filterReason ? /* @__PURE__ */ jsxs(Box, { children: [
225
+ /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Qualification" }),
226
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "red", children: contact.filterReason })
227
+ ] }) : null,
228
+ contact.openingLine ? /* @__PURE__ */ jsxs(Box, { children: [
229
+ /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Personalization" }),
230
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: contact.openingLine })
231
+ ] }) : null,
232
+ /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
233
+ "Created: ",
234
+ formatDate(contact.createdAt),
235
+ contact.updatedAt && ` | Updated: ${formatDate(contact.updatedAt)}`
236
+ ] })
237
+ ] }) : null });
238
+ }
239
+ var LIST_TEMPLATE_OPTIONS = [
240
+ {
241
+ value: "blank",
242
+ label: "Blank",
243
+ description: "Create an empty draft list with qualification defaults and no pipeline steps."
244
+ },
245
+ {
246
+ value: "full_pipeline",
247
+ label: "Full 6-Stage",
248
+ description: "Create a complete lead-gen pipeline from scrape through personalization."
249
+ },
250
+ {
251
+ value: "personalize_only",
252
+ label: "Personalize Only",
253
+ description: "Start with personalization and upload-focused steps for already-prepared contacts."
254
+ },
255
+ {
256
+ value: "email_refresh",
257
+ label: "Email Refresh",
258
+ description: "Run discovery, verification, and personalization for an existing company set."
259
+ }
260
+ ];
261
+ function buildListConfig(template, targetDescription) {
262
+ const qualification = {
263
+ targetDescription,
264
+ minReviewCount: 5,
265
+ minRating: 4,
266
+ excludeFranchises: true,
267
+ customRules: ""
268
+ };
269
+ const personalization = {
270
+ industryContext: targetDescription,
271
+ emailBody: "",
272
+ creativeDirection: "",
273
+ exclusionRules: []
274
+ };
275
+ switch (template) {
276
+ case "full_pipeline":
277
+ return {
278
+ qualification,
279
+ enrichment: {
280
+ emailDiscovery: { primary: "tomba" },
281
+ emailVerification: { provider: "millionverifier", threshold: "ok" }
282
+ },
283
+ personalization,
284
+ pipeline: {
285
+ steps: [
286
+ {
287
+ key: "scrape",
288
+ label: "Scrape Companies",
289
+ resourceId: "lgn-01a-google-maps-scrape-workflow",
290
+ inputTemplate: {},
291
+ enabled: true,
292
+ order: 1
293
+ },
294
+ {
295
+ key: "acquire",
296
+ label: "Import Companies",
297
+ resourceId: "lgn-01b-apify-acquire-workflow",
298
+ inputTemplate: {},
299
+ enabled: true,
300
+ order: 2
301
+ },
302
+ {
303
+ key: "extract",
304
+ label: "Extract Website Data",
305
+ resourceId: "lgn-02-website-extract-workflow",
306
+ inputTemplate: {},
307
+ enabled: true,
308
+ order: 3
309
+ },
310
+ {
311
+ key: "qualify",
312
+ label: "Qualify Companies",
313
+ resourceId: "lgn-03-company-qualification-workflow",
314
+ inputTemplate: {},
315
+ enabled: true,
316
+ order: 4
317
+ },
318
+ {
319
+ key: "discover",
320
+ label: "Discover Emails",
321
+ resourceId: "lgn-04-email-discovery-workflow",
322
+ inputTemplate: {},
323
+ enabled: true,
324
+ order: 5
325
+ },
326
+ {
327
+ key: "verify",
328
+ label: "Verify Emails",
329
+ resourceId: "lgn-05-email-verification-workflow",
330
+ inputTemplate: {},
331
+ enabled: true,
332
+ order: 6
333
+ },
334
+ {
335
+ key: "personalize",
336
+ label: "Personalize Outreach",
337
+ resourceId: "ist-personalization-workflow",
338
+ inputTemplate: {},
339
+ enabled: true,
340
+ order: 7
341
+ }
342
+ ]
343
+ }
344
+ };
345
+ case "personalize_only":
346
+ return {
347
+ qualification,
348
+ personalization,
349
+ pipeline: {
350
+ steps: [
351
+ {
352
+ key: "personalize",
353
+ label: "Personalize Outreach",
354
+ resourceId: "ist-personalization-workflow",
355
+ inputTemplate: {},
356
+ enabled: true,
357
+ order: 1
358
+ },
359
+ {
360
+ key: "upload",
361
+ label: "Upload Contacts",
362
+ resourceId: "ist-upload-contacts-workflow",
363
+ inputTemplate: { requireOpeningLine: true },
364
+ enabled: true,
365
+ order: 2
366
+ }
367
+ ]
368
+ }
369
+ };
370
+ case "email_refresh":
371
+ return {
372
+ qualification,
373
+ enrichment: {
374
+ emailDiscovery: { primary: "tomba" },
375
+ emailVerification: { provider: "millionverifier", threshold: "ok" }
376
+ },
377
+ personalization,
378
+ pipeline: {
379
+ steps: [
380
+ {
381
+ key: "discover",
382
+ label: "Discover Emails",
383
+ resourceId: "lgn-04-email-discovery-workflow",
384
+ inputTemplate: {},
385
+ enabled: true,
386
+ order: 1
387
+ },
388
+ {
389
+ key: "verify",
390
+ label: "Verify Emails",
391
+ resourceId: "lgn-05-email-verification-workflow",
392
+ inputTemplate: {},
393
+ enabled: true,
394
+ order: 2
395
+ },
396
+ {
397
+ key: "personalize",
398
+ label: "Personalize Outreach",
399
+ resourceId: "ist-personalization-workflow",
400
+ inputTemplate: {},
401
+ enabled: true,
402
+ order: 3
403
+ }
404
+ ]
405
+ }
406
+ };
407
+ case "blank":
408
+ default:
409
+ return {
410
+ qualification,
411
+ pipeline: {
412
+ steps: []
413
+ }
414
+ };
415
+ }
416
+ }
417
+ function useDeleteLists() {
418
+ const { apiRequest, organizationId } = useElevasisServices();
419
+ const queryClient = useQueryClient();
420
+ return useMutation({
421
+ mutationFn: async (listIds) => {
422
+ const uniqueIds = [...new Set(listIds)].filter(Boolean);
423
+ if (uniqueIds.length === 0) return;
424
+ await Promise.all(
425
+ uniqueIds.map(
426
+ (listId) => apiRequest(`/acquisition/lists/${listId}`, {
427
+ method: "DELETE"
428
+ })
429
+ )
430
+ );
431
+ },
432
+ onSuccess: () => {
433
+ queryClient.invalidateQueries({ queryKey: acquisitionListKeys.list(organizationId) });
434
+ queryClient.invalidateQueries({ queryKey: acquisitionListKeys.telemetry(organizationId) });
435
+ showSuccessNotification("Lists deleted");
436
+ },
437
+ onError: (error) => {
438
+ showApiErrorNotification(error);
439
+ }
440
+ });
441
+ }
442
+ var EM_DASH = "\u2014";
443
+ function computeCompletionRatio(populated, personalized) {
444
+ if (populated === 0) return null;
445
+ return personalized / populated;
446
+ }
447
+ function formatPercentage(ratio, fractionDigits = 0) {
448
+ if (ratio == null) return EM_DASH;
449
+ return `${(ratio * 100).toFixed(fractionDigits)}%`;
450
+ }
451
+ function computeCompletionPercentage(populated, personalized) {
452
+ return formatPercentage(computeCompletionRatio(populated, personalized));
453
+ }
454
+ function computeBounceRate(d) {
455
+ const denominator = d.valid + d.risky + d.invalid + d.bounced;
456
+ if (denominator === 0) return EM_DASH;
457
+ return `${(d.bounced / denominator * 100).toFixed(1)}%`;
458
+ }
459
+ function computeBacklog(current, completed) {
460
+ return Math.max(current - completed, 0);
461
+ }
462
+ function getOverviewStatus(list) {
463
+ if ((list.activeWorkflows?.length ?? 0) > 0) {
464
+ return { label: "Active Work", color: "green" };
465
+ }
466
+ if (list.stageCounts.personalized > 0 && list.stageCounts.personalized === list.stageCounts.uploaded) {
467
+ return { label: "Complete", color: "blue" };
468
+ }
469
+ return { label: "Idle", color: "gray" };
470
+ }
471
+ function getNextFocus(list) {
472
+ if ((list.activeWorkflows?.length ?? 0) > 0) {
473
+ return { label: "Workflow running", count: list.activeWorkflows?.length ?? 0 };
474
+ }
475
+ if (list.totalCompanies === 0 && list.totalContacts === 0) {
476
+ return { label: "Populate list", count: null };
477
+ }
478
+ const extractGap = computeBacklog(list.stageCounts.populated, list.stageCounts.extracted);
479
+ if (extractGap > 0) {
480
+ return { label: "Extract website data", count: extractGap };
481
+ }
482
+ const qualifyGap = computeBacklog(list.stageCounts.extracted, list.stageCounts.qualified);
483
+ if (qualifyGap > 0) {
484
+ return { label: "Qualify companies", count: qualifyGap };
485
+ }
486
+ const discoverGap = computeBacklog(list.stageCounts.qualified, list.stageCounts.discovered);
487
+ if (discoverGap > 0) {
488
+ return { label: "Discover contacts", count: discoverGap };
489
+ }
490
+ const verifyGap = computeBacklog(list.stageCounts.discovered, list.stageCounts.verified);
491
+ if (verifyGap > 0) {
492
+ return { label: "Verify emails", count: verifyGap };
493
+ }
494
+ const personalizeGap = computeBacklog(list.stageCounts.verified, list.stageCounts.personalized);
495
+ if (personalizeGap > 0) {
496
+ return { label: "Personalize outreach", count: personalizeGap };
497
+ }
498
+ const uploadGap = computeBacklog(list.stageCounts.personalized, list.stageCounts.uploaded);
499
+ if (uploadGap > 0) {
500
+ return { label: "Upload contacts", count: uploadGap };
501
+ }
502
+ return { label: "Complete", count: null };
503
+ }
504
+ function formatCountLabel(value, noun) {
505
+ return `${value} ${noun}${value === 1 ? "" : "s"}`;
506
+ }
507
+ function getPrimaryAction({
508
+ uploadBacklog,
509
+ personalizationBacklog,
510
+ verificationBacklog,
511
+ deliverabilityRiskCount,
512
+ activeListCount,
513
+ totalLists
514
+ }) {
515
+ if (uploadBacklog > 0) {
516
+ return {
517
+ title: `Upload ${formatCountLabel(uploadBacklog, "personalized contact")}`,
518
+ detail: "No verification blockers are left on the contacts already ready for outreach.",
519
+ buttonLabel: "Review lists",
520
+ buttonTo: "/lead-gen/lists",
521
+ tone: "blue"
522
+ };
523
+ }
524
+ if (personalizationBacklog > 0) {
525
+ return {
526
+ title: `Personalize ${formatCountLabel(personalizationBacklog, "verified contact")}`,
527
+ detail: "Verification is done. The next bottleneck is drafting outreach copy.",
528
+ buttonLabel: "Open lists",
529
+ buttonTo: "/lead-gen/lists",
530
+ tone: "blue"
531
+ };
532
+ }
533
+ if (verificationBacklog > 0) {
534
+ return {
535
+ title: `Verify ${formatCountLabel(verificationBacklog, "discovered contact")}`,
536
+ detail: "Email validation is the main blocker before personalization can continue.",
537
+ buttonLabel: "Open lists",
538
+ buttonTo: "/lead-gen/lists",
539
+ tone: "orange"
540
+ };
541
+ }
542
+ if (deliverabilityRiskCount > 0) {
543
+ return {
544
+ title: `Resolve ${formatCountLabel(deliverabilityRiskCount, "deliverability risk")}`,
545
+ detail: "Risky, invalid, or bounced records are the main campaign health issue right now.",
546
+ buttonLabel: "View deliverability",
547
+ buttonTo: "/lead-gen/deliverability",
548
+ tone: "orange"
549
+ };
550
+ }
551
+ if (activeListCount > 0) {
552
+ return {
553
+ title: `Monitor ${formatCountLabel(activeListCount, "active list")}`,
554
+ detail: "Workflows are running and there are no immediate contact-stage backlogs to clear.",
555
+ buttonLabel: "Open lists",
556
+ buttonTo: "/lead-gen/lists",
557
+ tone: "green"
558
+ };
559
+ }
560
+ if (totalLists > 0) {
561
+ return {
562
+ title: "No blockers right now",
563
+ detail: "Lists are idle and the current pipeline does not show an urgent follow-up step.",
564
+ buttonLabel: "Review lists",
565
+ buttonTo: "/lead-gen/lists",
566
+ tone: "gray"
567
+ };
568
+ }
569
+ return {
570
+ title: "Create your first list",
571
+ detail: "Lead gen telemetry will appear here once a list starts moving through the pipeline.",
572
+ buttonLabel: "Open lists",
573
+ buttonTo: "/lead-gen/lists",
574
+ tone: "gray"
575
+ };
576
+ }
577
+ function CompactPipelineStage({
578
+ label,
579
+ value,
580
+ ratio
581
+ }) {
582
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
583
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "xs", wrap: "nowrap", children: [
584
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: label }),
585
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: "blue", children: formatPercentage(ratio) })
586
+ ] }),
587
+ /* @__PURE__ */ jsx(Title, { order: 4, children: value }),
588
+ /* @__PURE__ */ jsx(Progress, { value: ratio == null ? 0 : ratio * 100, size: "sm", radius: "xl", color: "blue" })
589
+ ] }) });
590
+ }
591
+ function LeadGenOverviewPage() {
592
+ const telemetryQuery = useListsTelemetry();
593
+ const listsQuery = useLists();
594
+ const data = telemetryQuery.data;
595
+ const listMetaById = new Map((listsQuery.data ?? []).map((list) => [list.id, list]));
596
+ const totalCompanies = data?.reduce((sum, list) => sum + list.totalCompanies, 0) ?? 0;
597
+ const totalContacts = data?.reduce((sum, list) => sum + list.totalContacts, 0) ?? 0;
598
+ const stageTotals = data?.reduce(
599
+ (acc, list) => ({
600
+ populated: acc.populated + list.stageCounts.populated,
601
+ extracted: acc.extracted + list.stageCounts.extracted,
602
+ qualified: acc.qualified + list.stageCounts.qualified,
603
+ discovered: acc.discovered + list.stageCounts.discovered,
604
+ verified: acc.verified + list.stageCounts.verified,
605
+ personalized: acc.personalized + list.stageCounts.personalized,
606
+ uploaded: acc.uploaded + list.stageCounts.uploaded
607
+ }),
608
+ { populated: 0, extracted: 0, qualified: 0, discovered: 0, verified: 0, personalized: 0, uploaded: 0 }
609
+ ) ?? { populated: 0, extracted: 0, qualified: 0, discovered: 0, verified: 0, personalized: 0, uploaded: 0 };
610
+ const deliverabilityTotals = data?.reduce(
611
+ (acc, list) => ({
612
+ valid: acc.valid + list.deliverability.valid,
613
+ risky: acc.risky + list.deliverability.risky,
614
+ invalid: acc.invalid + list.deliverability.invalid,
615
+ unknown: acc.unknown + list.deliverability.unknown,
616
+ bounced: acc.bounced + list.deliverability.bounced
617
+ }),
618
+ { valid: 0, risky: 0, invalid: 0, unknown: 0, bounced: 0 }
619
+ ) ?? { risky: 0, invalid: 0, bounced: 0 };
620
+ const activeListCount = data?.filter((list) => (list.activeWorkflows?.length ?? 0) > 0).length ?? 0;
621
+ const verificationBacklog = computeBacklog(stageTotals.discovered, stageTotals.verified);
622
+ const personalizationBacklog = computeBacklog(stageTotals.verified, stageTotals.personalized);
623
+ const uploadBacklog = computeBacklog(stageTotals.personalized, stageTotals.uploaded);
624
+ const deliverabilityRiskCount = deliverabilityTotals.risky + deliverabilityTotals.invalid + deliverabilityTotals.bounced;
625
+ const totalLists = data?.length ?? 0;
626
+ const summaryLine = [
627
+ `${totalLists} ${totalLists === 1 ? "list" : "lists"} active`,
628
+ `${formatCountLabel(uploadBacklog, "contact")} ready for upload`,
629
+ deliverabilityRiskCount === 0 ? "no risks" : `${formatCountLabel(deliverabilityRiskCount, "risk")}`
630
+ ].join(" \u2022 ");
631
+ const primaryAction = getPrimaryAction({
632
+ uploadBacklog,
633
+ personalizationBacklog,
634
+ verificationBacklog,
635
+ deliverabilityRiskCount,
636
+ activeListCount,
637
+ totalLists
638
+ });
639
+ const pipelineStages = [
640
+ {
641
+ label: "Populated",
642
+ value: stageTotals.populated,
643
+ ratio: totalCompanies === 0 ? null : stageTotals.populated / totalCompanies
644
+ },
645
+ {
646
+ label: "Qualified",
647
+ value: stageTotals.qualified,
648
+ ratio: totalCompanies === 0 ? null : stageTotals.qualified / totalCompanies
649
+ },
650
+ {
651
+ label: "Verified",
652
+ value: stageTotals.verified,
653
+ ratio: totalContacts === 0 ? null : stageTotals.verified / totalContacts
654
+ },
655
+ {
656
+ label: "Personalized",
657
+ value: stageTotals.personalized,
658
+ ratio: totalContacts === 0 ? null : stageTotals.personalized / totalContacts
659
+ },
660
+ {
661
+ label: "Uploaded",
662
+ value: stageTotals.uploaded,
663
+ ratio: totalContacts === 0 ? null : stageTotals.uploaded / totalContacts
664
+ }
665
+ ];
666
+ const overviewRows = data?.map((list) => {
667
+ const listMeta = listMetaById.get(list.listId);
668
+ const completionRatio = computeCompletionRatio(list.stageCounts.populated, list.stageCounts.personalized);
669
+ const nextFocus = getNextFocus(list);
670
+ return {
671
+ ...list,
672
+ name: listMeta?.name ?? `List ${list.listId.slice(0, 8)}`,
673
+ completionRatio,
674
+ nextFocus,
675
+ status: getOverviewStatus(list)
676
+ };
677
+ }).sort((a, b) => {
678
+ const aActive = a.status.label === "Active Work" ? 1 : 0;
679
+ const bActive = b.status.label === "Active Work" ? 1 : 0;
680
+ if (aActive !== bActive) return bActive - aActive;
681
+ const aBacklog = computeBacklog(a.stageCounts.discovered, a.stageCounts.verified) + computeBacklog(a.stageCounts.verified, a.stageCounts.personalized) + computeBacklog(a.stageCounts.personalized, a.stageCounts.uploaded);
682
+ const bBacklog = computeBacklog(b.stageCounts.discovered, b.stageCounts.verified) + computeBacklog(b.stageCounts.verified, b.stageCounts.personalized) + computeBacklog(b.stageCounts.personalized, b.stageCounts.uploaded);
683
+ if (aBacklog !== bBacklog) return bBacklog - aBacklog;
684
+ return b.totalContacts - a.totalContacts;
685
+ }) ?? [];
686
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
687
+ /* @__PURE__ */ jsx(
688
+ PageTitleCaption,
689
+ {
690
+ title: "Lead Gen Overview",
691
+ caption: summaryLine,
692
+ rightSection: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
693
+ /* @__PURE__ */ jsx(Button, { component: Link, to: "/lead-gen/lists", size: "sm", variant: "light", children: "Lists" }),
694
+ /* @__PURE__ */ jsx(Button, { component: Link, to: "/lead-gen/deliverability", size: "sm", variant: "light", children: "Deliverability" })
695
+ ] })
696
+ }
697
+ ),
698
+ telemetryQuery.isLoading ? /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) }) : telemetryQuery.isError ? /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error: telemetryQuery.error, title: "Failed to load list telemetry" }) }) : !data?.length ? /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { h: 300, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "gray", variant: "light", children: "No lists yet." }) }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
699
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 3 }, children: [
700
+ /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconPlayerPlay, value: activeListCount, label: "Active Lists" }),
701
+ /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconArrowRight, value: uploadBacklog, label: "Ready Contacts" }),
702
+ /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconAlertCircle, value: deliverabilityRiskCount, label: "At Risk" })
703
+ ] }),
704
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
705
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", gap: "md", children: [
706
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
707
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 700, children: "Next Action" }),
708
+ /* @__PURE__ */ jsx(Title, { order: 3, children: primaryAction.title }),
709
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", maw: 620, children: primaryAction.detail })
710
+ ] }),
711
+ /* @__PURE__ */ jsx(Button, { component: Link, to: primaryAction.buttonTo, size: "sm", variant: "light", children: primaryAction.buttonLabel })
712
+ ] }),
713
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, md: 3 }, children: [
714
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
715
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Verification" }),
716
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", mt: 6, children: [
717
+ /* @__PURE__ */ jsx(Title, { order: 4, children: verificationBacklog }),
718
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "waiting" })
719
+ ] })
720
+ ] }),
721
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
722
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Personalization" }),
723
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", mt: 6, children: [
724
+ /* @__PURE__ */ jsx(Title, { order: 4, children: personalizationBacklog }),
725
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "ready" })
726
+ ] })
727
+ ] }),
728
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
729
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Completion" }),
730
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", mt: 6, children: [
731
+ /* @__PURE__ */ jsx(Title, { order: 4, children: computeCompletionPercentage(stageTotals.populated, stageTotals.personalized) }),
732
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "personalized" })
733
+ ] })
734
+ ] })
735
+ ] })
736
+ ] }) }),
737
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
738
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", children: [
739
+ /* @__PURE__ */ jsxs("div", { children: [
740
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Pipeline" }),
741
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Keep the funnel visible without giving every zero state its own section." })
742
+ ] }),
743
+ /* @__PURE__ */ jsxs(Badge, { variant: "light", color: "blue", children: [
744
+ stageTotals.extracted,
745
+ " extracted \u2022 ",
746
+ stageTotals.discovered,
747
+ " discovered"
748
+ ] })
749
+ ] }),
750
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2, xl: 5 }, children: pipelineStages.map((stage) => /* @__PURE__ */ jsx(
751
+ CompactPipelineStage,
752
+ {
753
+ label: stage.label,
754
+ value: stage.value,
755
+ ratio: stage.ratio
756
+ },
757
+ stage.label
758
+ )) })
759
+ ] }) }),
760
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
761
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", children: [
762
+ /* @__PURE__ */ jsxs("div", { children: [
763
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "List Snapshot" }),
764
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Ranked to surface active work first, then the largest unresolved backlogs." })
765
+ ] }),
766
+ /* @__PURE__ */ jsx(Button, { component: Link, to: "/lead-gen/lists", size: "sm", variant: "subtle", children: "Open lists" })
767
+ ] }),
768
+ /* @__PURE__ */ jsxs(Table, { children: [
769
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
770
+ /* @__PURE__ */ jsx(Table.Th, { children: "List" }),
771
+ /* @__PURE__ */ jsx(Table.Th, { children: "Companies" }),
772
+ /* @__PURE__ */ jsx(Table.Th, { children: "Contacts" }),
773
+ /* @__PURE__ */ jsx(Table.Th, { children: "Next Focus" }),
774
+ /* @__PURE__ */ jsx(Table.Th, { children: "Progress" }),
775
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" })
776
+ ] }) }),
777
+ /* @__PURE__ */ jsx(Table.Tbody, { children: overviewRows.map((list) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
778
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
779
+ /* @__PURE__ */ jsx(Link, { to: "/lead-gen/lists/$listId", params: { listId: list.listId }, children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, component: "span", c: "blue.4", children: list.name }) }),
780
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: list.listId })
781
+ ] }) }),
782
+ /* @__PURE__ */ jsx(Table.Td, { children: list.totalCompanies }),
783
+ /* @__PURE__ */ jsx(Table.Td, { children: list.totalContacts }),
784
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
785
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: list.nextFocus.label }),
786
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: list.nextFocus.count == null ? "No outstanding count" : `${list.nextFocus.count} item${list.nextFocus.count === 1 ? "" : "s"} queued` })
787
+ ] }) }),
788
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", miw: 150, children: [
789
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "xs", children: [
790
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatPercentage(list.completionRatio) }),
791
+ /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
792
+ list.stageCounts.personalized,
793
+ "/",
794
+ list.stageCounts.populated || 0
795
+ ] })
796
+ ] }),
797
+ /* @__PURE__ */ jsx(
798
+ Progress,
799
+ {
800
+ value: list.completionRatio == null ? 0 : list.completionRatio * 100,
801
+ size: "sm",
802
+ radius: "xl",
803
+ color: "blue"
804
+ }
805
+ )
806
+ ] }) }),
807
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: list.status.color, children: list.status.label }) })
808
+ ] }, list.listId)) })
809
+ ] })
810
+ ] }) })
811
+ ] })
812
+ ] }) }) });
813
+ }
814
+ function LeadGenDeliverabilityPage() {
815
+ const { data, isLoading, error } = useListsTelemetry();
816
+ if (isLoading) {
817
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
818
+ /* @__PURE__ */ jsx(
819
+ PageTitleCaption,
820
+ {
821
+ title: "Deliverability",
822
+ caption: "Email validity breakdown and bounce health across lists"
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
826
+ ] }) }) });
827
+ }
828
+ if (error) {
829
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
830
+ /* @__PURE__ */ jsx(
831
+ PageTitleCaption,
832
+ {
833
+ title: "Deliverability",
834
+ caption: "Email validity breakdown and bounce health across lists"
835
+ }
836
+ ),
837
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load deliverability data" }) })
838
+ ] }) }) });
839
+ }
840
+ if (data?.length === 0) {
841
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
842
+ /* @__PURE__ */ jsx(
843
+ PageTitleCaption,
844
+ {
845
+ title: "Deliverability",
846
+ caption: "Email validity breakdown and bounce health across lists"
847
+ }
848
+ ),
849
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { h: 300, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconMailCheck, { size: 16 }), color: "gray", variant: "light", children: "No lists yet. Deliverability will populate once contacts are validated." }) }) })
850
+ ] }) }) });
851
+ }
852
+ const lists = data ?? [];
853
+ const totals = lists.reduce(
854
+ (acc, list) => ({
855
+ valid: acc.valid + list.deliverability.valid,
856
+ risky: acc.risky + list.deliverability.risky,
857
+ invalid: acc.invalid + list.deliverability.invalid,
858
+ unknown: acc.unknown + list.deliverability.unknown,
859
+ bounced: acc.bounced + list.deliverability.bounced
860
+ }),
861
+ { valid: 0, risky: 0, invalid: 0, unknown: 0, bounced: 0 }
862
+ );
863
+ const summaryTiles = [
864
+ { label: "Valid", value: totals.valid, icon: IconMailCheck },
865
+ { label: "Risky", value: totals.risky, icon: IconAlertCircle },
866
+ { label: "Invalid", value: totals.invalid, icon: IconX },
867
+ { label: "Unknown", value: totals.unknown, icon: IconQuestionMark },
868
+ { label: "Bounced", value: totals.bounced, icon: IconAlertCircle },
869
+ { label: "Bounce Rate", value: computeBounceRate(totals), icon: IconChecklist }
870
+ ];
871
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
872
+ /* @__PURE__ */ jsx(
873
+ PageTitleCaption,
874
+ {
875
+ title: "Deliverability",
876
+ caption: "Email validity breakdown and bounce health across lists"
877
+ }
878
+ ),
879
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 2, sm: 3, lg: 6 }, children: summaryTiles.map((tile) => /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: tile.icon, value: tile.value, label: tile.label }, tile.label)) }),
880
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Table, { children: [
881
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
882
+ /* @__PURE__ */ jsx(Table.Th, { children: "List ID" }),
883
+ /* @__PURE__ */ jsx(Table.Th, { children: "Valid" }),
884
+ /* @__PURE__ */ jsx(Table.Th, { children: "Risky" }),
885
+ /* @__PURE__ */ jsx(Table.Th, { children: "Invalid" }),
886
+ /* @__PURE__ */ jsx(Table.Th, { children: "Unknown" }),
887
+ /* @__PURE__ */ jsx(Table.Th, { children: "Bounced" }),
888
+ /* @__PURE__ */ jsx(Table.Th, { children: "Bounce Rate" })
889
+ ] }) }),
890
+ /* @__PURE__ */ jsx(Table.Tbody, { children: lists.map((list) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
891
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: list.listId }) }),
892
+ /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.valid }),
893
+ /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.risky }),
894
+ /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.invalid }),
895
+ /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.unknown }),
896
+ /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.bounced }),
897
+ /* @__PURE__ */ jsx(Table.Td, { children: computeBounceRate(list.deliverability) })
898
+ ] }, list.listId)) })
899
+ ] }) })
900
+ ] }) }) });
901
+ }
902
+ var PAGE_SIZE_DEFAULT = 20;
903
+ function LeadGenListsPage() {
904
+ const navigate = useNavigate();
905
+ const [searchQuery, setSearchQuery] = useState("");
906
+ const [showCreateList, setShowCreateList] = useState(false);
907
+ const [newListName, setNewListName] = useState("");
908
+ const [newListDescription, setNewListDescription] = useState("");
909
+ const [selectedTemplate, setSelectedTemplate] = useState("full_pipeline");
910
+ const [showBatchDelete, setShowBatchDelete] = useState(false);
911
+ const { data: lists, isLoading: listsLoading } = useLists();
912
+ const { data: telemetry, isLoading: telemetryLoading } = useListsTelemetry();
913
+ const createListMutation = useCreateList();
914
+ const deleteListsMutation = useDeleteLists();
915
+ const { sort, toggleSort } = useTableSort("created");
916
+ const contactCountByListId = useMemo(
917
+ () => new Map((telemetry ?? []).map((item) => [item.listId, item.totalContacts])),
918
+ [telemetry]
919
+ );
920
+ const sortAccessors = useMemo(
921
+ () => ({
922
+ name: (list) => list.name,
923
+ description: (list) => list.description || "",
924
+ contacts: (list) => contactCountByListId.get(list.id) ?? 0,
925
+ created: (list) => list.createdAt
926
+ }),
927
+ [contactCountByListId]
928
+ );
929
+ const filteredLists = useMemo(() => {
930
+ if (!lists) return [];
931
+ if (!searchQuery.trim()) return lists;
932
+ const query = searchQuery.toLowerCase();
933
+ return lists.filter((list) => list.name.toLowerCase().includes(query));
934
+ }, [lists, searchQuery]);
935
+ const sortedLists = useMemo(() => sortData(filteredLists, sort, sortAccessors), [filteredLists, sort, sortAccessors]);
936
+ const pagination = usePaginationState(PAGE_SIZE_DEFAULT, [searchQuery], sortedLists.length);
937
+ const paginatedLists = useMemo(
938
+ () => sortedLists.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT),
939
+ [sortedLists, pagination.offset]
940
+ );
941
+ const selection = useTableSelection(paginatedLists, sortedLists);
942
+ const selectedTemplateMeta = LIST_TEMPLATE_OPTIONS.find((option) => option.value === selectedTemplate);
943
+ function resetCreateListModal() {
944
+ setNewListName("");
945
+ setNewListDescription("");
946
+ setSelectedTemplate("full_pipeline");
947
+ setShowCreateList(false);
948
+ }
949
+ function handleCreateList() {
950
+ const trimmedName = newListName.trim();
951
+ if (!trimmedName) return;
952
+ const body = {
953
+ name: trimmedName,
954
+ description: newListDescription.trim() || null,
955
+ type: selectedTemplate,
956
+ config: buildListConfig(selectedTemplate, trimmedName)
957
+ };
958
+ createListMutation.mutate(body, {
959
+ onSuccess: (list) => {
960
+ resetCreateListModal();
961
+ navigate({ to: "/lead-gen/lists/$listId", params: { listId: list.id } });
962
+ }
963
+ });
964
+ }
965
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
966
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(
967
+ PageTitleCaption,
968
+ {
969
+ title: "Lists",
970
+ caption: "Lead lists and contact organization",
971
+ rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateList(true), children: "New List" })
972
+ }
973
+ ) }),
974
+ /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
975
+ /* @__PURE__ */ jsx(
976
+ FilterBar,
977
+ {
978
+ actions: /* @__PURE__ */ jsx(
979
+ TableSelectionToolbar,
980
+ {
981
+ selectedCount: selection.selectedCount,
982
+ onDelete: () => setShowBatchDelete(true),
983
+ isDeleting: deleteListsMutation.isPending
984
+ }
985
+ ),
986
+ children: /* @__PURE__ */ jsx(
987
+ TextInput,
988
+ {
989
+ placeholder: "Search by name...",
990
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
991
+ value: searchQuery,
992
+ onChange: (e) => setSearchQuery(e.currentTarget.value)
993
+ }
994
+ )
995
+ }
996
+ ),
997
+ listsLoading || telemetryLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : !filteredLists.length ? /* @__PURE__ */ jsx(
998
+ EmptyState,
999
+ {
1000
+ icon: IconList,
1001
+ title: searchQuery.trim() ? "No lists match your search" : "No lists yet",
1002
+ description: searchQuery.trim() ? void 0 : "Create one to get started.",
1003
+ action: searchQuery.trim() ? void 0 : {
1004
+ label: "Create List",
1005
+ onClick: () => setShowCreateList(true),
1006
+ icon: /* @__PURE__ */ jsx(IconPlus, { size: 16 })
1007
+ }
1008
+ }
1009
+ ) : /* @__PURE__ */ jsxs(Table, { children: [
1010
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1011
+ /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
1012
+ Checkbox,
1013
+ {
1014
+ checked: selection.isPageAllSelected,
1015
+ indeterminate: selection.isPagePartiallySelected,
1016
+ onChange: selection.togglePage
1017
+ }
1018
+ ) }),
1019
+ /* @__PURE__ */ jsx(SortableHeader, { column: "name", sort, onToggle: toggleSort, children: "Name" }),
1020
+ /* @__PURE__ */ jsx(SortableHeader, { column: "description", sort, onToggle: toggleSort, children: "Description" }),
1021
+ /* @__PURE__ */ jsx(SortableHeader, { column: "contacts", sort, onToggle: toggleSort, children: "Contacts" }),
1022
+ /* @__PURE__ */ jsx(SortableHeader, { column: "created", sort, onToggle: toggleSort, children: "Created" })
1023
+ ] }) }),
1024
+ /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedLists.map((list) => /* @__PURE__ */ jsxs(
1025
+ Table.Tr,
1026
+ {
1027
+ style: { cursor: "pointer" },
1028
+ onClick: () => navigate({ to: "/lead-gen/lists/$listId", params: { listId: list.id } }),
1029
+ children: [
1030
+ /* @__PURE__ */ jsx(Table.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(Checkbox, { checked: selection.isSelected(list.id), onChange: () => selection.toggle(list.id) }) }),
1031
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: list.name }) }),
1032
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: list.description || "-" }) }),
1033
+ /* @__PURE__ */ jsx(Table.Td, { children: contactCountByListId.get(list.id) ?? 0 }),
1034
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDate(list.createdAt) }) })
1035
+ ]
1036
+ },
1037
+ list.id
1038
+ )) })
1039
+ ] }),
1040
+ sortedLists.length > PAGE_SIZE_DEFAULT && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
1041
+ Pagination,
1042
+ {
1043
+ value: pagination.page,
1044
+ onChange: pagination.setPage,
1045
+ total: pagination.totalPages(sortedLists.length),
1046
+ size: "sm"
1047
+ }
1048
+ ) })
1049
+ ] }) }),
1050
+ /* @__PURE__ */ jsx(
1051
+ CustomModal,
1052
+ {
1053
+ opened: showCreateList,
1054
+ onClose: () => !createListMutation.isPending && resetCreateListModal(),
1055
+ size: "md",
1056
+ loading: createListMutation.isPending,
1057
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1058
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1059
+ /* @__PURE__ */ jsx(IconSparkles, { size: 24 }),
1060
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Create List" })
1061
+ ] }),
1062
+ /* @__PURE__ */ jsx(
1063
+ TextInput,
1064
+ {
1065
+ label: "List Name",
1066
+ placeholder: "e.g. Orange County Vets Q2",
1067
+ value: newListName,
1068
+ onChange: (event) => setNewListName(event.currentTarget.value),
1069
+ disabled: createListMutation.isPending,
1070
+ required: true
1071
+ }
1072
+ ),
1073
+ /* @__PURE__ */ jsx(
1074
+ Textarea,
1075
+ {
1076
+ label: "Description",
1077
+ placeholder: "Optional context for this list",
1078
+ value: newListDescription,
1079
+ onChange: (event) => setNewListDescription(event.currentTarget.value),
1080
+ disabled: createListMutation.isPending,
1081
+ minRows: 3
1082
+ }
1083
+ ),
1084
+ /* @__PURE__ */ jsx(
1085
+ Select,
1086
+ {
1087
+ label: "Pipeline Template",
1088
+ data: LIST_TEMPLATE_OPTIONS.map((option) => ({
1089
+ value: option.value,
1090
+ label: option.label
1091
+ })),
1092
+ value: selectedTemplate,
1093
+ onChange: (value) => setSelectedTemplate(value ?? "full_pipeline"),
1094
+ disabled: createListMutation.isPending,
1095
+ allowDeselect: false
1096
+ }
1097
+ ),
1098
+ selectedTemplateMeta ? /* @__PURE__ */ jsx(Alert, { variant: "light", color: "blue", children: /* @__PURE__ */ jsx(Text, { size: "sm", children: selectedTemplateMeta.description }) }) : null,
1099
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "The list will open in draft mode after creation so you can review configuration, edit pipeline steps, and run workflows from the detail page." }),
1100
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
1101
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: resetCreateListModal, disabled: createListMutation.isPending, children: "Cancel" }),
1102
+ /* @__PURE__ */ jsx(Button, { onClick: handleCreateList, loading: createListMutation.isPending, disabled: !newListName.trim(), children: "Create List" })
1103
+ ] })
1104
+ ] })
1105
+ }
1106
+ ),
1107
+ /* @__PURE__ */ jsx(
1108
+ CustomModal,
1109
+ {
1110
+ opened: showBatchDelete,
1111
+ onClose: () => !deleteListsMutation.isPending && setShowBatchDelete(false),
1112
+ size: "sm",
1113
+ loading: deleteListsMutation.isPending,
1114
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1115
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1116
+ /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
1117
+ /* @__PURE__ */ jsxs(Title, { order: 4, children: [
1118
+ "Delete ",
1119
+ selection.selectedCount,
1120
+ " Lists"
1121
+ ] })
1122
+ ] }),
1123
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1124
+ "Are you sure you want to delete",
1125
+ " ",
1126
+ /* @__PURE__ */ jsx(Text, { span: true, fw: 600, children: selection.selectedCount }),
1127
+ " ",
1128
+ "selected lists?"
1129
+ ] }),
1130
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone." }),
1131
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
1132
+ /* @__PURE__ */ jsx(
1133
+ Button,
1134
+ {
1135
+ variant: "light",
1136
+ onClick: () => setShowBatchDelete(false),
1137
+ disabled: deleteListsMutation.isPending,
1138
+ children: "Cancel"
1139
+ }
1140
+ ),
1141
+ /* @__PURE__ */ jsx(
1142
+ Button,
1143
+ {
1144
+ color: "red",
1145
+ loading: deleteListsMutation.isPending,
1146
+ onClick: () => {
1147
+ deleteListsMutation.mutate([...selection.selectedIds], {
1148
+ onSuccess: () => {
1149
+ setShowBatchDelete(false);
1150
+ selection.clear();
1151
+ }
1152
+ });
1153
+ },
1154
+ children: "Delete"
1155
+ }
1156
+ )
1157
+ ] })
1158
+ ] })
1159
+ }
1160
+ )
1161
+ ] }) });
1162
+ }
1163
+ function formatDateTime(value) {
1164
+ if (!value) return "Not yet";
1165
+ return new Date(value).toLocaleString("en-US", {
1166
+ month: "short",
1167
+ day: "numeric",
1168
+ year: "numeric",
1169
+ hour: "numeric",
1170
+ minute: "2-digit"
1171
+ });
1172
+ }
1173
+ function LeadGenListDetailPage({ listId }) {
1174
+ const navigate = useNavigate();
1175
+ const [selectedStepKey, setSelectedStepKey] = useState(null);
1176
+ const listQuery = useList(listId);
1177
+ const progressQuery = useListProgress(listId);
1178
+ const executionsQuery = useListExecutions(listId);
1179
+ const selectedStep = useMemo(
1180
+ () => listQuery.data?.config.pipeline?.steps?.slice().sort((a, b) => a.order - b.order).find((step) => step.key === selectedStepKey) ?? null,
1181
+ [listQuery.data?.config.pipeline?.steps, selectedStepKey]
1182
+ );
1183
+ const resourceDefinitionQuery = useResourceDefinition(selectedStep?.resourceId ?? "", !!selectedStep?.resourceId);
1184
+ const isLoading = listQuery.isLoading || progressQuery.isLoading || executionsQuery.isLoading;
1185
+ const error = listQuery.error ?? progressQuery.error ?? executionsQuery.error;
1186
+ if (isLoading) {
1187
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1188
+ /* @__PURE__ */ jsx(
1189
+ PageTitleCaption,
1190
+ {
1191
+ title: "List Detail",
1192
+ caption: "Configuration, progress, and execution history for a single lead-gen list",
1193
+ rightSection: /* @__PURE__ */ jsx(
1194
+ Button,
1195
+ {
1196
+ variant: "light",
1197
+ size: "sm",
1198
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1199
+ onClick: () => navigate({ to: "/lead-gen/lists" }),
1200
+ children: "Lists"
1201
+ }
1202
+ )
1203
+ }
1204
+ ),
1205
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
1206
+ ] }) }) });
1207
+ }
1208
+ if (error) {
1209
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1210
+ /* @__PURE__ */ jsx(
1211
+ PageTitleCaption,
1212
+ {
1213
+ title: "List Detail",
1214
+ caption: "Configuration, progress, and execution history for a single lead-gen list",
1215
+ rightSection: /* @__PURE__ */ jsx(
1216
+ Button,
1217
+ {
1218
+ variant: "light",
1219
+ size: "sm",
1220
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1221
+ onClick: () => navigate({ to: "/lead-gen/lists" }),
1222
+ children: "Lists"
1223
+ }
1224
+ )
1225
+ }
1226
+ ),
1227
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load list detail" }) })
1228
+ ] }) }) });
1229
+ }
1230
+ if (!listQuery.data || !progressQuery.data) {
1231
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1232
+ /* @__PURE__ */ jsx(
1233
+ PageTitleCaption,
1234
+ {
1235
+ title: "List Not Found",
1236
+ caption: "The requested lead-gen list is unavailable.",
1237
+ rightSection: /* @__PURE__ */ jsx(
1238
+ Button,
1239
+ {
1240
+ variant: "light",
1241
+ size: "sm",
1242
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1243
+ onClick: () => navigate({ to: "/lead-gen/lists" }),
1244
+ children: "Lists"
1245
+ }
1246
+ )
1247
+ }
1248
+ ),
1249
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { h: 240, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "gray", variant: "light", children: "The requested list could not be found." }) }) })
1250
+ ] }) }) });
1251
+ }
1252
+ const list = listQuery.data;
1253
+ const progress = progressQuery.data;
1254
+ const executions = executionsQuery.data ?? [];
1255
+ const qualification = list.config.qualification;
1256
+ const pipelineSteps = list.config.pipeline?.steps?.slice().sort((a, b) => a.order - b.order) ?? [];
1257
+ const stageTiles = [
1258
+ { label: "Populated", value: progress.stageCounts.populated },
1259
+ { label: "Extracted", value: progress.stageCounts.extracted },
1260
+ { label: "Qualified", value: progress.stageCounts.qualified },
1261
+ { label: "Discovered", value: progress.stageCounts.discovered },
1262
+ { label: "Verified", value: progress.stageCounts.verified },
1263
+ { label: "Personalized", value: progress.stageCounts.personalized },
1264
+ { label: "Uploaded", value: progress.stageCounts.uploaded }
1265
+ ];
1266
+ const deliverabilityTiles = [
1267
+ { label: "Valid", value: progress.deliverability.valid },
1268
+ { label: "Risky", value: progress.deliverability.risky },
1269
+ { label: "Invalid", value: progress.deliverability.invalid },
1270
+ { label: "Unknown", value: progress.deliverability.unknown },
1271
+ { label: "Bounced", value: progress.deliverability.bounced }
1272
+ ];
1273
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1274
+ /* @__PURE__ */ jsxs(Stack, { children: [
1275
+ /* @__PURE__ */ jsx(
1276
+ PageTitleCaption,
1277
+ {
1278
+ title: list.name,
1279
+ caption: list.description ?? "Configuration, progress, and execution history for this list",
1280
+ rightSection: /* @__PURE__ */ jsx(
1281
+ Button,
1282
+ {
1283
+ variant: "light",
1284
+ size: "sm",
1285
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1286
+ onClick: () => navigate({ to: "/lead-gen/lists" }),
1287
+ children: "Lists"
1288
+ }
1289
+ )
1290
+ }
1291
+ ),
1292
+ /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1293
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: getStatusColor(list.status), children: list.status }),
1294
+ /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
1295
+ "Created ",
1296
+ formatDateTime(list.createdAt)
1297
+ ] })
1298
+ ] }),
1299
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2, lg: 4 }, children: [
1300
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
1301
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mb: "xs", children: "Companies" }),
1302
+ /* @__PURE__ */ jsx(Title, { order: 3, children: progress.totalCompanies })
1303
+ ] }),
1304
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
1305
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mb: "xs", children: "Contacts" }),
1306
+ /* @__PURE__ */ jsx(Title, { order: 3, children: progress.totalContacts })
1307
+ ] }),
1308
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
1309
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mb: "xs", children: "Pipeline Steps" }),
1310
+ /* @__PURE__ */ jsx(Title, { order: 3, children: pipelineSteps.length })
1311
+ ] }),
1312
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
1313
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mb: "xs", children: "Executions" }),
1314
+ /* @__PURE__ */ jsx(Title, { order: 3, children: executions.length })
1315
+ ] })
1316
+ ] }),
1317
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1318
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Pipeline Progress" }),
1319
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 2, sm: 3, lg: 7 }, children: stageTiles.map((tile) => /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
1320
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: tile.label }),
1321
+ /* @__PURE__ */ jsx(Title, { order: 4, children: tile.value })
1322
+ ] }, tile.label)) })
1323
+ ] }) }),
1324
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1325
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Deliverability" }),
1326
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 2, sm: 3, lg: 5 }, children: deliverabilityTiles.map((tile) => /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
1327
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: tile.label }),
1328
+ /* @__PURE__ */ jsx(Title, { order: 4, children: tile.value })
1329
+ ] }, tile.label)) })
1330
+ ] }) }),
1331
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1332
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Configuration" }),
1333
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, lg: 2 }, children: [
1334
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1335
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: "Qualification" }),
1336
+ qualification ? /* @__PURE__ */ jsxs(Fragment, { children: [
1337
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1338
+ "Target: ",
1339
+ qualification.targetDescription
1340
+ ] }),
1341
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1342
+ "Minimum reviews: ",
1343
+ qualification.minReviewCount
1344
+ ] }),
1345
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1346
+ "Minimum rating: ",
1347
+ qualification.minRating
1348
+ ] }),
1349
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1350
+ "Exclude franchises: ",
1351
+ qualification.excludeFranchises ? "Yes" : "No"
1352
+ ] })
1353
+ ] }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Qualification settings are not configured for this list." })
1354
+ ] }) }),
1355
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1356
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: "Enrichment + Personalization" }),
1357
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1358
+ "Discovery provider: ",
1359
+ list.config.enrichment?.emailDiscovery?.primary ?? "Inherited default"
1360
+ ] }),
1361
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1362
+ "Verification provider: ",
1363
+ list.config.enrichment?.emailVerification?.provider ?? "Inherited default"
1364
+ ] }),
1365
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1366
+ "Industry context: ",
1367
+ list.config.personalization?.industryContext ?? "Inherited default"
1368
+ ] }),
1369
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1370
+ "Creative direction: ",
1371
+ list.config.personalization?.creativeDirection ?? "Inherited default"
1372
+ ] })
1373
+ ] }) })
1374
+ ] }),
1375
+ list.config.personalization?.emailBody ? /* @__PURE__ */ jsxs(Box, { children: [
1376
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, mb: "xs", children: "Email Body Template" }),
1377
+ /* @__PURE__ */ jsx(Code, { block: true, children: list.config.personalization.emailBody })
1378
+ ] }) : null
1379
+ ] }) }),
1380
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1381
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Pipeline Steps" }),
1382
+ !pipelineSteps.length ? /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "gray", variant: "light", children: "This list does not have any configured pipeline steps yet." }) : /* @__PURE__ */ jsxs(Table, { children: [
1383
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1384
+ /* @__PURE__ */ jsx(Table.Th, { children: "Order" }),
1385
+ /* @__PURE__ */ jsx(Table.Th, { children: "Step" }),
1386
+ /* @__PURE__ */ jsx(Table.Th, { children: "Resource" }),
1387
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1388
+ /* @__PURE__ */ jsx(Table.Th, { children: "Action" })
1389
+ ] }) }),
1390
+ /* @__PURE__ */ jsx(Table.Tbody, { children: pipelineSteps.map((step) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
1391
+ /* @__PURE__ */ jsx(Table.Td, { children: step.order }),
1392
+ /* @__PURE__ */ jsxs(Table.Td, { children: [
1393
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: step.label }),
1394
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: step.key })
1395
+ ] }),
1396
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Code, { children: step.resourceId }) }),
1397
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: step.enabled ? "light" : "outline", color: step.enabled ? "green" : "gray", children: step.enabled ? "Enabled" : "Disabled" }) }),
1398
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1399
+ Button,
1400
+ {
1401
+ size: "xs",
1402
+ variant: "light",
1403
+ leftSection: /* @__PURE__ */ jsx(IconPlayerPlay, { size: 14 }),
1404
+ disabled: !step.enabled,
1405
+ onClick: () => setSelectedStepKey(step.key),
1406
+ children: "Run"
1407
+ }
1408
+ ) })
1409
+ ] }, step.key)) })
1410
+ ] })
1411
+ ] }) }),
1412
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1413
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Execution History" }),
1414
+ !executions.length ? /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconClockHour4, { size: 16 }), color: "gray", variant: "light", children: "No executions recorded for this list yet." }) : /* @__PURE__ */ jsxs(Table, { children: [
1415
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1416
+ /* @__PURE__ */ jsx(Table.Th, { children: "Resource" }),
1417
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1418
+ /* @__PURE__ */ jsx(Table.Th, { children: "Started" }),
1419
+ /* @__PURE__ */ jsx(Table.Th, { children: "Completed" }),
1420
+ /* @__PURE__ */ jsx(Table.Th, { children: "Duration" })
1421
+ ] }) }),
1422
+ /* @__PURE__ */ jsx(Table.Tbody, { children: executions.map((execution) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
1423
+ /* @__PURE__ */ jsxs(Table.Td, { children: [
1424
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: execution.resourceId }),
1425
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: execution.executionId })
1426
+ ] }),
1427
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: getStatusColor(execution.status), children: execution.status }) }),
1428
+ /* @__PURE__ */ jsx(Table.Td, { children: formatDateTime(execution.createdAt) }),
1429
+ /* @__PURE__ */ jsx(Table.Td, { children: formatDateTime(execution.completedAt) }),
1430
+ /* @__PURE__ */ jsx(Table.Td, { children: execution.durationMs == null ? "n/a" : `${execution.durationMs} ms` })
1431
+ ] }, execution.executionId)) })
1432
+ ] })
1433
+ ] }) })
1434
+ ] }),
1435
+ selectedStep ? /* @__PURE__ */ jsx(
1436
+ ResourceExecuteDialog,
1437
+ {
1438
+ opened: !!selectedStep && !resourceDefinitionQuery.isLoading,
1439
+ onClose: () => setSelectedStepKey(null),
1440
+ resource: {
1441
+ resourceId: selectedStep.resourceId,
1442
+ resourceType: "workflow",
1443
+ name: selectedStep.label,
1444
+ formSchema: resourceDefinitionQuery.data?.interface?.form
1445
+ }
1446
+ }
1447
+ ) : null,
1448
+ /* @__PURE__ */ jsx(
1449
+ Modal,
1450
+ {
1451
+ opened: resourceDefinitionQuery.isLoading && !!selectedStep,
1452
+ onClose: () => setSelectedStepKey(null),
1453
+ withCloseButton: false,
1454
+ centered: true,
1455
+ children: /* @__PURE__ */ jsx(Center, { p: "md", children: /* @__PURE__ */ jsx(Loader, {}) })
1456
+ }
1457
+ )
1458
+ ] }) });
1459
+ }
1460
+ var PAGE_SIZE_DEFAULT2 = 20;
1461
+ function LeadGenCompaniesPage() {
1462
+ const [companySearch, setCompanySearch] = useState("");
1463
+ const [segmentFilter, setSegmentFilter] = useState(null);
1464
+ const [categoryFilter, setCategoryFilter] = useState(null);
1465
+ const [statusFilter, setStatusFilter] = useState(null);
1466
+ const [selectedCompany, setSelectedCompany] = useState(null);
1467
+ const [showBatchDelete, setShowBatchDelete] = useState(false);
1468
+ const { data: companies, isLoading: companiesLoading } = useCompanies({
1469
+ search: companySearch || void 0,
1470
+ segment: segmentFilter || void 0,
1471
+ category: categoryFilter || void 0,
1472
+ status: statusFilter === "active" || statusFilter === "invalid" ? statusFilter : void 0
1473
+ });
1474
+ const deleteCompaniesMutation = useDeleteCompanies();
1475
+ const { sort, toggleSort } = useTableSort("company", "asc");
1476
+ const sortAccessors = useMemo(
1477
+ () => ({
1478
+ company: (company) => company.name,
1479
+ domain: (company) => company.domain || "",
1480
+ segment: (company) => company.segment || "",
1481
+ category: (company) => company.category || "",
1482
+ employees: (company) => company.numEmployees ?? 0,
1483
+ enrichment: (company) => getEnrichmentStatus(company.enrichmentData),
1484
+ contacts: (company) => company.contactCount
1485
+ }),
1486
+ []
1487
+ );
1488
+ const sortedCompanies = useMemo(
1489
+ () => sortData(companies ?? [], sort, sortAccessors),
1490
+ [companies, sort, sortAccessors]
1491
+ );
1492
+ const pagination = usePaginationState(PAGE_SIZE_DEFAULT2, [companySearch, segmentFilter, categoryFilter, statusFilter], sortedCompanies.length);
1493
+ const paginatedCompanies = useMemo(
1494
+ () => sortedCompanies.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT2),
1495
+ [sortedCompanies, pagination.offset]
1496
+ );
1497
+ const selection = useTableSelection(paginatedCompanies, sortedCompanies);
1498
+ function handleBatchDelete() {
1499
+ deleteCompaniesMutation.mutate([...selection.selectedIds], {
1500
+ onSuccess: () => {
1501
+ selection.clear();
1502
+ setShowBatchDelete(false);
1503
+ }
1504
+ });
1505
+ }
1506
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1507
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Companies", caption: "Company records and enrichment tracking" }) }),
1508
+ /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1509
+ /* @__PURE__ */ jsxs(
1510
+ FilterBar,
1511
+ {
1512
+ actions: /* @__PURE__ */ jsx(
1513
+ TableSelectionToolbar,
1514
+ {
1515
+ selectedCount: selection.selectedCount,
1516
+ onDelete: () => setShowBatchDelete(true),
1517
+ isDeleting: deleteCompaniesMutation.isPending
1518
+ }
1519
+ ),
1520
+ children: [
1521
+ /* @__PURE__ */ jsx(
1522
+ Select,
1523
+ {
1524
+ placeholder: "All Segments",
1525
+ data: ["Technology", "Marketing", "Finance", "Healthcare"],
1526
+ value: segmentFilter,
1527
+ onChange: setSegmentFilter,
1528
+ style: { minWidth: 180 },
1529
+ size: "sm",
1530
+ clearable: true
1531
+ }
1532
+ ),
1533
+ /* @__PURE__ */ jsx(
1534
+ Select,
1535
+ {
1536
+ placeholder: "All Categories",
1537
+ data: ["SaaS", "Service Agency", "Software"],
1538
+ value: categoryFilter,
1539
+ onChange: setCategoryFilter,
1540
+ style: { minWidth: 160 },
1541
+ size: "sm",
1542
+ clearable: true
1543
+ }
1544
+ ),
1545
+ /* @__PURE__ */ jsx(
1546
+ Select,
1547
+ {
1548
+ placeholder: "All Statuses",
1549
+ data: [
1550
+ { value: "active", label: "Active" },
1551
+ { value: "invalid", label: "Invalid" }
1552
+ ],
1553
+ value: statusFilter,
1554
+ onChange: setStatusFilter,
1555
+ style: { minWidth: 160 },
1556
+ size: "sm",
1557
+ clearable: true
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ jsx(
1561
+ TextInput,
1562
+ {
1563
+ placeholder: "Search by name or domain...",
1564
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
1565
+ style: { minWidth: 250 },
1566
+ size: "sm",
1567
+ value: companySearch,
1568
+ onChange: (e) => setCompanySearch(e.target.value)
1569
+ }
1570
+ )
1571
+ ]
1572
+ }
1573
+ ),
1574
+ companiesLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : !sortedCompanies.length ? /* @__PURE__ */ jsx(EmptyState, { icon: IconBuildingFactory2, title: "No companies yet", description: "Add one to get started." }) : /* @__PURE__ */ jsxs(Table, { children: [
1575
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1576
+ /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
1577
+ Checkbox,
1578
+ {
1579
+ checked: selection.isPageAllSelected,
1580
+ indeterminate: selection.isPagePartiallySelected,
1581
+ onChange: selection.togglePage
1582
+ }
1583
+ ) }),
1584
+ /* @__PURE__ */ jsx(SortableHeader, { column: "company", sort, onToggle: toggleSort, children: "Company" }),
1585
+ /* @__PURE__ */ jsx(SortableHeader, { column: "domain", sort, onToggle: toggleSort, children: "Domain" }),
1586
+ /* @__PURE__ */ jsx(SortableHeader, { column: "segment", sort, onToggle: toggleSort, children: "Segment" }),
1587
+ /* @__PURE__ */ jsx(SortableHeader, { column: "category", sort, onToggle: toggleSort, children: "Category" }),
1588
+ /* @__PURE__ */ jsx(SortableHeader, { column: "employees", sort, onToggle: toggleSort, children: "Employees" }),
1589
+ /* @__PURE__ */ jsx(SortableHeader, { column: "enrichment", sort, onToggle: toggleSort, children: "Enrichment" }),
1590
+ /* @__PURE__ */ jsx(SortableHeader, { column: "contacts", sort, onToggle: toggleSort, children: "Contacts" })
1591
+ ] }) }),
1592
+ /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedCompanies.map((company) => {
1593
+ const enrichStatus = getEnrichmentStatus(company.enrichmentData);
1594
+ return /* @__PURE__ */ jsxs(
1595
+ Table.Tr,
1596
+ {
1597
+ style: { cursor: "pointer" },
1598
+ onClick: () => setSelectedCompany(company),
1599
+ children: [
1600
+ /* @__PURE__ */ jsx(
1601
+ Table.Td,
1602
+ {
1603
+ onClick: (e) => {
1604
+ e.stopPropagation();
1605
+ selection.toggle(company.id);
1606
+ },
1607
+ children: /* @__PURE__ */ jsx(
1608
+ Checkbox,
1609
+ {
1610
+ checked: selection.isSelected(company.id),
1611
+ onChange: () => selection.toggle(company.id),
1612
+ onClick: (e) => e.stopPropagation()
1613
+ }
1614
+ )
1615
+ }
1616
+ ),
1617
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: company.name }) }),
1618
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: company.domain || "-" }) }),
1619
+ /* @__PURE__ */ jsx(Table.Td, { children: company.segment || "-" }),
1620
+ /* @__PURE__ */ jsx(Table.Td, { children: company.category || "-" }),
1621
+ /* @__PURE__ */ jsx(Table.Td, { children: company.numEmployees || "-" }),
1622
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: getEnrichmentColor(enrichStatus), size: "sm", children: enrichStatus }) }),
1623
+ /* @__PURE__ */ jsx(Table.Td, { children: company.contactCount })
1624
+ ]
1625
+ },
1626
+ company.id
1627
+ );
1628
+ }) })
1629
+ ] }),
1630
+ sortedCompanies.length > PAGE_SIZE_DEFAULT2 ? /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
1631
+ Pagination,
1632
+ {
1633
+ value: pagination.page,
1634
+ onChange: pagination.setPage,
1635
+ total: pagination.totalPages(sortedCompanies.length),
1636
+ size: "sm"
1637
+ }
1638
+ ) }) : null
1639
+ ] }) }),
1640
+ /* @__PURE__ */ jsx(CompanyDetailModal, { company: selectedCompany, onClose: () => setSelectedCompany(null) }),
1641
+ /* @__PURE__ */ jsx(CustomModal, { opened: showBatchDelete, onClose: () => setShowBatchDelete(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1642
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1643
+ /* @__PURE__ */ jsx(IconAlertTriangle, { size: 20, color: "var(--mantine-color-red-6)" }),
1644
+ /* @__PURE__ */ jsxs(Title, { order: 5, children: [
1645
+ "Delete ",
1646
+ selection.selectedCount,
1647
+ " companies?"
1648
+ ] })
1649
+ ] }),
1650
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This will permanently delete the selected companies and cannot be undone." }),
1651
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
1652
+ /* @__PURE__ */ jsx(Button, { variant: "default", size: "sm", onClick: () => setShowBatchDelete(false), children: "Cancel" }),
1653
+ /* @__PURE__ */ jsx(Button, { color: "red", size: "sm", loading: deleteCompaniesMutation.isPending, onClick: handleBatchDelete, children: "Delete" })
1654
+ ] })
1655
+ ] }) })
1656
+ ] }) });
1657
+ }
1658
+ var PAGE_SIZE_DEFAULT3 = 20;
1659
+ function LeadGenContactsPage() {
1660
+ const [contactSearch, setContactSearch] = useState("");
1661
+ const [statusFilter, setStatusFilter] = useState(null);
1662
+ const [listFilter, setListFilter] = useState(null);
1663
+ const [selectedContact, setSelectedContact] = useState(null);
1664
+ const [showBatchDelete, setShowBatchDelete] = useState(false);
1665
+ const { data: lists } = useLists();
1666
+ const { data: contacts, isLoading: contactsLoading } = useContacts({
1667
+ search: contactSearch || void 0,
1668
+ contactStatus: statusFilter === "active" || statusFilter === "invalid" ? statusFilter : void 0,
1669
+ listId: listFilter || void 0
1670
+ });
1671
+ const deleteContactsMutation = useDeleteContacts();
1672
+ const { sort, toggleSort } = useTableSort("name", "asc");
1673
+ const sortAccessors = useMemo(
1674
+ () => ({
1675
+ name: (contact) => [contact.firstName, contact.lastName].filter(Boolean).join(" ") || contact.email,
1676
+ email: (contact) => contact.email,
1677
+ company: (contact) => contact.company?.name || "",
1678
+ title: (contact) => contact.title || "",
1679
+ status: (contact) => contact.status || "",
1680
+ personalized: (contact) => contact.openingLine ? "yes" : "no"
1681
+ }),
1682
+ []
1683
+ );
1684
+ const sortedContacts = useMemo(
1685
+ () => sortData(contacts ?? [], sort, sortAccessors),
1686
+ [contacts, sort, sortAccessors]
1687
+ );
1688
+ const pagination = usePaginationState(PAGE_SIZE_DEFAULT3, [contactSearch, statusFilter, listFilter], sortedContacts.length);
1689
+ const paginatedContacts = useMemo(
1690
+ () => sortedContacts.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT3),
1691
+ [sortedContacts, pagination.offset]
1692
+ );
1693
+ const selection = useTableSelection(paginatedContacts, sortedContacts);
1694
+ function handleBatchDelete() {
1695
+ deleteContactsMutation.mutate([...selection.selectedIds], {
1696
+ onSuccess: () => {
1697
+ selection.clear();
1698
+ setShowBatchDelete(false);
1699
+ }
1700
+ });
1701
+ }
1702
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
1703
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Contacts", caption: "Contact records and outreach tracking" }) }),
1704
+ /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1705
+ /* @__PURE__ */ jsxs(
1706
+ FilterBar,
1707
+ {
1708
+ actions: /* @__PURE__ */ jsx(
1709
+ TableSelectionToolbar,
1710
+ {
1711
+ selectedCount: selection.selectedCount,
1712
+ onDelete: () => setShowBatchDelete(true),
1713
+ isDeleting: deleteContactsMutation.isPending
1714
+ }
1715
+ ),
1716
+ children: [
1717
+ /* @__PURE__ */ jsx(
1718
+ Select,
1719
+ {
1720
+ placeholder: "All Statuses",
1721
+ data: [
1722
+ { value: "active", label: "Active" },
1723
+ { value: "invalid", label: "Invalid" }
1724
+ ],
1725
+ value: statusFilter,
1726
+ onChange: setStatusFilter,
1727
+ style: { minWidth: 160 },
1728
+ size: "sm",
1729
+ clearable: true
1730
+ }
1731
+ ),
1732
+ /* @__PURE__ */ jsx(
1733
+ Select,
1734
+ {
1735
+ placeholder: "All Lists",
1736
+ data: (lists ?? []).map((list) => ({ value: list.id, label: list.name })),
1737
+ value: listFilter,
1738
+ onChange: setListFilter,
1739
+ style: { minWidth: 200 },
1740
+ size: "sm",
1741
+ clearable: true
1742
+ }
1743
+ ),
1744
+ /* @__PURE__ */ jsx(
1745
+ TextInput,
1746
+ {
1747
+ placeholder: "Search by name or email...",
1748
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
1749
+ style: { minWidth: 250 },
1750
+ size: "sm",
1751
+ value: contactSearch,
1752
+ onChange: (e) => setContactSearch(e.target.value)
1753
+ }
1754
+ )
1755
+ ]
1756
+ }
1757
+ ),
1758
+ contactsLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : !sortedContacts.length ? /* @__PURE__ */ jsx(EmptyState, { icon: IconUsers, title: "No contacts yet", description: "Add one to get started." }) : /* @__PURE__ */ jsxs(Table, { children: [
1759
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1760
+ /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
1761
+ Checkbox,
1762
+ {
1763
+ checked: selection.isPageAllSelected,
1764
+ indeterminate: selection.isPagePartiallySelected,
1765
+ onChange: selection.togglePage
1766
+ }
1767
+ ) }),
1768
+ /* @__PURE__ */ jsx(SortableHeader, { column: "name", sort, onToggle: toggleSort, children: "Name" }),
1769
+ /* @__PURE__ */ jsx(SortableHeader, { column: "email", sort, onToggle: toggleSort, children: "Email" }),
1770
+ /* @__PURE__ */ jsx(SortableHeader, { column: "company", sort, onToggle: toggleSort, children: "Company" }),
1771
+ /* @__PURE__ */ jsx(SortableHeader, { column: "title", sort, onToggle: toggleSort, children: "Title" }),
1772
+ /* @__PURE__ */ jsx(SortableHeader, { column: "status", sort, onToggle: toggleSort, children: "Status" }),
1773
+ /* @__PURE__ */ jsx(SortableHeader, { column: "personalized", sort, onToggle: toggleSort, children: "Personalized" })
1774
+ ] }) }),
1775
+ /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedContacts.map((contact) => {
1776
+ const fullName = [contact.firstName, contact.lastName].filter(Boolean).join(" ") || contact.email;
1777
+ const hasPersonalization = !!contact.openingLine;
1778
+ return /* @__PURE__ */ jsxs(
1779
+ Table.Tr,
1780
+ {
1781
+ style: { cursor: "pointer" },
1782
+ onClick: () => setSelectedContact(contact),
1783
+ children: [
1784
+ /* @__PURE__ */ jsx(
1785
+ Table.Td,
1786
+ {
1787
+ onClick: (e) => {
1788
+ e.stopPropagation();
1789
+ selection.toggle(contact.id);
1790
+ },
1791
+ children: /* @__PURE__ */ jsx(
1792
+ Checkbox,
1793
+ {
1794
+ checked: selection.isSelected(contact.id),
1795
+ onChange: () => selection.toggle(contact.id),
1796
+ onClick: (e) => e.stopPropagation()
1797
+ }
1798
+ )
1799
+ }
1800
+ ),
1801
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: fullName }) }),
1802
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: contact.email }) }),
1803
+ /* @__PURE__ */ jsx(Table.Td, { children: contact.company?.name || "-" }),
1804
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: contact.title || "-" }) }),
1805
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: getStatusColor(contact.status), size: "sm", children: contact.status || "unknown" }) }),
1806
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: hasPersonalization ? "green" : "gray", size: "sm", children: hasPersonalization ? "Yes" : "No" }) })
1807
+ ]
1808
+ },
1809
+ contact.id
1810
+ );
1811
+ }) })
1812
+ ] }),
1813
+ sortedContacts.length > PAGE_SIZE_DEFAULT3 ? /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
1814
+ Pagination,
1815
+ {
1816
+ value: pagination.page,
1817
+ onChange: pagination.setPage,
1818
+ total: pagination.totalPages(sortedContacts.length),
1819
+ size: "sm"
1820
+ }
1821
+ ) }) : null
1822
+ ] }) }),
1823
+ /* @__PURE__ */ jsx(ContactDetailModal, { contact: selectedContact, onClose: () => setSelectedContact(null) }),
1824
+ /* @__PURE__ */ jsx(CustomModal, { opened: showBatchDelete, onClose: () => setShowBatchDelete(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1825
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1826
+ /* @__PURE__ */ jsx(IconAlertTriangle, { size: 20, color: "var(--mantine-color-red-6)" }),
1827
+ /* @__PURE__ */ jsxs(Title, { order: 5, children: [
1828
+ "Delete ",
1829
+ selection.selectedCount,
1830
+ " contacts?"
1831
+ ] })
1832
+ ] }),
1833
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This will permanently delete the selected contacts and cannot be undone." }),
1834
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
1835
+ /* @__PURE__ */ jsx(Button, { variant: "default", size: "sm", onClick: () => setShowBatchDelete(false), children: "Cancel" }),
1836
+ /* @__PURE__ */ jsx(Button, { color: "red", size: "sm", loading: deleteContactsMutation.isPending, onClick: handleBatchDelete, children: "Delete" })
1837
+ ] })
1838
+ ] }) })
1839
+ ] }) });
1840
+ }
1841
+
1842
+ export { CompanyDetailModal, ContactDetailModal, LEAD_GEN_ROUTE_LINKS, LIST_TEMPLATE_OPTIONS, LeadGenCompaniesPage, LeadGenContactsPage, LeadGenDeliverabilityPage, LeadGenListDetailPage, LeadGenListsPage, LeadGenOverviewPage, LeadGenRouteShell, LeadGenSidebar, LeadGenSidebarMiddle, LeadGenSidebarTop, buildListConfig, formatDate, getEnrichmentColor, getEnrichmentStatus, getStatusColor, leadGenManifest, useDeleteLists };